Ruby on Rails many-to-many Tutorial
Ruby on Rails June 13th, 2008When I started using Rails I was pretty confused with the differences in different ways you can setup relationship in your model. I will run you through the best way to implement a many-to-many relationship between your Rails models.
Related: Ruby on Rails one-to-many Tutorial
Related: Ruby on Rails one-to-one Tutorial
There are two ways to implement many-to-many:
- has_many :through
- has_and_belongs_to_many
There some differences in the two. For instance, if we have a many-to-many relationship between artists and albums we will need a joining table. We will call this table features (couldn’t think of anything better).
has_and_belongs_to_many does not support attributes inside your join table. This is why it is generally good practice to use has_many :through.
Database Migration
Artists
# artists table create_table :artists do |t| t.string :name t.timestamps end |
Albums
# albums table create_table :albums do |t| t.string :genre t.string :name t.string :description t.timestamps end |
Features
Now for our join table. Create a table with the id’s of both our other tables.
Don’t forget the :id => false. This will create a table with no incrementing unique ID column
# features table create_table :features, :id => false do |t| t.integer :artist_id t.integer :album_id t.integer :number_of_songs end |
Models
# feature.rb class Feature < ActiveRecord::Base belongs_to :album belongs_to :artist end # artist.rb class Artist < ActiveRecord::Base has_many :features has_many :albums, :through => :features end # album.rb class Album < ActiveRecord::Base has_many :features has_many :artists, :through => :features end |
Controller and View
In your controller you can simply collect all the Artists from your database using a find method.
# controllers/artists_controller.rb def index @artists = Artist.find(:all) end |
In your view you can iterate through artists and their albums:
# views/artists/index.html.erb <% for artist in @artists %> Artist: <%= artist.name %> <% for album in artist.albums %> Featured in album: <%= album.name %> <% end %> <% end %> |

June 23rd, 2008 at 2:19 pm
Hello ,
Nice tutorial
I am new to ruby.
I have a small doubt, while saving artists we can albums that’s ok how data will be stored in features table
please clarify me
my id is gouthamireddych@gmail.com
June 23rd, 2008 at 3:48 pm
Well because you have a relationship with the Features table you can simply use that relationship to store your attribute and the album object.
Eg.
artist = Artist.find(:first)
album = Album.new(:name => “test album”);
artist.features.create(:performances => 2, :album => album)
June 30th, 2008 at 11:22 am
Thanks for the overview, but what do the controller and views look like when you create an artist, album, and feature?
I have been trying to get this but have been unable to. When you create an Artist, how do you say which feature it belongs to and then when you create an album, how do you say which artist there is through which feature?
I am doing this with concert, performer (join), and artist so I can create an artist and he/she can be in multiple concerts through performers.
June 30th, 2008 at 1:03 pm
Hey Ryan,
Ok if we have an Artist, and we want to make it perform at a particular Concert.
concert = Concert.find_by_name(”Classical”)
artist.concerts < < concert
# Create an concert and assign an artist to it.
concert = Concert.create(:name => ‘Rock’)
concert.artists << artist
# Get the artists at a concert
@artists_at_concert = concert.artists
You don’t really need to use the features relationship unless you want to access attributes inside your join table.
July 12th, 2008 at 8:12 am
Andrew, this is a great tutorial. I already get the concept of join models, what I’ve been trying to figure out is whether or not you can have multiple relationships between the same two objects with different relationship data.
To use your Artist/Album/Feature example above, let’s say the feature migration looks instead like so:
create_table :features, :id => false do |t|
t.integer :artist_id
t.integer :album_id
t.integer :song_title
end
so now, in the ruby console:
# Create an artist
artist = Artist.create(:name => “Linkin Park”)
# Create an album
album = Album.create(:genre => “Soundtrack”, :name => “Transformers the Movie Soundtrack”, :description => “Soundtrack for the blockbuster film Transformers.”)
# Create two features
feature1 = Feature.create(:song_title => “What I’ve Done”)
feature2 = Feature.create(:song_title => “A Place for My Head”)
# Add features to artist & album
album.features << feature1
album.features << feature2
artist.features << feature1
artist.features << feature2
————————————————
Am quite curious about this - I intend to create a setup like this and test it myself, but am not sure I’ll understand the information behind any errors that come of it.
August 3rd, 2008 at 6:06 am
Lets say I wanted to add a “feature_type” to this example. So the artist maybe featured as a singer, guitarist, drummer, etc. I would add a “feature_type_id” to the “features” class, and a new class called FeatureTypes that has all the possible values.
On the album’s views, if I just wanted to show an album’s singers (eg: feature type 1), how would I do that?
August 27th, 2008 at 7:18 am
I’m new to ruby and I want to search for different artists in an artist table wich consist of id,member and album but I always get the error “Couldn’t find Artist without an ID” no matter how I try. the view is the following:
{:action => ’search’} do |f| %>
and the controller:
def search
@artist=Artist.find(params[:id])
end
end
I guess id have to be passed as an argument but I don’t know where in form_for.
Thanks in advance
September 6th, 2008 at 12:24 am
Great tutorial, thanks.
I was studying many to many relationships all afternoon and this tutorial gave me the final push in the right direction.
It all works now.
Thanks again!
September 11th, 2008 at 6:45 am
Echoing Mark B.,
The tutorial boiled it down to the bare essence then gave a complete example from creation to presentation allowing me (a rails newbie) to move past a particular hurdle.
September 26th, 2008 at 4:54 am
Great tutorial. I tried implementing a through model just like you describe. To map photos to people I created a table people_photos with :id => false and with two foreign keys: person_id and photo_id. Then I created has_many and has_many :through relationships in the two existing tables (Photos and People). I put it all together, then ran the console for my app. Grabbed a photo record and a person record, then tried p.foo << q. It blew up saying ‘relation “people_photos_id_seq” does not exist. And this is true, there is no id in the through table, just like in your example. Why does Rails think it still needs one?
November 19th, 2008 at 2:48 am
You have syntax error:
# views/artists/index.html.erb
Artist:
Featured in album:
It should be:
# views/artists/index.html.erb
Artist:
Featured in album:
November 19th, 2008 at 2:53 am
You have syntax error:
# views/artists/index.html.erb
<% for artist in @artists %>
Artist: <%= artist.name %>
<% for album in artist.albums %>
Featured in album: <%= album.name %>
<% end %>
<% end %>
It should be:
# views/artists/index.html.erb
<% for artist in @artists %>
Artist: <%= artist.name %>
<% for album in @artist.albums %>
Featured in album: <%= album.name %>
<% end %>
<% end %>