This blog is part of our Rails 6 series. Rails 6.0 was recently released.

Rails 6 fixes a bug where after_commit callbacks are called on failed update in a transaction block.

Let’s checkout the bug in Rails 5.2 and the fix in Rails 6.

Rails 5.2

Let’s define an after_commit callback in User model and try updating an invalid user object in a transaction block.

>> class User < ApplicationRecord >> validates :name , :email , presence: true >> >> after_commit :show_success_message >> >> private >> >> def show_success_message >> p 'User has been successfully saved into the database.' >> end >> end => :show_success_message >> user = User . create ( name: 'Jon Snow' , email: 'jon@bigbinary.com' ) begin transaction User Create ( 0.8 ms ) INSERT INTO "users" ( "name" , "email" , "created_at" , "updated_at" ) VALUES ( ?, ?, ?, ?) [[ "name" , "Jon Snow" ], [ "email" , "jon@bigbinary.com" ], [ "created_at" , "2019-07-14 15:35:33.517694" ], [ "updated_at" , "2019-07-14 15:35:33.517694" ]] commit transaction "User has been successfully saved into the database." => #<User id: 1, name: "Jon Snow", email: "jon@bigbinary.com", created_at: "2019-07-14 15:35:33", updated_at: "2019-07-14 15:35:33"> >> User . transaction do >> user . email = nil >> p user . valid? >> user . save >> end begin transaction false commit transaction "User has been successfully saved into the database." => false

As we can see here, that that the after_commit callback show_success_message was called even if object was never saved in the transaction.

Rails 6.0.0.rc1

Now, let’s try the same thing in Rails 6.

>> class User < ApplicationRecord >> validates :name , :email , presence: true >> >> after_commit :show_success_message >> >> private >> >> def show_success_message >> p 'User has been successfully saved into the database.' >> end >> end => :show_success_message >> user = User . create ( name: 'Jon Snow' , email: 'jon@bigbinary.com' ) SELECT sqlite_version ( * ) begin transaction User Create ( 1.0 ms ) INSERT INTO "users" ( "name" , "email" , "created_at" , "updated_at" ) VALUES ( ?, ?, ?, ?) [[ "name" , "Jon Snow" ], [ "email" , "jon@bigbinary.com" ], [ "created_at" , "2019-07-14 15:40:54.022045" ], [ "updated_at" , "2019-07-14 15:40:54.022045" ]] commit transaction "User has been successfully saved into the database." => #<User id: 1, name: "Jon Snow", email: "jon@bigbinary.com", created_at: "2019-07-14 15:40:54", updated_at: "2019-07-14 15:40:54"> >> User . transaction do >> user . email = nil >> p user . valid? >> user . save >> end false => false

Now, we can see that after_commit callback was never called if the object was not saved.

Here is the relevant issue and the pull request.