Associations

Reviewed & published by Brayan K

Real apps are made of related data: authors and books, posts and comments, doctors and patients. Rails associations let you describe those relationships once and get a rich set of helper methods for free.

Part of the free Ruby course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end of this lesson you'll wire up has_many, belongs_to, has_many :through, and has_and_belongs_to_many, and understand foreign keys and dependent: :destroy.

What You'll Learn in This Lesson

1️⃣ One-to-Many: has_many and belongs_to

The most common association is one-to-many. The "one" side declares has_many; the "many" side declares belongs_to and holds the foreign key column (author_id). Add dependent: :destroy so children don't outlive their parent.

# A one-to-many association: one Author writes many Books.
# The foreign key (author_id) lives on the "belongs_to" side (books).

class Author < ApplicationRecord
  # The collection side. dependent: :destroy deletes an author's
  # books when the author is destroyed.
  has_many :books, dependent: :destroy
end

class Book < ApplicationRecord
  # The single side. This is where the author_id column lives.
  belongs_to :author
end

# The matching migration created books.author_id:
#
#   create_table :books do |t|
#     t.string :title
#     t.references :author, foreign_key: true   # adds author_id
#     t.timestamps
#   end
#
# Now in the rails console you get helper methods for free:
#
#   author = Author.create(name: "Le Guin")
#   author.books.create(title: "A Wizard of Earthsea")
#   author.books.count        # => 1
#   book.author.name          # => "Le Guin"

2️⃣ Many-to-Many: :through and HABTM

When records relate on both sides, use a many-to-many. Prefer has_many :through when the join needs its own data; reach for has_and_belongs_to_many only for a plain link with nothing extra to store.

# Many-to-many WITH extra data: use has_many :through.
# A Doctor and a Patient meet through Appointments, and the
# appointment itself carries data (the date).

class Doctor < ApplicationRecord
  has_many :appointments
  has_many :patients, through: :appointments
end

class Patient < ApplicationRecord
  has_many :appointments
  has_many :doctors, through: :appointments
end

# The JOIN model belongs_to BOTH sides and can have its own columns.
class Appointment < ApplicationRecord
  belongs_to :doctor
  belongs_to :patient
  # appointments table also has an :appointment_date column
end

# Usage:
#   doctor.patients            # patients seen, via appointments
#   doctor.appointments.first.appointment_date

# ---------------------------------------------------------------
# Simpler many-to-many with NO extra data and NO join model:
# has_and_belongs_to_many (HABTM) over a join table authors_books.

class AuthorHabtm < ApplicationRecord
  has_and_belongs_to_many :books
end

class BookHabtm < ApplicationRecord
  has_and_belongs_to_many :authors
end

Your turn. Complete a blog's Post and Comment models so the one-to-many works end to end.

Mini-Challenge: A Tagging System

Model tags with has_many :through: a Post and a Tag joined by a Tagging. Write all three model classes.

# 🎯 MINI-CHALLENGE: a tagging system with has_many :through.
#
# 1. A Post has many Taggings, and has many Tags THROUGH taggings.
# 2. A Tag has many Taggings, and has many Posts THROUGH taggings.
# 3. The Tagging join model belongs_to :post and belongs_to :tag.
#
# Write the three model classes below. Then in the console you should
# be able to call post.tags and tag.posts.
#
# ✅ Goal:
#    post.tags << Tag.create(name: "ruby")
#    post.tags.map(&:name)   # => ["ruby"]

# your code here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Associations

RelationshipDeclaration
One-to-many (one side)has_many :books
One-to-many (many side)belongs_to :author
Delete children toodependent: :destroy
Many-to-many (join model)has_many :tags, through: :taggings
Many-to-many (no model)has_and_belongs_to_many :books
Foreign key (migration)t.references :author

🎉 Lesson Complete!

Practice quiz

Which association declares that one Author has many Books?

  • belongs_to :books
  • has_many :books
  • has_one :books
  • habtm :books

Answer: has_many :books. has_many :books goes in the Author model to express the one-to-many side.

