ActiveRecord allows you to link independent, but related tables in a many-to-many association mapping. This many-to-many mapping is made using a special mapping table.
Examples of this:
has_and_belongs_to_many.If you create a new instance of a model that is participating in a HABTM relationship, you must save it first before adding relationships. For example:
product = Products.new(params[:product])
if product.save
product.options << Option.find_by_name("red")
end
has_and_belongs_to_many is great for what it does, but it is only adequate for simple many-to-many relationships. If your intermediary table needs to track additional data, then you may instead want to use ThroughAssociations instead.
HABTM works with this example:
PRODUCTS
id
name
OPTIONS
id
name
OPTIONS_PRODUCTS
option_id
product_id
But not with this example:
PRODUCTS
id
name
OPTIONS
id
name
OPTIONS_PRODUCTS
option_id
product_id
price # (Notice the difference)
price_level_id # (Or, to make things more complex…)
In the second example, the intermediary table needs to track more than just the associations – it also needs to be readable and writable for a price field, or for a price_level_id foreign key.
What is the best way to handle this?
edit: use :through associations as seen [here][ThroughAssociations].
You could use push_with_attributes as shown here but this has been deprecated since 1.2.
Or, you can use the above but add an ‘id’ field to OPTIONS_PRODUCTS, thus making it an ActiveRecord in its own right. Rails doesn’t care, and will cheerfully maintain the correct habtm – but you can now change price (etc) by accessing a ProductOption model (say)…
class ProductOption << ActiveRecord::Base
set_table_name ‘OPTIONS_PRODUCTS’ def self.set_price(product, option, price) x = self.find(:first, :conditions = ‘product_id = ? and option_id = ?’, product.id, option.id]) x.price = price x.save endend
The names of the join table must have the models in alphabetical order. Eg _options_products_
Using the opposite, ie. _products_options_, will throw an “ActiveRecord::StatementInvalid” error.
- That’s convention over configuration! (pity the fool)
When you create your habtm table, make sure to leave out the “id” column. Otherwise, the habtm join table id will be populated to contain the id of your joined model, not an auto-inc number. This, of course, leads to trouble; some people have even thought it was a bug.
To overcome it, create your custom. join tables like so, leaving the id column out.
class RelationsTranzaction < ActiveRecord::Migration
def self.up
create_table :relations_tranzaction, :id => false do |t|
t.column :invoice_id, :integer
t.column :payment_id, :integer
end
add_index :relations_tranzaction, [:invoice_id]
add_index :relations_tranzaction, [:payment_id]
end
def self.down
drop_table :relations_tranzaction
end
end
And alternative to leaving the id field out of the join table is to use a :select in the habmt. This ensures the id of the join record doesn’t end up in the model.
has_and_belongs_to_many :invoices, :select => 'invoices.*'