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</span> = <span class="co">City</span>.find(<span class="sy">:first</span>) <span class="no"> 8</span> <span class="r">end</span> <span class="no"> 9</span> <span class="no"><strong>10</strong></span> <span class="r">def</span> <span class="fu">test_prevent_destruction_if_associated_to_any_contact</span> <span class="no">11</span> <span class="iv">city.contacts << contacts(:jill)
12 city</span>.destroy <span class="no">13</span> assert_not_nil <span class="co">City</span>.find(<span class="iv">city.id), should not have been destroyed
14 assert_match /cannot destroy.*contacts/i, city</span>.errors.on_base, <span class="no"><strong>15</strong></span> <span class="s"><span class="dl">'</span><span class="k">reports error condition to user</span><span class="dl">'</span></span> <span class="no">16</span> <span class="r">end</span> <span class="no">17</span> <span class="no">18</span> <span class="r">def</span> <span class="fu">test_allow_destruction_if_not_associated_to_any_contact</span> <span class="no">19</span> <span class="iv">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

Search

Your Host

A picture of me

I am François Beausoleil, a Ruby on Rails and Scala developer. During the day, I work on Seevibes, a platform to measure social interactions related to TV shows. At night, I am interested many things. Read my biography.

Top Tags

Books I read and recommend

Links

Projects I work on

Projects I worked on