In one of my projects, I wanted to prevent instances of particular classes to be deleted if they were in any way associated to another object. In database terms, I wanted an ON DELETE RESTRICT constraint.
Since I cannot rely on the database to enforce it for me (MySQL 4, MyISAM engine), I coded the following:
test/unit/city_test.rb
1 require File.dirname(__FILE__) + '/../test_helper' 2 3 class CityTest < Test::Unit::TestCase 4 fixtures :cities, :contacts 5 6 def setup 7 @city = City.find(:first) 8 end 9 10 def test_prevent_destruction_if_associated_to_any_contact 11 @city.contacts << contacts(:jill) 12 @city.destroy 13 assert_not_nil City.find(@city.id), 'should not have been destroyed' 14 assert_match /cannot destroy.*contacts/i, @city.errors.on_base, 15 'reports error condition to user' 16 end 17 18 def test_allow_destruction_if_not_associated_to_any_contact 19 @city.destroy 20 assert_raises ActiveRecord::RecordNotFound do 21 City.find(@city.id) 22 end 23 end 24 end
app/models/city.rb
1 class City < ActiveRecord::Base 2 has_and_belongs_to_many :contacts, :join_table => 'contacts_cities' 3 4 def destroy 5 unless self.contacts.empty? 6 self.errors.add_to_base \ 7 'We cannot destroy this instance since one or more contacts refer to it') 8 return 9 end 10 11 super 12 end 13 end
I had to override destroy because in has_and_belongs_to_many relationships, Rails deletes join table records before deleting the record. This means that in before_destroy filters, self.contacts.empty? will always report true. Ticket #1183: dependents are destroyed before client before_destroy hooks are called is already opened on this issue.
UPDATE (2006-03-08) Fixed since 2006-02-13