Commit, Recover & Transactions in Ruby
The spec says 'transactions', we immediately think 'InnoDB'. But what if you're not using a database, working on a light-weight application, or simply want to ensure a graceful recovery process? In the past that usually meant layers of additional logic, variables to keep and capture intermediate state, and all the other associated headaches. That is, until Gregory Brown and Austin Ziegler published Transaction::Simple - a gem, both literally and figuratively, which makes transaction support in Ruby a walk in the park.
Transaction::Simple provides a generic way to inject transaction support into any object - that's right, any Ruby object. All the magic happens directly in memory, and thus there are no dependencies on additional backends. Your only theoretical limit is the memory of your machine, as object versions are created transparently and on the fly. In addition, the gem offers nested transactions, named transactions, transaction groups, commit and recovery procedures. Talk about fully featured! An example is due:
require 'rubygems'
require 'transaction/simple'
# Create a Ruby hash to store the balance
bank = {"Bob" => 100, "Jon" => 50}
# Add tranasction support to our Ruby hash
bank.extend(Transaction::Simple)
bank.start_transaction(:deposit)
bank['Bob'] += 25 # -> 125
bank.commit_transaction(:deposit)
# block-form usage
Transaction::Simple.start(bank) do |t|
# v has been extended with Transaction::Simple and an unnamed
t.transaction_open? # -> true
bank['Bob'] -= 25 # -> 100
# Hmm, I think we made an error, let's abort!
t.rewind_transaction
t.transaction_open? # -> true
bank['Bob'] -= 25 # -> 100
puts "Bob's balance: #{bank['Bob']}, expecting: 100"
# Break out of the transaction block and abort the transaction
t.abort_transaction
end
puts "Bob's balance: #{bank['Bob']}, expecting: 125"
# -> Bob's balance: 100, expecting: 100
# -> Bob's balance: 125, expecting: 125
Advanced Commit and Recovery
Applying similar patterns as we saw above, we can greatly simplify our recovery procedures in almost any Ruby program with minimal intervention:
# applying to arbitrary objects
obj = SomeObject.new
obj.extend(Transaction::Simple)
# Recover, rewind, and retry use-case
begin
v.start_transaction
if v.do_work
v.commit_transaction
else
v.abort_transaction
rescue Exception
v.rewind_transaction
v.recover
retry
end
And as if this is not enough, make sure to explore transaction groups once you dive into the documentation. Named transactions, in conjunction with transaction groups will allow you to easily manage a group of objects as if they were a single object. Man, I love Ruby. What is your (most recent) favorite gem?