Ruby on Rails Project — Using Scope Methods to Narrow My Database Queries (May 2020)

Harry Wilson
3 min readJul 14, 2020

--

This week I completed my Rails Project, a full-stack web application designed to help those learning foreign languages by giving them access to a myriad of high-quality language course reviews. On this site, the users can search for courses by title, language or author/publisher. Reviews must be completed in full and meet a minimum character count which will provide users with as much information as possible when deciding which language learning course they would like to invest in.

I quickly realized that my application had the potential of becoming overcrowded with course reviews, authors/publishers and languages. I therefore needed a way to organize my index pages (the pages which show all instances of a specific class) so that a user would be able to find a review in the easiest way possible.

Photo by Luke Chesser on Unsplash

At this point I started researching scope methods. Scope methods add a class method for retrieving and querying objects. These methods are designed to help narrow down all instances of a certain class in your database and can be used in a variety of ways. The syntax is also much cleaner than using a class method. For example, if you wished to create a class method which searched for instances where the color of the object was red, your method would look something like this:

class Shirt < ActiveRecord::Base
def self.red
where(color: ‘red’)
end
end

This can be achieved in a single line of code using a scope method with the exact same result:

class Shirt < ActiveRecord::Base
scope :red, -> { where(color: ‘red’) }
end

Unlike built in ActiveRecord methods such as .find, the Object returned by these methods is not an Array, but an ActiveRecord::Relation, which resembles the association object made by using a has_many macro. This means that you can stack other scope methods easily onto your method and do things such as Shirt.red.count, or even Shirt.red.where(size: ‘small’).

In my project, I wanted to only see a limited number of Courses on my index page (and the same for Languages and Authors on their index pages) so I knew that I could take advantage of the .limit(x) scope method. The idea of my method was to show the 10 Courses with the most reviews. Let’s take a look at the method:

scope :most_reviews, -> {joins(:reviews).group(:course_id).order(“COUNT(reviews.course_id) DESC”).limit(10)}

This method joins the Courses table with the reviews table, and orders the Courses by the COUNT (total number of) of the course_id values that we see in that table. So if we see 100 reviews with the course_id of 2 which corresponds to Teach Yourself German, then we know that that particular course has 100 reviews. I then arrange the courses based on this value DESC (from largest to smallest) and limit the results to the top 10. This method can now be called in my controller actions so when I pass @courses to my index view template, instead of sending Course.all, I can send Course.most_reviews and when this is iterated over in the view template, it will only contain the 10 Courses with the most reviews, in order:

def index
if params[:search] != “”
@courses = Course.search(params[:search])
else
@courses = Course.most_reviews
end
end

We can see the @courses variable being iterated over in the index view template here:

<% @courses.each do |course| %>
<h2><%= link_to “#{course.title}”, course_path(course) %></h2>
<p><%= course.language.name %></p>
<p><%= course.level %></p>
<p><%= course.reviews.count%> Review(s)</p>
<% end %>

Scope methods can be powerful tools and can easily allow you to chain methods to create complex helpers when you wish to narrow your database queries! If you have very similar methods in other classes, you can even abstract a layer further by passing this methods arguments and interpolating. The possibilities are truly endless with chaining, special characters and interpolating. I hope that you have enjoyed seeing how I chose to implement them in my project and look forward to any comments/questions about their usage!

--

--