My name is MatthewRudy, and I'm a Rails Developer. I am.... MATTHEW RUDY ON RAILS!!!


Rebuilding your test database from migrations

Today I submitted my first patch for the rails source.
Take a look here

The chief testing tasks in Rails are "test", "test:units", "test:functionals", ....

If you run

rake:test --trace
you'll get the following trace;
** Invoke test (first_time)
** Execute test
** Invoke test:units (first_time)
** Invoke db:test:prepare (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute db:test:prepare
** Invoke db:test:clone (first_time)
** Invoke db:schema:dump (first_time)
** Invoke environment
** Execute db:schema:dump
** Invoke db:test:purge (first_time)
** Invoke environment
** Execute db:test:purge
** Execute db:test:clone
** Invoke db:schema:load (first_time)
** Invoke environment
** Execute db:schema:load
** Execute test:units

It's a long trace, but the key is that it runs the following; db:test:prepare which does the following;
  1. purges the test database
  2. dumps the schema of the development* database
  3. rebuilds the test database with this dump
* actually it's whatever environment is active - by default this is development.

There are a few problems with this;

  1. If you run through rake, the test database will always be empty at boot, this can cause code which is fine in real circumstances to break just in these tests. Here's an obscure example;

    class Model
    Joins.find(:all).each do |join|
    eval "has_many #{joins.name}, :foreign_key => #{joins.foreign_key}"
    end
    end

    that's not the best example. But here's a better point.


  2. If you have a "ModelTypes" table which has specific code attached. Whenever you add a new type, you'll add a migration to add this.
    But you also need to update your fixtures.
Now imagine if rake test worked directly from your migrations...well that's exactly what my ticket says.

Stick this in your environment.rb
Rails::Initializer.run do |config|
# Rebuild your database from your migrations in your tests
config.active_record.schema_format = :migrations
end

And stick a .rake file in lib/tasks with the following text
module Rake
class Task
attr_accessor :actions
end
end

# make sure its overwriting and not just adding to the method
Rake::Task["db:test:prepare"].prerequisites.clear
Rake::Task["db:test:prepare"].actions.clear

namespace :db do
namespace :test do
desc 'Drop and rebuild the test database from migrations'
task :rebuild_from_migrations => ["db:test:purge"] do
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
ActiveRecord::Schema.verbose = false
Rake::Task["db:migrate"].invoke
end

desc 'Prepare the test database and load the schema'
task :prepare => :environment do
if defined?(ActiveRecord::Base) && !ActiveRecord::Base.configurations.blank?
Rake::Task[{
:sql => "db:test:clone_structure",
:ruby => "db:test:clone",
:migrations => "db:test:rebuild_from_migrations"
}[ActiveRecord::Base.schema_format]].invoke
end
end
end
end
And you'll get exactly what I'm suggesting.
The other thing perhaps worth doing is to run
"rake db:fixtures:load" in the same process.

(you'd have to turn off foreign keys, else it'd probably break.)