Rails and REST: Nested XML and the Law of Demeter

When you’re writing a RESTful web service designed to expose a resource to the outside world, the case for including first-order associations is pretty strong. ActiveRecord’s default serialization method, to_xml, makes it pretty easy: simply pass in the :include key and the list of associations you’d like to nest, and the XML is automatically generated for you. But it can kill performance pretty quickly if you’re not careful. Consider a User resource, which includes, as a first-order association, the Address of that user. The XML will look something like this:

<user>
    <first-name>Guybrush</first-name>
    <last-name>Threepwood</last-name>
    <job-title>Fearsome Pirate</job-title>
    <address>
      <street-address>1 Governors Mansion</street-address>
      <city>Melee Island</city>
      <state>The Caribbean</state>
      <zip-code type="integer">12345</zip-code>
    </address>
  </user>

This is fine for a single resource, and probably what I want to see when I ask for /users/gthreepwood.xml. But when I ask for /users.xml?job_title=Pirate, and I’m getting back a list of resources that can stretch into the dozens or hundreds (there are a lot of pirates on Melee Island) it can start to drag quickly. You’re asking for more data from your database server (be sure you’re including the associations not only on the to_xml call but in the initial find call as well), it takes longer to serialize each object, and it takes longer (often much, much longer) for the client to parse that XML.

Worst of all, your client probably doesn’t need the full address. There’s plenty of precedent in the web world for this; when you’re displaying a list of things you probably want an abbreviated view, but when you’re looking at a specific resource you probably want a much richer view. You’re also designing an API that forces the client to reach through multiple layers of an object in order to dig out the one piece of information they need.

Instead, follow the Law of Demeter. If all the client of your resource needs when displaying a list of users is a text description of their street address, consider adding a method to your model that provides it:

def full_address “#{address.street_address}, #{address.city}, #{address.state} #{address.zip_code}” end

And use it in your controller as follows:

respond_to do |format| format.xml do render :xml => @users.to_xml(:methods => [:full_address]) end end

By my count, the former representation comes to 282 bytes and the latter to 200—a savings of almost one third. Multiplied over a large XML document with multiple resources that translates into a big win for both network bandwidth and XML parsing speed.

There’s one hitch, and this applies anytime you include a method as part of your XML. If the client tries to modify that resource update it by POSTing it back to your server, and you handle it by passing params[:user] straight into the update_attributes method of the relevant User object, your model will complain that it doesn’t know anything about a full_address= method. You’ve provided a getter but no setter.

There are two ways around this: either remove the offending parameter from the attributes you pass into the User model in your controller, or create no-op setter on your model. I personally prefer the latter, simply because I like my controllers to do as little as possible (and for the record, I think that models should either know how to serialize themselves to XML or that responsibility for doing so should be offloaded to an ERB template, but that’s another post). But either works as long as you’re aware of it and handling it appropriately.

This is part of a series of posts on Rails and REST. Read the rest.

Rails and REST: A reference to commonly-used HTTP status codes and their use in REST APIs

I'm Not Sure That Word Means What You Think It Means