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.