Skip to content

Commit

Permalink
Ensure Model#destroy respects optimistic locking [#1966 state:resolved]
Browse files Browse the repository at this point in the history
Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
  • Loading branch information
Curtis Hawthorne authored and lifo committed Mar 9, 2009
1 parent 1e6c50e commit 0d92288
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 0 deletions.
33 changes: 33 additions & 0 deletions activerecord/lib/active_record/locking/optimistic.rb
Expand Up @@ -23,6 +23,16 @@ module Locking
# 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.
#
Expand All @@ -39,6 +49,7 @@ def self.included(base) #:nodoc:
base.lock_optimistically = true

base.alias_method_chain :update, :lock
base.alias_method_chain :destroy, :lock
base.alias_method_chain :attributes_from_column_definition, :lock

class << base
Expand Down Expand Up @@ -98,6 +109,28 @@ def update_with_lock(attribute_names = @attributes.keys) #:nodoc:
end
end

def destroy_with_lock #:nodoc:
return destroy_without_lock unless locking_enabled?

unless new_record?
lock_col = self.class.locking_column
previous_value = send(lock_col).to_i

affected_rows = connection.delete(
"DELETE FROM #{self.class.quoted_table_name} " +
"WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id} " +
"AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}",
"#{self.class.name} Destroy"
)

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

This comment has been minimized.

Copy link
@schorsch

schorsch Mar 30, 2009

would be nice to have more information about the object in stale:

raise ActiveRecord::StaleObjectError, “Attempted to delete a stale object: #{self.class.name}”

end
end

freeze
end

module ClassMethods
DEFAULT_LOCKING_COLUMN = 'lock_version'

Expand Down
18 changes: 18 additions & 0 deletions activerecord/test/cases/locking_test.rb
Expand Up @@ -38,6 +38,24 @@ def test_lock_existing
assert_raise(ActiveRecord::StaleObjectError) { p2.save! }
end

def test_lock_destroy
p1 = Person.find(1)
p2 = Person.find(1)
assert_equal 0, p1.lock_version
assert_equal 0, p2.lock_version

p1.first_name = 'stu'
p1.save!
assert_equal 1, p1.lock_version
assert_equal 0, p2.lock_version

assert_raises(ActiveRecord::StaleObjectError) { p2.destroy }

assert p1.destroy
assert_equal true, p1.frozen?
assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) }
end

def test_lock_repeating
p1 = Person.find(1)
p2 = Person.find(1)
Expand Down

1 comment on commit 0d92288

@toothrot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome.

Please sign in to comment.