BrainsToBytes

Quick tips: How to create many-to-many associations in Rails

In Rails, there are two main ways of creating many-to-many relationships between models:

  • using a has_and_belongs_to_many association
  • using has_many :through association

This is Quick tips, so let's cut to the chase.

Using has_and_belongs_to_many

This is the easiest way to establish a many-to-many relationship. It will create a database table to relate the two models (but not a third model to represent the relation).

Suppose you have two models that need to have a many-to-many relationship:

  • instruments
  • musicians

The first step is to write a migration, that (for this example) would look like this:

class AddHABTMForInstrumentsAndmusicians < ActiveRecord::Migration[6.0]
  def change
    create_table :instruments_musicians, id: false do |t|
      t.belongs_to :instrument
      t.belongs_to :musician
    end
  end
end

If you run this migration it will generate the following table description in schema.rb:

  create_table "instruments_musicians", id: false, force: :cascade do |t|
    t.bigint "instrument_id"
    t.bigint "musician_id"
    t.index ["instrument_id"], name: "index_instruments_musicians_on_instrument_id"
    t.index ["musician_id"], name: "index_instruments_musicians_on_musician_id"
  end

Now the only thing you need is to add the has_and_belongs_to_many line to each model, like this:

class Instrument < ApplicationRecord
  has_and_belongs_to_many :musicians
end

class Musician < ApplicationRecord
  has_and_belongs_to_many :instruments
end

And we are done, that's all you need to create a has_and_belongs_to_many relationship.

Using has_many :through

The main difference between this option and the first one is that now a third model is created. Such model can contain things like custom behavior or validations, and it might be useful in many cases.

Going back to our example, for creating a has_many :through association we need to write a migration that creates a model that logically ties musicians and instruments, something like a songbook:

    create_table :songbooks do |t|
      t.belongs_to :instrument
      t.belongs_to :musician
      t.string :title #we can add extra attributes if we want
      t.timestamps
    end

Now, we can add the associations to the models:

class Songbook < ApplicationRecord
    belongs_to :instrument
    belongs_to :musician
end

class Instrument < ApplicationRecord
  has_many :songbooks
  has_many :musicians, through: :songbooks
end

class musician < ApplicationRecord
  has_many :songbooks
  has_many :instruments, through: :songbooks
end

And that's how you create a has_many :through association.

Which one should I use?

My rule of thumb is the following: If I need validations or any form of custom behavior in the relationship, I go with has_many :through. Otherwise (most cases) I go with has_and_belongs_to_many.

Thank you for reading!

What to do next

Author image
Budapest, Hungary
Hey there, I'm Juan. A programmer currently living in Budapest. I believe in well-engineered solutions, clean code and sharing knowledge. Thanks for reading, I hope you find my articles useful!