Module: ActiveRecord::Locking::Optimistic

Defined in:
lib/active_record/locking/optimistic.rb

Overview

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

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

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object

:nodoc:



25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/active_record/locking/optimistic.rb', line 25

def self.included(base) #:nodoc:
  super
  base.extend ClassMethods

  base.cattr_accessor :lock_optimistically, :instance_writer => false
  base.lock_optimistically = true

  base.alias_method_chain :update, :lock
  base.alias_method_chain :attributes_from_column_definition, :lock
  
  class << base
    alias_method :locking_column=, :set_locking_column
  end
end

Instance Method Details

#attributes_from_column_definition_with_lockObject



44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/active_record/locking/optimistic.rb', line 44

def attributes_from_column_definition_with_lock
  result = attributes_from_column_definition_without_lock
  
  # If the locking column has no default value set,
  # start the lock version at zero.  Note we can't use
  # locking_enabled? at this point as @attributes may
  # not have been initialized yet
  
  if lock_optimistically && result.include?(self.class.locking_column)
    result[self.class.locking_column] ||= 0
  end
  
  return result
end

#locking_enabled?Boolean

:nodoc:

Returns:

  • (Boolean)


40
41
42
# File 'lib/active_record/locking/optimistic.rb', line 40

def locking_enabled? #:nodoc:
  lock_optimistically && respond_to?(self.class.locking_column)
end

#update_with_lockObject

:nodoc:



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/active_record/locking/optimistic.rb', line 59

def update_with_lock #:nodoc:
  return update_without_lock unless locking_enabled?

  lock_col = self.class.locking_column
  previous_value = send(lock_col)
  send(lock_col + '=', previous_value + 1)

  affected_rows = connection.update(<<-end_sql, "#{self.class.name} Update with optimistic locking")
    UPDATE #{self.class.table_name}
    SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))}
    WHERE #{self.class.primary_key} = #{quote_value(id)}
    AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}
  end_sql

  unless affected_rows == 1
    raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
  end

  return true
end