aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record
diff options
context:
space:
mode:
authorCurtis Hawthorne <curtis@thefjord.org>2010-02-21 22:47:30 -0500
committerJeremy Kemper <jeremy@bitsweat.net>2010-04-27 21:53:51 -0700
commit7e06494e32f944f8c99d7d21e17224509332ee6b (patch)
tree39694f359da970abf03fb1da66865e31b9baee28 /activerecord/lib/active_record
parentef5dadaf93f9cb5cffd5913cdee3b9278d9c5c65 (diff)
downloadrails-7e06494e32f944f8c99d7d21e17224509332ee6b.tar.gz
rails-7e06494e32f944f8c99d7d21e17224509332ee6b.tar.bz2
rails-7e06494e32f944f8c99d7d21e17224509332ee6b.zip
Destroy respects optimistic locking.
Now works with :dependent => :destroy and includes unit tests for that case. Also includes better error messages when updating/deleting stale objects. [#1966 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
Diffstat (limited to 'activerecord/lib/active_record')
-rwxr-xr-xactiverecord/lib/active_record/associations.rb11
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb35
2 files changed, 44 insertions, 2 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index d94cc03938..6dbee9f4bf 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1500,7 +1500,16 @@ module ActiveRecord
when :destroy
method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
define_method(method_name) do
- send(reflection.name).each { |o| o.destroy }
+ send(reflection.name).each do |o|
+ # No point in executing the counter update since we're going to destroy the parent anyway
+ counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym
+ if(o.respond_to? counter_method) then
+ class << o
+ self
+ end.send(:define_method, counter_method, Proc.new {})
+ end
+ o.destroy
+ end
end
before_destroy method_name
when :delete_all
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 9044ca418b..60ad23f38c 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -23,6 +23,16 @@ module ActiveRecord
# 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.
#
@@ -39,6 +49,7 @@ module ActiveRecord
self.lock_optimistically = true
alias_method_chain :update, :lock
+ alias_method_chain :destroy, :lock
alias_method_chain :attributes_from_column_definition, :lock
class << self
@@ -88,7 +99,7 @@ module ActiveRecord
unless affected_rows == 1
- raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
+ raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}"
end
affected_rows
@@ -100,6 +111,28 @@ module ActiveRecord
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: #{self.class.name}"
+ end
+ end
+
+ freeze
+ end
+
module ClassMethods
DEFAULT_LOCKING_COLUMN = 'lock_version'