Rob Zolkos
February 10, 2020
Execution order of after_commit and after_rollback ActiveRecord callback
Rails allows the adding of various callbacks that trigger during the lifecycle of an ActiveRecord operation. The standard callbacks are before_validation
, after_validation
, before_save
, before_create
, after_create
, after_save
, after_commit
, and after_rollback
.
These are documented quite extensively in the Rails Guides and the Rails API documentation.
The documentation is quite good at showing you the order the callbacks are executed - eg before_validation
fires before validate
which fires before before_save
etc. Most of these validations execute in the order they are defined. For example, if you have two before_save
callbacks defined, then they will execute one after the other.
However there are two callbacks that do not execute in this order - after_commit
and after_rollback
. It is important to be aware of ActiveRecord execution order for these types of callback.
For example if you have three after_commit
callbacks defined, how do you know which one is going to execute first? What if you have two other after_commit
callbacks that are defined in concern modules and mixed in?
These two callbacks will fire in reverse order to how they are declared in your model.
Let’s go through a few scenarios with after_commit
A Single callback
class Post
after_commit :send_to_slack
end
This is easy, it will get fired once, after the data is commited to the database.
Two callbacks
class Post
after_commit :send_to_slack
after_commit :send_to_messenger
end
The :send_to_messenger
method will run before :send_to_slack
Three callbacks, one introduced from a concern
# app/models/concerns/emailable.rb
module Emailable
extend ActiveSupport::Concern
included do
after_commit :send_to_email
end
end
# app/models/post.rb
class Post
include Emailable
after_commit :send_to_slack
after_commit :send_to_messenger
end
The :send_to_messenger
method will run before :send_to_slack
which will run before :send_to_email
defined in the concern.
Four callbacks, two introduced from a concern
# app/models/concerns/emailable.rb
module Emailable
extend ActiveSupport::Concern
included do
after_commit :send_to_email
after_commit :send_to_admin_email
end
end
# app/models/post.rb
class Post
include Emailable
after_commit :send_to_slack
after_commit :send_to_messenger
end
The callbacks will run in this order :send_to_messenger
, :send_to_slack
, :send_to_admin_email
, :send_to_email
Notice how they are in reverse order to how they’ve been declared.
Five callbacks, three introduced from two concerns
# app/models/concerns/emailable.rb
module Emailable
extend ActiveSupport::Concern
included do
after_commit :send_to_email
after_commit :send_to_admin_email
end
end
# app/models/concerns/carrier_pidgeonable.rb
module CarrierPidgeonable
extend ActiveSupport::Concern
included do
after_commit :send_by_carrier_pidgeon
end
end
# app/models/post.rb
class Post
include Emailable
include CarrierPidgeonable
after_commit :send_to_slack
after_commit :send_to_messenger
end
Here we include a new concern - CarrierPidgeonable
after Emailable
. So the callback sequence changes to :send_to_messenger
, :send_to_slack
, :send_by_carrier_pidgeon
, :send_to_admin_email
, :send_to_email
Summary
Knowing the order of how your callbacks fire is important to debugging subtle bugs that can be caused by the misuse of callbacks. Whilst most callbacks fire in the order they are defined, after_commit
and after_rollback
callbacks fire in reverse order to how they are declared.