Using scopes in Ruby on Rails
Hello dear coders, enthusiasts, and prospective programmers!
After a long time, I’m finally back with an article, that gives you an intro about usage of “scopes” in my beloved Ruby on Rails. I’m actively using it in my projects, so I thought it can be useful to many learners that would like to integrate it into their next projects and write a beautiful code.
Even though this is a basic intro about “scopes”, keep in mind that you can use them to make complex queries and moreover, use several “scopes” together, by chaining them one after another.
What are Scopes?
In short, Scopes are used to assign complex ActiveRecord queries into customized methods using Ruby on Rails.
Every scope takes two arguments:
- A name, which you use to call that scope in your code,
- A lambda, which implements that query.
Here’s a simple example of a scope, where we filter only active users:
scope :active_users, -> { where(active: true) }
Examples of scopes
As we mentioned above, a scope must have a name, by which you will call it later in your code and a lambda part which will actually execute the required code. Down below, take a look at a widely used example of a scope:
class Post < ApplicationRecord
scope :published, -> { where(status: "Published") }
end
In this scope, we are naming it ‘published’ and filtering only the records which have its status set as ‘Published’. Later on, you can use it to get only published posts, by simply calling the scope we just created.
class PostsController < ApplicationController
def index
# only returns Posts that have a "Published" status
@posts = Post.published
end
end
Using Rails Scopes With Arguments
Of course there will be many cases where you will want to filter records based on a given parameter. For instance, filter posts by a provided status. In this case, you can also use scopes, but pass a status parameter to it, so that you can filter the post records by its status.
Here is the code snippet that shows how you can use scopes with arguments:
class Post < ApplicationRecord
scope :by_post_status, -> (post_status) { where('post_status = ?', post_status) }
end
Here, we see the same structure, but with an argument passed to it.
Chaining scopes
Imagine a case where you want to filter posts by several arguments and you constructed several scopes for it. This is the case where you can use chaining of scopes.
But for this to be useful, you need to create the scopes you want to use, be it with arguments or without them. Here below, you can see a Product model that has several scopes defined.
class Product < ApplicationRecord
scope :visible, -> { where(hidden: false) }
scope :by_category, -> (category) { where('category_id = ?', category.id) }
scope :has_likes, ->(likes) { where('likes >= ?', likes) }
end
After defining our Product model, imagine you want to get products that are visible to the users online, and at the same the ones that belong to “Laptops” category and have at least 20 likes. So, no problem, you can use all of the scopes defined in your product model, by chaining them one after another. Here is the code snippet for this:
class ProductsController < ApplicationController
def index
# only returns Products that are not hidden, that belong to
# to category "Laptops" and has at least 20 likes
@category = Category.find_by_title("Laptops")
@products = Product.visible.by_category(@category).has_likes(20)
end
end
Scope vs Class Method
The important thing we have to know is that scopes are actually converted to class methods. And this is what Ruby on Rails guides say about this:
To define a simple scope, we use the
scope
method inside the class, passing the query that we'd like to run when this scope is called:
class Article < ActiveRecord::Base
scope :published, -> { where(published: true) }
end
This is exactly the same as defining a class method, and which you use is a matter of personal preference:
class Article < ActiveRecord::Base
def self.published
where(published: true)
end
end
So which you use is a matter of preference and it is even recommended that you use class methods for scopes that take arguments.
When your scope logic gets complicated, a class method feels like the right place to put it.
Don’t Use Default Scopes
When first using them, you will have the desire to define default scope. It looks attractive, if you want to apply this filter to many of the records when fetching from DB.
class Student < ApplicationRecord
default_scope { where(completed_course: true) }
end
Using this kind of default scope might seem attractive at first. But let’s imagine a situation where we want to get all the students of our school, including ones that failed to complete the whole course. In this case, the default scope will be executed for “Student” model and you will not get all the students.
In such situations there is an ‘unscoped’ method that we can call to ignore the default scope. It returns a scope for this class without taking into account the default_scope.
def index
@students = Student.unscoped.all
end
Conclusion
So, this is it, friends. Scope is a very useful tool provided by Ruby on rails to grab some data that is not too complex to construct. Besides that, they look expressive, clear, and concise.
Hope so much, that this adds up to your programming skills and teaches you some extra tips you can use in your next project.
Stay hungry for knowledge, stay safe !!!