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.