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なし
これらを意識し、安全で効果的にコールバックを活用したいですね。