Today I’ve learned something interesting. The new Rails callbacks
after_destroy_commit can behave in a way I didn’t expect.
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
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?
Let’s take a look at the source code of the
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!
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]