124
votes

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.

4

4 Answers

268
votes

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:

41
votes

The answers here are quite dated. As of Rails 4.0.2, your migrations make use of create_join_table.

To create the migration, run:

rails g migration CreateJoinTableRestaurantsUsers restaurant user

This will generate the following:

class CreateJoinTableRestaurantsUsers < ActiveRecord::Migration
  def change
    create_join_table :restaurants, :users do |t|
      # t.index [:restaurant_id, :user_id]
      # t.index [:user_id, :restaurant_id]
    end
  end
end

If you want to index these columns, uncomment the respective lines and you're good to go!

26
votes

When creating the join table, pay careful attention to the requirement that the two tables need to be listed in alphabetical order in the migration name/class. This can easily bite you if your model names are similar, e.g. "abc" and "abb". If you were to run

rails g migration create_abc_abb_table

Your relations will not work as expected. You must use

rails g migration create_abb_abc_table

instead.

6
votes

For HABTM relationships, you need to create a join table. There is only join table and that table should not have an id column. Try this migration.

def self.up
  create_table :restaurants_users, :id => false do |t|
    t.integer :restaurant_id
    t.integer :user_id
  end
end

def self.down
  drop_table :restaurants_users
end

You must check this relationship rails guide tutorials