Associations aren't :dependent => true anymore
Posted by marcel April 28, 2006 @ 04:46 PM
Up until the 1.1 release, the way to automatically destroy has_many associations when the owner was itself destroyed was to either use the :dependent or :exclusively_dependent option when declaring the has_many.
class Account < ActiveRecord::Base
has_many :members, :dependent => true
end
or
class Brand < ActiveRecord::Base
has_many :products, :exclusively_dependent => true
end
The :dependent option instantiated all the associated objects and called destroy on each one. Destroy in turn triggers the callbacks defined on that associated model, such as those declared with before_destroy and after_destroy.
The :exclusively_dependent option, on the other hand, did not instantiate all the associated objects. Rather, it just generated a single SQL statement which deleted the associated records without first creating objects for each one. This buys you efficiency when you have no need for the flexibility of triggering model callbacks.
Since 1.1, the API for garbage collecting associated records has been consolidated into the :dependent option. Rather than saying :dependent => true, you now pass one of several symbols to the :dependent option which describes how the association is dependent on the owner.
Declaring the has many as :dependent => :destroy is the same as what used to be declared as :dependent => true. When the owner is destroyed, all the associated records are instantiated and destroyed.
class Account < ActiveRecord::Base
# Deprecated
# has_many :members, :dependent => true
# In favor of
has_many :members, :dependent => :destroy
end
The new way to achieve the now deprecated :exclusively_dependent configuration is to use :dependent => :delete_all rather than :dependent => :destroy.
class Brand < ActiveRecord::Base
# Deprecated
# has_many :products, :exclusively_dependent => true
# In favor of
has_many :products, :dependent => :delete_all
end
The :destroy and :delete_all option symbols are so named because they correspond with the behavior achieved by calling destroy versus delete on a model object. One triggers callbacks, the other just generates the delete SQL statement.
As an aside, another valid option is :dependent => :nullify which is similar to :dependent => :delete_all except rather than deleting the associated records, it just sets their foreign keys to NULL. This effectively removes the association, without removing the associated records from the database table.
As always, the semantics of :dependent => :destroy and :dependent => :delete_all are mutually exclusive, which this new API makes a bit more apparent.
It should be noted that declaring dependencies is not required when setting up has many associations. It is simply an option for when you desire such functionality.
Keep in mind that for now :dependent => true and :exclusively_dependent => true will still be supported, but they have been marked as deprecated and could be taken out in the future.

While on the subject, I have a question regarding the :dependent => :destroy functionality.
What happens if the foreign key definition (in the SQL table) specifies “ON DELETE CASCADE ON UPDATE NO ACTION” (or one of the other combinations)? Which happens first? Does the the SQL cascaded delete happen before ActiveRecord has a chance to instatiate and destroy the dependent records?
Thanks…!
My question is this: why? You’re deprecating something that’s working okay, and replacing it with different syntax that does the same thing.
If there are technical reasons, please elucidate. If you’re setting up the potential to break working code (Should the deprecated syntax no longer become supported) just for syntactic sugar, that seems… counterproductive.
khudgins:
The short answer is that this is a better API. It has lower memory overhead (for the programmer) by not introducing as many different options for things that are more similar than they are different. When we went to add the :nullify option, it was clear that that api wasn’t going to scale. It’s just a natural evolution to the API. Similar changes have happened to ActiveRecord::find and ActionController::render. All things considered this is a pretty simple and minor change to your application code. The resistance to these small refactorings are interesting. You should have been around in the wild early days when I was spending all my time refactoring entire applications on account of huge API changes and never actually got any apps written. This is, by comparison, quite benign.
These changes make sense. I think the docs should mention that the default is to not delete/destroy the associated objects (right?).
Khudgins,
Just a FYI, these changes were made actually 7 months ago (see tickets 2009 and 2015). The
truevariation was limiting and was technically deprecated at 1.0. Marcel is simply doing the community a favor and pointing out some common oversights due to older blog entries, articles, and books. :-)Thanks Marcel!
My biggest problem with the
=> truewas… who decides what the default behavior for an associated record should be on a destroy? This clears us from false assumptions…. and that’s a good thing, in my opinion.I really appreciate these blog entries on API changes.
Does this change also apply to ‘has_one’ associations?
The order that callbacks are executed in for relations is unclear (including the order in which deletions occur (not necessarily relevant, as it occurs within a transaction)). I say unclear, because the ordering appears to be a result of the alphabetizing of
requirestatements in activerecord.rb.I posted about some ActiveRecord gotchas last week.
Moog, yes. :-)
Joe, these changes are already in API docs. Just get latest CHM file below and search for has_many in index to get all the details:
http://www.delynnberry.com/files/rails-documentation-1-1-2.chm
Marcel, thanks for taking the time to post these API related comments here. I think this is a great service to the community and an aid in keeping up with the changes for those of us who don’t watch the rails-core timeline religiously.
-A
I also appreciate these API related blog entries!
Thanks guys. I wasn’t trying to stir the pot (much) or stuck in the mud, I was mainly hoping to get an explanation of why the syntactical changes were made. My point was (and is) that, especially at this point, changes to the API need to be carefully considered and anything critical that changes important methods or classes should be brought out before the community and put on display. (Just like Marcel’s post)
I’ve run into a similar gripe, but much more annoying, with the system variables in PHP4 to PHP5. There’s no reason to change $HTTP_VARS to $_VARS just so you don’t have to type as much, which is basically what they did.
I’ve noticed the find and render changes as well, although I don’t keep up with the changelogs and trac tickets as much as I should. Thanks again for keeping us all in the know.
Thanks guys. I wasn’t trying to stir the pot (much) or stuck in the mud, I was mainly hoping to get an explanation of why the syntactical changes were made. My point was (and is) that, especially at this point, changes to the API need to be carefully considered and anything critical that changes important methods or classes should be brought out before the community and put on display. (Just like Marcel’s post)
I’ve run into a similar gripe, but much more annoying, with the system variables in PHP4 to PHP5. There’s no reason to change $HTTP_VARS to $_VARS just so you don’t have to type as much, which is basically what they did.
I’ve noticed the find and render changes as well, although I don’t keep up with the changelogs and trac tickets as much as I should. Thanks again for keeping us all in the know.
Good work all.
Now, how to get rid of habtm…
I think that the dependent features are incomplete. I made an enhancement patch (3837) before the 1.1 release to add :dependent => :protect/:restrict support to has_many associations but David didn’t see the need for that. I think it completes the functionality so it can compare to the DBMSs features.
Why the inconsistency with destroy and delete_all for has_many associations?
For has_one assoctions we have:
has_one :thing, :dependent => :delete has_one :thing, :dependent => :destroyAnd for has_many we have:
has_many :s, :dependent => :delete_all has_many :s, :dependent => :destroyWhy not call it destroy_all, or change delete_all to just delete?
Ok, so has_one doesn’t have a delete function on the dependent option. I guess we don’t really need this for deleting just one record. It just throws off the consistency a bit :)