diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 1a2cd15458..0ef11609ee 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,11 @@ *SVN* +* Allow :dependent options to be used with polymorphic joins. #3820 [Rick Olson] + + class Foo < ActiveRecord::Base + has_many :attachments, :as => :attachable, :dependent => :delete_all + end + * Upgrade to Transaction::Simple 1.3 [Jamis Buck] * Catch FixtureClassNotFound when using instantiated fixtures on a fixture that has no ActiveRecord model [Rick Olson] diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index a098c0f326..5d9672a0d9 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -485,7 +485,7 @@ module ActiveRecord if association.updated? self["#{reflection.primary_key_name}"] = association.id - self["#{reflection.options[:foreign_type]}"] = ActiveRecord::Base.send(:class_name_of_active_record_descendant, association.class).to_s + self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s end end EOF @@ -811,13 +811,20 @@ module ActiveRecord # See HasManyAssociation#delete_records. Dependent associations # delete children, otherwise foreign key is set to NULL. + + # Add polymorphic type if the :as option is present + dependent_conditions = %(#{reflection.primary_key_name} = \#{record.quoted_id}) + if reflection.options[:as] + dependent_conditions += " AND #{reflection.options[:as]}_type = '#{base_class.name}'" + end + case reflection.options[:dependent] when :destroy, true module_eval "before_destroy '#{reflection.name}.each { |o| o.destroy }'" when :delete_all - module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{reflection.primary_key_name} = \#{record.quoted_id})) }" + module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }" when :nullify - module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL), %(#{reflection.primary_key_name} = \#{record.quoted_id})) }" + module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL), %(#{dependent_conditions})) }" when nil, false # pass else diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 32e812a86a..e54e38e184 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -118,10 +118,11 @@ module ActiveRecord def primary_key_name return @primary_key_name if @primary_key_name - - case macro - when :belongs_to + case + when macro == :belongs_to @primary_key_name = options[:foreign_key] || class_name.foreign_key + when options[:as] + @primary_key_name = options[:foreign_key] || "#{options[:as]}_id" else @primary_key_name = options[:foreign_key] || active_record.name.foreign_key end diff --git a/activerecord/test/associations_join_model_test.rb b/activerecord/test/associations_join_model_test.rb index b14693d7a6..2d74895664 100644 --- a/activerecord/test/associations_join_model_test.rb +++ b/activerecord/test/associations_join_model_test.rb @@ -69,13 +69,72 @@ class AssociationsJoinModelTest < Test::Unit::TestCase end def test_create_polymorphic_has_many_with_scope - tagging = posts(:welcome).taggings.create(:tag => tags(:general)) + old_count = posts(:welcome).taggings.count + tagging = posts(:welcome).taggings.create(:tag => tags(:misc)) assert_equal "Post", tagging.taggable_type + assert_equal old_count+1, posts(:welcome).taggings.count end def test_create_polymorphic_has_one_with_scope - tagging = posts(:welcome).tagging.create(:tag => tags(:general)) + old_count = Tagging.count + tagging = posts(:welcome).tagging.create(:tag => tags(:misc)) assert_equal "Post", tagging.taggable_type + assert_equal old_count+1, Tagging.count + end + + def test_delete_polymorphic_has_many_with_delete_all + assert_equal 1, posts(:welcome).taggings.count + posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDeleteAll' + post = find_post_with_dependency(1, :has_many, :taggings, :delete_all) + + old_count = Tagging.count + post.destroy + assert_equal old_count-1, Tagging.count + assert_equal 0, posts(:welcome).taggings.count + end + + def test_delete_polymorphic_has_many_with_destroy + assert_equal 1, posts(:welcome).taggings.count + posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDestroy' + post = find_post_with_dependency(1, :has_many, :taggings, :destroy) + + old_count = Tagging.count + post.destroy + assert_equal old_count-1, Tagging.count + assert_equal 0, posts(:welcome).taggings.count + end + + def test_delete_polymorphic_has_many_with_nullify + assert_equal 1, posts(:welcome).taggings.count + posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyNullify' + post = find_post_with_dependency(1, :has_many, :taggings, :nullify) + + old_count = Tagging.count + post.destroy + assert_equal old_count, Tagging.count + assert_equal 0, posts(:welcome).taggings.count + end + + def test_delete_polymorphic_has_one_with_destroy + assert posts(:welcome).tagging + posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneDestroy' + post = find_post_with_dependency(1, :has_one, :tagging, :destroy) + + old_count = Tagging.count + post.destroy + assert_equal old_count-1, Tagging.count + assert_nil posts(:welcome).tagging(true) + end + + def test_delete_polymorphic_has_one_with_nullify + assert posts(:welcome).tagging + posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneNullify' + post = find_post_with_dependency(1, :has_one, :tagging, :nullify) + + old_count = Tagging.count + post.destroy + assert_equal old_count, Tagging.count + assert_nil posts(:welcome).tagging(true) end def test_has_many_with_piggyback @@ -139,4 +198,15 @@ class AssociationsJoinModelTest < Test::Unit::TestCase tagging.destroy assert posts(:welcome, :reload)[:taggings_count].zero? end + + private + # create dynamic Post models to allow different dependency options + def find_post_with_dependency(post_id, association, association_name, dependency) + class_name = "PostWith#{association.to_s.classify}#{dependency.to_s.classify}" + Post.find(post_id).update_attribute :type, class_name + klass = Object.const_set(class_name, Class.new(ActiveRecord::Base)) + klass.set_table_name 'posts' + klass.send(association, association_name, :as => :taggable, :dependent => dependency) + klass.find(post_id) + end end diff --git a/activerecord/test/fixtures/post.rb b/activerecord/test/fixtures/post.rb index 7c1f8e1961..e5b95aff32 100644 --- a/activerecord/test/fixtures/post.rb +++ b/activerecord/test/fixtures/post.rb @@ -18,7 +18,7 @@ class Post < ActiveRecord::Base has_many :special_comments has_and_belongs_to_many :categories - has_and_belongs_to_many :special_categories, :join_table => "categories_posts" + has_and_belongs_to_many :special_categories, :join_table => "categories_posts", :association_foreign_key => 'category_id' has_many :taggings, :as => :taggable has_many :tags, :through => :taggings diff --git a/activerecord/test/fixtures/taggings.yml b/activerecord/test/fixtures/taggings.yml index dced625580..38ec1ba5b1 100644 --- a/activerecord/test/fixtures/taggings.yml +++ b/activerecord/test/fixtures/taggings.yml @@ -9,3 +9,9 @@ thinking_general: tag_id: 1 taggable_id: 2 taggable_type: Post + +fake: + id: 3 + tag_id: 1 + taggable_id: 1 + taggable_type: FakeModel \ No newline at end of file