Skip to content

Today’s Gotcha: Dynamic data in Rails scopes

I recently got caught out by an aspect of ActiveRecord scopes that I had totally forgotten about.

Lets say you are building a library book tracking system in Rails and you want to know which books are due today. You might be tempted to write something like this

class Book < ActiveRecord::Base
  scope :due, where('due_date = ?', Date.today)
end

That looks nice, and if you are not paying attention then you might think that Book.due would return all the books due that day.

You’d be (mostly) wrong.

A scope is defined at initialisation, so what you start up your server, console, or in my case the DelayedJob worker, Date.today gets executed once and a scope is created called due that will return all the Books that are due on the day you start the process.

So how do you create a scope that does what we want?  There are two approaches you could take…

1) Lambdas

You can pass a lambda to scope which will be executed at runtime, not at initialisation time…

scope :due, lambda { where('due < = ?', Date.now) }

2) Don’t use scopes

Scopes are nice, but as the likes of ‘where’ return ActiveRecord::Relation objects, just like a scope, we can replace our scopes with ordinary class methods…

class << self
  def self.due
    where('due = ?', Date.today)
  end
end

This will behave in the same way as the lambda version, but personally I find it cleaner, especially if the query is non-trivial.

This is all covered in the Rails Guides and was a good reminder for me that it’s worth having a quick read of them every once in a while to refresh your memory, especially for things that you don’t use that often.