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.