Logo Devoh

Relaxed and Relieved

I've been working on a rewrite of Relax for a little while now. My desire is to create a DSL for defining web service consumers that's flexible enough to work with the wide variety of different services out there. For example, some web services may require the method to be specified as part of the path, while others use query parameters. Some use HTTP Basic authentication, while others rely on OAuth. Some produce XML responses, but others may return JSON. The ultimate goal is be able to quickly produce a simple consumer for situations where you may not need a full-blown API.

While at RailsConf this week, I had some time between sessions to wrap up some of these changes and have released an initial version of the rewrite. During this final stretch of development I ended up stripping out a lot of unnecessary complexity. I had originally created factories for performers (used to make the actual network requests), authenticators, and parsers, but I had only implemented a single concrete implementation for each. I figure that it's better to simplify the initial release, and then worry about adding in additional implementations as the need arises. I think this will result in better code since development will be driven by practical requirements rather than speculative abstraction.

Here's what a basic Flickr search consumer might look like with Relax:

class Flickr < Relax::Service
  defaults do
    parameter :api_key, :required => true
  end

  endpoint 'http://api.flickr.com/services/rest' do
    defaults do
      parameter :method, :required => true
    end

    action :search do
      set :method, 'flickr.photos.search'
      parameter :per_page, :default => 5
      parameter :page
      parameter :tags

      parser :rsp do
        attribute :stat, :as => :status

        element :photos do
          attribute :page
          attribute :pages
          attribute :perpage, :as => :per_page
          attribute :total

          elements :photo do
            attribute :id
            attribute :owner
            attribute :secret
            attribute :server
            attribute :farm
            attribute :title
            attribute :ispublic, :as => :is_public
            attribute :isfriend, :as => :is_friend
            attribute :isfamily, :as => :is_family
          end
        end
      end
    end
  end
end

And here's a search for photos of cucumbers and lemons:

flickr = Flickr.new(:api_key => FLICKR_API_KEY)
flickr.search(:tags => 'cucumbers,lemons')

An unintentional byproduct of this process was the extraction of the XML parser into a standalone gem called Relief. The API is somewhat inspired by SAX-Machine, but it returns hashses and arrays instead of objects with instance methods. It's also not SAX-based at the moment, but it does support the use of XPath for element definitions.

The parser in the Relax example above is using Relief, but here's a simpler, standalone example:

parser = Relief::Parser.new(:photos) do
  elements :photo do
    element :name
    element :url
  end
end

The same parser could be implemented using XPaths like so:

parser = Relief::Parser.new(:photos) do
  elements '//photo', :as => :photo do
    element 'name/text()', :as => :name
    element 'url/text()', :as => :url
  end
end

I'm happy with the direction these libraries are headed, but I'm happier still to have wrapped up the work that I've been putting into them and have actually released some gems. So, sudo gem install relax relief and let me know what you think.