many_to_many relationships in Rails

In Rails, we have model classes or for short, models. ActiveRecord provides six associations that allow models to interact with each other by creating relationships.

I will focus on two of the six associations today.

  • belongs_to
  • has_one
  • has_many
  • has_many :through or HMT
  • has_one :through
  • has_and_belongs_to_many or HABTM

The HMT and HABTM associations both result in a many-to-many relationship, but there are subtle differences.

When and why should you use one or the other?

The HABTM association uses a join table to manage the relationship while the HMT association uses a join model.

The HABTM association is often considered the 'old-school' way of creating a many-to-many relationship. In fact, most people in the Rails community would advise against using this relationship.

The HMT association is more flexible and should be the default approach when a many-to-many relationship is needed.

For example, the cost of a show that involves both a movie and a theatre can be added to the join model as an additional column. This flexibility is not possible using a HABTM with a join table.

How to setup an HMT association

Let's use an example of movie theatres and movies.

In the ActiveRecord parlance, Movie hasmany Theatres and Theatre hasmany Movies.

To manage this manytomany relationship using an HMT association, we must create a third model class that represents this relationship.

This model class should describe the relationship. I will use Showtimes.

Use the Rails generate command to create the three model classes.

rails g model movie title:string
rails g model theatre name:string
rails g model showtime

Run the migration:

rake db:migrate

Create a new migration:

rails g migration AddColumnToShowtime

Update the migration file:

class AddColumnToShowtime < ActiveRecord::Migration
  def change
    add_column :showtimes, :movie_id, :integer
    add_column :showtimes, :theatre_id, :integer
  end
end

Run the migration:

rake db:migrate

Now add the relationships to the model classes:

# models/movie.rb
class Movie < ActiveRecord::Base
  has_many :showtimes
  has_many :theatres, through: :showtimes
end

# models/theatre.rb
class Theatre < ActiveRecord::Base
  has_many :showtimes
  has_many :movies, through: :showtimes
end

# models/showtime.rb
class ShowTime < ActiveRecord::Base
  belongs_to :movie
  belongs_to :theatre
end

How to setup an HABTM association

A HABTM association will probably never be used in a new Rails application since the HMT is the best and most flexible choice for most situations, but for reference, here it is.

This example is similar to the HMT association, but, instead of a join model a join table is used instead.

Use the Rails generate command to create the model classes.

rails g model movie title:string
rails g model theatre name:string

Run the migration:

rake db:migrate

Create a new migration for the join table. Rails 4 provides the new createjointable method.

rails g migration create_join_table movie theatre

Verify your migration file looks like this.

class CreateJoinTable < ActiveRecord::Migration
  def change
    create_join_table :movies, :theatres do |t|
      t.index [:movie_id, :theatre_id]
      t.index [:theatre_id, :movie_id]
    end
  end
end

Run the migration:

rake db:migrate

Now add the relationships to the model classes:

# models/movie.rb
class Movie < ActiveRecord::Base
  has_and_belongs_to_many :theatres
end

# models/theatre.rb
class Theatre < ActiveRecord::Base
  has_and_belongs_to_many :movies
end

Remember, the example for a HABTM association is almost never used in the Rails world. I'm showing this example only for a comparison. Keep you life simple and use the HTM instead.

Using an HMT Relationship

Now that a many-to-many relationship is setup using an HMT association, how do we use it? The following applies only to the HMT relationship

Let's start up the rails console.

rails c

Create movie and theatre instances.

movie = Movie.create(title: "Jaws")
theatre = Theatre.create(name: "Sundance Kabuki Cinemas")

Now let's create the relationship between Jaws and Sundance Kabuki Cinema.

movie.showtimes.create(theatre: theatre)

There you have it. Since we used a join model, we can now create a new migration to add the movie time and the price of Jaws as it's played at the Sundance Kabuki Cinema.