Few things are more head bashing inducing than code that passes all unit tests, runs perfectly on your development machine but fails on your staging/production servers. In that vein, both of these examples are wrong:
1 2 3 4 5 6 7 8 9 10 |
|
In development mode this will work absolutely fine. When you deploy this code onto the production server it will work fine too, but after a while it won’t behave quite right. For example Person.recent_posts will start returning posts older than 1 week.
The key to this is understanding when the code runs. In particular when does “1.week.ago” get turned into an instance of Time with some fixed value such as 1st November 2008 at 20:32?
The statements has_many, validates_inclusion_of etc… are just method calls, so their arguments are evaluated when that function is called. You can look in the options hash for an association to see this (assuming you’ve just typed in the Person class given above):
1 2 |
|
So when are these functions called? Quite simply when ruby loads person.rb. In development mode your source code is reloaded for each request[1], providing the illusion that the “1.week.ago” is re-evaluated whenever it is used. In production mode person.rb would only be read once per Rails instance and so once your mongrels had been running for a week Post.recent_posts would return anything written in the last 2 weeks (1 week before the date at which your mongrels were launched). You would also notice this if you were running script/console and keeping an eye on the sql generated: you’d see that the date in the WHERE clause didn’t change.
Fixing it.
Fortunately it’s not hard to fix this. In this case of the awesome named_scope you probably already know that you can supply a Proc for when you want your scope to take arguments. We can equally make one with no arguments, just to ensure that the time condition is evaluated whenever the scope is accessed.
1 2 3 |
|
For conditions on things like associations we can use a little trick called interpolation. As I’m sure you know when ruby encounters “#{ ‘hello world’ }” it evaluates the things inside the #{}, but if you use single quotes (or equivalently things like %q() then it doesn’t. What you may not know is that Active Record will perform that interpolation again at the point where sql is generated. For example we can write the recent posts associations like this:
1 2 3 4 |
|
When person.rb is loaded the stuff in the #{} will not be evaluated, however when Active Record generates the sql needed to load the association it will be[2].
Validations can’t play any of the clever little games that the other 2 examples can. You’ll just have to something like
1 2 3 4 5 6 7 8 9 |
|
[1] Assuming you’ve got config.cache_classes
set to false in development mode which is the default
[2] You can do a lot more with interpolation. Normally the code is interpolated in the context of the instance of the model so you can use any model methods, instance variables etc… When an association is fetched with :include it will be interpolated in the context of the class (since the whole point is to bulk load instances it does not make sense (nor would it work) to work per instance data in there.