野球やサッカーなどで、手持ちの選手から試合ごとのメンバーを決める場合のクラス図は上図のようになる。Lineupという中間クラスを通して、GameクラスとPlayerクラスが多対多の関係になっている。
こういう構造の場合に、Gameクラスにデータを追加→Playerクラスにデータを追加→Lineupクラスにデータを追加というように、順々に画面を操作していくのは面倒である。
下図のように、GameクラスとLineupクラスを一画面で同時に追加することはできないだろうか?
まずはscaffold。(実行結果は省略。)
rails g scaffold game date:date rails g scaffold player name:string rails g scaffold lineup game:references player:references rake db:migrate次に、modelにクラス間のアソシエーションを設定する。
class Game < ActiveRecord::Base has_many :players, :through => :lineups has_many :lineups accepts_nested_attributes_for :lineups end class Lineup < ActiveRecord::Base belongs_to :game belongs_to :player end「has_many 子クラス :through 中間クラス」と書くことで、中間クラスを通じて子クラスの属性にアクセスすることができるようなる。
つまり、下記のように中間クラスLineupを意識せずに、子クラスPlayerを扱えるようになる。
irb(main):001:0> Game.find(1).players[0].name => "陽岱鋼"「accepts_nested_attributes_for 中間クラス」と書くことで、子クラスの変更を親クラスで保存できるようになる。
irb(main):002:0> Game.find(1).players << Player.find(2) irb(main):003:0> Game.find(1).save irb(main):004:0> Game.find(1).players.last.name => "今浪"view側の変更は下記のようになる。
<%= form_for(@game) do |f| %> <% if @game.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@game.errors.count, "error") %> prohibited this game from being saved:</h2> <ul> <% @game.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :date %><br /> <%= f.date_select :date, :use_month_numbers => true %> </div> <div class="field"> <%= f.label :lineups %><br /> <%= f.fields_for :lineups do |lf| %> <%= lf.hidden_field :id %> <%= lf.select :player_id, Player.all.map {|player| [player.name, player.id]}, {:selected=>lf.object.player_id}, :size => Player.all.size %> <% end %> </div> <div class="actions"> <%= f.submit %> </div> <% end %>「fields_for」の内側に子クラスのform要素を記述する。
これで1つのview内に複数のmodelを扱えるようになる。
「:selected=>lf.object.player_id」は、こう記述しておくと、表示しているPlayerクラスの一覧から、Lineupクラスに登録されているものが選択された状態になる。
次に、controller側の変更は下記のようになる。
# GET /games/new # GET /games/new.json def new @game = Game.new @game.lineups.build respond_to do |format| format.html # new.html.erb format.json { render json: @game } end end「@game.lineups.build」の行を追加したのみ。
ここでbuildをしておかないと、view側のfields_forで指定している:lineupsが空になってしまうために、下図のようにリストボックスが表示されなくなってしまう。
model、view、controllerの修正後は、下図のようにGameクラスの画面から、子クラスPlayerの追加、修正ができるようになる。
しかしながら、現状では、せっかくリストボックスを使っているのに複数選択に対応していない。
実質的にコンボボックスと同じ機能になってしまっている。
複数選択の対応は次回の課題としたい。
以下は、今回参考にさせていただいたブログ。
has_manyな関連先をまとめてINSERTする
http://aerial.st/archive/2011/06/11/insert-has-many-relations/
has_manyな関連で、1アクションで複数のモデルを同時に保存するには?
http://d.hatena.ne.jp/zariganitosh/20080104/1199437301
[Rails]accepts_nested_attributes_forでネストした別モデルのフォーム[Ruby]
http://shasou.blogspot.jp/2011/05/railsacceptsnestedattributesforruby.html
Nested AttributesとNested Model Formsを使って親子オブジェクトを一括で登録/変更するには
http://www.everyleaf.com/tech/ror_tips/nested-attributesnested-model-forms/
0 件のコメント:
コメントを投稿