Rails ActiveRecord Scopes With Arguments

I came across another interesting syntax used in a Rails application.

scope :graduated_after, ->(date) { where('graduation_date > ?', date) }

Warning: Before we start please note that variable scoping in Ruby and ActiveRecord scopes in Rails are totally different concepts. Ruby scopes are about where in the code values of certain variables are visible or accessible. Here we are discussing Rails scopes which provide a means of “scoping” ActiveRecord query results according to specific criteria.

In our applications, we frequently use many queries. While doing this many beginner developers usually define methods in the model class that perform specific queries. Nothing is wrong with this. However, model class methods are not as flexible as another available alternative, namely Rails scopes.

What are Rails scopes?

In essence, the ’scope’ is a method that our model class inherits when it is defined as a subclass of class ‘ApplicationRecord’.

Method ‘scope’ takes two arguments:

  • name of the scope - defined as symbol value (not as a string value)
  • a lambda function that implements the query

Method ‘scope’ can be used with or without an argument. In our example above we want to get a list of students who graduated after a certain year. The year is passed as an argument.

If we needed a list of students who graduated during the last calendar year then we could skip passing the argument:

scope :graduated_within_a_year, -> { where('graduation_date > ?', 1.year.ago) }

Or, we can also use default value for the argument:

scope :graduated_after, ->(date = 2015) { where('graduation_date > ?', date) }

Rails scopes have several benefits compared to model class methods: unlike model class methods Rails scopes can be chained with other scopes Rails scopes can be merged with custom ActiveRecord queries Rails scopes can be called either with a class or on an association consisting of that class’s objects For example, we can define another Rails scope, like this:

scope :GPA_higher_than, ->(threshold) { where('gpa > ?', threshold) }

With these two scopes defined we can easily make a query like

Student.GPA_higher_than(3.5).graduated_after(2015)

If we add two model class methods with names ‘GPA_higher_than’ and ‘graduated_after’ those methods would not work if chained.

This means that using scopes we can do separate queries

recently_graduated = Student.graduated_after(2015)
recently_graduated.GPA_higher_than(3.5)

Alternatively, because ‘recently_graduated’ is an ActiveRecord::Relation we also could do

threshold = 3.5
recently_graduated.where('gpa > ?', threshold)

This shows that scopes are much more flexible than model class methods.

If we use:

Student.GPA_higher_than(3.5).graduated_after(2015)

The resulting query will first fetch higher GPA students and then fetch recent graduates out of the highest GPA students’ list. If we want to fetch recent graduates first then we simply reverse the order like so:

Student.GPA_higher_than(3.5).merge(Student.graduated_after(2015))

So far we used the ‘where’ query method in defining our scopes. However, we can use any other ActiveRecord query method with Rails scopes. The following scope selects only needed attributes.

scope :get_name_and_major, -> { select(:id, :name, :major) }

We can also set a default scope for model classes that inherit from ActiveRecord. For example, when we process records related to students we may want to ignore all students who have dropped from the course. If that is the case we could use the following default_scope

default_scope { where(completed_course: true) }

This might be desirable in most cases but now imagine that we have to count all students who have ever studied in our university. Then we do not want to ignore dropped students. If we have already defined a default scope as above we can request that the query be ‘unscoped’:

Student.unscoped.count

As you can see, Rails scopes are a very powerful tool that offers a lot of flexibility. Beginner Rails developers often use model class methods but I have noticed that experienced Rails developers use scopes instead, where possible.

Related Posts

Want to learn about the types of products we build?

Check out our projects

Like what you're seeing? Let's keep in touch.

Subscribe to Our Newsletter