The unexpected after_commit behaviour

jkostolansky

Juraj Kostolanský

Posted on September 30, 2019

The unexpected after_commit behaviour

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

And now we can use:

after_create_commit  :action1
after_update_commit  :action2
after_destroy_commit :action3
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

Original article: The unexpected after_commit behaviour

💖 💪 🙅 🚩
jkostolansky
Juraj Kostolanský

Posted on September 30, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

The unexpected after_commit behaviour
rails The unexpected after_commit behaviour

September 30, 2019