Space Vatican

Ramblings of a curious coder

When Cache_classes Gets You Down

As I’ve mentioned before, in Rails 2.2 if config.cache_classes is true (which it is in production) then all of your models, controllers etc are loaded up as part of application initialization. This is great if you’re firing up a server that should actually be handling requests as it avoids all sorts of nasty race conditions to do with requiring files and defining classes in ruby. It’s not so great if you’re running a small script, cron job or something like a migration.

The reasons are two-fold. First off, it makes startup slower. This is irrelevant when starting up a web server because it is just getting out of the way stuff you would do anyway but it is time wasted if you’ve just got a single threaded script that doesn’t care about dependency race conditions (and probably only touches one or two models anyway), especially if the script only takes a second or two to run once the environment is loaded. Secondly it can break stuff if models or controllers try and look at the database when the class is loaded. One example of this is ActiveScaffold. For those of you not familiar with it, ActiveScaffold helps you create adminy CRUD style interfaces. A very basic controller might look like this:

1
2
3
class ProductsController < ApplicationController
  active_scaffold
end

When the controller is loaded ActiveScaffold sees that it’s working on the ProductsController, infers that the corresponding model is Product and goes off to find out what columns that model has. Imagine you’ve just added that model and controller and it’s time to deploy on your production machine. At this point the products table doesn’t exist (since the migration hasn’t run yet). Since you’re running in your production environment when the rake task causes rails to be loaded it will load your application’s classes, including ProductsController which will try and introspect the products table. Fail.

There are two workarounds I can think of. One is a separate rails environment: define an environment with cache_classes set to false but that still uses the production database. Instead of running your migrations in the production environment run them in the production_without_cache_classes environment. This works but can be a bit annoying, especially for adhoc scripts and stuff like that and is even more of a pain if you have multiple productiony environments (eg a staging environment).

Another is to edit your production.rb so that it reads

1
2
3
4
5
if defined?(DONT_CACHE_CLASSES) && DONT_CACHE_CLASSES
  config.cache_classes = false
else
  config.cache_classes = true
end

Then for any script which you want to run without class caching turned on stick DONT_CACHE_CLASSES=true at the top (before you require config/environment). If you want to extend this to your rake files then edit the Rakefile at the top level of the app. This feels slightly neater to me as I don’t have to remember to fiddle with the environment when running the script.

Another variation upon this is to use an environment variable instead of a constant which would allow you to do things such as rake db:migrate CACHE_CLASSES=false or similar (although of the default tasks I can’t of one you’d run in production where you would want class caching to be on).

This is far from beautiful but appears to get the job done for now.