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.

Posted in Documentation | 18 comments

Comments

  1. Judd on 28 Apr 18:55:

    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…!

  2. khudgins on 28 Apr 18:55:

    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.

  3. Marcel Molina Jr. on 28 Apr 19:04:

    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.

  4. Joe on 28 Apr 19:24:

    These changes make sense. I think the docs should mention that the default is to not delete/destroy the associated objects (right?).

  5. Robby Russell on 28 Apr 19:38:

    Khudgins,

    Just a FYI, these changes were made actually 7 months ago (see tickets 2009 and 2015). The true variation 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 => true was… 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.

  6. Observer on 28 Apr 20:05:

    I really appreciate these blog entries on API changes.

  7. Moog on 28 Apr 21:28:

    Does this change also apply to ‘has_one’ associations?

  8. Seth on 28 Apr 21:56:

    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 require statements in activerecord.rb.

    I posted about some ActiveRecord gotchas last week.

  9. Robby Russell on 29 Apr 00:07:

    Moog, yes. :-)

  10. Metin Amiroff on 29 Apr 08:12:

    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

  11. Amr on 29 Apr 14:13:

    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

  12. Charlie Bowman on 30 Apr 02:28:

    I also appreciate these API related blog entries!

  13. khudgins on 30 Apr 06:28:

    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.

  14. khudgins on 30 Apr 06:28:

    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.

  15. Godi on 01 May 15:21:

    Good work all.

    Now, how to get rid of habtm…

  16. Diego Algorta on 07 May 17:46:

    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.

  17. Shane Vitarana on 16 May 16:33:

    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 => :destroy

    And for has_many we have:

    has_many :s, :dependent => :delete_all has_many :s, :dependent => :destroy

    Why not call it destroy_all, or change delete_all to just delete?

  18. Shane Vitarana on 16 May 16:58:

    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 :)