module

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 exception is thrown if that has occurred and the update is ignored.

Check out ActiveRecord::Locking::Pessimistic for an alternative.

Usage

Active Record supports optimistic locking if the lock_version field 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 an 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 an 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.

This locking mechanism will function inside a single Ruby process. To make it work across all web requests, the recommended approach is to add lock_version as a hidden field to your form.

This behavior can be turned off by setting ActiveRecord::Base.lock_optimistically = false. To override the name of the lock_version column, set the locking_column class attribute:

class Person < ActiveRecord::Base
  self.locking_column = :lock_person
end

Files

  • activerecord/lib/active_record/locking/optimistic.rb

Nested classes and modules

1Note

How I use Optimistic Locking

rubymaverick · Feb 4, 20091 thank

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