ActiveRecordにはコールバックがたくさんあります。
実行順序がよくわからなくなるので、Rails 4が出たことだし改めてまとめてみました。
※ActiveRecord::Callbacks
のヘッダコメントに全部書いてあります
使うモデル
まずは、このようなクラスを作りました。
class Work < ActiveRecord::Base
callbacks = %w(before after).product(%w(validation save create update destroy)).map{|a|a.join('_')}
callbacks += %w(after_commit after_rollback after_initialize)
callbacks.each do |callback|
send(callback) { logger.debug callback }
end
validates :name, presence: true
def initialize(*)
logger.debug "initialize"
super
end
end
create/updateしてみる
まずは手始めにcraete
/update
してみます。validation
、save
などのコールバックが呼ばれます。
### newするとinitializeが呼ばれる
work = Work.new(name: 'hoge')
# initialize
# after_initialize
### saveするとvalidation, save, create, commitが呼ばれる
work.save!
# -- BEGIN TRANSACTION
# before_validation
# -- validate
# after_validation
# before_save
# before_create
# -- INSERT INTO "works" ...
# after_create
# after_save
# -- COMMIT
# after_commit
### updateするとvalidation, save, update, commitが呼ばれる
work.update name: 'piyo'
# -- BEGIN TRANSACTION
# before_validation
# -- validate
# after_validation
# before_save
# before_update
# -- UPDATE "works" SET ...
# after_update
# after_save
# -- COMMIT
# after_commit
### validateに失敗するとrollbackが呼ばれる
work.update name: nil
# -- BEGIN TRANSACTION
# before_validation
# -- validate
# after_validation
# -- ROLLBACK
# after_rollback
### update_attributeではvalidateが実行されない
work.update_attribute :name, nil
# -- BEGIN TRANSACTION
# before_save
# before_update
# -- UPDATE "works" SET ...
# after_update
# after_save
# -- COMMIT
# after_commit
### update_columnではcallbackも何も呼ばれない
### ※serializeなども処理されないので注意
work.update_column :name, nil
# -- UPDATE "works" SET ...
find/destroyしてみる
create
時だけでなく、find
したときもインスタンスは作られるのでinitialize
が呼ばれます。
初期値を設定するときにafter_initialize
を使う場合は注意しましょう。
また、destroyしたあともDBからデータが消えるだけでモデルオブジェクトは残るので、IDなど値は参照できます。
### findするとinitializeが呼ばれる
work = Work.first
# -- SELECT "works".* FROM ...
# initialize
# after_initialize
### pluckではインスタンスが作られず、callbackも呼ばれない
### ※serializeなどは処理される
Work.pluck(:name)
# --- SELECT "works".name FROM ...
### destroyするとdestroy, commitが呼ばれる
work.destroy
# -- BEGIN TRANSACTION
# before_destroy
# -- DELETE FROM "works" ...
# after_destroy
# -- COMMIT
# after_commit
コールバックでfalseを返してみる
before_save
やbefore_destroy
などでfalse
を返すことで、ROLLBACKさせることができます。
### before_コールバックでfalseを返すとrollbackする
class Work
before_save { logger.debug "before_save2"; false }
end
work = Work.first
work.save
# -- BEGIN TRANSACTION
# before_validation
# -- validate
# after_validation
# before_save
# before_save 2
# -- ROLLBACK
# after_rollback
これはvalidationをスキップするupdate_attribute
もROLLBACKされます。
update_column
はそもそもコールバックが呼ばれないので、そのまま保存されます。
まとめ
さんざん書かれていることですが、update
/update_attribute
/update_column
は、コールバック・validationの観点からは以下のような違いがあります。
update
(旧update_attributes
)- コールバックあり、validationあり
update_attribute
- コールバックあり、validationなし
update_column
- コールバックなし、validationなし
これらを意識し、安全で効果的にコールバックを活用したいですね。