dependent support for has_many through?

Given the following

class Programmer < ActiveRecord::Base    has_many :assignments    has_many :projects, :through => :assignments end

if I call Programmer#projects.clear, it will delete_all the joining assignments; however, I have a situation where I'd like the assignments to get destroyed instead so that their after_destroy callbacks get called. It would be simple to implement (just call destroy_all rather than delete_all), it's just not quite clear how the option should be set; since :dependent currently gets ignored when using :through, the seems a logical place, although it's not quite intuitive either.

Anybody have any thoughts on this?

Yep, it is not implemented. I just added some docs about it this week:

    http://github.com/rails/rails/commit/92ff71bb14b1b589a3d5c04d120e4b9210b243b1

You can add :dependent => :destroy to the assignments has_many and clear those instead.

Thanks for the info. I realize that clearing the source is one solution, but implementing this is quite simple, I've done it my own project via a small patch. I'd be willing to submit a patch, I'm just not certain what the configuration option should be and where it should go; maybe something like this?

   has_many :projects, :through => :assignments, :dependent => :destroy_source

I think it should be just :destroy.

I believe this is essentially the issue discussed at https://rails.lighthouseapp.com/projects/8994/tickets/2251-associationcollectiondestroy-should-only-delete-join-table-records - where the suggestion to infer the destroyability from the belongs_to seemed most sensible.

It is certainly not appropriate to always destroy the source records when the target records are destroyed, so calling the option just :destroy would be dangerous - see examples on that ticket.

This isn't the same issue; what I want is the source records destroyed (rather than deleted, which is what currently happens) when the target records are removed from the collection, not when the target records are destroyed.

But I think that points out a certain confusion if :dependent => :destroy option is reused, as in all other cases it implies what happens to the target; maybe we need a separate option to configure what happens to the source, something like :source_dependent ?

As a guideline in my mind, has many through is an augmented habtm. In that sense, destroying the owner of the association does not affect the models in the target collection themselves, only the join models.

In a habtm there's no join model, just an auxiliary table. But in hmt the join table is a model, and thus support for some sort of :dependent would be nice.

Since the target is gonna be untouched, :destroy is enough for me. It can only mean what you want to do with the join table.

It could also be the case that we implement and document that you are gonna use whatever is configured in the ordinary has many of the join model in the owner side. (People in examples seem to forget :dependent in the has many with the join model, but you rarely want the default :dependent.)

   class User < AR::Base      # this :dependent is what you normally would configure      has_many :subscriptions, :dependent => :destroy      has_many :magazines, :through => :subscriptions    end

In that case user.magazines=, user.magazines.clear, etc. could check the config of has_many :subscriptions.

That interface could also emphasize that the important bit in these associations is has_many :subscriptions, while the hmt is just a convenience way to let AR do the joins and maintain the m-n table for you.

Should use it a bit to be convinced, but at first sight it looks reasonable to me.

But I think that points out a certain confusion if :dependent => :destroy option is reused, as in all other cases it implies what happens to the target; maybe we need a separate option to configure what happens to the source, something like :source_dependent ?

As a guideline in my mind, has many through is an augmented habtm. In that sense, destroying the owner of the association does not affect the models in the target collection themselves, only the join models.

In some case has_many through is an augmented habtm, but not all; sometimes it's just a convenient way of traversing associations i.e.: class Company    has_many :contacts    has_many :projects, :through => :contacts end

In a habtm there's no join model, just an auxiliary table. But in hmt the join table is a model, and thus support for some sort of :dependent would be nice.

Since the target is gonna be untouched, :destroy is enough for me. It can only mean what you want to do with the join table.

I'm not sure that is necessarily obvious, given that the :dependent option has a very specific meaning in other contexts. Reusing it here this way may lead to confusion.

It could also be the case that we implement and document that you are gonna use whatever is configured in the ordinary has many of the join model in the owner side. (People in examples seem to forget :dependent in the has many with the join model, but you rarely want the default :dependent.)

  class User < AR::Base     # this :dependent is what you normally would configure     has_many :subscriptions, :dependent => :destroy     has_many :magazines, :through => :subscriptions   end

In that case user.magazines=, user.magazines.clear, etc. could check the config of has_many :subscriptions.

There may be cases where you wouldn't want the same behavior though, not to mention that the default dependent behavior of has_many (nullify) is not the same as the current behavior of has_many through.

I think a seperate configuration option is the most logical way of dealing with this...

In some case has_many through is an augmented habtm, but not all; sometimes it's just a convenient way of traversing associations i.e.: class Company has_many :contacts has_many :projects, :through => :contacts end

Yeah.

It could also be the case that we implement and document that you are gonna use whatever is configured in the ordinary has many of the join model in the owner side. (People in examples seem to forget :dependent in the has many with the join model, but you rarely want the default :dependent.)

There may be cases where you wouldn't want the same behavior though

not to mention that the default dependent behavior of has_many (nullify) is not the same as the current behavior of has_many through.

That's a good point (indeed by default it does nothing).

And I think you are right, they could be different. It could be the case that the has many of the join model has no :dependent option because the parent model does cascade delete in the database, while clearing the has many through should delete the join models.

I think the most common case is that they match though, and it would be a smell to be forced to repeat :dependent => :destroy twice in every single pair (with or without that name).

Also think that new :dependent should have a good default. Can we combine them somehow perhaps?

Regarding the name, if there's a problem there I see it in overloading :dependent rather than :destroy. Because that's a ":dependent" that has nothing to do with the deletion of the parent model.

The option should say "what to do with the join models when we undo the association?", and the natural answer I think would be :destroy, :delete_all, or :nullify. Same option values as in has_many.

It could be called instead... I don't know, :unlink, :clear perhaps... I don't know something that makes apparent we are talking about unlinking them, no deletion of the models at both ends of the has many through is involved here.

Since the target is gonna be untouched, :destroy is enough for me. It can only mean what you want to do with the join table.

I'm not sure that is necessarily obvious, given that the :dependent option has a very specific meaning in other contexts. Reusing it here this way may lead to confusion.

Regarding the name, if there's a problem there I see it in overloading :dependent rather than :destroy. Because that's a ":dependent" that has nothing to do with the deletion of the parent model.

The option should say "what to do with the join models when we undo the association?", and the natural answer I think would be :destroy, :delete_all, or :nullify. Same option values as in has_many.

Agreed

It could be called instead... I don't know, :unlink, :clear perhaps... I don't know something that makes apparent we are talking about unlinking them, no deletion of the models at both ends of the has many through is involved here.

The way I see it is, :dependent governs what happens to a target when it's removed from the collection, and we want to configure what happens to the source when the target is removed from the collection. I think that something like :source_dependent might make sense?