Juraj Kostolanský • 23 January 2019

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: