From 35b4bdcff0e697765b562a74bd881e78de97f4d1 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Tue, 8 Nov 2005 10:19:09 +0000 Subject: Destroy associated has_and_belongs_to_many records after all before_destroy callbacks but before destroy. This allows you to act on the habtm association as you please while preserving referential integrity. Closes #2065. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2940 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- activerecord/CHANGELOG | 2 ++ activerecord/lib/active_record/associations.rb | 13 +++++++++++-- activerecord/test/associations_test.rb | 9 ++++++--- activerecord/test/fixtures/developer.rb | 10 ++++++++++ 4 files changed, 29 insertions(+), 5 deletions(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 32d9ddda0b..76b4d0062e 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Destroy associated has_and_belongs_to_many records after all before_destroy callbacks but before destroy. This allows you to act on the habtm association as you please while preserving referential integrity. #2065 [larrywilliams1@gmail.com, sam.kirchmeier@gmail.com, elliot@townx.org, Jeremy Kemper] + * Deprecate the old, confusing :exclusively_dependent option in favor of :dependent => :delete_all. [Jeremy Kemper] * More compatible Oracle column reflection. #2771 [Ryan Davis , Michael Schoen ] diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 077245559a..a14aed1700 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -652,8 +652,17 @@ module ActiveRecord collection_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasAndBelongsToManyAssociation) - before_destroy_sql = "DELETE FROM #{options[:join_table]} WHERE #{association_class_primary_key_name} = \\\#{self.quoted_id}" - module_eval(%{before_destroy "self.connection.delete(%{#{before_destroy_sql}})"}) # " + # Don't use a before_destroy callback since users' before_destroy + # callbacks will be executed after the association is wiped out. + old_method = "destroy_without_habtm_shim_for_#{association_name}" + class_eval <<-end_eval + alias_method :#{old_method}, :destroy_without_callbacks + def destroy_without_callbacks + #{association_name}.clear + #{old_method} + end + end_eval + add_association_callbacks(association_name, options) # deprecated api diff --git a/activerecord/test/associations_test.rb b/activerecord/test/associations_test.rb index fc2afb721f..4ad40f338c 100755 --- a/activerecord/test/associations_test.rb +++ b/activerecord/test/associations_test.rb @@ -1246,10 +1246,13 @@ class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase end def test_removing_associations_on_destroy - Developer.find(1).destroy - assert Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1").empty? + david = DeveloperWithBeforeDestroyRaise.find(1) + assert !david.projects.empty? + assert_nothing_raised { david.destroy } + assert david.projects.empty? + assert DeveloperWithBeforeDestroyRaise.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1").empty? end - + def test_additional_columns_from_join_table # SQL Server doesn't have a separate column type just for dates, # so the time is in the string and incorrectly formatted diff --git a/activerecord/test/fixtures/developer.rb b/activerecord/test/fixtures/developer.rb index ce65ff78c0..29555d926a 100644 --- a/activerecord/test/fixtures/developer.rb +++ b/activerecord/test/fixtures/developer.rb @@ -28,3 +28,13 @@ class DeveloperWithAggregate < ActiveRecord::Base self.table_name = 'developers' composed_of :salary, :class_name => 'DeveloperSalary', :mapping => [%w(salary amount)] end + +class DeveloperWithBeforeDestroyRaise < ActiveRecord::Base + self.table_name = 'developers' + has_and_belongs_to_many :projects, :join_table => 'developers_projects', :foreign_key => 'developer_id' + before_destroy :raise_if_projects_empty! + + def raise_if_projects_empty! + raise if projects.empty? + end +end -- cgit v1.2.3