- 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 fromActiveRecord::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:
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:
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:
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 Order
, Vendor
, or 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 ActiveRecord::Base
!
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
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:
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.