きっかけ
「order_idが変わったらorder_statusの値を変更する」という実装でコールバックを使用した際に、
コールバックの挙動を理解できておらず躓いてしまったのでアウトプットとしてまとめてみました。
ケース1
コールバック発火しない
order.rb
enum status: {
unchecked: 0,
checked: 1,
error: 3
}
after_update :order_status_reset, if: -> { order_id_changed? }
def order_status_reset
self.unchecked!
end
原因
after_updateではorder_id_changed?に該当しない。
after_saveも同様だが、changed?は
現在の値と変更しようとしている値を検証するもの。
そのため、updateやsaveした後は
すでに変更しようとした値は保存されており、
changed?は発火しない。
ケース2
afrter_updateでは発火しないのでbefore_updateに変更したところ
updateが無限に呼ばれ以下のエラーが発生
SystemStackError: stack level too deep
order.rb
enum status: {
unchecked: 0,
checked: 1,
error: 3
}
before_update :order_status_reset, if: -> { order_id_changed? }
def order_status_reset
self.unchecked!
end
原因
before_updateでorder_status_resetメソッドが実行されるものの、
メソッド内でupdateが行われており、再びbefore_updateが発火(以降繰り返し)。
解決策
コールバックで呼び出したメソッドの中ではupdateやsaveは行わず代入で対応。
order.rb
enum status: {
unchecked: 0,
checked: 1,
error: 3
}
before_update :order_status_reset, if: -> { order_id_changed? }
def order_status_reset
self.order_status = :unchecked
end
まとめ
・before_update系の中でupdateしてはいけない
・after_update系でchanged?は効かない
コールバックの中でも限られた部分ですが、参考になれば幸いです。