Rails tip #5: Atom param parser

Posted on April 25, 2008

The Atom Publishing Protocol is wonderfully RESTful, so it’s a natural fit for a Rails application.

One thing you’ll run into, though, is that AtomPub posts application/atom+xml data rather than the usual application/x-www-form-urlencoded stuff, so you’ll have to write some extra code to handle it.

The good news is that ActionController::Base helps you out. Instead of having to branch on the request type and fatten your controller, you can register a custom param_parser.

So, we wrote Hash.from_atom to transform the incoming xml into the usual { :entry => { ... } } params. Then, we registered it in an initializer:

ActionController::Base.param_parsers[Mime::ATOM] = lambda do |body|
  Hash.from_atom(body)
end

And our controller can now handle either regular form postings or AtomPub entries with the same line of code:

class EntriesController < ApplicationController
  def create
    @entry = @collection.entries.build(params[:entry])
    # ...
  end
end

Not bad.

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
  5. Atom param parser

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