Safer SQL: Using ActiveRecord Transactions
transactionmethod takes a block, and will only execute the block and write to your database if no exceptions are raised.
- You can defined the
transactionmethod on any class that inherits from
ActiveRecord::Base, and that transaction will open up a single new database connection.
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:
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.
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:
Order.transaction do @order.process @user.charge @vendor.add_sale end
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:
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:
@order.transaction do end @user.transaction do end @vendor.transaction do end
And if we were writing a method inside of the
User classes, these options would have worked as well:
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
So, what do? Well, just call the transaction method on to
ActiveRecord::Base directly! there’s a quick fix for that.
ActiveRecord::Base.transaction do # methods we want to call go here end
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
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
destroy! methods instead! These are both ActiveRecord methods which raise an exception if they don’t execute successfully:
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.