Skip to content
This repository
Browse code

Ensure Model#destroy respects optimistic locking [#1966 state:resolved]

Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
  • Loading branch information...
commit 0d922885fb54c19f04680482f024452859218910 1 parent 1e6c50e
authored March 09, 2009 lifo committed March 09, 2009
33  activerecord/lib/active_record/locking/optimistic.rb
@@ -23,6 +23,16 @@ module Locking
23 23
     #   p2.first_name = "should fail"
24 24
     #   p2.save # Raises a ActiveRecord::StaleObjectError
25 25
     #
  26
+    # Optimistic locking will also check for stale data when objects are destroyed.  Example:
  27
+    #
  28
+    #   p1 = Person.find(1)
  29
+    #   p2 = Person.find(1)
  30
+    #
  31
+    #   p1.first_name = "Michael"
  32
+    #   p1.save
  33
+    #
  34
+    #   p2.destroy # Raises a ActiveRecord::StaleObjectError
  35
+    #
26 36
     # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
27 37
     # or otherwise apply the business logic needed to resolve the conflict.
28 38
     #
@@ -39,6 +49,7 @@ def self.included(base) #:nodoc:
39 49
         base.lock_optimistically = true
40 50
 
41 51
         base.alias_method_chain :update, :lock
  52
+        base.alias_method_chain :destroy, :lock
42 53
         base.alias_method_chain :attributes_from_column_definition, :lock
43 54
 
44 55
         class << base
@@ -98,6 +109,28 @@ def update_with_lock(attribute_names = @attributes.keys) #:nodoc:
98 109
           end
99 110
         end
100 111
 
  112
+  def destroy_with_lock #:nodoc:
  113
+    return destroy_without_lock unless locking_enabled?
  114
+
  115
+    unless new_record?
  116
+      lock_col = self.class.locking_column
  117
+      previous_value = send(lock_col).to_i
  118
+
  119
+      affected_rows = connection.delete(
  120
+        "DELETE FROM #{self.class.quoted_table_name} " +
  121
+        "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id} " +
  122
+              "AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}",
  123
+        "#{self.class.name} Destroy"
  124
+      )
  125
+
  126
+      unless affected_rows == 1
  127
+        raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object"
  128
+      end
  129
+    end
  130
+
  131
+    freeze
  132
+  end
  133
+
101 134
       module ClassMethods
102 135
         DEFAULT_LOCKING_COLUMN = 'lock_version'
103 136
 
18  activerecord/test/cases/locking_test.rb
@@ -38,6 +38,24 @@ def test_lock_existing
38 38
     assert_raise(ActiveRecord::StaleObjectError) { p2.save! }
39 39
   end
40 40
 
  41
+  def test_lock_destroy
  42
+    p1 = Person.find(1)
  43
+    p2 = Person.find(1)
  44
+    assert_equal 0, p1.lock_version
  45
+    assert_equal 0, p2.lock_version
  46
+
  47
+    p1.first_name = 'stu'
  48
+    p1.save!
  49
+    assert_equal 1, p1.lock_version
  50
+    assert_equal 0, p2.lock_version
  51
+
  52
+    assert_raises(ActiveRecord::StaleObjectError) { p2.destroy }
  53
+
  54
+    assert p1.destroy
  55
+    assert_equal true, p1.frozen?
  56
+    assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) }
  57
+  end
  58
+
41 59
   def test_lock_repeating
42 60
     p1 = Person.find(1)
43 61
     p2 = Person.find(1)

1 note on commit 0d92288

alexander rakoczy

Awesome.

Georg Leciejewski

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

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

Please sign in to comment.
Something went wrong with that request. Please try again.