Associations in Rails

· 7 min read · Updated March 28, 2026 · intermediate
rails activerecord associations

Active Record associations let you declare relationships between models in plain Ruby, without writing SQL. Rails uses those declarations to give you a rich set of methods for working with related records. Getting the right association in place for each relationship is one of the most consequential design decisions in a Rails application.

belongs_to

belongs_to sets up a one-to-one connection where the foreign key lives on this model’s table. If a Book belongs to an Author, the books table needs an author_id column.

class Book < ApplicationRecord
  belongs_to :author
end
```ruby

The association name must be singular. Rails infers the class name from it (singularizes `author` → `Author`) and looks for `author_id` on the current model's table. If either convention doesn't fit, you can override them:

```ruby
class Book < ApplicationRecord
  belongs_to :author, foreign_key: :writer_id
end
```ruby

This tells Rails to use the `writer_id` column instead of `author_id`. Use `class_name` when the association name doesn't match the actual class:

```ruby
class Book < ApplicationRecord
  belongs_to :author, class_name: 'Writer'
end
```ruby

Declaring `belongs_to :author` also generates methods: `author`, `author=`, `build_author`, `create_author`, and `reload_author`.

## has_one

`has_one` also represents a one-to-one relationship, but the foreign key lives on the *other* model's table. A `Supplier` has one `Account`, so the `accounts` table carries the `supplier_id` column.

```ruby
class Supplier < ApplicationRecord
  has_one :account
end
```ruby

The table structure reflects which side holds the key:

```ruby
create_table :accounts do |t|
  t.belongs_to :supplier, index: true, unique: true
end
```ruby

## has_many

`has_many` is the "one-to-many" side. An `Author` has many `Book` records. The name is always pluralized — unlike `belongs_to`, which requires singular.

```ruby
class Author < ApplicationRecord
  has_many :books
end
```ruby

Because `has_many` is plural, Rails looks for a `author_id` column on the books table automatically. Beyond basic getter and setter methods, a `has_many` association generates an entire collection proxy: `books <<`, `books.delete`, `books.destroy`, `books.empty?`, `books.size`, `books.find`, `books.build`, `books.create`.

## has_many :through

Use `has_many :through` when you need a many-to-many relationship via an intermediate model. A physician has many patients through appointments, for example.

```ruby
class Physician < ApplicationRecord
  has_many :appointments
  has_many :patients, through: :appointments
end

class Appointment < ApplicationRecord
  belongs_to :physician
  belongs_to :patient
end
```ruby

The join model (`Appointment`) is a real Active Record model with its own table, which means it can have validations, callbacks, and extra attributes. Choose `has_many :through` over `has_and_belongs_to_many` whenever you need that flexibility.

When you assign to a through-collection, Rails manages the join rows automatically:

```ruby
physician.patients = patients
```ruby

New join rows are created for newly associated patients. Orphan join rows are deleted. One thing to watch: join row deletion happens with direct SQL, so callbacks on the join model do not fire.

### The source Option

When the name of the association on the join model doesn't match what you're calling it on the parent, use `source` to point Rails in the right direction:

```ruby
class Document < ApplicationRecord
  has_many :sections
  has_many :paragraphs, through: :sections
end

class Section < ApplicationRecord
  belongs_to :document
  has_many :paragraphs
end
```ruby

`Document.first.paragraphs` follows the chain `document → sections → paragraphs`. If the association on `Section` were named `texts` instead of `paragraphs`, you'd write `source: :texts` on the through declaration.

## has_one :through

`has_one :through` gives you a one-to-one relationship through an intermediate model. A supplier has one account, and through that account it has one account history:

```ruby
class Supplier < ApplicationRecord
  has_one :account
  has_one :account_history, through: :account
end
```ruby

## has_and_belongs_to_many

For simple many-to-many relationships where the join has no extra data, HABTM avoids the overhead of a full join model:

```ruby
class Assembly < ApplicationRecord
  has_and_belongs_to_many :parts
end

class Part < ApplicationRecord
  has_and_belongs_to_many :assemblies
end
```ruby

HABTM requires a join table with no primary key. Rails convention names it from the two table names sorted alphabetically:

```ruby
create_table :assemblies_parts, id: false do |t|
  t.belongs_to :assembly, index: true
  t.belongs_to :part, index: true
end
```ruby

Use HABTM only when the join has no behaviour of its own. As soon as you need validations or extra columns, promote it to a proper join model and use `has_many :through`.

## dependent Options

When you destroy a record, what happens to its associated records? The `dependent` option on `has_many` and `has_one` controls this.

`dependent: :destroy` calls `destroy` on each associated record, which runs callbacks and validations:

```ruby
class Author < ApplicationRecord
  has_many :books, dependent: :destroy
