Has_many :through

This seems like it should have been asked numerous times already, but my
Google-fu isn’t strong enough to find the answer. DHH advocates
separating HABTM relationships into a join model, and I need to store
some metadata about the join, so this makes sense. The problem is
creation order and validations.

Say, you have three models, like so:

class Foo < AR::Base
has_many :bars, :through => :quux
has_many :quuxes
end

class Bar < AR::Base
has_many :quuxes, :dependent => :destroy
has_many :foos, :through => :quuxes
end

class Quux < AR::Base
belongs_to :foo
belongs_to :bar
end

Foo is valid without any Bars, and a Bar may belong to multiple Foos.
However, a Bar should never exist without being associated with at least
one Foo.

So, how would you ensure that every time a Bar is created, the proper
Quux is also created, without ever leaving an opportunity for a Bar to
exist without a Foo?

My initial thought is that a transaction is necessary, in this order:

  1. Create Bar (rollback on fail)
  2. Create Quux, referencing bar.id and foo.id (rollback on fail)

How do I enforce this order and maintain database integrity in the model
layer?

This seems silly; if there’s an obvious answer/resource I’m missing,
please let me know. Thanks.

Brandon Mitchell wrote:

has_many :quuxes
end

  1. Create Bar (rollback on fail)
  2. Create Quux, referencing bar.id and foo.id (rollback on fail)

How do I enforce this order and maintain database integrity in the model
layer?

This seems silly; if there’s an obvious answer/resource I’m missing,
please let me know. Thanks.

Posted via http://www.ruby-forum.com/.

This is quite simple with validations. In your Bar model you will want
the following:

#Validate belongs to a foo
validates_presence_of :foo_id
#Validates foo exists - ONLY use this if the foo object is already
saved.
validates_associated :foos

Rails will do all the work with Quux on your behalf.


FYI, If you do not need to directly work with the Quux model itself,
you may find it tidier to use a has_and_belongs_to_many association
with a join table instead of your proposed has_many :through.

Like so:

class Foo < AR::Base
has_and_belongs_to_many :bars, :join_table => :quuxes
end

class Bar < AR::Base
has_and_belongs_to_many :foos, :join_table => :quuxes
end

and have a join table called quuxes (or maybe foo_bar depending on how
you like to name your join tables) with columns foo_id and bar_id

Matt wrote:

This is quite simple with validations. In your Bar model you will want
the following:

#Validate belongs to a foo
validates_presence_of :foo_id
#Validates foo exists - ONLY use this if the foo object is already
saved.
validates_associated :foos

Rails will do all the work with Quux on your behalf.

This is, perhaps, what I would like to do, but Bar does not have a
foo_id. At least, Rails 2.0.2 doesn’t seem to be proxying #foo_id on Bar
to #foo_id on Quux, by default.

This has to be one of the most used paradigms in Rails >= 1.1, but I
can’t seem to find the “conventional” method for assuring that a Bar
cannot be created without an associated Foo.

FYI, If you do not need to directly work with the Quux model itself,
you may find it tidier to use a has_and_belongs_to_many association
with a join table instead of your proposed has_many :through.

I do need to use a join model, rather than a join table. I have other
metadata I need to store in Quux.

#Validate belongs to a foo
validates_presence_of :foo_id
This is, perhaps, what I would like to do, but Bar does not have a
foo_id. At least, Rails 2.0.2 doesn’t seem to be proxying #foo_id on Bar
to #foo_id on Quux, by default.
Apologies, that should be :foo_ids (plural, as it is a has_many
association) and it should be managed directly by the association. It
is an array of the Foo id’s associated with Bar. Updating this should
be updating the quuxes table according to the associations you have
set up.