ActiveRecord::Locking::Optimistic
What is Optimistic Locking
Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of conflicts with the data. It does this by checking whether another process has made changes to a record since it was opened, an ActiveRecord::StaleObjectError is thrown if that has occurred and the update is ignored.
Check out ActiveRecord::Locking::Pessimistic for an alternative.
Usage
Active Records support optimistic locking if the field lock_version is present. Each update to the record increments the lock_version column and the locking facilities ensure that records instantiated twice will let the last one saved raise a StaleObjectError if the first was also updated. Example:
p1 = Person.find(1) p2 = Person.find(1) p1.first_name = "Michael" p1.save p2.first_name = "should fail" p2.save # Raises a ActiveRecord::StaleObjectError
Optimistic locking will also check for stale data when objects are destroyed. Example:
p1 = Person.find(1) p2 = Person.find(1) p1.first_name = "Michael" p1.save p2.destroy # Raises a ActiveRecord::StaleObjectError
You’re then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging, or otherwise apply the business logic needed to resolve the conflict.
You must ensure that your database schema defaults the lock_version column to 0.
This behavior can be turned off by setting ActiveRecord::Base.lock_optimistically = false. To override the name of the lock_version column, invoke the set_locking_column method. This method uses the same syntax as set_table_name
Files
- activerecord/lib/active_record/locking/optimistic.rb
Nested classes and modules
1Note
How I use Optimistic Locking
I have used Optimistic locking often, but usually I only need it in one or two places in the codebase, not everywhere an object is saved whose model has a lock_version column. So what I usually end up doing is using a little module I wrote called OptimisticallyLockable (awesome name right?). Here it is:
module OptimisticallyLockable
def self.included(receiver)
receiver.lock_optimistically = false
receiver.class_eval do
def self.with_optimistic_locking
original_lock = self.lock_optimistically
self.lock_optimistically = true
begin
yield
ensure
self.lock_optimistically = original_lock
end
end
end
end
end
When included in a model that has a lock_version column it will turn off optimistic locking. Then when you want to actually use optimistic locking you can just use the with_optimistic_locking method like this:
class Blog
include OptimisticallyLockable
def do_something_destructive!
self.class.with_optimistic_locking do
# do something important here
end
end
end