For this upcoming March release, we plan to upgrade from Rails 2.1 to Rails 2.2.2. When testing bootstrapping fresh instances of our app, we ran across an annoying migrations problem. Read on to see how we resolved it.
The bootstrap process for our app is pretty straightforward. We prepare the new empty database by loading db/schema.rb from our last stable release. db/schema.rb is kept in sync as we move from one release to the next. Once the structure of the database is created with db:schema:load, we setup initial defaults in the app by loading yaml fixtures via a custom db:seed:load rake task.
desc "Initialize database"
task :seed, :roles => :app do
run <<-CMD
cd #{current_path} &&
rake db:schema:load &&
rake db:seed:load SRC=db/blank SEEDS=all
CMD
end
Easy peasey right? I was very annoyed when db:schema:load stopped dead in it's page long backtrace. The offending error was:
Mysql::Error: Table 'coupa_blah.setup doesn't exist: SHOW FIELDS FROM `setup`
Looking a little deeper, it became apparent that models and controllers assumes the existence of the 'setup' table, and reference it when they are loaded for configuration settings. However, the 'setup' table is created and populated by db:schema:load and db:seed:load. Chicken and egg, you have foiled me again!
I knew it was a problem with class caching because when I tried
running db:schema:load in development mode, it worked without a
hiccup.
Rails::Configuration
told me that there is a variable eager_load_paths
that
determines where to eager load. In our environment.rb, I added this
to the Rails::Initializer
block:
# Hint: this doesn't fix our problem!!
config.eager_load_paths = []
At first glance, this not only fixed the db:schema:load problem, but had the added side benefit of speeding up start up times of Passenger app servers, and script/console. Of course, it was too good to be true. I soon realized that weird things in our app were broken. We were getting method undefined errors on classes that obviously had them. It was frustrating, but at least I know I'm not alone. It turns out that we need our model classes to be eager loaded. I ended up turning eager loading back on and adding this hack to environments/production.rb:
# kids, this is what an ugly hack looks like. Don't worry, Rails
# 2.3 will fix this.
config.cache_classes = (File.basename($0) == "rake" && !ARGV.grep(/db:/).empty?) ? false : true
If cache_classes
is false, then the paths in
eager_load_paths
is ignored, and no eager loading is
done. The conditional basically turns off eager loading for any rake
task within the 'db' namespace. The hack was already there, but the
previous version specifically looked for 'db:migrate', whereas this
one will exempt db:schema:load and db:seed:load as well.
Fortunately, according to the lighthouse ticket on this issue, it looks like Rails 2.3 will default to turning off eager loading of classes for rake tasks. Until then, this hack will have to do.