Active Record Associations
Associations are connections between models. They make common operations simpler and easier by using the relationships between models.
Why Associations?
Without associations, you'd have to write a lot of manual code:
1# Without associations
2class Article < ApplicationRecord
3end
4
5class Comment < ApplicationRecord
6end
7
8# Getting all comments for an article
9comments = Comment.where(article_id: article.id)
10
11# Creating a comment for an article
12comment = Comment.create(article_id: article.id, body: "Great article!")With associations, it becomes much cleaner:
1# With associations
2class Article < ApplicationRecord
3 has_many :comments
4end
5
6class Comment < ApplicationRecord
7 belongs_to :article
8end
9
10# Getting all comments
11comments = article.comments
12
13# Creating a comment
14article.comments.create(body: "Great article!")belongs_to Association
belongs_to sets up a connection with another model where the current model contains the foreign key.
Basic Usage
1# The comments table has an article_id column
2class Comment < ApplicationRecord
3 belongs_to :article
4end
5
6# Migration
7class CreateComments < ActiveRecord::Migration[7.1]
8 def change
9 create_table :comments do |t|
10 t.text :body
11 t.references :article, null: false, foreign_key: true
12 t.timestamps
13 end
14 end
15endUsing belongs_to
1comment = Comment.first
2
3# Access the associated article
4comment.article # => #<Article id: 1, title: "...">
5comment.article.title # => "My Article"
6
7# Set the association
8comment.article = Article.find(2)
9comment.save
10
11# Build a new article (doesn't save)
12comment.build_article(title: "New Article")
13
14# Create a new article (saves immediately)
15comment.create_article(title: "New Article")belongs_to Options
1class Comment < ApplicationRecord
2 # Basic
3 belongs_to :article
4
5 # Custom class name
6 belongs_to :author, class_name: "User"
7
8 # Custom foreign key
9 belongs_to :article, foreign_key: "post_id"
10
11 # Optional association (allows nil)
12 belongs_to :category, optional: true
13
14 # Polymorphic association
15 belongs_to :commentable, polymorphic: true
16
17 # Touch parent on update
18 belongs_to :article, touch: true
19
20 # Counter cache
21 belongs_to :article, counter_cache: true
22 # Requires articles.comments_count column
23
24 # Inverse of (for bidirectional associations)
25 belongs_to :article, inverse_of: :comments
26
27 # Dependent behavior
28 belongs_to :article, dependent: :destroy
29endOptional belongs_to
By default in Rails 5+, belongs_to requires the associated record to exist:
1class Comment < ApplicationRecord
2 belongs_to :article # article_id cannot be nil
3end
4
5comment = Comment.new(body: "Test")
6comment.valid? # => false
7comment.errors # => { article: ["must exist"] }
8
9# Make it optional
10class Comment < ApplicationRecord
11 belongs_to :article, optional: true
12end
13
14# Or globally in config
15# config/application.rb
16config.active_record.belongs_to_required_by_default = falsehas_one Association
has_one sets up a one-to-one connection where the other model contains the foreign key.
Basic Usage
1class User < ApplicationRecord
2 has_one :profile
3end
4
5class Profile < ApplicationRecord
6 belongs_to :user
7end
8
9# Migration for profiles
10class CreateProfiles < ActiveRecord::Migration[7.1]
11 def change
12 create_table :profiles do |t|
13 t.string :bio
14 t.string :avatar_url
15 t.references :user, null: false, foreign_key: true
16 t.timestamps
17 end
18 end
19endUsing has_one
1user = User.first
2
3# Access the associated profile
4user.profile # => #<Profile id: 1, ...>
5user.profile.bio # => "Ruby developer"
6
7# Check if profile exists
8user.profile.present? # => true
9
10# Set the association
11user.profile = Profile.new(bio: "New bio")
12user.save
13
14# Build a new profile (doesn't save)
15user.build_profile(bio: "My bio")
16
17# Create a new profile (saves immediately)
18user.create_profile(bio: "My bio")has_one Options
1class User < ApplicationRecord
2 # Basic
3 has_one :profile
4
5 # Custom class name
6 has_one :account, class_name: "UserAccount"
7
8 # Custom foreign key
9 has_one :profile, foreign_key: "owner_id"
10
11 # Dependent behavior
12 has_one :profile, dependent: :destroy
13 has_one :profile, dependent: :delete
14 has_one :profile, dependent: :nullify
15
16 # Conditions
17 has_one :active_subscription, -> { where(active: true) }, class_name: "Subscription"
18
19 # Through another association
20 has_one :account_history, through: :profile
21
22 # Source (when through is used)
23 has_one :main_address, through: :profile, source: :address
24endhas_one vs belongs_to
The key difference is where the foreign key lives:
┌─────────────┐ ┌─────────────┐
│ User │ │ Profile │
├─────────────┤ ├─────────────┤
│ id │◀────────│ user_id │
│ name │ has_one │ bio │
│ email │ │ avatar_url │
└─────────────┘ └─────────────┘
belongs_to
The model with the foreign key uses belongs_to.
Touch Option
Update parent's updated_at when child changes:
1class Comment < ApplicationRecord
2 belongs_to :article, touch: true
3end
4
5# When comment is saved/updated/destroyed:
6comment.save
7# Article's updated_at is also updated!
8
9# Touch specific column
10belongs_to :article, touch: :comments_updated_atCounter Cache
Track the number of associated records efficiently:
1# Migration
2class AddCommentsCountToArticles < ActiveRecord::Migration[7.1]
3 def change
4 add_column :articles, :comments_count, :integer, default: 0
5
6 # Backfill existing counts
7 Article.find_each do |article|
8 Article.reset_counters(article.id, :comments)
9 end
10 end
11end
12
13# Model
14class Comment < ApplicationRecord
15 belongs_to :article, counter_cache: true
16end
17
18# Usage
19article.comments_count # No database query!
20
21# Custom column name
22belongs_to :article, counter_cache: :total_commentsAutosave
Automatically save associated records:
1class Article < ApplicationRecord
2 has_one :metadata, autosave: true
3end
4
5article = Article.find(1)
6article.metadata.description = "New description"
7article.save # Also saves metadata!These are the foundational one-to-one relationships in Rails!
