Skip to content

Rails Associations Deep — Advanced ActiveRecord Relationships

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Rails Associations Deep. We cover key concepts, practical examples, and best practices to help you master this topic.

Rails associations define relationships between models with polymorphic belongs_to, has_many :through, counter cache, dependent actions, touch for cache invalidation, and inverse_of.

What You'll Learn

By the end of this tutorial, you'll implement polymorphic associations, has_many :through with join models, counter cache, dependent strategies, and association callbacks.

Why Associations Matter

Associations define how models relate to each other. Proper associations enable eager loading, cascading operations, and clean domain modeling.

Real-World Use

A social platform uses polymorphic likes on posts, comments, and photos. A has_many :through matches users to groups via memberships with roles.

Associations Path

flowchart LR
  A[ActiveRecord] --> B[Associations Deep]
  B --> C[Polymorphic]
  B --> D[has_many through]
  B --> E[Counter Cache]
  B --> F[Dependent]
  B --> G{You Are Here}
  style G fill:#f90,color:#fff

Polymorphic Associations

One association for multiple models.

class Like < ApplicationRecord
  belongs_to :user
  belongs_to :likeable, polymorphic: true, counter_cache: :likes_count
end

class Post < ApplicationRecord
  has_many :likes, as: :likeable, dependent: :destroy
end

class Comment < ApplicationRecord
  has_many :likes, as: :likeable, dependent: :destroy
end

# Migration
class CreateLikes < ActiveRecord::Migration[7.1]
  def change
    create_table :likes do |t|
      t.references :user, null: false, foreign_key: true
      t.references :likeable, polymorphic: true, null: false
      t.timestamps
    end
    add_index :likes, [:likeable_type, :likeable_id, :user_id], unique: true
  end
end

has_many :through

Associate through a join model.

class User < ApplicationRecord
  has_many :memberships, dependent: :destroy
  has_many :groups, through: :memberships
  has_many :admin_groups, -> { where(memberships: { role: :admin }) }, through: :memberships, source: :group
end

class Membership < ApplicationRecord
  belongs_to :user
  belongs_to :group
  enum :role, { member: 0, admin: 1, owner: 2 }
end

class Group < ApplicationRecord
  has_many :memberships, dependent: :destroy
  has_many :members, through: :memberships, source: :user
  has_many :admins, -> { where(memberships: { role: :admin }) }, through: :memberships, source: :user
end

Counter Cache

Cache association counts in the parent.

# Migration
class AddCommentsCountToPosts < ActiveRecord::Migration[7.1]
  def change
    add_column :posts, :comments_count, :integer, default: 0, null: false
  end
end

# Model
class Comment < ApplicationRecord
  belongs_to :post, counter_cache: true
  # belongs_to :post, counter_cache: :custom_comments_count
end

class Post < ApplicationRecord
  has_many :comments
end

# Access cached count
post.comments_count    # Uses cached value, no COUNT query
post.comments.size     # Also uses counter cache
post.comments.count    # Always executes COUNT query

Dependent Options

Cascade operations on associated records.

class User < ApplicationRecord
  has_many :posts, dependent: :destroy          # Delete posts
  has_many :comments, dependent: :delete_all     # Delete comments without callbacks
  has_one :profile, dependent: :destroy          # Delete profile
  has_many :logs, dependent: :nullify            # Set user_id to null

  # Restrict with error
  has_many :orders, dependent: :restrict_with_error
  # Restrict with exception
  has_many :owned_groups, dependent: :restrict_with_exception

  # For has_many :through
  has_many :memberships, dependent: :destroy
  has_many :groups, through: :memberships        # Does not delete groups
end

# Before destroy callback
class User < ApplicationRecord
  before_destroy :ensure_no_active_orders

  private

  def ensure_no_active_orders
    if orders.active.any?
      errors.add(:base, "Cannot delete user with active orders")
      throw :abort
    end
  end
end

Common Mistakes

1. Missing Inverse Of

Without inverse_of, Rails loads the same object twice from the database.

2. Polymorphic Without Proper Indexing

Always index [:likeable_type, :likeable_id] for polymorphic queries.

3. Counter Cache Without Migration

The column must exist before using counter_cache: true.

4. Dependent Destroy in has_many :through

dependent: :destroy on through associations destroys join records, not target records.

5. Not Using Source for Custom Through Associations

When the through association name differs from the target, specify source.

Practice Questions

1. What is a polymorphic association?

An association where a model belongs to multiple other models using type and ID columns.

2. How does counter cache improve performance?

It caches the count in a column, avoiding COUNT queries.

3. What is the difference between dependent: :destroy and :delete_all?

destroy runs callbacks. delete_all deletes directly in SQL.

4. What does inverse_of do?

Prevents loading the same object twice by setting the inverse association.

5. Challenge: Create polymorphic comments with counter cache.

class Comment < ApplicationRecord
  belongs_to :commentable, polymorphic: true, counter_cache: true
  belongs_to :user
end

class Post < ApplicationRecord
  has_many :comments, as: :commentable, dependent: :destroy
end

class Video < ApplicationRecord
  has_many :comments, as: :commentable, dependent: :destroy
end

FAQ

What is has_one through?

Creates a one-to-one association through a join model.

Can I have multiple counter caches on one model?

Yes. Each association can have its own counter cache column.

What is the source option?

Specifies the actual association name on the through model.

How do I auto-invalidate cache on association change?

Use touch: true to update the parent's updated_at.

What is the difference between has_many and has_many through?

has_many uses direct foreign key. has_many through uses a join table.

Mini Project: Social Platform Associations

Model a social platform with polymorphic associations.

class Post < ApplicationRecord
  has_many :likes, as: :likeable, dependent: :destroy
  has_many :comments, as: :commentable, dependent: :destroy
  belongs_to :user, counter_cache: true
end

class Like < ApplicationRecord
  belongs_to :user
  belongs_to :likeable, polymorphic: true, counter_cache: true
end

class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :commentable, polymorphic: true, counter_cache: true
  has_many :likes, as: :likeable, dependent: :destroy
end

What's Next

Rails Forms Deep Rails Validations Deep Rails ActiveRecord Deep

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro