Time Travel Toaster

Sending household appliances twirling through the 4th dimension since twenty aught five

    • Edit
    • Delete
    • Tags
    • Autopost

    Rails app, continuous integration, your database, and you

    I recently setup a Jenkins CI server for a client and put their Rails app in it. When someone committed a change, it ran a CI build. However, when someone committed a migration, rake would abort with the dreaded "pending migrations" error. So I just added a db:migrate step to the ci:build task, and all was well…

    …or so I thought. In this particular case, the CI server builds anytime someone pushes code to either the "develop" or "master" branch (using git). Pushes to develop happen much more often than master, because master is the "stable production release branch" and develop is the "push everything here when it's ready to be shared with the rest of the team and integrated into the bleeding edge version of the app" branch. One consequence of this is that master builds will often assume an older database schema than develop builds because those migrations haven't been merged with the master branch yet.

    So running "rake db:migrate" in the ci:build task broke. The migrate task doesn't understand moving the schema backwards and forwards unless you explicitly tell it what to do. However, there's a much better way to do this. The file db/schema.rb is a current snapshot of your database and it should be checked into your code repo. So if you just load that into the test DB before each CI build (via the handy "rake db:schema:load" task), then you're golden. Master has its snapshot, and develop has its snapshot. Everything's fixed! Er...

    Here's the wrinkle: When you run rake spec after doing that (within the same rake ci:build über-task), it executes the following tasks:

    ** Invoke spec (first_time)
    ** Invoke db:test:prepare (first_time)
    ** Invoke db:abort_if_pending_migrations (first_time)
    ** Invoke environment (first_time)
    ** Execute environment
    ** Execute db:abort_if_pending_migrations
    ** Execute db:test:prepare
    ** Invoke db:test:load (first_time)
    ** Invoke db:test:purge (first_time)
    ** Invoke environment** Execute db:test:purge
    ** Execute db:test:load
    ** Invoke db:schema:load
    ** Invoke environment** Execute spec


    So what? It runs db:schema:load again, no big deal. Nope. Chuck Testa. If you look closely, you'll see that while db:schema:load is *invoked*, it is never actually *executed*. And that's because rake remembers that it has already been executed when we ran it manually before. So the last thing that's been done to your test database is db:test:purge. If you look at your test database at this point, you'll see that is has no tables. And your tests will fail (at least they better).

    Why are there no tables? Why didn't the manual db:schema:load put all the tables into the test database? Because it put them into the development database. Remember that most tasks default to the development environment when you don't specify one. So we loaded the DB schema snapshot into the development database, and then later when rake is asked to run db:schema:load on the test database, it doesn't run it.

    So what's the solution? We need to tell rake to re-run db:schema:load. (Don't be tempted here to force Rails.env = 'test' and use the test database for everything. This comes with its own issues and you'll be fighting against the way rake spec is designed to work.)

    Here's what my final rake ci:build task looks like:

    namespace :ci do
      desc 'Setup CI environment'
      task :env do
        ENV['CI_BUILD'] = 'true'
        ENV['RSPEC_FORMAT'] = 'progress'
        ENV['CUCUMBER_FORMAT'] = 'progress'
        ENV.delete('http_proxy') # need cukes to be able to connect to localhost
      end

      desc 'Run a continuous integration build'
      task :build => ['ci:env'] do
        Rake::Task['db:reset'].invoke # drops, re-creates, loads schema.rb
        Rake::Task['db:schema:load'].reenable # so it can be run later on the test db
        Rake::Task['spec'].invoke
        Rake::Task['cucumber'].invoke
      end
    end

    That line "Rake::Task['db:schema:load'].reenable" is the key. Once you add that, rake spec will happily resume loading up your test DB's schema and you'll be off to the races.

    Tags » rails
    • 19 January 2012
    • Views
    • 0 Comments
    • Permalink
    • Favorited 0 Times
    • Tweet
    • Tweet

    Comments 0 Comments

    Leave a Comment

  • wesmorgan's Posterous

    Wes Morgan

    Contributed by Wes Morgan

  • About wesmorgan

  • Subscribe

    Subscribe to this posterous
    Unsubscribe
    Follow this posterous RSS
    You're a contributor here (Edit)
    This is your Space (Edit)
    Follow by email »
    Get the latest updates in your email box automatically.
  • Follow Me

      TwitterTwitterFacebook

Theme created for Posterous by Obox