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

Post a Comment