Rails transactions: The Complete Guide

Rails transactions: The Complete Guide

  • 2016-09-19
  • 592

Rails transactions are a way to ensure that a set of database operations will only occur if all of them succeed. Otherwise they will rollback to the previous state of data.

Our examples will demonstrate it in the most useful scenario for transactions: money transfers. In this case you only want Ted to receive money if John loses the same money.

Basic usage

 
def transfer_money
 
  ActiveRecord::Base.transaction do
    john.update!(money: john.money + 100)
    ted.update!(money: ted.money - 100)
  end
 
end

Simple, isn’t it?

This way if any error happens inside the transaction block the entire operations will be roll-backed to the previous state.

This is why we use the .update! instead of just .update. Since a normal update doesn’t raise errors and just return “false”, it would not trigger a rollback in the transaction.

Remember that the errors thrown by the .update! will still show an error page for you user. But we will learn how to treat that in the next topic.

Rescuing from Transaction Errors

Since we are using the ! methods from ActiveRecord, we should expect some errors to occur. The most common of them is the ActiveRecord::RecordInvalid.

This error is thrown when for some reason the changes you want to make in the records would turn them invalid.

Other errors may occur in your rails transactions and it is up to you to decide which ones you will treat.

def transfer_money(amount)
 
  ActiveRecord::Base.transaction do
    john.update!(money: john.money + amount)
    ted.update!(money: ted.money - amount)
  end
 
rescue ActiveRecord::RecordInvalid
  puts "Oops. We tried to do an invalid operation!"
end
 

In this example the “amount” argument could be invalid, leading to an invalid value for the “money” attribute.

We take care of this rescuing from the RecordInvalid error and printing out a friendly message to our users.

Triggering a Rollback

Sometimes we want to cancel the transaction manually. Like in the following example:

def transfer_money
 
  ActiveRecord::Base.transaction do
    john.update!(money: john.money + 100)
    ted.update!(money: ted.money - 100)
    raise ActiveRecord::Rollback if john.is_an_asshole?
  end
 
end

In this scenario we raise an error according to the business logic of our application.

One thing to take note is that the ActiveRecord::Rollback error does not actually raises an error, it is just used to trigger the rollback from the inside of a transaction.

Nested transactions

But what happens when we have this?

def transfer_money
 
  ActiveRecord::Base.transaction do
    john.update!(money: john.money + 100)
    ted.update!(money: ted.money - 100)
 
    ActiveRecord::Base.transaction do
      transfer.create!(amount: 100, receiver: john, sender: ted)
    end
 
  end
 
end

In this case each transaction will happen and rollback independently of one another. But will still be tied to the same database connection (see the bonus tips for a little more insight on this).

A thing to notice is that in the example above an error inside the inner transaction WILL rollback the outer transaction, because we are not rescuing anything.

Bonus tips!

Different ways to call a transaction

Along the guide we only showed examples of rails transactions using:

ActiveRecord::Base.transaction do

But the same thing could be archived by:

User.transaction do

Or even:

john.transaction do

There is actually no difference between any of those, but I personally try to use record.transaction as much as I can, because I find it easier to test the transaction later (for example, making a mocked record that responds to the method .transaction with an error).

Rails transactions are tied to one database connection

And as long as the transaction block is running this one database connection is open. So try to do as little as needed inside the transaction block, otherwise you will be blocking a database connection for more time than you should.

Know something that is not in the guide? Share with us in the comments

Suggest

Ruby on Rails for Complete Beginners

Beginners Ruby Programming Training - No Experience Required

The Professional Ruby on Rails Developer

Make Medium (medium.com) Clone in Ruby on Rails

The Complete Ruby on Rails Developer Course

Python and Ruby programming Complete step-by-step Tutorial