The Spaghetti Refactory Established 2015

Scopes vs class methods in Ruby on Rails

When I first started writing this, the rails guides didn’t make this distinction. I’m excited to say I made my first contribution to Rails by adding this to the docs. Bangarang!

Before I learned about scopes in ActiveRecord, I regularly used class methods for ActiveRecord querying. When I learned scopes and how to use them, I thought, “Well, that’s nice syntactic sugar.” But turns out there’s more to it than that.

Class methods and scopes accomplish basically the same purpose, except in one particular case: conditionals.

Say that you have the following class methods on a User class:

def self.created_before(time)
  where("created_at < ?", time) if time.present?
end

def self.active
  where(active: true)
end

If you wanted to get active users created before 2 days ago, you could chain them together into something like this (in Rails):

User.created_before(2.days.ago).active

No problem, right? But what if you didn’t pass in a time?

User.created_before('').active

Now, created_before returns nil because time isn’t present, and when it gets to the next method in the chain, it gives you a NoMethodError on NilClass. This is where scopes are way cooler - they always return an ActiveRecord::Relation object, so they are always chainable. So, the same queries in scope form…

scope :created_before, ->(time) { where("created_at < ?", time) if time.present? }
scope :active, -> { where(active: true) }

…throws no error on the same method chain…

User.created_before('').active

It’s a small difference, but an important one.

Kudos to plataformatec for a great writeup on the subject.

N.B. Also, a colleague of mine asked if class methods can be called on a relation? For example, user.posts.class_method. Answer: Yes, indeed! Just like scopes, class methods can be called on a relation. Another tick mark in the “things they have in common” column.

Tags