Gotcha with after_commit callback in Rails

Prathamesh Sonpatki

By Prathamesh Sonpatki

on March 1, 2015

after_commit callback in Rails is triggered after the end of transaction.

For eg. I have a Post model for which the number of lines of the content are calculated in the after_commit callback:

1class Post < ActiveRecord::Base
2  after_commit :calculate_total_lines, if: -> (post) { post.previous_changes.include?(:content) }
4  def calculate_total_lines
5    update! total_lines: content.split("\n").length
6  end
1post = Post.create! content: "Lets discuss Rails 5.\n", author: 'Prathamesh'
2assert_equal 1, post.total_lines

Now lets wrap the creation of post inside a transaction block:

1Post.transaction do
2  post = Post.create! content: "Lets discuss Rails 5.\n", author: 'Prathamesh'
3  assert_equal 1, post.total_lines

The test will fail now.

1#   1) Failure:
2# BugTest#test_within_transaction [after_commit_test.rb:45]:
3# Expected: 1
4#   Actual: nil

Why? Lets recall. after_commit callback will get executed after the end of transaction.

So until all the code inside transaction is completed, the callback is not going to get executed.

Here is a gist with complete test.

Next time you are using an after_commit callback and a transaction, make sure that code inside the transaction is not dependent on the result of the callback.