Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for All About ActiveRecord Scopes
Tunde Oretade
Tunde Oretade

Posted on

     

All About ActiveRecord Scopes

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
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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)}
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

or

defindex@equips=Equipment.allrenderjson:@equips,status:200enddefavailability@equip=Equipment.is_availablerenderjson:@equip,status:200end
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

So we can now disable the scope in this manner

equip=Equipment.firstequip.unscoped
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode
  • 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
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode
Equipment.chaining(["scope_one","scope_two"])
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
oluseyeo profile image
oluseyeo
Self-taught Developer passionate about technology, and world class engineering.
  • Location
    Canada
  • Work
    Site Reliability Engineer
  • Joined

Great read, Tunde.

CollapseExpand
 
tundeiness profile image
Tunde Oretade
  • Location
    United States
  • Work
    Freelance Software Developer/Cloud Developer
  • Joined

Thank you!

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

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

  • Location
    United States
  • Work
    Freelance Software Developer/Cloud Developer
  • Joined

More fromTunde Oretade

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp