
Introduction
When I started learning Rails, I came across scopes while rummaging google for solutions to several Rails project challenges. During those times, Scopes were terrifying for me at first, and every opportunity I had, I'd sidestep using this incredible concept in any of my Rails projects.
The fact is that as one proceeds in our different software development journeys, there are some useful concepts and tools that are important to pick up to make our coding experience scale faster. I accepted scope because I later understood what it was all about and how it could help my backend coding journey.
In this article, I will be explaining what scope is and how to use it in different parts of any Rails application. I have set up an Equipment API with Rails, and this will be the examples utilized as the codebase in this write-up. Check out thisrepository to follow along. Let's dive in.
What Are Scopes
Scopes are SQL queries that you can build in any rails model. Often time, we tend to run similar queries in our rails console to query our database and also understand the structure of the result we are receiving before going further with the development of our applications.
According to the official ruby on rails guide, "Scoping allows us to make use of commonly-used queries which can be referenced as method calls on the association objects or models." The general expectation is that all scope bodies should return anActiveRecord::Relation
or nil. As a result of this, it makes it easy to call other ActiveRecord methods on it. Simply put, a scope is just a custom chain of active record methods. They are sets of pre-defined queries that can be chained to build other complex Queries.
Why use Scopes
Scopes help you D.R.Y out your active record calls and also keep your codes organized. Scopes make it easy to find the records you need at a particular time.
Also, using scopes helps us develop a healthy habit of keeping the heavy stuffs away from the controller. Rails convention suggests that implementation of the business logic should exist in Rails model instead of the controller/view.
Scoping also allows you to specify frequently used queries which can be referenced as method calls on the association objects or models.
Furthermore, during testing sometimes, you do not want your test to go into your database to fetch results. With scopes, this is achievable as it allows for easier stubbing.
Types of Scopes
Default ScopesvsNamed scopes
Default scopes take the namedefault_scope
when defined in the model and, they are scopes that are applied across all queries to the model in question. An example can be seen in the code block below:
classEquipment<ApplicationRecordhas_many:requestshas_many:customers,through: :requestsvalidates:brand,presence:truevalidates:model,presence:truevalidates:equipment_type,presence:truevalidates:serial_no,presence:truevalidates:accessories,presence:truedefault_scope,->{order("updated_at desc")}end
Conversely, named scopes are scopes that take a name and are defined in a model for active record database queries. Typically, a named scope is made up of thescope
keyword, followed by the name you want to give to the scope, and a lambda. Below is an example of a named scope:
classEquipment<ApplicationRecordhas_many:requestshas_many:customers,through: :requestsvalidates:brand,presence:truevalidates:model,presence:truevalidates:equipment_type,presence:truevalidates:serial_no,presence:truevalidates:accessories,presence:truescope:not_available,->{where("available = ?",false)}end
The difference between both examples is glaring: one has a custom name and, one has a statutory name called default_scope. In the above example, the named scope above is callednot_available. The following arrow sign->
(lambda) can be re-written as:
scope:not_available,lambda{where("available = ?",false)}
note that->
is replaced withlambda. A similar approach can be used with the default_scope as well.
So in my rails console I can now do this:
equip=Equipment.allequip.is_available
The lambda is what actually does the query implementation.
It will produce anActiveRecord::Relation
object, that contains list of available equipment.
This also gives us the opportunity to use the created scope in the controller in this manner:
defindex@equip=Equipment.is_availablerenderjson:@equip,status:200end
or
defindex@equips=Equipment.allrenderjson:@equips,status:200enddefavailability@equip=Equipment.is_availablerenderjson:@equip,status:200end
In the first code block, the route toindex
will always display only available equipment and not all equipment. However, in the second code-block,index
will display a list of all equipment irrespective of whether it is available or not. Also,availability
will only display a list of all equipment that is available if I hit theavailability
route. In this way, the controller is being used to decide what information is rendered. When it comes to which scope type to use, please avoid using default_scopes. Want to know why? see thisarticle
Taking arguments
Named scopes can also take arguments like so:
classEquipment<ApplicationRecordhas_many:requestshas_many:customers,through: :requestsvalidates:brand,presence:truevalidates:model,presence:truevalidates:equipment_type,presence:truevalidates:serial_no,presence:truevalidates:accessories,presence:truescope:not_available,->(bool){where("available = ?",bool)}end
With the above arrangement, it is now possible to pass parameters from the controller when a request is made to the corresponding route.
Using Unscoped
If you have to work with default scopes, you may need to use the unscoped method to disable all currently applied default scopes. Let's work with an example:
classEquipment<ApplicationRecordhas_many:requestshas_many:customers,through: :requestsvalidates:brand,presence:truevalidates:model,presence:truevalidates:equipment_type,presence:truevalidates:serial_no,presence:truevalidates:accessories,presence:truedefault_scope->{where("available = ?",true)}end
So we can now disable the scope in this manner
equip=Equipment.firstequip.unscoped
What can you do with scopes
Now let's see examples of what we can do with scopes:
- chaining scopes
we can chain scopes together to build a bigger SQL statement. See the example below:
classEquipment<ApplicationRecordscope:available,->{where("available = ?",true)}scope:available_and_created,->{available.order(:created_at)}end
First, we define the scope we want to chain, and then define a second scope. In the body of the second scope, we will chain the first scope and anActiveRecord
method to make our query. The chaining can be seen, when we run the kind of query below in the rails console:
Equipment.brand.is_available
- Using Conditionals with scopes.
The scope namedavailable
, in the code block below is a typical example of how to use conditionals with scopes.
classEquipment<ApplicationRecordscope:available,(bool)->{where("available = ?",bool)ifbool.present?}end
Here we are saying, select all equipment if available is true.
- Calling multiple scopes in a class method scope
This is achievable by using thesend()
method.
We can supply several scopes as arguments in the send method and call send on each one.
The code block below (chaining
) allows us to dynamically call as many scopes as we want. The code block below is another way of writing scopes and it is called a class method.
We define a class method with theself
keyword and it takes an array of arguments calledmultiple_method
. This is then supplied to thesend
method ininject()
.
defself.chaining(multiple_method)multiple_method.inject(self,:send)end
Equipment.chaining(["scope_one","scope_two"])
so in the controller we can have this kind of arrangement:
defindexifparams[:equipment]args=params[:equipment][:multiple_method]@equip=Equipment.chaining(args)else@equip=Equipment.allendend
Summary & Conclusion
By now, using scopes should no longer be a difficult task. With scopes, we can easily create SQL queries on the fly.
Using the equipment API as examples in this article, we can now create a new scope from scratch, chain scopes together, use conditionals in scopes, and supply multiple scopes as an argument in another scope using the send method. The article focused strictly on creating scopes with the lambda approach. Another way to create scope is the use of a class method (which was mentioned when calling multiple scopes as arguments in a scope.).
Found this article to be useful? Please like, share or drop a comment below. You can also reach me via mytwitter handle.
References
https://medium.com/@pojotorshemi/ruby-on-rails-named-scope-and-default-scope-74ee3db2a15f
https://devblast.com/b/jutsu-11-rails-scopes
https://jasoncharnes.com/importance-rails-scopes/
https://www.sitepoint.com/dynamically-chain-scopes-to-clean-up-large-sql-queries/
Top comments(2)

Thank you!
For further actions, you may consider blocking this person and/orreporting abuse