Multiple Checkboxes with 'has_many through' AND 'accepts_nested_attributes_for'

railsにおいて、予め選択肢をいくつか用意し、チェックボックスで複数回答してもらいたいときがある。ところが、Viewで現れるのは複数個のModelため、これをRailsで表現するのは、少し手間がいる。自分の場合、上手くいくまで試行錯誤したので、忘れないように記しておく。

言うまでもないが、accepts_nested_attributes_forを利用しているので、対応するrailsを使用すること。

できる限り誤記がないように注意したが、実際に動かしたコードとは違うので間違いが含まれているかも。

Model:

class User < ActiveRecord::Base
	has_many :user_kinds
	has_many :kinds, :through => :user_kinds
	accepts_nested_attributes_for :user_kinds
end

class Kind < ActiveRecord::Base
	has_many :user_kinds
	has_many :users, :through => :user_kinds
end

class UserKind < ActiveRecord::Base
	belongs_to :kinds
	belongs_to :users
	#validates_uniqueness_of :kind_id, :scope => :user_id
end

Controller:
ほとんどScaffoldの吐くコードで大丈夫だが、一点だけ修正。

  def update
    @user = User.find(params[:id])
    UserKind.delete_all(['user_id = ?', @user.id]) # ここを追加, .clear の方が懸命かも??? 未検証

    if @building.update_attributes(params[:user])
      flash[:notice] = 'User was successfully updated.'
      redirect_to(@user)
    else
      render :action => 'edit'
    end
  end

このコードでは、updateを行う度にチェックボックスの最新の状態を追加する。例えば、ある選択肢をチェック入れたまま更新すると、同じ値のモデルをもう一つ作成してしまうことになる。これでは、チェックボックスがチェックされた状態から外れた状態にした場合、うまく動かない場合があるので、対象となるユーザーのUserKindを全部削除する必要がある。

View(/app/views/users/_form.html.erb):

<% for kind in Kind.find(:all) -%>
      <%= check_box_tag("user[user_kinds_attributes][][kind_id]", kind.id, @user.user_kinds.include?(kind)) -%>
      <%=h kind.name %>
<% end -%>


例えば、こんな感じで表示される

□独身 □イケメン ■金持ち □非モテ

参考:
http://satishonrails.wordpress.com/2007/06/29/multiple-checkboxes-with-habtm/