August 09, 2008
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
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.
Aug 09, 2008
@James – Github that thing! Share the wealth. :)
Aug 09, 2008
Wow – I haven’t been maintaining that library for a loooong time :) Glad to see that you found it somewhat useful though!
Aug 09, 2008
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.
Aug 10, 2008
@Anders – Ha.
@Nate – Agreed. The benefit of mapping responses (however you end up doing it) is definitely type casting and things like associations.
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.
Aug 22, 2008
Very usefull post!
Aug 22, 2008
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.