ややこしいタイトルだけど、Ruby on Rails のモデルコールバックで迷った箇所があったのでメモ。
A has_many(has_one) B な関係で、B モデルの callback 実行を以下のパターンで制御したい。
- 親要素の A モデルと一緒に B モデルのデータが作成された時だけ callback を実行
- 親要素の A モデルの
dependent: :destroy
で削除された時だけ callback を実行 - 子要素の B モデルが単独で作成・更新された時だけ callback を実行
- 子要素の B モデルが単独で削除された時だけ callback を実行
まずはモデルのシンプルな関係を示すコード。Article has_many Comment という感じ。
class Article < ApplicationRecord
has_many :comments, dependent: :destroy
end
class Comment < ApplicationRecord
belongs_to :article
end
これを上記の4つのパターン別に callback を登録してみるとこんな感じ。
class Comment < ApplicationRecord
belongs_to :article
after_save_commit :created_by_association_only, if: :created_by_association?
after_save_commit :created_by_self_only, unless: :created_by_association?
after_destroy_commit :destroyed_by_association_only, if: :destroyed_by_association?
after_destroy_commit :destroyed_by_self_only, unless: :destroyed_by_association?
private
def created_by_association?
article.saved_change_to_updated_at?
end
def destroyed_by_association?
destroyed_by_association.present?
end
def created_by_association_only
logger.info('created_by_association_only')
end
def created_by_self_only
logger.info('created_by_self_only')
end
def destroyed_by_association_only
logger.info('destroyed_by_association_only')
end
def destroyed_by_self_only
logger.info('destroyed_by_self_only')
end
end
親要素から作られたか?
これを判定するメソッドが Rails には見当たらなかったので、親要素のオブジェクトが更新されているかどうかを確認して代替とすることにした。確認する方法は model.saved_change_to_カラム名?
のマジックメソッドを使って、更新日が更新されているかどうかで判定している。
親要素が更新されていれば、親と一緒に作成されているし、親要素が更新されていなければ子要素が単独で作成・更新されている…という風に判断している。果たしてこれで問題がないのかは若干の不安があるが、基本的には問題がなさそう。
親要素と一緒に削除されたか?
削除に関しては destroyed_by_association
という便利なメソッドが生えている。これは dependent: :destroy
指定で一緒に削除される際に、削除元となったモデル情報が格納されている。子要素が単独で削除された場合は nil
になる。これを使えば、一緒に削除されたのか単独なのかが判断できる。
あとは callback の if
オプションで条件を指定してあげれば4パターンそれぞれで callback を使い分けることができる。