Rails tip #4: Writing Capistrano recipes to be loaded from gems

Making a gem of your custom Capistrano recipes is a nice way to remove duplicated knowledge across your Rails projects.

On your first pass, you'll probably do what I did: gank and modify capistrano/recipes/deploy.rb.

This works great, but you'll find it's a little tricky to use your new recipes from a Capfile. It turns out you can't just "require mygem" and "load 'mygem/recipes/deploy'" because Capistrano doesn't load from ruby's $LOAD_PATH—it keeps its own minimally-initialized load_paths setting instead.

So, you have to either modify the load_paths or use an absolute path, like this:

%w( rubygems wordpress ).each { |lib| require lib }
load Gem.required_location('wordpress', 'wordpress/recipes/deploy.rb')
load 'config/deploy'

This is what I did for a while, but something didn't seem right. My Capfile didn't look as nice as I expected, and I wasn't using require, whose comments clearly mention third-party recipes.

Take two

Staring at capistrano/configuration/loading.rb until it made sense, I saw instance, which triggered some vague distant memory that somehow turned into a productive thought:

If you wrap your deploy.rb recipes in a load block, like this:

Capistrano::Configuration.instance(:must_exist).load do
  # previous file contents here

You can simplify your Capfile with a shorter require statement:

require 'rubygems'
require 'wordpress/recipes/deploy'
load    'config/deploy'

Nice. I'm still a little bummed by the require / load imbalance, but on the whole this feels more like things were supposed to be.

5 Rails tips

Each day this week, Joachim and I will post something we've learned in our time programming together. It's fun to do, and we might just win something as well.

So far, we've written:

  1. Reloadable custom FormBuilder
  2. Faking DATA in tests
  3. Filter BLOBs from ActiveRecord logging
  4. Writing Capistrano recipes to be loaded from gems