Posted over 5 years ago. Visible to the public. Linked content.

Safer SQL: Using ActiveRecord Transactions

  • ActiveRecord’s transaction method takes a block, and will only execute the block and write to your database if no exceptions are raised.
  • You can defined the transaction method on any class that inherits from ActiveRecord::Base, and that transaction will open up a single new database connection.

1. Opening database connections

A transaction opens up a single database connection. This means that when we call the transaction method, the method can only be invoked on the current database connection. This is important to remember if our application writes to multiple database at once; for example, if our Order and our Vendor data lived in two different databases, we’d need to nest our transactions:

Copy
Order.transaction do Vendor.transaction do order.process user.charge vendor.add_sale end end

It’s generally a good idea to avoid nested transactions, mostly because the relationship between parent and child transactions can get complicated. This is especially the case because rollbacks are contained inside of their transactions blocks.

2. Different classes, one transaction

Because transactions are bound to database connections, we can mix different types of models inside of a transaction block. In fact, that’s exactly what we were doing when we wrote our initial transaction:

Copy
Order.transaction do @order.process @user.charge @vendor.add_sale end

3. Class and instance methods

The great part about transaction is that it is available to us as both a class and an instance method for our ActiveRecord models. What does this mean, exactly? Well, the short answer is that we can write a transaction is lots of different ways, since we can invoke the transaction method on a class or an instance.
For example, we could have written this:

Copy
User.transaction do # methods we want to call go here end Vendor.transaction do # methods we want to call go here end

Or any of these:

Copy
@order.transaction do end @user.transaction do end @vendor.transaction do end

And if we were writing a method inside of the Order, Vendor, or User classes, these options would have worked as well:

Copy
self.transaction do end self.class.transaction do end

The key here is that the transaction can be called on any class that inherits from ActiveRecord::Base. Why is that the key? Well, you might remember that we initially started off wanting to write a transaction inside of our service object…right? In that case, we can’t use something like transaction do, because self is the service object class, which does not inherit from ActiveRecord::Base!
So, what do? Well, just call the transaction method on to ActiveRecord::Base directly! there’s a quick fix for that.

Copy
ActiveRecord::Base.transaction do # methods we want to call go here end

4. Exceptions are the rule

There’s one golden rule of the transaction block: it will only rollback the transaction if an error is raised. Why is this important? Well, calling something like save or destroy inside of a transaction will not raise an error; if something goes wrong, these methods will simply return false. Which means that our transaction block will continue, since there was no error raised! Uh oh…how to fix? Just use the save! and destroy! methods instead! These are both ActiveRecord methods which raise an exception if they don’t execute successfully:

Copy
ActiveRecord::Base.transaction do @order.destroy! @user.save! end

If we really, really wanted to use save instead of save!, we’d have to manually raise an error in the block for our transaction to work as expected.

Owner of this card:

Avatar
Alexander M
Last edit:
over 5 years ago
by Alexander M
Posted by Alexander M to Ruby and RoR knowledge base
This website uses short-lived cookies to improve usability.
Accept or learn more