Scopes Are Obsolete

This post was originally published on the Elabs blog, before Elabs and Varvet joined forces.

I admit, I’ve never been a big fan of named_scope or just scope as it’s been renamed in Rails 3. When it was first introduced I remember not being particularly impressed, as a Merb acolyte we’d had this chaining functionality in Datamapper for ages, only it was much better. In fact in Datamapper, every query you could construct was chainable. Thankfully in Rails 3 and ActiveRecord 3, queries have finally grown up so that everything is now chainable in ActiveRecord too:

class Person < ActiveRecord::Base
  def self.alphabetically
    order(:first_name, :last_name)
  end

  def self.active
    where(:archived_at => nil)
  end
end

Person.active # => [...]
Person.alphabetically.active # => [...]

In this case it seems like the scope method will give us much nicer more concise syntax:

class Person < ActiveRecord::Base
  scope :alphabetically, order(:first_name, :last_name)
  scope :active, where(:archived_at => nil)
end

But imagine if we want to add a new kind of scope to find user’s with a given last name:

scope :by_last_name, lambda { |name| where(:last_name => name) }

It’s getting a bit less nice, aside from the gratuitous lambda, it’s still pretty okay though.

What I believe is wrong with this code though is that it is essentially recreating Ruby functionality. We’re defining a method called by_last_name which will execute some code when called, only we’re doing it through meta-programming for essentially no reason at all. The above could have been written as:

def self.by_last_name(name); where(:last_name => name); end

And it would have worked exactly the same. The only difference that I can tell is that scope allows you to define extension methods by passing a block, which I’m sure no one has ever used, since it’s so completely useless.

The problem becomes even more striking when the code is even the slightest bit complicated.

scope :for_user, lambda { |user|
  if user.admin?
    where(:active => true)
  else
    where(:active => true, :user_id => user.id)
  end
}

That’s just horrible.

def self.for_user(user)
  if user.admin?
    where(:active => true)
  else
    where(:active => true, :user_id => user.id)
  end
end

This looks much more like Ruby code and less like some kind of weird JavaScript concoction.

Stop replicating functionality that already exists, stop using scope. It is obsolete.