ruby
30 lines · 9 steps
How scopes compose in Rails
A single Article model uses scopes, default_scope, and class methods that chain into composable, reusable queries.
Explained by
highlit
1class Article < ApplicationRecord
2 belongs_to :author
3
4 default_scope { where(deleted_at: nil) }
5
6 scope :published, -> { where(published: true) }
7 scope :draft, -> { where(published: false) }
8 scope :recent, -> { order(created_at: :desc) }
9 scope :by_author, ->(author) { where(author: author) }
10 scope :published_since, ->(date) { published.where("published_at >= ?", date) }
11
12 scope :popular, -> { where("view_count >= ?", 1_000).recent }
13
14 scope :search, ->(term) {
15 return all if term.blank?
16 where("title ILIKE :q OR body ILIKE :q", q: "%#{term}%")
17 }
18
19 scope :with_comments, -> {
20 joins(:comments).distinct
21 }
22
23 def self.archived
24 unscope(where: :deleted_at).where.not(deleted_at: nil)
25 end
26
27 def self.trending(limit = 10)
28 published.popular.limit(limit)
29 end
30end
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Scopes are just chainable methods returning relations, so they compose freely into larger queries.
- 2default_scope silently filters every query, which is powerful but easy to forget — unscope when you need to escape it.
- 3Class methods and scopes are interchangeable for building queries, letting you mix lambdas with conditional Ruby logic.
Related explainers
ruby
require "csv" class SalesReport def initialize(path)
Aggregating CSV sales data in Ruby
data-aggregation
memoization
group_by
Intermediate
6 steps
ruby
module DurationFormatter UNITS = [ ['week', 604_800], ['day', 86_400],
Turning seconds into human-readable durations in Ruby
greedy-decomposition
modular-arithmetic
formatting
Intermediate
7 steps
ruby
class Comment < ApplicationRecord belongs_to :post belongs_to :author, class_name: "User"
Live-updating comments with Turbo in Rails
turbo-streams
callbacks
associations
Intermediate
8 steps
ruby
require 'json' require 'set' class SensitiveScrubber
Recursively scrubbing secrets from JSON
recursion
data-masking
pattern-matching
Intermediate
7 steps
ruby
class ReportBatcher BATCH_SIZE = 500 def initialize(account)
Batching monthly email summaries in Rails
batching
service-object
background-jobs
Intermediate
7 steps
ruby
class Comment < ApplicationRecord belongs_to :commentable, polymorphic: true, counter_cache: true belongs_to :author, class_name: "User"
How polymorphic comments work in Rails
polymorphic-association
concerns
counter-cache
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-scopes-compose-in-rails-explained-ruby-edf6/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.