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 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.
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
Private Instance Methods
# File lib/active_record/locking/optimistic.rb, line 114 def destroy_row affected_rows = super if locking_enabled? && affected_rows != 1 raise ActiveRecord::StaleObjectError.new(self, "destroy") end affected_rows end
# File lib/active_record/locking/optimistic.rb, line 63 def increment_lock lock_col = self.class.locking_column previous_lock_value = send(lock_col).to_i send(lock_col + '=', previous_lock_value + 1) end
# File lib/active_record/locking/optimistic.rb, line 124 def relation_for_destroy relation = super if locking_enabled? column_name = self.class.locking_column column = self.class.columns_hash[column_name] substitute = self.class.connection.substitute_at(column) relation = relation.where(self.class.arel_table[column_name].eq(substitute)) relation.bind_values << [column, self[column_name].to_i] end relation end