Rails many-to-many association tutorial

March 23rd, 2011

Has and belongs to many

If you just need to set a simple many-to-many relationship the easiest way to do this is with has_and_belongs_to_many. Here is how your models should look like.

1
2
3
4
5
6
7
class Post < ActiveRecord::Base
  has_and_belongs_to_many :tags
end
 
class Tag < ActiveRecord::Base
  has_and_belongs_to_many :posts
end

This association is supported by an additional database table that will store post_id and tag_id for each relationship. This table should be called posts_tags and it will not be created for you, so you will have to create your own migration.

has and belongs to many er-model

Notice that model names inside join table are in lexical order, so the table will be called posts_tags and not tags_posts (because ‘p’ comes before ‘t’). You can also specify a different name for that table using the :join_table option.

So generate a new migration (you could do this with “rails generate migration create_post_tag_join_table” and enter following code. Then run “rake db:migrate” to make the changes inside your database.

You should use “:id=>false” option in your migration, because join table does not need additional primary key, also notice that this table won’t have “created_at” and “updated_at” timestamps.

> rails generate migration create_post_tag_join_table
1
2
3
4
5
6
7
8
9
10
11
12
classCreatePostTagJoinTable < ActiveRecord::Migration
  defself.up
    create_table :posts_tags, :id => false do |t|
      t.integer :post_id
      t.integer :tag_id
    end
  end
 
  defself.down
    drop_table:posts_tags
  end
end
> rake db:migrate

Accessing related posts and tags is now easy. You can get an array of related tags by calling ‘tags’ method.

1
2
@post = Post.find(1)
@tags = @post.tags

Simplest way to add existing objects is using the << operator. For example:

1
2
@tag = Tag.find(3)
@tag.posts << post

Or if you want to create brand new object you can use a create method. This method returns created object. The object will be saved in databases, and link through join table will be created.

1
@tag.posts.create( {:title => 'New title', :content => 'Hello world' })

Collections also come with delete method. It will remove objects relationship, but will not destroy the object.

1
@post.tags.delete( @tag )

More info can be found inside rails documentation.

Has many through

In case we need to store additional information inside join table, we will need to use a more complicated version of many-to-many relationship. Lets say that we have two models Student and Test. Each student has taken many tests and each test was taken by many students, but each time a student took a test he got a grade. This is where has_many :through relationship comes in.

has many through er-model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
classStudent < ActiveRecord::Base
  has_many :grades
  has_many :tests, :through => :grades
end
 
class Grade < ActiveRecord::Base
  belongs_to :student
  belongs_to :test
end
 
classTest < ActiveRecord::Base
  has_many :grades
  has_many :students, :through => :grades
end

Notice that beside setting the has_many :tests relationship, in order to use :through option you need to notify model that it has grades. In other words, you cant ‘go through’ models that you don’t have. Notice that in this case Grade is a normal model, and it must include ID field to work properly. However timestamps may be excluded if you don’t plan to use them.

This relationships behave similar to has_and_belongs_to_many relationship. More info can be found inside rails documentation.

  • vi
    Thanks, you're good!
  • Maruff Bulbul
    Hey Please tell me... How to find the existing relation in relational join table. I wanna know how to validate that same tag is not join multiple time to the same post.
  • nolastan
    I've found plenty of explanations on how to setup the models for many-to-many associations, but I have no idea how to setup the corresponding view and controller.
blog comments powered by Disqus