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".