126

How do I do a script/generate migration to create a join table for a has_and_belongs_to_many relationship?

The application runs on Rails 2.3.2, but I also have Rails 3.0.3 installed.

ma11hew28
  • 121,420
  • 116
  • 450
  • 651

7 Answers7

231

Where:

class Teacher < ActiveRecord::Base
  has_and_belongs_to_many :students
end

and

class Student < ActiveRecord::Base
  has_and_belongs_to_many :teachers
end

for rails 4:

rails generate migration CreateJoinTableStudentTeacher student teacher

for rails 3:

rails generate migration students_teachers student_id:integer teacher_id:integer

for rails < 3

script/generate migration students_teachers student_id:integer teacher_id:integer

(note the table name lists both join tables in alphabetical order)

and then for rails 3 and below only, you need to edit your generated migration so an id field is not created:

create_table :students_teachers, :id => false do |t|
dangerousdave
  • 6,331
  • 8
  • 45
  • 62
  • 16
    This is the only reply that actually answers the question. – pingu Aug 17 '12 at 10:57
  • 8
    @pingu: except that it doesn't work, at least in Rails 3.2. The generated migration file is blank. – hoffmanc Jul 07 '13 at 19:23
  • 2
    @hoffmanc It will generate an empty migration file if you do not specify any fields. You must specify the fields if you want Rails to automatically add them to the migration file. – Andrew Jan 21 '15 at 19:44
  • 1
    hello, I'm trying `rails generate migration CreateJoinTableTeacherStudent teacher student` instead of `rails generate migration CreateJoinTableStudentTeacher student teacher`, is that the same? Does S(tudent) need to before T(eacher)? – zx1986 Apr 28 '15 at 06:07
  • 1
    @zx1986, yes, it needs to be in that order. – dangerousdave Apr 29 '15 at 07:44
  • oh! thanks @dangerousdave , I read this blog recently: http://ruby-journal.com/rails-4-changes-join-table-naming-convention/ – zx1986 May 03 '15 at 17:28
  • i want use it command to create `has_many :through` to declare a many-to-many relationship between models. but i can't :( – Zakaria Jun 08 '15 at 04:04
  • Thank you, this worked perfectly for my Rails 4 app. – jgrant May 15 '16 at 23:05
139

A has_and_belongs_to_many table must match this format. I'm assuming the two models to be joined by has_and_belongs_to_many are already in the DB : apples and oranges:

create_table :apples_oranges, :id => false do |t|
  t.references :apple, :null => false
  t.references :orange, :null => false
end

# Adding the index can massively speed up join tables. Don't use the
# unique if you allow duplicates.
add_index(:apples_oranges, [:apple_id, :orange_id], :unique => true)

If you use the :unique => true on the index, then you should (in rails3) pass :uniq => true to has_and_belongs_to_many.

More information: Rails Docs

UPDATED 2010-12-13 I've updated it to remove the id and timestamps... Basically ma11hew28 and nunopolonia are correct: There must not be an id and there must not be timestamps or rails won't allow has_and_belongs_to_many to work.

ma11hew28
  • 121,420
  • 116
  • 450
  • 651
docwhat
  • 11,435
  • 6
  • 55
  • 54
  • 6
    Actually, a join table should only have the two references columns and doesn't have id or timestamp columns. Here is a better example of a [has_and_belongs_to_many migration](http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_and_belongs_to_many) from the link you gave. I'm looking for a way to do it from the command line with `script/generate migration` ... – ma11hew28 Dec 07 '10 at 20:44
  • Well, it doesn't have to have the timestamps; I marked it optional in my example. I would recommend adding the id, though. There are cases where either the ID or the timestamp can be useful. But I strongly recommend the ID. – docwhat Dec 08 '10 at 02:07
  • Ok. What is a case where the ID would be useful? – ma11hew28 Dec 15 '10 at 15:20
  • One example is if the relationship is important enough to have a view. It can also be used to speed up accessing the databas by passing around the relationship.id instead of looking it up repeatedly. It also makes troubleshooting the database easier. Especially if the ids of the other columns is really high. It's easier to remember id:12345 instead of id:54321-id:67890 -- But that being said, if the table gets really big then you may want to be able to save space by not allocating another id for each relationship. – docwhat Jun 05 '11 at 03:39
  • Hi, I've been using has_and_belongs_to_many associations for quite a bit with id in my join table without any hassle. What kind of trouble is this supposed to generate? – ndemoreau Mar 23 '12 at 03:08
  • 2
    I don't think the multicolumn index is the right solution for this. It will work for queries for particular apples to find the related oranges but not the other way round. Two single column indexes would allow both directions to be queried efficiently possibly at a small loss to existence checks of a particular apple, orange combination). – Joseph Lord Feb 28 '14 at 14:50
  • @JosephLord is correct. You should verify which direction you want to optimize. Also, you must know that indices slow down insertion a bit, so be carefull if data input speed is a constraint to you. – Mauricio Moraes Mar 20 '15 at 14:44
14

You should name the table the names of 2 models you want to connect by alphabetical order and put the two model id's in the table. Then connect each model to each other creating the associations in the model.

Here's an example:

# in migration
def self.up
  create_table 'categories_products', :id => false do |t|
    t.column :category_id, :integer
    t.column :product_id, :integer
  end
end

# models/product.rb
has_and_belongs_to_many :categories

# models/category.rb
has_and_belongs_to_many :products

But this is not very flexible and you should think about using has_many :through

nunopolonia
  • 14,157
  • 3
  • 26
  • 29
6

The top answer shows a composite index that I don't believe will be used to lookup apples from oranges.

create_table :apples_oranges, :id => false do |t|
  t.references :apple, :null => false
  t.references :orange, :null => false
end

# Adding the index can massively speed up join tables.
# This enforces uniqueness and speeds up apple->oranges lookups.
add_index(:apples_oranges, [:apple_id, :orange_id], :unique => true)
# This speeds up orange->apple lookups
add_index(:apples_oranges, :orange_id)

I did find the answer this is based on by 'The Doctor What' useful and the discussion certainly so too.

Joseph Lord
  • 6,446
  • 1
  • 28
  • 32
5

In rails 4, you can simple use

create_join_table :table1s, :table2s

it is all.

Caution: you must offord table1, table2 with alphanumeric.

zw963
  • 1,157
  • 15
  • 11
  • this is a good up to date solution. Note, the join table is not accessible as a model, but through the has_and_belongs_to_many relations that are set up on both of the joined tables. – Taylored Web Sites Jan 11 '18 at 17:08
1

I like doing:

rails g migration CreateJoinedTable model1:references model2:references. That way I get a migration that looks like this:

class CreateJoinedTable < ActiveRecord::Migration
  def change
    create_table :joined_tables do |t|
      t.references :trip, index: true
      t.references :category, index: true
    end
    add_foreign_key :joined_tables, :trips
    add_foreign_key :joined_tables, :categories
  end
end

I like having index on these columns because I'll often be doing lookups using these columns.

Jwan622
  • 11,015
  • 21
  • 88
  • 181
1

This HABTM section of the Associations Rails Guide is great, but doesn't explain exactly how to create a join table.

However, the Migrations Rails Guide explains how to make a join table:

The migration method create_join_table creates an HABTM (has and belongs to many) join table. A typical use would be:

create_join_table :products, :categories

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.

stevec
  • 41,291
  • 27
  • 223
  • 311