Today I’ve learned something interesting. The new Rails callbacks after_create_commit
, after_update_commit
and after_destroy_commit
can behave in a way I didn’t expect.
The after_commit
callback is a well-known part of the Ruby on Rails framework. It’s called after a record has been created, updated, or destroyed and after the corresponding database transaction has been commited. It’s the primary method to use if we want to trigger a background job associated with a record.
class User < ActiveRecord::Base
after_commit :schedule_welcome_email, on: :create
def schedule_welcome_email
WelcomeEmailJob.perform_later(id)
end
end
The Ruby on Rails 5 came with some new after_*_commit
callbacks. Before it looked like this:
after_commit :action1, on: :create
after_commit :action2, on: :update
after_commit :action3, on: :destroy
And now we can use:
after_create_commit :action1
after_update_commit :action2
after_destroy_commit :action3
The problem
Let’s say we want to trigger the broadcast
method after a record has been created or destroyed. We can try using the new callbacks:
class Comment < ActiveRecord::Base
after_create_commit :broadcast
after_destroy_commit :broadcast
def broadcast
BroadcastJob.perform_later(id)
end
end
That looks good! The after_destroy_commit
works as expected. However, for some reason, the first after_create_commit
is never triggered. But why?
The reason
Let’s take a look at the source code of the after_create_commit
method:
def after_create_commit(*args, &block)
set_options_for_callbacks!(args, on: :create)
set_callback(:commit, :after, *args, &block)
end
As you can see, these methods are effectively aliases for the old after_commit
callback with the :on
option specified. And subsequent after_commit
declarations override former declarations for the same method. That can be pretty surprising!
The solution
To solve this issue, we can use the old callback. The :on
option supports an array of multiple life cycle events, so the solution is simple and looks like this:
after_commit :broadcast, on: [:create, :destroy]