jlaine.net

Making Cached_model and Observers Like Each Other in Rails 1.2

I recently updated my RubyGems to 0.9.2. In that upgrade, require_gem was deprecated in favor of gem and running Rails would generate a bunch of deprecation warnings. However, it was easy enough to fix by running rake rails:update in the app root.

Doing the update caused us some problems, though, because we are using the cached_model gem to store objects in memcached. cached_model was required in environment.rb before the Rails::Initializer.run block but with the new autoloading mechanism, it didn’t work anymore because the Rails classes weren’t at the disposal of cached_model before the initializer block. Instead I got all these weird errors:

$ script/console 
Loading development environment.
/opt/local/lib/ruby/gems/1.8/gems/cached_model-1.3.1/lib/cached_model.rb:21:NameError: uninitialized constant ActiveRecord
/opt/local/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/assertions/selector_assertions.rb:525:NoMethodError: undefined method `camelize' for "top":String
./script/../config/../config/../app/controllers/application.rb:1:NameError: uninitialized constant ActionController::Base

Lo and behold, the cached_model docs instruct to put the require line after the initializer block. This is the right way and should be ok in most cases. However, in our app, we also have an observer for the User class, which is a subclass of CachedModel. Observers are loaded inside the initializer block (config.active_record.observers = :user_observer) and when loaded, they also initialize the actual ActiveRecord model they’re observing (in this case User). But since User subclasses CachedModel, which is not loaded yet, we have a chicken and egg problem:

$ rake test:units
rake aborted!
uninitialized constant CachedModel

We can’t require cached_model before the initializer because ActiveRecord::Base doesn’t exist then, and we can’t require it after the initializer because it would need to be initialized when the observers are loaded. What’s a man to do?

Rick Olson pointed me to a list of articles (1, 2, 3) that he recently wrote about the Rails initializing mechanism (+ a link to another useful article by Tim Lucas). This lead to a trick that solved my problem. Namely, writing a plugin.

The gist of the trick is that the loading of observers is deferred until all plugins are loaded. So I created an empty plugin called cached_model_plugin, put the require 'cached_model' line in its init.rb, and everything started working again. Woo-hoo!

The weird thing about this is that actually, the code in environment.rb after the Rails::Initializer.run block should also be run before the observers are loaded. However, for whatever reason that didn’t happen in our case, so I’m happy that using a dummy plugin helped.

With the new config/initializers in Rails Edge this hack should become history, but until we update beyond 1.2.X, we’ll be camping happily with my plugin.