When 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 %>