Should You Use Scopes or Class Methods?

This article is also available in Korean, thanks to Soonsang Hong!

Scopes are a great way to grab the right objects out of your database:

class Review < ActiveRecord::Base
  scope :most_recent, -> (limit) { order("created_at desc").limit(limit) }

You’d use the scope like this:

@recent_reviews = Review.most_recent(5)

Calling that scope, though, looks exactly like calling a class method on Review. And it’s easy to build it as a class method, instead:

def self.most_recent(limit)
  order("created_at desc").limit(limit)
@recent_reviews = Review.most_recent(5)

So why would you use a scope when you could use regular Ruby class methods? Is it worth keeping these totally separate, but equivalent, concepts in your head? What if you run into weird bugs? Isn’t all this extra stuff the kind of thing that makes Rails harder to learn?

When would it make sense to use a scope instead of a class method?

Why use scopes when we already have class methods?

What if you wanted to grab all the reviews written after a specific date? But if no date was specified, you wanted all the reviews returned instead?

As a scope, that looks like this:

scope :created_since, ->(time) { where("reviews.created_at > ?", time) if time.present? }

Easy enough, right? What about the class method?

def self.created_since(time)
  if time.present?
    where("reviews.created_at > ?", time)

It takes a little bit of extra work. Scopes prefer to return scopes, so they’re easy to chain together:


But to get the class method to work the same way, you have to specifically handle the case where time is nil. Otherwise, the caller would have to figure out whether it has a valid, chainable scope.

Methods that always return the same kind of object are really useful. You don’t have to worry as much about edge cases or errors. You can assume you’ll always be handed back an object you can use.

Here, it means you can chain scopes together, without having to worry about nil values coming back.

There are still ways you can break the assumption that you’d always get a scope back:

scope :broken, -> { "Hello!!!" }
irb(main):001:0> Review.broken.most_recent(5)
NoMethodError: undefined method `most_recent' for "Hello!!!":String

But I’ve never had that happen in real code.

The thing I love most about scopes is that they express intent. You’re telling the next person who reads your code, “This method can be chained, will eventually turn into a list of objects, and will help you select the right set of objects.” That’s a whole lot more than a generic class method says.

When should you use a class method instead of a scope?

Because scopes express intent, I use them whenever I’m chaining simple, built-in scopes (like where and limit) into more complicated scopes. Finding the right bunch of objects is what scopes were designed for.

There are two exceptions:

  1. When I need to preload scopes, I turn them into associations instead.
  2. When I do more than chain built-in scopes into larger scopes, I use class methods.

When your scope logic gets complicated, a class method feels like the right place to put it.

Inside a class method, you can easily mix Ruby code with database code. If you have sorting code that’s easier to write in Ruby, you could grab your objects in their default order, and use sort_by to put them in the right order.

Or, if you’re feeling particularly tricky, a class method could grab data from a few different places: your database, Redis, or an external API or service. Then, it could assemble it all into a collection of objects that feels like a scope that’s been turned into an array.

Even then, it’s still good to put your selecting, sorting, joining, and filtering code inside scopes. Then, use your scopes inside your class method. You’ll end up with a clearer class method, and scopes you can use throughout your app.

Scopes are one of my favorite Rails features. You can do some powerful stuff – read my article on sorting and filtering Rails models to see an especially useful scope example.

And there’s a really simple way to master using scopes: play with them inside tiny, focused apps. The free sample chapter of Practicing Rails will show you how. Check it out!

Pushing through tutorials, and still not learning anything?

Have you slogged through the same guide three times and still don't know how to build a real app?

In this free 7-day Rails course, you'll learn specific steps to start your own Rails apps — without giving up, and without being overwhelmed.

You'll also discover the fastest way to learn new Rails features with your 32-page sample of Practicing Rails: Learn Rails Without Being Overwhelmed.

Sign up below to get started:

Powered by ConvertKit

Did you like this article? You should read these: