Here’s an example of how to use Rails’ optimistic locking to protect against concurrency issues while updating a user’s account balance.
First thing you want to do is isolate the column you want to lock. The reason for this is that Rails will raise a StaleObjectError whenever there are simultaneous updates to a table with a lock_version column. So if you smack this column onto your users table it means you have to rescue that error wherever you update a user record. Instead we break off the locked column into a new table and only do updates to that table from a one or a few small pieces of code. This makes handling the errors much easier.
So say we have:
class User < ActiveRecord::Base has_one :account after_create :create_account end class Account < ActiveRecord::Base # add_column :lock_version, :integer, :default => 0 # add_column :balance, :integer, :default => 0 belongs_to :user end
To deal with the actual updates to the balance we add a single method to User to handle them:
class User
def update_balance(amount)
account ||= self.account
account.balance += amount
account.save
account.balance
rescue ActiveRecord::StaleObjectError
account.reload
retry
end
end
This will correctly handle any number of simultaneous updates to the user’s balance. But now every time we want to query the user along with his balance we need to do a JOIN on the accounts table. Let’s denormalize. We can add a before_save callback on Account to perform the denormalization.
class User
# add_column :account_balance, :integer, :default => 0
end
class Account
before_save :denormalize_balance
def denormalize_balance
user.update_attribute :account_balance, balance
end
end
Now we can safely update the user’s account balance in parallel from multiple threads/processes and have denormalized the data so we can easily order by or access the balance directly from the users table.

hey steve, useful tip. hope all is well.
Comment by ben — February 10, 2009 @ 2:44 am