end
```ruby

This is thorough but expensive for large collections because every associated record gets instantiated and processed individually.

`dependent: :delete_all` sends a direct `DELETE` SQL statement per record without instantiating them. Callbacks are skipped, which makes it faster but less safe if you rely on `before_destroy` hooks:

```ruby
class Author < ApplicationRecord
  has_many :books, dependent: :delete_all
end
```ruby

`dependent: :nullify` sets all foreign keys to `NULL` without destroying any records:

```ruby
class Author < ApplicationRecord
  has_many :books, dependent: :nullify
end
```ruby

This is the right choice for polymorphic associations, where nullifying both the `_id` and `_type` columns avoids the expensive nested delete problem.

`restrict_with_error` and `restrict_with_exception` prevent deletion entirely if associated records exist, which is useful for enforcing referential integrity at the application level.

## Polymorphic Associations

Sometimes a single association needs to point to different models. Pictures might belong to either an `Employee` or a `Product`. Polymorphic associations solve this with a `_type` column that stores the class name alongside the `_id`:

```ruby
class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

class Employee < ApplicationRecord
  has_many :pictures, as: :imageable
end

class Product < ApplicationRecord
  has_many :pictures, as: :imageable
end
```ruby

The migration uses `references` with `polymorphic: true`:

```ruby
create_table :pictures do |t|
  t.references :imageable, polymorphic: true, index: true
end
# Creates: imageable_id (integer) + imageable_type (string)
```ruby

Now `@picture.imageable` returns whichever parent object is actually stored there. One limitation: polymorphic associations cannot use `inverse_of` because the actual associated class changes at runtime.

## Avoiding N+1 Queries with includes

Loading a list of authors then accessing their books triggers one query per author — the classic N+1 problem:

```ruby
# N+1: 1 query for authors + N queries for books
authors = Author.all
authors.each { |a| a.books.map(&:title) }
```ruby

Fix it by eager-loading the associated records with `includes`:

```ruby
authors = Author.includes(:books)
authors.each { |a| a.books.map(&:title) }  # 2 queries total
```ruby

Rails loads all books in a single second query using `IN` with the author IDs. For nested associations:

```ruby
Author.includes(books: [:author, :publisher])
```ruby

This pattern works with polymorphic associations too:

```ruby
Picture.includes(:imageable).where(...)
```ruby

## Self-Referential Associations

A model can reference itself. Employees have a manager who is also an employee, or users can have friends who are also users:

```ruby
class Employee < ApplicationRecord
  belongs_to :manager, class_name: 'Employee', foreign_key: 'manager_id'
  has_many :subordinates, class_name: 'Employee', foreign_key: 'manager_id'
end
```ruby

`foreign_key` tells Rails which column holds the reference on the other side. `class_name` resolves the ambiguity that self-reference creates.

For a many-to-many self-referential relationship like friendships, you need a join table and a proper join model:

```ruby
class User < ApplicationRecord
  has_many :friendships
  has_many :friends, through: :friendships, source: :friend
end

class Friendship < ApplicationRecord
  belongs_to :user
  belongs_to :friend, class_name: 'User'
end
```ruby

The `friendships` table needs both `user_id` and `friend_id` columns. The `source: :friend` option tells Rails to look for a `friend` association on the `Friendship` model rather than assuming `friends`.

## Bi-Directional Associations and inverse_of

When two associations point to the same record, you can hint this to Rails with `inverse_of`:

```ruby
class Author < ApplicationRecord
  has_many :books, inverse_of: :author
end

class Book < ApplicationRecord
  belongs_to :author, inverse_of: :books
end
```ruby

Without `inverse_of`, modifying `@author.book.title` and then `@book.author.name` in the same request can cause unexpected behaviour because Rails may have loaded two separate instances of the same record. With `inverse_of` set, Rails keeps both sides in sync automatically.

## Conclusion

Associations are the connective tissue of a Rails application's data model. `belongs_to` and `has_one` handle one-to-one relationships, `has_many` covers one-to-many, and `has_many :through` handles any many-to-many relationship that needs a real join model. Polymorphic associations let a single association point to multiple model types without duplicating columns.

The choices you make with `dependent`, `inverse_of`, and eager loading with `includes` have real consequences at runtime. Getting them right from the start keeps your application predictable as it grows.

## See Also

- [ActiveRecord Basics](/tutorials/rails-activerecord-basics/) — understand the model layer before working with associations
- [Rails Routing](/tutorials/rails-routing/) — how routing and model associations interact
- [Ruby Blocks and Iterators](/tutorials/ruby-blocks-and-iterators/) — the collection methods that associations generate are built on blocks