August 09, 2008

Posted by John

Tagged gems, httparty, rexml, and xml

Older: How To Use Google for Authentication in your Rails App

Newer: Parsing XML with Ruby

Ruby Object to XML Mapping Library

While watching the opening ceremonies for the Olympics tonight, I came across a library named ROXML. An excerpt from the ROXML website:

ROXML is a Ruby library designed to make it easier for Ruby developers to work with XML. Using simple annotations, it enables Ruby classes to be custom-mapped to XML. ROXML takes care of the marshalling and unmarshalling of mapped attributes so that developers can focus on building first-class Ruby classes. As a result, ROXML simplifies the development of RESTful applications, Web Services, and XML-RPC.

I’ve been wanting something like this for a while so I decided to give it a go. Below is an example of how to use it with the Delicious API and HTTParty.

%w[rubygems roxml httparty pp].each { |x| require x }

# <post href="http://code.google.com/p/sparrow/" hash="1df8a7cb9e8960992556518c0ea0d146" description="sparrow - Google Code" tag="ruby sparrow memcache queue" time="2008-08-06T15:07:24Z" others="115" extended="Sparrow is a really fast lightweight queue written in Ruby that speaks memcache. That means you can use Sparrow with any memcached client library (Ruby or otherwise)."/>  
class Post
  include ROXML
  
  [:href, :hash, :description, :tag, :time, :others, :extended].each do |attribute|
    xml_attribute attribute
  end
end

# <posts user="jnunemaker" tag="ruby">
#   <post href="http://code.google.com/p/sparrow/" hash="1df8a7cb9e8960992556518c0ea0d146" description="sparrow - Google Code" tag="ruby sparrow memcache queue" time="2008-08-06T15:07:24Z" others="115" extended="Sparrow is a really fast lightweight queue written in Ruby that speaks memcache. That means you can use Sparrow with any memcached client library (Ruby or otherwise)."/>  
# </posts>
class Posts
  include ROXML
  
  xml_attribute :user  
  xml_attribute :tag
  xml_object :post, Post, ROXML::TAG_ARRAY
end

class Delicious
  include HTTParty
  
  def initialize(u, p)
    @auth = {:username => u, :password => p}
  end
  
  def recent(options={})
    self.class.get('https://api.del.icio.us/v1/posts/recent', options.merge({:basic_auth => @auth}))
  end
end

config    = YAML::load(File.read(File.join(ENV['HOME'], '.delicious')))
delicious = Delicious.new(config['username'], config['password'])
recent    = delicious.recent(:query => {:tag => 'ruby'})
pp Posts.parse(recent)

It uses REXML behind the scenes so it’s not as fast as Hpricot or libxml-ruby, but I thought it was kind of interesting. I found the fact that I have to define the outer Posts class kind of annoying. I wish I could just say parse all of the Post objects out of the xml or something like that. Also, to get at the array of posts, you have to do Posts.parse('...').post, with the singular post method returning an array of Post objects. I’d like control over that method name so it could be plural.

This is a cool start and gives me some ideas. I might have to throw something together someday. For now, it goes on my rainy day list of things to do.

8 Comments

  1. James Healy James Healy

    Aug 09, 2008

    I did some research into this issue a few weeks back when I was looking to provide an interface for reading and writing ONIX files (a book industry schema).

    xml-mapping is similar to ROXML, although it suffers from the same REXML induced speed issues.

    koders.com also turned up a libxml based effort, but it didn’t look to be complete or maintained. http://www.koders.com/ruby/fid9A5871CE8637C894C252E1C4CC1E61301B7910CA.aspx?s=%22David+Gasson%22#L16 .

    I toyed with them, but ended up rolling my own solution with libxml for speed reasons.

  2. @James – Github that thing! Share the wealth. :)

  3. Wow – I haven’t been maintaining that library for a loooong time :) Glad to see that you found it somewhat useful though!

  4. I have been working on API integrations with our upcoming website at www.clevelandwebstandards.org. I am integrating with Upcoming, Flickr, Delicious, and Twitter.

    What you have mentioned in this post is the one thing I have been looking for. I ended up rolling my own methods to do this, simply because I wanted to control some formatting and associations. I have methods that are more active record like, where they will typecast certain values, as well as maintain associations.

    It seems like a lot of work, but it makes it much easier to integrate withour database schema when I am working with Ruby Objects instead of hashes from the XML.

    For instance, I have the following for Upcoming:

    @event = Api::Upcoming::Event.find(id), which I can then do @event.venue and have the Api::Upcoming::Venue object, and @event.user where I get the Api::Upcoming::User object, and so on and so forth. It maintains all associations within the Upcoming API, and it typecasts the dates, bool, integer, etc – so I know exactly what I am working with when I go to sync with our database.

    I may need to look deeper into this library. Thanks for posting this, couldn’t have been better timing.

  5. @Anders – Ha.

    @Nate – Agreed. The benefit of mapping responses (however you end up doing it) is definitely type casting and things like associations.

  6. James Healy James Healy

    Aug 14, 2008

    The solution I coded wasn’t a generic XML→object mapping library. I just wrote a thin wrapper around a libxml node that extracts the values I need.

    In any case, the code is on github as a private repository, and will be switched to public as soon as I get approval. It might be a useful example for other people working with libxml.

  7. Very usefull post!

  8. Thanks for the tip, been playing with roxml over the past two weeks and it’s been pretty good for us. I’ve ninjafied it slightly to use libxml so it’s even pretty speedy!

Sorry, comments are closed for this article to ease the burden of pruning spam.

About

Authored by John Nunemaker (Noo-neh-maker), a programmer who has fallen deeply in love with Ruby. Learn More.

Projects

Flipper
Release your software more often with fewer problems.
Flip your features.