Gotcha: HABTM Relationships Use Join Table Id As Model Id
April 10, 2008 | code, rails
Having problems with Has and Belongs To Many(habtm) relationships loading your models with the wrong Ids? I was, the story follows.
Admittedly, HABTM is a relationship in Rails that I use rarely. This little snag caught me off-guard last week, and the usual internet searches didn’t yield particularly fruitful results.
Say you have a simple HABTM relationship:
#Predator.rb class Predator < ActiveRecord::Base has_and_belongs_to_many :prey end #Prey.rb class Predator < ActiveRecord::Base has_and_belongs_to_many :predators end
So you’ve added the relationship, now you go to add your join table as a new migration:
class CreatePreyPredators < ActiveRecord::Migration
def self.up
create_table :prey_predators do |t|
t.integer :prey_id
t.integer :predator_id
end
end
def self.down
drop_table :prey_predators
end
end
Now, say you’ve added a few relationships in your database:
select * from prey_predators; +---+--------------+--------------+ | id | prey_id | predator_id | +------------------+--------------+ | 1 | 218872420 | 368007386 | +------------------+--------------+ 1 row in set (0.00 sec)
BUT, when you look at your shiny new relationship in the console, you see this:
>> Prey.find(:first).predators #<Predator id: 1, name: "test_predator" >
You can see that IRB is reporting the predator’s id as 1 and not 368007386. What you’re seeing isn’t the primary key of the predator record, but the primary key of the relationship record in the join table where predator was found.
It would appear that Rails gives any column named ‘id’ precedence when determining the id of the record– ignoring the foreign kesy in the join table. I tried specifying the :foreign_key and :associated_foreign_key on the has_and_belongs_to_many relationship, but that only appears to affect the query that selects the records, not the model’s “reconstitution” from the database.
Unless you want this behavior, just add :id => false to your migration and you’re woes will be gone:
class CreatePreyPredators < ActiveRecord::Migration
def self.up
create_table :prey_predators, :id => false do |t|
t.integer :prey_id
t.integer :predator_id
end
end
def self.down
drop_table :prey_predators
end
end