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

Posted on April 24, 2008

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
end

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

Rails tip #3: Filter BLOBs from ActiveRecord logging

Posted on April 23, 2008

We threw attachment_fu into our app the other day, starting off with the default :db_file setting to store images in the database. (I’ve been bitten by this approach before, but this time around I’m intrigued by page caching. We’ll see.)

In the meantime, we’ve got blob after blob flying by in the script/server output, and it’s messing up our Terminal fonts. No good.

Here’s a quick fix, config/initializers/filter_db_files_logging.rb:

class ActiveRecord::ConnectionAdapters::AbstractAdapter
  def format_log_entry_with_db_files_filtering(message, dump = nil)
    dump = 'INSERT INTO db_files' if dump.to_s =~ /^INSERT INTO db_files/
    dump = 'UPDATE db_files'      if dump.to_s =~ /^UPDATE db_files/
    format_log_entry_without_db_files_filtering(message, dump)
  end

  alias_method_chain :format_log_entry, :db_files_filtering
end

Note that this isn’t yet a general solution (other tables’ blobs will still be logged), but it’s good enough for our needs, and we’ve got a known place we can come back to should we need to filter more blobs in the future.

Also, looking at AbstractAdapter#log_info just now, we notice that could have written our own Logger and saved the monkeypatch. Maybe we’ll look into doing that some day.

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

Rails tip #2: Faking DATA in tests

Posted on April 22, 2008

Ruby gives you access to the raw text after an __END__ statement in the currently running file through the DATA IO object.

Sometimes you’d like to use DATA in a test. You’ll find it works when you run the test individually but then fails when you run the whole suite. (Remember DATA comes from $0, the currently running file, only.)

We’ve found a nice way to fake it in the rubyforge gem: read __FILE__ instead, then split the results.

Here’s an example of the technique in use, functionally testing a wiki parser we’ve been tinkering with. It can often be painful to work with metaprogrammed tests like this, but on balance, we like the results here:

require 'test_helper'

class TestParser < Test::Unit::TestCase
  EXAMPLES = /={80}\n/m
  PARTS    = /-{80}\n/m

  # Faking data = DATA.read
  data = File.read(__FILE__).split('__END__').last

  data.split(EXAMPLES).map { |example| example.split(PARTS) }.each do |comment, markup, html|
    define_method "test_#{comment.strip.downcase.gsub(/\W/, '_')}" do
      assert_equal html, Parser.new.parse(markup).result.to_html
    end
  end
end

__END__
A single wiki word should be linked and wrapped in a paragraph.
--------------------------------------------------------------------------------
WikiWord
--------------------------------------------------------------------------------
<p><a href="WikiWord">WikiWord</a></p>
================================================================================
Two wiki words on separate lines should become two paragraphs
--------------------------------------------------------------------------------
WikiWord
WikiWord
--------------------------------------------------------------------------------
<p><a href="WikiWord">WikiWord</a></p>
<p><a href="WikiWord">WikiWord</a></p>

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