Skip
Arish's avatar

28. Has Many Associations


has_many Association

has_many is the most common association in Rails. It indicates a one-to-many relationship where one model has multiple instances of another model.

Basic has_many

ruby
1class User < ApplicationRecord
2  has_many :articles
3end
4
5class Article < ApplicationRecord
6  belongs_to :user
7end
8
9# Migration
10class CreateArticles < ActiveRecord::Migration[7.1]
11  def change
12    create_table :articles do |t|
13      t.string :title
14      t.text :body
15      t.references :user, null: false, foreign_key: true
16      t.timestamps
17    end
18  end
19end

Using has_many

Reading Associated Records

ruby
1user = User.find(1)
2
3# Get all articles
4user.articles              # => Collection of articles
5user.articles.count        # => 5
6user.articles.size         # => 5 (uses counter_cache if available)
7user.articles.length       # => 5 (loads all records)
8user.articles.empty?       # => false
9user.articles.any?         # => true
10
11# Access specific articles
12user.articles.first        # => First article
13user.articles.last         # => Last article
14user.articles.find(5)      # => Article with id 5
15user.articles[0]           # => First article (loads all!)
16
17# Chain with scopes and conditions
18user.articles.published
19user.articles.where(published: true)
20user.articles.order(created_at: :desc)
21user.articles.limit(10)

Creating Associated Records

ruby
1user = User.find(1)
2
3# Build (doesn't save)
4article = user.articles.build(title: "New Article")
5article.user_id  # => 1 (automatically set)
6article.save
7
8# Create (saves immediately)
9article = user.articles.create(title: "New Article")
10
11# Create with bang (raises on failure)
12article = user.articles.create!(title: "New Article")
13
14# Create multiple
15user.articles.create([
16  { title: "Article 1" },
17  { title: "Article 2" }
18])

Modifying Associations

ruby
1user = User.find(1)
2
3# Add existing articles
4user.articles << Article.find(5)
5user.articles.push(Article.find(6))
6
7# Replace all articles
8user.articles = [Article.find(1), Article.find(2)]
9
10# Remove article (sets foreign key to nil)
11user.articles.delete(Article.find(5))
12
13# Remove all articles
14user.articles.clear
15
16# Destroy articles (also deletes from database)
17user.articles.destroy(Article.find(5))
18user.articles.destroy_all
19
20# Check if exists
21user.articles.exists?
22user.articles.exists?(5)
23user.articles.include?(some_article)

has_many Options

Class Name and Foreign Key

ruby
1class User < ApplicationRecord
2  # Different class name
3  has_many :posts, class_name: "Article"
4  
5  # Different foreign key
6  has_many :articles, foreign_key: "author_id"
7  
8  # Both
9  has_many :written_articles, class_name: "Article", foreign_key: "author_id"
10end

Dependent Option

What happens to associated records when the parent is destroyed:

ruby
1class User < ApplicationRecord
2  # Destroy all articles when user is destroyed
3  has_many :articles, dependent: :destroy
4  
5  # Delete without callbacks
6  has_many :articles, dependent: :delete_all
7  
8  # Set foreign key to null
9  has_many :articles, dependent: :nullify
10  
11  # Raise error if any exist
12  has_many :articles, dependent: :restrict_with_exception
13  
14  # Add error to parent, prevent deletion
15  has_many :articles, dependent: :restrict_with_error
16end
17
18user.destroy  # Also destroys all user.articles

Ordering and Conditions

ruby
1class User < ApplicationRecord
2  # Default ordering
3  has_many :articles, -> { order(created_at: :desc) }
4  
5  # With conditions
6  has_many :published_articles, -> { where(published: true) }, class_name: "Article"
7  has_many :draft_articles, -> { where(published: false) }, class_name: "Article"
8  
9  # Multiple conditions
10  has_many :recent_published_articles, -> { 
11    where(published: true).order(created_at: :desc).limit(5) 
12  }, class_name: "Article"
13  
14  # With scope parameter
15  has_many :articles_after, ->(date) { where("created_at > ?", date) }, class_name: "Article"
16end
17
18user.recent_published_articles
19user.articles_after(1.week.ago)

Counter Cache

ruby
1class User < ApplicationRecord
2  has_many :articles
3end
4
5class Article < ApplicationRecord
6  belongs_to :user, counter_cache: true
7end
8
9# Migration
10add_column :users, :articles_count, :integer, default: 0
11
12# Now you can use:
13user.articles_count  # No database query!
14user.articles.size   # Uses counter cache automatically

Inverse Of

Helps Rails maintain consistency between associated objects:

ruby
1class User < ApplicationRecord
2  has_many :articles, inverse_of: :user
3end
4
5class Article < ApplicationRecord
6  belongs_to :user, inverse_of: :articles
7end
8
9user = User.first
10article = user.articles.first
11
12user.name = "New Name"
13article.user.name  # => "New Name" (same object in memory)

Collection Methods

has_many adds many helpful methods:

ruby
1user.articles              # All articles
2user.articles=(articles)   # Replace all
3user.article_ids           # Array of IDs
4user.article_ids=(ids)     # Replace by IDs
5user.articles.build        # Build new
6user.articles.create       # Create new
7user.articles.create!      # Create new with exception
8user.articles.reload       # Reload from database
9user.articles.size         # Count
10user.articles.length       # Count (loads all)
11user.articles.count        # SQL COUNT
12user.articles.empty?       # Check if empty
13user.articles.any?         # Check if any
14user.articles.many?        # More than one?
15user.articles.include?(a)  # Check membership
16user.articles.exists?      # Any exist?
17user.articles.distinct     # Unique records
18user.articles.reset        # Reset cached collection
19user.articles.find(...)    # Find within collection
20user.articles.where(...)   # Filter collection
21user.articles.destroy_all  # Destroy all
22user.articles.delete_all   # Delete all
23user.articles << article   # Add to collection
24user.articles.delete(a)    # Remove from collection
25user.articles.destroy(a)   # Destroy from collection
26user.articles.clear        # Remove all

Preloading Associations

Avoid N+1 queries:

ruby
1# N+1 problem (bad)
2users = User.all
3users.each do |user|
4  puts user.articles.count  # Query for each user!
5end
6
7# Eager loading (good)
8users = User.includes(:articles)
9users.each do |user|
10  puts user.articles.size  # No additional queries
11end
12
13# Preload (similar to includes)
14users = User.preload(:articles)
15
16# Eager load (uses LEFT OUTER JOIN)
17users = User.eager_load(:articles)

Scopes on Associations

ruby
1class User < ApplicationRecord
2  has_many :articles do
3    def published
4      where(published: true)
5    end
6    
7    def drafts
8      where(published: false)
9    end
10    
11    def find_by_title(title)
12      find_by(title: title)
13    end
14  end
15end
16
17user.articles.published
18user.articles.drafts
19user.articles.find_by_title("Hello World")

The has_many association is fundamental to building relationships in Rails applications!