QA@IT

多対多関連への変更の検知

3125 PV

以下のように、多対多の関連をチェックボックスでオンオフできるような画面を考えます。
(Tagはマスタデータとして、既に作られているとします)

class User < ActiveRecord::Base
  has_many :taggings
  has_many :tags, through: :taggings
end
class Tagging < ActiveRecord::Base
  belongs_to :user
  belongs_to :tag
end
class Tag < ActiveRecord::Base
  has_many :taggings
  has_many :users, through: :taggings
end
# app/views/users/edit.html.erb
<%= form_for @user do |f| %>
  <p>name: <%= f.text_field :name %></p>
  <ul>
  <% Tag.all.each do |tag| %>
    <li><label><%= f.check_box :tag_ids, { name: 'user[tag_ids][]', checked: tag.id.in?(@user.tag_ids) }, tag.id, '' %> <%= tag.name %></label></li>
  <% end %>
  </ul>
  <%= f.submit %>
<% end %>
# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def edit
    @user = User.find(params[:id])
  end

  def update
    @user = User.find(params[:id])
    @user.attributes = params.require(:user).permit(:name, :tag_ids => [])
    if @user.changed?
      # 何か処理
    end
    @user.save!
    redirect_to edit_user_path(@user)
  end
end

ここで、@userを保存する前に、「データに変更が発生するときは何らかの処理を実行する」というのがやりたいことです。

Userの属性(上の例だと name)であれば、@user.changed?などによって変更されているかどうかを取得できますが、
この場合はこの手法を使うことが出来ません。
(そもそも、@user.attributesに代入した時点でDBに保存されてしまいます)

@user.attributesに代入する前に、@user.tagging_idsparams[:user][:tag_ids]を比較することで変更を検知すること自体はできますが、他にスマートな方法はあるのでしょうか?

回答

どんな処理をするのかにもよりますが、has_many のオプションで指定できるコールバックが役に立ちそうな気がします。

たとえば、下記の例では、追加されるタグ、削除されるタグがログに出力されます (Tag#name でタグ名が得られるものとします)。

class User < ActiveRecord::Base
  has_many :taggings
  has_many :tags, through: :taggings, before_add: :before_add_tag, before_remove: :before_remove_tag

  private
  def before_add_tag(tag)
    logger.debug "#{tag.name} added"
  end

  def before_remove_tag(tag)
    logger.debug "#{tag.name} removed"
  end
end
編集 履歴 (0)

callbackを使って実現はできないでしょうか?before_saveやbefore_updateなどをUserモデルに実装してあげる。

編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。