Scopes in Ruby on Rails
How to Write Efficient and Reusable Queries
Scopes in Rails 7 are a way to define common query fragments that can be reused across different parts of an application. Scopes are defined on model classes and return an ActiveRecord::Relation object, which can then be further chained with other query methods.
Scopes are defined using the scope
method, which takes two arguments: the first argument is a name for the scope, and the second argument is a block that defines the query fragment for the scope. The block should return an ActiveRecord::Relation object.
For example, suppose we have a Book
model with a published
attribute that indicates whether the book has been published or not. We can define a scope that returns all published books like this:
class Book < ApplicationRecord
scope :published, -> { where(published: true) }
end
We can then use this scope in different parts of our application to retrieve all published books:
published_books = Book.published
Scopes can also take arguments, which can be used to parameterize the query fragment. For example, we could define a scope that returns all books published after a certain date:
class Book < ApplicationRecord
scope :published_after, ->(date) { where("published_at > ?", date) }
end
We can then use this scope to retrieve all books published after a certain date:
recent_books = Book.published_after(1.month.ago)
Scopes can be chained together with other query methods to further refine the query. For example, we could chain the published_after
scope with an order
method to retrieve the most recent books published after a certain date:
recent_books = Book.published_after(1.month.ago).order(published_at: :desc)
In summary, scopes in Rails 7 provide a convenient way to define and reuse common query fragments, which can help to keep our code DRY and more readable.
Examples of how to test scopes in RSpec and Rails using FactoryBot
Let’s say we have a Book
model with a published
attribute that indicates whether the book has been published or not, and we have defined a scope called published
that returns all published books.
Here’s an example of how to test the published
scope using RSpec and FactoryBot:
RSpec.describe Book, type: :model do
describe ".published" do
it "returns only published books" do
# Create some published books
FactoryBot.create_list(:book, 3, published: true)
# Create some unpublished books
FactoryBot.create_list(:book, 2, published: false)
# Call the published scope
published_books = described_class.published
# Expect only published books to be returned
expect(published_books.count).to eq(3)
expect(published_books.all? { |book| book.published? }).to be true
end
end
end
In this example, we use FactoryBot to create some books with the published
attribute set to true or false. We then call the published
scope on the Book
model and expect only the published books to be returned.
Here’s another example of how to test a scope with an argument. Let’s say we have a published_after
scope that returns all books published after a given date:
class Book < ApplicationRecord
scope :published_after, ->(date) { where("published_at > ?", date) }
end
Here’s how we can test the published_after
scope using RSpec and FactoryBot:
RSpec.describe Book, type: :model do
describe ".published_after" do
it "returns only books published after the given date" do
# Create some books with different publication dates
FactoryBot.create(:book, published_at: 2.weeks.ago)
FactoryBot.create(:book, published_at: 1.week.ago)
FactoryBot.create(:book, published_at: 1.day.ago)
# Call the published_after scope with a date
recent_books = described_class.published_after(1.week.ago)
# Expect only books published after the given date to be returned
expect(recent_books.count).to eq(2)
expect(recent_books.all? { |book| book.published_at > 1.week.ago }).to be true
end
end
end
In this example, we use FactoryBot to create some books with different publication dates. We then call the published_after
scope on the Book
model with a date and expect only the books published after that date to be returned.
I hope these examples help! Testing scopes is an important part of ensuring that your application behaves correctly, and using FactoryBot can make it easy to create the necessary objects for your tests.