Tech Racho エンジニアの「?」を「!」に。
  • 開発

[Rails 4.0] ActiveRecordのコールバックが呼ばれる順番まとめ

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してみます。validationsaveなどのコールバックが呼ばれます。

### 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_savebefore_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なし

これらを意識し、安全で効果的にコールバックを活用したいですね。


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。