Scopes
Query scopes in CQL Active Record allow you to define reusable query constraints on your models. They help in making your code D.R.Y. (Don't Repeat Yourself) by encapsulating common query logic, leading to more readable and maintainable model and controller code.
The primary way to define scopes in CQL is by using the scope
macro. Alternatively, scopes can also be defined as class methods.
What are Scopes?
Often, you'll find yourself writing the same query conditions repeatedly. For example:
Fetching all published articles.
Finding all active users.
Retrieving items created in the last 7 days.
Scopes let you give names to these common queries. A scope is essentially a pre-defined query or a piece of a query that can be easily applied and chained.
Benefits of using scopes:
Readability:
Article.published.all
is much clearer thanArticle.query.where(status: "published").order(published_at: :desc).all(Article)
scattered throughout your codebase.Reusability: Define the logic once and use it anywhere you need that specific dataset.
Maintainability: If the definition of "published" changes, you only need to update it in one place (the scope definition).
Chainability: Scopes can be chained with other scopes or standard query methods.
Defining Scopes
There are two main ways to define scopes in CQL: using the scope
macro (recommended) or defining them as class methods.
1. Using the scope
Macro (Recommended)
scope
Macro (Recommended)The most concise and recommended way to define scopes is by using the scope
macro. This macro is provided by the CQL::ActiveRecord::Scopes
module, which you should include in your model.
Syntax:
scope :scope_name, ->(optional_arguments) { query_logic }
:scope_name
: The name of the scope (aSymbol
), which will become a class method on your model.->(optional_arguments) { ... }
: A proc that defines the query logic.It can optionally take arguments with their types, e.g.,
->(count : Int32, category : String) { ... }
.Inside the proc, you use standard CQL query methods like
where
,order
,limit
, etc. These methods are called on the current query context, which is typically aChainableQuery(YourModel)
instance or the model class itself if starting a new chain.The proc should return a
CQL::Query
or aChainableQuery(YourModel)
. If a rawCQL::Query
is returned (e.g., by starting withquery.where(...)
), thescope
macro intelligently wraps it in aChainableQuery(YourModel)
to ensure it remains chainable with other scopes or Active Record query methods.
Examples:
Using these scopes:
Article.published
returns aChainableQuery(Article)
.Post.created_after(7.days.ago)
returns aChainableQuery(Post)
.
The scope
macro generates a class method. For example, scope :published, ->{ where(status: "published") }
on Article
is roughly equivalent to:
This keeps your model definitions clean and focused on the query logic.
2. Defining Scopes with Class Methods (Alternative)
Alternatively, you can define scopes by creating class methods directly. This approach might be preferred for very complex logic that is hard to express in a single proc or if you prefer the explicitness of a full method definition.
When defining scopes as class methods, they should generally return a ChainableQuery(YourModel)
instance to maintain chainability. The Queryable
module (included via CQL::ActiveRecord::Model
) provides methods like where
, order
, etc., that return ChainableQuery(YourModel)
instances and can be used here.
Example: Basic Scopes as Class Methods
Example: Scopes with Arguments as Class Methods
Scopes Returning CQL::Query
(Less Common for Chaining)
CQL::Query
(Less Common for Chaining)While ChainableQuery(YourModel)
is preferred for easy chaining (and is what the scope
macro ensures), a class method scope can return a raw CQL::Query
object. This is less common if you want to chain with other Active Record-style methods but might be useful for specific scenarios where you intend to pass the query object to a system expecting a base CQL::Query
.
When a scope returns CQL::Query
, terminate it with methods like .all(Product)
or .first(Product)
.
Using Scopes
Once defined (via macro or class method), scopes are called like any other class method:
Chaining Scopes
Scopes returning ChainableQuery(YourModel)
are designed for chaining with each other and standard query methods (.where
, .order
, etc.):
The ChainableQuery
class often uses forward_missing_to Target
(where Target
is your model class). This allows class methods on your model (including those generated by the scope
macro or defined manually) to be called on a ChainableQuery
instance, enabling natural chaining like Article.where(...).published
.
Scopes are a powerful feature for organizing your database query logic, making your application easier to read, write, and maintain. They promote the principle of keeping data logic within the model layer.
Last updated
Was this helpful?