I recently battled some serious frustration in my Rails project; I’m posting what I learned here in hopes that it helps someone in the future.
I like to have a single Rake task named ci
for my continuous integration setup to use. Whether it’s a standard Ruby project, a Rails project, or even an Android project, continuous integration should be able to run rake ci
and produce all of the results it needs. No chaining tasks. No chaining commands. No rain dance with environment variables. rake ci.
Here’s how I did it in my Rails 3.2 app.
Here’s my ci
task:
desc 'Continuous integration task'
task ci: %w[ ci:prepare file_store:clear db:migrate ] do
exec({ 'RAILS_ENV' => nil }, 'rake', 'spec', 'spec:javascripts')
end
The problem stems from chaining tasks involving the database, which puts the Rails.env
into the development environment, and RSpec’s tasks, which puts Rails.env
into the test environment. Run either one by itself and it’s fine, but chain them, with say rake db:migrate spec
, and the spec
task doesn’t work properly. This is because the Rails.env
is development, not test.
Specifying the command as RAILS_ENV=test rake db:migrate spec
works, but again, I only want to depend on environment variables as a last resort. Likewise, specifying the command as two chained commands works— rake db:migrate && rake spec
, but I want to avoid chaining as well. My preference is for a developer to type rake ci
and that’s it. No forgetting what to type. No digging through history to remember the proper commands 2 years later. No emailing former developers and hoping they remember. The less typing, the better.
This guy ran into the same problem and wrote about it here ; it sounds like he was going for the same goal—a single ci
task—but ran out of options and fell back on chaining commands. After I read this I almost went with the same approach, but at the last moment remembered exec
.
Here’s my ci
task again:
desc 'Continuous integration task'
task ci: %w[ ci:prepare file_store:clear db:migrate ] do
exec({ 'RAILS_ENV' => nil }, 'rake', 'spec', 'spec:javascripts')
end
I have the ci
task’s dependences setup to run all of the database migrations and what not. The guts of the task then use exec
to re-exec rake, but with the RAILS_ENV
cleared out. Now the spec
and spec:javascripts
tasks can setup the test environment the way they expect.
Now, of course, this isn’t perfect. If I’d wanted to run some code after the exec
, well, that wouldn’t work. And there are some tradeoffs and preferences to be made – I could have set ENV['RAILS_ENV'] = nil
instead of passing 'RAILS_ENV' => nil
to exec
. But I like how the environment changes are encapsulated to the exec
call.
Last but not least, my preference for a single ci
task is also, well, a preference. But it’s worked quite well for me over the last half dozen years. If you share this preference then I hope this use of exec
is helpful.
Edit 3/26/2012: Fixed an accidental textile formatting problem.
Edit 3/27/2012: Corrected the capitalization of a proper noun.
I do this: https://gist.github.com/2220877
It’s faster and prevents you from needing a development environment on your ci machine. Of course if you want one in order to test migrations that’s fine, but we test our migrations during staging deploy which is more “real”.
[…] as a valuable practice, but getting all the pieces together can be a pain. Matt Fletcher shared some of his difficulties in getting things setup satisfactorally for Rails. After much experimentation, I’m actually […]