Named Scopes
Scopes allow you to define commonly-used queries as reusable methods. They make your code cleaner and more expressive.
Defining Scopes
ruby
1class Article < ApplicationRecord
2 # Basic scope
3 scope :published, -> { where(published: true) }
4 scope :draft, -> { where(published: false) }
5 scope :recent, -> { order(created_at: :desc) }
6
7 # Scope with argument
8 scope :by_category, ->(category) { where(category: category) }
9 scope :created_after, ->(date) { where("created_at > ?", date) }
10
11 # Scope with default argument
12 scope :recent_count, ->(count = 10) { order(created_at: :desc).limit(count) }
13endUsing Scopes
ruby
1# Use like class methods
2Article.published
3Article.draft
4Article.recent
5
6# With arguments
7Article.by_category("technology")
8Article.created_after(1.week.ago)
9Article.recent_count(5)
10
11# Chain scopes
12Article.published.recent.limit(10)
13Article.published.by_category("technology").recentScopes vs Class Methods
Both achieve the same result:
ruby
1class Article < ApplicationRecord
2 # As scope
3 scope :published, -> { where(published: true) }
4
5 # As class method (equivalent)
6 def self.published
7 where(published: true)
8 end
9endWhen to Use Class Methods
ruby
1class Article < ApplicationRecord
2 # Use class methods for complex logic
3 def self.visible_to(user)
4 if user&.admin?
5 all
6 elsif user
7 where(published: true).or(where(user_id: user.id))
8 else
9 where(published: true)
10 end
11 end
12
13 # Or when returning non-relation
14 def self.statistics
15 {
16 total: count,
17 published: published.count,
18 draft: draft.count
19 }
20 end
21endCommon Scope Patterns
Status Scopes
ruby
1class Order < ApplicationRecord
2 # Status-based scopes
3 scope :pending, -> { where(status: "pending") }
4 scope :processing, -> { where(status: "processing") }
5 scope :completed, -> { where(status: "completed") }
6 scope :cancelled, -> { where(status: "cancelled") }
7
8 # Combined status scopes
9 scope :active, -> { where(status: %w[pending processing]) }
10 scope :finished, -> { where(status: %w[completed cancelled]) }
11
12 # Not status
13 scope :not_cancelled, -> { where.not(status: "cancelled") }
14endTime-Based Scopes
ruby
1class Article < ApplicationRecord
2 scope :today, -> { where(created_at: Date.today.all_day) }
3 scope :yesterday, -> { where(created_at: Date.yesterday.all_day) }
4 scope :this_week, -> { where(created_at: 1.week.ago..) }
5 scope :this_month, -> { where(created_at: 1.month.ago..) }
6 scope :this_year, -> { where(created_at: 1.year.ago..) }
7
8 scope :between, ->(start_date, end_date) {
9 where(created_at: start_date..end_date)
10 }
11
12 scope :on_date, ->(date) {
13 where(created_at: date.beginning_of_day..date.end_of_day)
14 }
15
16 scope :older_than, ->(days) { where("created_at < ?", days.days.ago) }
17 scope :newer_than, ->(days) { where("created_at > ?", days.days.ago) }
18endOrdering Scopes
ruby
1class Product < ApplicationRecord
2 scope :by_price, ->(direction = :asc) { order(price: direction) }
3 scope :by_name, -> { order(:name) }
4 scope :cheapest_first, -> { order(price: :asc) }
5 scope :expensive_first, -> { order(price: :desc) }
6 scope :newest_first, -> { order(created_at: :desc) }
7 scope :oldest_first, -> { order(created_at: :asc) }
8 scope :most_popular, -> { order(sales_count: :desc) }
9 scope :alphabetical, -> { order(:name) }
10 scope :random, -> { order(Arel.sql("RANDOM()")) }
11endSearch Scopes
ruby
1class User < ApplicationRecord
2 scope :search_name, ->(query) {
3 where("name ILIKE ?", "%#{sanitize_sql_like(query)}%")
4 }
5
6 scope :search_email, ->(query) {
7 where("email ILIKE ?", "%#{sanitize_sql_like(query)}%")
8 }
9
10 scope :search, ->(query) {
11 return all if query.blank?
12
13 search_name(query).or(search_email(query))
14 }
15
16 scope :filter_by_role, ->(role) {
17 return all if role.blank?
18 where(role: role)
19 }
20endPagination Scope
ruby
1class Article < ApplicationRecord
2 scope :page, ->(page_number, per_page = 25) {
3 limit(per_page).offset((page_number.to_i - 1) * per_page)
4 }
5end
6
7# Usage
8Article.published.recent.page(2, 10)Chaining Scopes
ruby
1class Article < ApplicationRecord
2 scope :published, -> { where(published: true) }
3 scope :featured, -> { where(featured: true) }
4 scope :recent, -> { order(created_at: :desc) }
5 scope :by_author, ->(author) { where(author_id: author.id) }
6 scope :in_category, ->(category) { where(category: category) }
7end
8
9# Chain multiple scopes
10Article.published
11 .featured
12 .recent
13 .in_category("tech")
14 .limit(10)
15
16# In controller
17def index
18 @articles = Article.published
19 .in_category(params[:category])
20 .by_author(params[:author_id])
21 .recent
22 .page(params[:page])
23endDefault Scope
ruby
1class Article < ApplicationRecord
2 # All queries include this by default
3 default_scope { order(created_at: :desc) }
4
5 # Or for soft delete
6 default_scope { where(deleted_at: nil) }
7
8 # Override with unscoped
9 scope :with_deleted, -> { unscope(where: :deleted_at) }
10end
11
12# Usage
13Article.all # Already ordered
14Article.unscoped.all # Without default scope
15Article.with_deleted # Include deleted recordsWarning About Default Scope
ruby
1# Default scope can cause unexpected behavior
2class Article < ApplicationRecord
3 default_scope { where(published: true) }
4end
5
6# This creates a PUBLISHED article, not draft!
7Article.create(title: "Draft", published: false)
8# The published: true from default scope may override!
9
10# Better: use explicit scopes
11class Article < ApplicationRecord
12 scope :visible, -> { where(published: true) }
13endMerging Scopes
ruby
1class Article < ApplicationRecord
2 scope :recent, -> { where("created_at > ?", 1.week.ago) }
3end
4
5class User < ApplicationRecord
6 scope :active, -> { where(active: true) }
7end
8
9# Merge scopes from different models
10Article.joins(:user).merge(User.active)Lambda Scopes with Associations
ruby
1class User < ApplicationRecord
2 has_many :articles
3 has_many :published_articles, -> { where(published: true) }, class_name: "Article"
4 has_many :recent_articles, -> { order(created_at: :desc).limit(5) }, class_name: "Article"
5end
6
7user.published_articles
8user.recent_articlesScopes make your models expressive and your controllers clean!
