OdeToCode IC Logo

OData and Ruby

Sunday, July 11, 2010

The Open Data Protocol is gaining traction and is something to look at if you expose data over the web. .NET 4 has everything you need to build and consume OData with WCF Data Services. It's also easy to consume OData from outside of .NET - everything from JavaScript to Excel.

I've been working with Ruby against a couple OData services, and using Damien White's ruby_odata. Here is some ruby code to dump out the available courses in Pluralsight's OData feed.

require 'lib/ruby_odata'

svc = OData::Service.new "http://www.pluralsight-training.net/Odata/"
svc.Courses
courses = svc.execute
courses.each do |c|
   puts "#{c.Title}"
end

ruby_odata builds types to consume the feed, and also maps OData query options.

svc.Courses.filter("substringof('Fundamentals', Title) eq true")
fundamentals = svc.execute
fundamentals.each do |c|
    puts "#{c.Title}"
end

There is one catch - the Pluralsight model uses inheritance for a few of the entities:

<EntityType Name="ModelItemBase">
    <Property Name="Title" Type="Edm.String" ...="" />
    <!-- ... -->
</EntityType>
<EntityType Name="Course" BaseType="ModelItemBase">
    <Property Name="Name" Type="Edm.String" Nullable="true" />
    <!-- ... -->
</EntityType>

Out of the box, ruby_odata doesn't handle the BaseType attribute and misses inherited properties, so I had to hack around in service.rb with some of the Nokogiri APIs that parse the service metadata (this code only handles a one level of inheritance, and gets invoked when building entity types):

def collect_properties(edm_ns, element, doc)
    props = element.xpath(".//edm:Property", "edm" => edm_ns)
    methods = props.collect { |p| p['Name'] }
    unless element["BaseType"].nil?
        base = element["BaseType"].split(".").last()
        baseType = doc.xpath("//edm:EntityType[@Name=\"#{base}\"]",
                             "edm" => edm_ns).first()
        props = baseType.xpath(".//edm:Property", "edm" => edm_ns)
        methods = methods.concat(props.collect { |p| p['Name']})
    end
    return methods    
end

The overall Ruby <-> OData experience has been quite good.