Rails has_and_belongs_to_many migration

dbslone picture dbslone · Jul 3, 2011 · Viewed 60.5k times · Source

I have two models restaurant and user that I want to perform a has_and_belongs_to_many relationship.

I have already gone into the model files and added the has_and_belongs_to_many :restaurants and has_and_belongs_to_many :users

I assume at this point I should be able to do something like with Rails 3:

rails generate migration ....

but everything I have tried seems to fail. I'm sure this is something really simple I'm new to rails so I'm still learning.

Answer

Dex picture Dex · Jul 3, 2011

You need to add a separate join table with only a restaurant_id and user_id (no primary key), in alphabetical order.

First run your migrations, then edit the generated migration file.

Rails 3

rails g migration create_restaurants_users_table

Rails 4:

rails g migration create_restaurants_users

Rails 5

rails g migration CreateJoinTableRestaurantUser restaurants users

From the docs:

There is also a generator which will produce join tables if JoinTable is part of the name:


Your migration file (note the :id => false; it's what prevents the creation of a primary key):

Rails 3

class CreateRestaurantsUsers < ActiveRecord::Migration
  def self.up
    create_table :restaurants_users, :id => false do |t|
        t.references :restaurant
        t.references :user
    end
    add_index :restaurants_users, [:restaurant_id, :user_id]
    add_index :restaurants_users, :user_id
  end

  def self.down
    drop_table :restaurants_users
  end
end

Rails 4

class CreateRestaurantsUsers < ActiveRecord::Migration
  def change
    create_table :restaurants_users, id: false do |t|
      t.belongs_to :restaurant
      t.belongs_to :user
    end
  end
end

t.belongs_to will automatically create the necessary indices. def change will auto detect a forward or rollback migration, no need for up/down.

Rails 5

create_join_table :restaurants, :users do |t|
  t.index [:restaurant_id, :user_id]
end

Note: There is also an option for a custom table name that can be passed as a parameter to create_join_table called table_name. From the docs

By default, the name of the join table comes from the union of the first two arguments provided to create_join_table, in alphabetical order. To customize the name of the table, provide a :table_name option: