ruby 32 lines · 8 steps

How scopes compose in Rails

An Article model layers named scopes that chain into complex, reusable queries.

Explained by highlit
1class Article < ApplicationRecord
2 belongs_to :author, class_name: "User"
3 has_many :taggings, dependent: :destroy
4 has_many :tags, through: :taggings
5 
6 scope :published, -> { where.not(published_at: nil) }
7 scope :draft, -> { where(published_at: nil) }
8 
9 scope :published_before, ->(time) { published.where(published_at: ..time) }
10 
11 scope :in_category, ->(slug) { joins(:category).where(categories: { slug: }) }
12 
13 scope :tagged_with, ->(*names) do
14 next all if names.empty?
15 
16 joins(:tags)
17 .where(tags: { name: names.flatten })
18 .group(:id)
19 .having("COUNT(DISTINCT tags.id) = ?", names.flatten.uniq.size)
20 end
21 
22 scope :search, ->(query) do
23 return all if query.blank?
24 
25 term = "%#{sanitize_sql_like(query)}%"
26 where("title ILIKE :term OR body ILIKE :term", term:)
27 end
28 
29 scope :recent, ->(limit = 10) { published.order(published_at: :desc).limit(limit) }
30 
31 scope :by_author, ->(author) { where(author:) }
32end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Scopes return relations, so they chain and compose into larger queries without duplicating SQL.
  2. 2Guarding a scope with `all` keeps it chainable even when its argument is empty or blank.
  3. 3Bind parameters and `sanitize_sql_like` keep user input from becoming SQL injection.

Related explainers

Share this explainer

Here's the card — post it anywhere.

How scopes compose in Rails — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code