:dependent => :destroy and before_destroy

I have a problem with one of my models. The parent object has 2
polymorphic relationships both specifying :dependent => :destroy. This
all works fine until you attempt to delete the parent and the parent’s
before_destroy callback returns false. When the parent’s before_destroy
callback returns false, the parent is not deleted, but the transaction
is still committed so all of the objects from the polymorphic
relationships are destroyed. This is obviously not desirable. I have
verified that the call back is returning false. Is there something I am
missing or is this just normal. I can remove the :dependent => :destroy
and place the relevant code in my before_destroy callback, but this
seems to defeat the purpose of :dependent and would get really messy on
objects with many dependencies.

Here is the abbreviated code in question:

class Location < AbstractBase

has_one :address, :as => :addressed, :dependent => :destroy
has_one :contact, :as => :contactable, :dependent => :destroy

before_destroy :any_dependent_assets?

end

class AbstractBase < ActiveRecord::Base

###########
protected
###########

#this is used as a filter to stop deletion if there are any dependent
assets
def any_dependent_assets?
if self.respond_to? :assets
c = self.assets.count
if c > 0
self.errors.add self.human_name, “is still assigned to #{c}
assets”
logger.debug “we are returning false”
return false
end
end
logger.debug “we are returning true”
true
end
end

Here is the log snippet from the transaction in question:

SQL (0.160060) BEGIN
Address Columns (0.158866) SHOW FIELDS FROM addresses
Address Load (0.163060) SELECT * FROM addresses WHERE
(addresses.addressed_id = 1 AND addresses.addressed_type = ‘Location’)
LIMIT 1
Address Destroy (0.159641)
WHERE id = 266

Contact Columns (0.035453) SHOW FIELDS FROM contacts
Contact Load (0.036147) SELECT * FROM contacts WHERE
(contacts.contactable_id = 1 AND contacts.contactable_type = ‘Location’)
LIMIT 1
Contact Destroy (0.031610)
WHERE id = 224

Asset Columns (0.036145) SHOW FIELDS FROM assets
SQL (0.029946) SELECT count(*) AS count_all FROM assets WHERE
(assets.deleted_at IS NULL OR assets.deleted_at > ‘2008-01-07 23:59:05’)
AND (assets.location_id = 1)
we are returning false
SQL (0.355231) COMMIT


Sincerely,

William P.

You can run this in a transaction block:
@parent = Parent.find(params[:id])
Parent.transaction do
@parent.destroy
end

if @parent.destroy raises any exception the entire transaction will be
rolled back and none of the children will be deleted.

On Jan 8, 2008 10:48 AM, William P. [email protected] wrote:

:dependent and would get really messy on objects with many dependencies.
end
if self.respond_to? :assets
end
WHERE id = 266
assets.deleted_at IS NULL OR assets.deleted_at > ‘2008-01-07 23:59:05’)


Ryan B.

Feel free to add me to MSN and/or GTalk as this email.

Hey Ryan, thank for the suggestion but that won’t help as the destroy is
already wrapped in a transaction and the children are still deleted. I
can get things to work if I raise an exception in the parent’s before
filter and catch it in a begin / rescue / end block in the controller. I
guess what I really want to know is why would :dependent children be
destroyed if the parent’s before_destroy filter returns false? Seems to
me that is something is “dependent” than it should be just that. It
would be deleted only if the parent can be. I was expecting to see the
transaction rolled back when the parent’s before_destroy callback
returned false

-Bill

Ryan B. wrote:

On Jan 8, 2008 10:48 AM, William P. <[email protected]
Is there something I am missing or is this just normal. I can
has_one :contact, :as => :contactable, :dependent => :destroy

      end
  Address Load (0.163060)   SELECT * FROM addresses WHERE
 WHERE `id` = 224

Ryan B.
http://www.frozenplague.net
Feel free to add me to MSN and/or GTalk as this email.


Sincerely,

William P.

I’m guessing you’re a little bit confused on precisely how the dependent
destroy works, along with a before_destroy.

  1. You tell Rails to delete the parent record.
  2. It checks for it’s associated records and their :dependent settings.
  3. If it is dependent => :destroy then it will delete these records.
  4. Processes before_destroy on parent record.
  5. Parent record is destroyed if before_destroy returns true.

The transaction will not be cancelled if the before_destroy returns
false.
Get it to raise an exception and it should roll it back.

On Jan 8, 2008 11:18 AM, William P. [email protected] wrote:

-Bill
rolled back and none of the children will be deleted.

destroyed. This is obviously not desirable. I have verified that the call
has_one :contact, :as => :contactable, :dependent => :destroy

  end

Address Load (0.163060) SELECT * FROM addresses WHERE (
WHERE id = 224

Ryan B.


Ryan B.

Feel free to add me to MSN and/or GTalk as this email.

Ok, thats what I ended up with. Thanks again.

Ryan B. wrote:

false. Get it to raise an exception and it should roll it back.
before_destroy filter returns false? Seems to me that is something

@parent.destroy
    2 polymorphic relationships both specifying :dependent =>
    purpose of :dependent and would get really messy on objects
    <snip>
      def any_dependent_assets?
        true
      Address Destroy (0.159641)  
      SQL (0.029946)   SELECT count(*) AS count_all FROM assets


Ryan B.
http://www.frozenplague.net
Feel free to add me to MSN and/or GTalk as this email.


Sincerely,

William P.