On which model does the foreign key column normally live?

  • The model that declares has_many
  • Neither model — it is in a join file
  • The model that declares belongs_to
  • Always the Author model

Answer: The model that declares belongs_to. The belongs_to side holds the foreign key, e.g. books.author_id.

Following Rails conventions, what is the foreign key column for Book belongs_to :author?

  • books_author
  • author_key
  • id_author
  • author_id

Answer: author_id. Rails expects a column named author_id by convention.

What does dependent: :destroy do on has_many?

  • Prevents deleting the parent
  • Deletes associated child records when the parent is destroyed
  • Marks children as archived
  • Renames the foreign key

Answer: Deletes associated child records when the parent is destroyed. dependent: :destroy removes the children (running their callbacks) when the parent is destroyed.

Which association is used for a many-to-many relationship WITH a join model you can add attributes to?

  • has_one
  • belongs_to
  • has_many :through
  • has_and_belongs_to_many

Answer: has_many :through. has_many :through uses an explicit join model, so the join can carry its own columns.

has_and_belongs_to_many (HABTM) requires what in the database?

  • A join table with no model
  • A single foreign key
  • Two separate tables only
  • A polymorphic column

Answer: A join table with no model. HABTM uses a join table (e.g. authors_books) with no dedicated model class.

For a Comment that belongs to a Post, what does post.comments return?

  • A single Comment
  • An ActiveRecord relation of that post's comments
  • The post's id
  • nil unless cached

Answer: An ActiveRecord relation of that post's comments. has_many gives a collection (relation) of the associated records.

Which line belongs in the Book model for a one-to-many with Author?

  • has_many :author
  • has_one :author_id
  • references :author
  • belongs_to :author

Answer: belongs_to :author. The child (Book) declares belongs_to :author.

In a has_many :through, what must the through model itself declare?

  • Only validations
  • has_many to both sides
  • Nothing special
  • belongs_to to BOTH joined models

Answer: belongs_to to BOTH joined models. The join model uses belongs_to to each of the two models it connects.

What does inverse_of help Rails do?

  • Reverse a migration
  • Recognise the two sides of an association point at the same in-memory object
  • Delete records faster
  • Validate uniqueness

Answer: Recognise the two sides of an association point at the same in-memory object. inverse_of tells Rails the two association ends are the same object, avoiding redundant queries.

Continue this course

Frequently asked questions

Which side gets has_many and which gets belongs_to?

The side that owns or contains many records gets has_many; the side that holds the foreign key gets belongs_to. In a blog, an Author has_many :books and a Book belongs_to :author, because the books table carries the author_id column. A handy rule of thumb: belongs_to goes on the table that has the _id column. The has_many side gives you a collection (author.books), while the belongs_to side gives you a single related object (book.author).

When should I use has_many :through versus has_and_belongs_to_many?

Use has_many :through when the relationship itself needs to store data or behaviour — for example an Appointment that joins a Doctor and Patient and records the appointment date. The join model is a real class with its own columns, validations, and callbacks. Use has_and_belongs_to_many (HABTM) only for a plain many-to-many with nothing extra to store; it relies on a simple join table (like authors_books) and has no model class. Most experienced Rails developers reach for has_many :through by default because requirements tend to grow.

What does dependent: :destroy actually do?

dependent: :destroy tells Rails that when you destroy a parent record, it should also destroy each associated child record — running their callbacks and their own dependent options along the way. Without it, deleting an author could leave orphaned books pointing at a missing author_id, or raise a foreign-key error. Related options include dependent: :delete_all (faster, but skips child callbacks) and dependent: :nullify (sets the foreign key to NULL instead of deleting). Choose :destroy when child records should never outlive their parent.

Where do foreign keys come from in Rails?

Foreign keys are plain database columns named by convention as <singular_association>_id, such as author_id on the books table. You add them in a migration, usually with t.references :author, foreign_key: true, which creates the author_id column and an index. The belongs_to :author declaration in the model then knows to read and write that column. The foreign_key: true part also adds a real database-level constraint so the database itself refuses rows that point at a non-existent author.