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?