Skip to content

Commit

Permalink
Destroy association habtm record before destroying the record itself.…
Browse files Browse the repository at this point in the history
… Fixes issue rails#402.
  • Loading branch information
Tomas D'Stefano authored and jonleighton committed Jul 8, 2011
1 parent e9f2c67 commit 28f057c
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 10 deletions.
21 changes: 11 additions & 10 deletions activerecord/lib/active_record/associations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1411,7 +1411,7 @@ def has_and_belongs_to_many(association_id, options = {}, &extension)
reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)

configure_after_destroy_method_for_has_and_belongs_to_many(reflection)
configure_destroy_hook_for_has_and_belongs_to_many(reflection)

add_association_callbacks(reflection.name, options)
end
Expand Down Expand Up @@ -1705,15 +1705,16 @@ def #{method_name}
end
end

def configure_after_destroy_method_for_has_and_belongs_to_many(reflection)
method_name = :"has_and_belongs_to_many_after_destroy_for_#{reflection.name}"
class_eval <<-eoruby, __FILE__, __LINE__ + 1
def #{method_name}
association = #{reflection.name}
association.delete_all if association
end
eoruby
after_destroy method_name
def configure_destroy_hook_for_has_and_belongs_to_many(reflection)
include(Module.new {
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def destroy_associations
association = #{reflection.name}
association.delete_all if association
super
end
RUBY
})
end

def delete_all_has_many_dependencies(record, reflection_name, association_class, dependent_conditions)
Expand Down
7 changes: 7 additions & 0 deletions activerecord/lib/active_record/persistence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ def delete
# Deletes the record in the database and freezes this instance to reflect
# that no changes should be made (since they can't be persisted).
def destroy
destroy_associations

if persisted?
self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
end
Expand Down Expand Up @@ -245,6 +247,11 @@ def touch(name = nil)
end

private

# A hook to be overriden by association modules.
def destroy_associations
end

def create_or_update
raise ReadOnlyRecord if readonly?
result = new_record? ? create : update
Expand Down
10 changes: 10 additions & 0 deletions activerecord/test/cases/habtm_destroy_order_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ class HabtmDestroyOrderTest < ActiveRecord::TestCase
assert !sicp.destroyed?
end

test 'should not raise error if have foreign key in the join table' do
student = Student.new(:name => "Ben Bitdiddle")
lesson = Lesson.new(:name => "SICP")
lesson.students << student
lesson.save!
assert_nothing_raised do
student.destroy
end
end

test "not destroying a student with lessons leaves student<=>lesson association intact" do
# test a normal before_destroy doesn't destroy the habtm joins
begin
Expand Down
2 changes: 2 additions & 0 deletions activerecord/test/schema/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,8 @@ def create_table(*args, &block)
end

execute "ALTER TABLE fk_test_has_fk ADD CONSTRAINT fk_name FOREIGN KEY (#{quote_column_name 'fk_id'}) REFERENCES #{quote_table_name 'fk_test_has_pk'} (#{quote_column_name 'id'})"

execute "ALTER TABLE lessons_students ADD CONSTRAINT student_id_fk FOREIGN KEY (#{quote_column_name 'student_id'}) REFERENCES #{quote_table_name 'students'} (#{quote_column_name 'id'})"
end
end

Expand Down

0 comments on commit 28f057c

Please sign in to comment.