Rails' has_many/has_one/has_many through/belongs_to mess

Dec 11, 2008 18:25

It confuses the hell out of me every time I try to think about about.  Here are some notes, in my own words, on when to use what.
There are actually many different forms of relationships in Rails:
  • belongs_to - this table has a foreign key to another table
  • belongs_to :polymorphic - a belongs_to table whose foreign key actually points at many tables, not one.  See below
  • has_one - Rails will enforce that only one of the referred tables' rows exist for each of the referent rows
  • has_many - the "one" side of a standard 1-to-many RDBMS relationship
  • has_and_belongs_to_many - a simple many-to-many relationship.  See below.
  • has_many :through - a slightly more complex many-to-many relationship. See below
  • single-table inheritance - is supported, but I don't like to use it.  Column width.
has_and_belongs_to_many vs. has_many :through

has_and_belongs_to_many and has_many :through require detailed explanation.

has_and_belongs_to_many is used on either side of a simple many-to-many RDMBS relationship. By default, Rails assumes the name of the pivot table is model1_model2 (eg categories_products). This type of relationship has no id primary key, nor baggage data.  The pivot table is totally invisible to the model at runtime.

has_many :through is very similar to has_and_belongs_to_many.  It is another syntax - syntactic sugar - for more complex many-to-many RDBMS relationship. The through argument is the "pivot table" (aka the crossref or "xref" table).  The important part: it's distinct from a has_and_belongs_to_many relationship in that the pivot table is allowed to have its own id primary key, and baggage data.  [an editorial note: why this syntax is hidden away in has_many, instead of being a separate call like has_and_belongs_to_many - I don't get.  The way this is set up is very confusing.]

belongs_to :polymorphic

Polymorphic also requires detailed explanation.  It's used when a table containing a foreign key is actually a reference to many tables at once, as opposed to a polymorphic = false table, which points at just one table.  This table will have a type discriminator field in it to determine which table a given foreign_key in a given row is from.  That is, a polymorphic relationship can support multiple-table-inheritance-like structures.

The "base class" table containing the foreign key and discriminator (aka the belongs_to model) uses the :polymorphic => true syntax.  The "derived class" tables pointed to by the belongs_to model uses the :as syntax (inside its has_many or has_one call.)

Note that the names used in the first argument for :belongs_to and in the :as argument must correspond to one another.  They are arbitrary (but see the next paragraph).  I suggest sticking to the examples' adverb use - "addressable", "favoritable" etc.

But note that the :as argument also tells Rails what the default name of the discriminator flag is.  So if it's "belongs_to :addressable, :polymorphic => true" and "has_many :modelname :as => :addressable", Rails will assume the discriminator flag is named "addressable_type".  This behavior is hard-coded in Rails 2.1.x [mysteriously...].  See activerecord-2.1.2/lib/active_record/associations.rb, line 1852.

Similarly, Rails expects the _id field to be named after the belongs_to symbol you used.  So "addressable_id".

By default, Rails will store the class name in the discriminator ( _type) field. [Editorial: gah! no! Rails dev don't care about selection speed?!]  So it expects it to be a lengthy varchar().  In Rails 2.1.x, this too appears to be hard-coded.

References

First, the "official" docs: 
  • Here is the (mostly unhelpful and very jargony) Rdocs on the subject.  The polymorphic section is semi-useful.
  • Here is the (again unhelpful) Wiki description of polymorphic relationships.
  • Here is the (underwritten) Wiki description of single-table inheritance.
[more editorial: My god, these things are written terribly. Not that I'm doing a better job or anything.  But the language used is very unclear, and often ambiguous or inaccurate, which is bad for "official" docs]

The most useful mnemonic I read was from here: "The table with the foreign key in it belongs_to the table that that foreign key references. The table with the referenced primary key in it has_one or has_many of the table with the foreign key in it."

This Rails Cheat Sheet has nice clear examples if you scroll down a bit to "Models".
Previous post Next post
Up