Michael Kovacs' photos More of Michael Kovacs' photos
Recommend Me Cable Car Software logo

Saturday, July 01, 2006

Rails realities part 14 (RTFM and don't work too late)

Sometimes I just don't learn that you can only be productive so many hours in a day. I wasted a good hour or two last night around 1AM simply because I worked for too long and didn't pay attention. If you don't want to read the entire post, the executive summary of today's post is this:

If you want your object deletion to cascade down your relationship hierarchy don't use :dependent => :delete_all on your has_many relations. And oh yeah, RTFM

I was running into behavior where objects that were more than one level deep in a relationship hierarchy weren't being destroyed when I destroyed the top level object. Turns out the option to :dependent I was using was the culprit but I didn't even think about it and ASSumed that it should cascade delete operations.

If I had paid attention to the API docs I would've seen:

:dependent - if set to :destroy all the associated objects are destroyed alongside this object by calling their destroy method. If set to :delete_all all associated objects are deleted without calling their destroy method. If set to :nullify all associated objects’ foreign keys are set to NULL without calling their save callbacks. NOTE: :dependent => true is deprecated and has been replaced with :dependent => :destroy. May not be set if :exclusively_dependent is also set.

I missed this and unfortunately my has_many relations specified the :delete_all flag since all instances are indeed dependent upon the parent object. I ended up creating a temporary workaround for a short period of time just to make sure that my objects would actually get deleted. Then I dug into the AR code to see what actually happens with the association definition with :dependent and sure enough at line 1000 of associations.rb in AR 1.14.2 I see...

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(%(#{dependent_conditions})) }"

This is where the callback is inserted into the parent class. The :delete_all option that I specified does just that, calls delete_all on the parent class. Reading the API docs for delete_all yields...

Deletes all the records that match the condition without instantiating the objects first (and hence not calling the destroy method).

Of course it was during when I started typing this entry the next morning that I actually saw the API doc for :delete_all, and I know that I've read it before but for some reason after a long day yesterday I just sort of zoned out.

Oh well, hopefully this post may help someone else who makes the same error with their has_many relations :-)


Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?