model.collection_model_ids (多対多)

元記事はこちら

翻訳

Bad Smell

class User < ActiveRecord::Base
  has_many :user_role_relationships
  has_many :roles, :through => :user_role_relationships
end

class UserRoleRelationship < ActiveRecord::Base
  belongs_to :user
  belongs_to :role
end

class Role < ActiveRecord::Base
end

<% form_for @user do |f| %>
  <%= f.text_field :email %>
  <% Role.all.each |role| %>
    <%= check_box_tag 'role_id[]', role.id, @user.roles.include?(role) %>
    <%= role.name %>
  <% end %>
<% end %>

class UsersController < ApplicationController
  def update
    @user = User.find(params[:id])
    if @user.update_attributes(params[:user])
      @user.roles.delete_all
      (params[:role_id] || []).each { |i| @user.roles << Role.find(i) }
    end
  end
end

見ての通り、UserモデルとRoleモデルが多対多の関係で、1ユーザーに複数のroleを設定できるフォームがあるとします。 コントローラーではまず、ユーザーの権限を全て削除してから、ユーザーがチェックボックスで選択した権限を割り当てています。 自前で権限の割り当てをするのは見苦しいので、railsの仕組みにのるべきです。

Refactor

<% form_for @user do |f| %>
  <% Role.all.each |role| %>
    <%= check_box_tag 'user[role_ids][]', role.id, @user.roles.include?(role) %>
    <%= role.name %>
  <% end %>
  <%= hidden_field_tag 'user[role_ids][]', '' %>
<% end %>

class UsersController < ApplicationController
  def update
    @user = User.find(params[:id])
    @user.update_attributes(params[:user])
    # the same as @user.role_ids = params[:user][:role_ids]
  end
end

チェックボックスの名前を"role_id“ から "user[role_ids]"に変更しました。こうすることで、role_idsは自動的にUserモデルに割り当てられます。 hidden_field_tag に'user[role_ids][]‘があることに注意してください。値は空で、ユーザー権限のチェックボックスが選択されなかった場合、権限が全て削除されることを保証します。 これでコントローラーが簡潔になりました。

感想

Rails4~だとStrongParams使うので、コントローラーは以下のようになるはず。

class HomeController < ApplicationController
  def update
    p params
    @user = User.find(params[:id])
    @user.update_attributes(role_params)
    # the same as @user.role_ids = params[:user][:role_ids]
  end

  private

    def role_params
      params.require(:user).permit(:role_ids)
    end
end