diff options
Diffstat (limited to 'activerecord/lib/active_record/locking')
-rw-r--r-- | activerecord/lib/active_record/locking/optimistic.rb | 117 | ||||
-rw-r--r-- | activerecord/lib/active_record/locking/pessimistic.rb | 2 |
2 files changed, 62 insertions, 57 deletions
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 67b8efac66..2659c60f1f 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -47,6 +47,8 @@ module ActiveRecord # self.locking_column = :lock_person # end # + # Please note that the optimistic locking will be ignored if you update the + # locking column's value. module Optimistic extend ActiveSupport::Concern @@ -60,13 +62,14 @@ module ActiveRecord end private + def increment_lock lock_col = self.class.locking_column previous_lock_value = send(lock_col).to_i - send(lock_col + '=', previous_lock_value + 1) + send(lock_col + "=", previous_lock_value + 1) end - def _create_record(attribute_names = self.attribute_names, *) # :nodoc: + def _create_record(attribute_names = self.attribute_names, *) if locking_enabled? # We always want to persist the locking version, even if we don't detect # a change from the default, since the database might have no default @@ -75,23 +78,26 @@ module ActiveRecord super end - def _update_record(attribute_names = self.attribute_names) #:nodoc: + def _update_record(attribute_names = self.attribute_names) return super unless locking_enabled? - return 0 if attribute_names.empty? lock_col = self.class.locking_column - previous_lock_value = send(lock_col).to_i - increment_lock - attribute_names += [lock_col] - attribute_names.uniq! + return super if attribute_names.include?(lock_col) + return 0 if attribute_names.empty? begin + previous_lock_value = read_attribute_before_type_cast(lock_col) + + increment_lock + + attribute_names.push(lock_col) + relation = self.class.unscoped affected_rows = relation.where( self.class.primary_key => id, - lock_col => previous_lock_value, + lock_col => previous_lock_value ).update_all( attributes_for_update(attribute_names).map do |name| [name, _read_attribute(name)] @@ -104,9 +110,9 @@ module ActiveRecord affected_rows - # If something went wrong, revert the version. + # If something went wrong, revert the locking_column value. rescue Exception - send(lock_col + '=', previous_lock_value) + send(lock_col + "=", previous_lock_value.to_i) raise end end @@ -132,59 +138,58 @@ module ActiveRecord relation end - module ClassMethods - DEFAULT_LOCKING_COLUMN = 'lock_version' + module ClassMethods + DEFAULT_LOCKING_COLUMN = "lock_version" - # Returns true if the +lock_optimistically+ flag is set to true - # (which it is, by default) and the table includes the - # +locking_column+ column (defaults to +lock_version+). - def locking_enabled? - lock_optimistically && columns_hash[locking_column] - end + # Returns true if the +lock_optimistically+ flag is set to true + # (which it is, by default) and the table includes the + # +locking_column+ column (defaults to +lock_version+). + def locking_enabled? + lock_optimistically && columns_hash[locking_column] + end - # Set the column to use for optimistic locking. Defaults to +lock_version+. - def locking_column=(value) - reload_schema_from_cache - @locking_column = value.to_s - end + # Set the column to use for optimistic locking. Defaults to +lock_version+. + def locking_column=(value) + reload_schema_from_cache + @locking_column = value.to_s + end - # The version column used for optimistic locking. Defaults to +lock_version+. - def locking_column - @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column) - @locking_column - end + # The version column used for optimistic locking. Defaults to +lock_version+. + def locking_column + @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column) + @locking_column + end - # Reset the column used for optimistic locking back to the +lock_version+ default. - def reset_locking_column - self.locking_column = DEFAULT_LOCKING_COLUMN - end + # Reset the column used for optimistic locking back to the +lock_version+ default. + def reset_locking_column + self.locking_column = DEFAULT_LOCKING_COLUMN + end - # Make sure the lock version column gets updated when counters are - # updated. - def update_counters(id, counters) - counters = counters.merge(locking_column => 1) if locking_enabled? - super - end + # Make sure the lock version column gets updated when counters are + # updated. + def update_counters(id, counters) + counters = counters.merge(locking_column => 1) if locking_enabled? + super + end - private - - # We need to apply this decorator here, rather than on module inclusion. The closure - # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the - # sub class being decorated. As such, changes to `lock_optimistically`, or - # `locking_column` would not be picked up. - def inherited(subclass) - subclass.class_eval do - is_lock_column = ->(name, _) { lock_optimistically && name == locking_column } - decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type| - LockingType.new(type) + private + + # We need to apply this decorator here, rather than on module inclusion. The closure + # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the + # sub class being decorated. As such, changes to `lock_optimistically`, or + # `locking_column` would not be picked up. + def inherited(subclass) + subclass.class_eval do + is_lock_column = ->(name, _) { lock_optimistically && name == locking_column } + decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type| + LockingType.new(type) + end + end + super end - end - super end - end end - # In de/serialize we change `nil` to 0, so that we can allow passing # `nil` values to `lock_version`, and not result in `ActiveRecord::StaleObjectError` # during update record. @@ -198,11 +203,11 @@ module ActiveRecord end def init_with(coder) - __setobj__(coder['subtype']) + __setobj__(coder["subtype"]) end def encode_with(coder) - coder['subtype'] = __getobj__ + coder["subtype"] = __getobj__ end end end diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb index 8ecdf76b72..e73cb4fc12 100644 --- a/activerecord/lib/active_record/locking/pessimistic.rb +++ b/activerecord/lib/active_record/locking/pessimistic.rb @@ -59,7 +59,7 @@ module ActiveRecord # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns # the locked record. def lock!(lock = true) - reload(:lock => lock) if persisted? + reload(lock: lock) if persisted? self end |