2012年11月8日木曜日

Railsでリストボックスを使って親子テーブルを同時に更新するサンプル(複数選択対応版)

前回作成したサンプルでは、リストボックスなのに複数選択に対応していない。HTML的に複数選択に対応しても、DBにうまくinsertされない。

しかも、手動でGameクラスにLineupクラスを複数追加すると、下図のようにリストボックスが複数表示されてしまって見栄えが悪い。


下記のサイトで同じ課題に取り組んでいる人がいたので、参考にさせていただいた。


Complex Forms for Many-to-Many Relationships
http://beacon.wharton.upenn.edu/learning/2012/02/complex-forms-for-many-to-many-relationships/

まずは改良版のGameクラスとLineupクラスのscaffold。(実行結果は省略。)
rails g scaffold bettergame date:date
rails g scaffold betterlineup bettergame:references player:references
rake db:migrate
次にbetterゲームクラスのmodelを下記のように修正する。
# encoding: utf-8

class Bettergame < ActiveRecord::Base
  has_many :players, :through => :betterlineups
  has_many :betterlineups
  accepts_nested_attributes_for :betterlineups

  def player_names
    players.collect{|p| p.name}.join(', ')
  end

  # PlayerクラスのIDを配列でget/setできる属性を追加
  def player_ids
    @player_ids || players.collect{|p| p.id}
  end
 
  def player_ids=(id_array)
    @player_ids = id_array.collect{|id| id.to_i};
  end

  after_save :assign_players

  private

  # PlayerクラスのIDの配列から、Betterlineupクラスを追加/編集/削除する。
  def assign_players
    if @player_ids
      new_ids = @player_ids
      old_ids = players.collect{|p| p.id}
      ids_to_delete = old_ids - (old_ids & new_ids)
      ids_to_add = new_ids - (old_ids & new_ids)
      bettergame_id = id
 
      ids_to_delete.each do |player_id|
        Betterlineup.destroy_all(:bettergame_id => bettergame_id, :player_id => player_id)
      end
 
      ids_to_add.each do |player_id|
        Betterlineup.create(:bettergame_id => bettergame_id, :player_id => player_id)
      end
    end
  end
end
リストボックスから複数送信されるPlayerのIDを配列としてまとめて扱うためのメソッドと、送信されたPlayerのIDとLineupクラスで既に保持しているPlayerのIDを比較して、追加/更新/削除を行うメソッドの2つが追加されている。

次に、viewの_form.html.erbを下記のように修正する。
<%= form_for(@bettergame) do |f| %>
  <% if @bettergame.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@bettergame.errors.count, "error") %> prohibited this bettergame from being saved:</h2>

      <ul>
      <% @bettergame.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.select(:player_ids, @player_select_data, {}, {:multiple => true, :size => Player.all.size}) %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
セレクトボックスのヘルパーには、先ほど modelクラスに追加した「:player_ids」を指定している。 その次の「@player_select_data」は、リストボックスに表示する値の配列となる。後で Controllerに追加する。 複数選択できるように、「:multiple => true」として、リストボックスの表示サイズは、Player全員とするために、「:size => Player.all.size」としている。

最後に、Controllerの修正は下記の通りとなる。(new~update部分のみ)
  # GET /bettergames/new
  # GET /bettergames/new.json
  def new
    @bettergame = Bettergame.new
    @player_select_data = Player.all.collect{|p| [p.name, p.id]}

    respond_to do |format|
      format.html # new.html.erb
      format.json { render json: @bettergame }
    end
  end

  # GET /bettergames/1/edit
  def edit
    @bettergame = Bettergame.find(params[:id])
    @player_select_data = Player.all.collect{|p| [p.name, p.id]}
  end

  # POST /bettergames
  # POST /bettergames.json
  def create
    @bettergame = Bettergame.new(params[:bettergame])

    respond_to do |format|
      if @bettergame.save
        format.html { redirect_to @bettergame, notice: 'Bettergame was successfully created.' }
        format.json { render json: @bettergame, status: :created, location: @bettergame }
      else
        format.html { render action: "new" }
        format.json { render json: @bettergame.errors, status: :unprocessable_entity }
      end
    end
  end

  # PUT /bettergames/1
  # PUT /bettergames/1.json
  def update
    @bettergame = Bettergame.find(params[:id])

    respond_to do |format|
      if @bettergame.update_attributes(params[:bettergame])
        format.html { redirect_to @bettergame, notice: 'Bettergame was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: "edit" }
        format.json { render json: @bettergame.errors, status: :unprocessable_entity }
      end
    end
  end
修正箇所は、newとeditのメソッド内で、「@player_select_data」の設定(Playerクラスの全データ取得)を行なっているのみ。

これで、複数選択の追加/編集/削除がリストボックス上から行えるようになった。


0 件のコメント:

コメントを投稿