accepts_nested_attributes_forしたときのchanged?に気をつけよう
TL; DR
- rails 5.0.2
accepts_nested_attributes_for
で子モデルを変更しても、親モデルのchanged?
はfalse
を返すよ- なので、
changed?
を見て何らかの処理をフックするようなコードを書くときは気をつけよう relations_changed?
メソッドを生やしておくのが良いと思うよ
検証コード
$ rails g model parent name:string $ rails g model child parent:belongs_to name:string
class Parent < ApplicationRecord has_many :children accepts_nested_attributes_for :children, allow_destroy: true end class Child < ApplicationRecord belongs_to :parent end
デフォルト挙動確認
そもそも、 accepts_nested_attributes_for
とか関係なく、association先のモデルが変更されても、association元モデルの changed?
は false
を返す。
# rails console p = Parent.create(name: 'test1-p') c = p.children.create(name: 'test1-c1') p.changed? => false c.name = 'changed' p.changed? => false
この挙動は、 accepts_nested_attributes_for
を使って子モデルの属性をセットした場合も変わらない。
# rails console p = Parent.create(name: 'test2-p') p.changed? => false p.attributes = { children_attributes: [{ name: 'test2-c1' }] } p.changed? => false p.children[0].changed? => true
ミスりやすいポイント
「associationはもともとこういう挙動ですよ」と捉えると問題無さそうだが、最後の部分をStrongParameterっぽく書いてみると、かなり不思議な挙動に見えると思う。
p.attributes = parent_params
p.changed?
=> false
「changed?
は true
になるはずだ」と思ってしまってもしょうがない気がする。なので、要注意。
どんなコードを書くべきか
changed?
メソッドをoverrideするという手もあるが、「accepts_nested_attributes_for
に関係なく、 changed?
はassociation先は見ない」という前提は覆さないほうが良いと思う。
というわけで、 children_changed?
メソッドを Parent
に生やそう。
# Parent.rb def children_changed? children.any? do |c| c.new_record? || c.changed? || c.marked_for_destruction? end end
最後の marked_for_destruction?
は accepts_nested_attributes_for
の allow_destroy: true
オプションを有効にして、削除フラグを立てた場合に true
を返してくれるメソッドなので、覚えておこう。