Staples Team Supervisor/Software Engineering Graduate of @flatiron-school
Published Dec 23, 2019
Named Scopes or scopes are just methods that you can use to define custom queries inside your Rails models with the scope
method. They can be used to grab a subset of objects from your database and are a great way to reduce redundant code as well as long and frequently used queries. Let’s look at an example of a scope method with a name of most_recent
in a model called Event
.
/app/models/event.rb
scope :most_recent, ->(limit){ order("created_at desc").limit(limit) }
This example shows a simple way to get the most recently created Event objects and allows a limit to be passed in as an argument in order to specify how many records we want returned. To get the most recent event objects created you would be able to call it like this @recently_created_events = Event.most_recent(5)
.
There are a few reasons why you would want to use scopes:
It’s true that you can do essentially the same thing by creating a class method, for instance you could implement the previous example as a class method like this
def self.most_recent(limit)
order("created_at desc").limit(limit)
end
You may be asking what is the point if you can just get away with using class methods? Well, you can chain them with other methods and implement certain code in a much more DRY way. An example of something like this is chaining scopes with other methods and not having to worry about edge cases breaking things, like when you get a nil
value returned back from a method call. Take this example where we want to be able to search for all of the records created since a certain date/time, and if we don’t provide a date at all then the method simply returns all the records.
scope :created_since, -> (time){where("events.created_at > ?", time) if time.present?}
This is much more DRY than the alternative class method which in this case would look similar to this example
def self.created_since(time)
if time_present?
self.where("events.created_at > ?", time)
else
self.all
end
end
So that way we can just call Event.created_since(7.days.ago)
to all event objects created within the last 7 days.
Another example could be if we wanted to get all the events that take place inside the US then we could create a named scope like this
scope :in_usa, -> {where(country: "US")}
So then we can call it like Event.in_usa
to get all the events taking place in the US, or to improve this a bit and abstract one more level we can code it like this
scope :location(country), ->(country){where(country: country)}
Now we can pass whatever country in as a argument and we can get the events by country. And if we had the previous example scope defined too we could chain them together like this Event.location("US").created_since(5.days.ago).limit(5)
without having to worry about getting a nil
response from any one of the methods called potentially preventing a lot of head aches.
A situation where scope becomes very useful is in the case where you have a database of users that does a soft-delete
when a user deletes their account and instead of deleting the data it just changes the user status to ‘inactive’ and the database still holds the account record. So now as your user database grows it can take significantly more time to search through ALL the user records, when in realistically for most tasks you only really want to be searching through the currently ‘active’ users. So you could improve the speed of your app by defining some scopes like this within a user model.
/app/models/user.rb
scope :active, -> {where(status: 'active')}
scope :inactive, -> {where(status: 'inactive')}
So then when you want to do a search within the active user set you would use this code User.all.active
and inversely User.all.inactive
to use the inactive user set.
Another good reason to use them is because scopes express intent, it’s a good idea to use them whenever your chaining simple scopes that are already built in to rails like limit
or where
into more complicated scopes. This is essentially what scope was designed for.
So in review you should use scopes when your chaining the built in methods of selecting, sorting, joining, and filtering code. Default scopes can be useful, but be careful because of the possible issues debugging them in the future. You can DRY out your code, make it easier to understand and possibly increase your apps efficiency if used in the proper way. It’s just another great tool that Ruby on Rails gives you to make your code better and your life easier, but you have to understand it first to really take advantage of all that named scopes can offer.