Rails Associations Deep — Advanced ActiveRecord Relationships
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
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