Ruby on Rails Explained
scope
Oct 22, 2020
Scoping in Ruby on Rails allows us to define frequently used queries which can be invoked as method calls on either association objects, relations or models. You can reference the standard query methods like where
, joins
and includes
. The requirements of defined scopes is that it must return either an instance of ActiveRecord::Relation
or nil
. This allows us to chain multiple scopes together for highly flexible query construction.
Defining a scope
To define a scope, we use the scope
class method, passing the name of the scope that we want to call it by, and a lambda function that will be evaluated in the context of either the class or from a relation instance.
class Post
scope :published, -> { where.not(published_at: nil) }
end
The published
scope can then be used to retrieve all posts that has a published_at
date, like this:
@published_posts = Post.published
Passing arguments to a scope
While static scopes is useful, it wouldn't solve everything if it could not be combined with passing dynamic arguments. This is how you define scope with arguments:
class Category
has_many :posts
end
class Post
belongs_to :category
scope :in_category, -> (category) { where(category: category) }
end
You can then use the scope like this:
@posts_in_category = Post.in_category(@category)
Now you are probably thinking "couldn't that also be achieved through the has_many :posts
association in the Category model?" The answer to that is, yes it can, and yes it should! But there are scenarios where using scopes is a great tool, which I will cover in the next section.
Chaning scopes
Chaining multiple scopes together is a good practice and lets you define simple scopes that when used together, can help you achieve complex scenarios. I'm going to show this by expanding the scenario from the previous section by including a third model called Author
.
class Author
has_many :posts
end
class Category
has_many :posts
end
class Post
belongs_to :author
belongs_to :category
scope :in_category, -> (category) { where(category: category) }
end
Now look at the following scenario on how to combine this associations and scopes:
@author = Author.find_by(name: params[:author])
@category = Category.find_by(name: params[:category])
@posts = @author.posts.published.in_category(@category)
See how we are starting with the posts
association from the author, which will return a relation for only that author's posts. Then it will chain that with published
to limit those posts to only include those that are published. Then lastly it will pass the category instance to the in_category
scope to only include the posts from that category.
That's it
I hope you found this post interesting.
Thank you for reading and happy coding!