Better Nesting in API Documents With Representable 1.7.6

{{{
The recent 1.7.6 release of “Representable”:https://github.com/apotonick/representable brings a really helpful feature to all the “Roar”:https://github.com/apotonick/roar and Representable users: Better nesting for flat hierarchies.

h3. Simpler Nesting In Documents

Sometimes an API requires you to nest a group of attributes into a separate section.

Imagine the following document.

{"title": "Roxanne",
 "details":
   {"track": 3,
    "length": "4:10"}
}

Both @track@ and @length@ are nested under a @details@ key. Now, this is the required _document_ structure. However, it doesn’t really fit into your model scope, as both nested keys are properties of the outer @Song@ object.

song.title  #=> "Roxanne"
song.track  #=> 3
song.length #=> "4:10"

In earlier versions of Roar/representable you had to provide a 1-to-1 mapping of your object to your document. This usually ended up in something clumsy like this.

class Song < ActiveRecord::Base
  # .. original code
  
  def details
    OpenStruct.new(
      track:  track
      length: length
    )
  end

It got even worse when parsing this nested document was to be accomplished! I’ll spare you the details here.

h3. More Than DSL Sugar: nested

Let’s experience the enjoyment of the new @::nested@ feature instead.

class SongRepresenter
  include Representable::JSON

  property :title

  nested :details do
    property :track
    property :length
  end
end

Life can be so easy. This simple change will advise representable to expect those two fellas @track@ and @length@ to be on the outer object, but it’ll still render them into a @details:@ section.

And, even better, this will also parse the document and set the nested attributes on the @song@ instance.

song.extend(SongRepresenter).from_json %{
  {"title": "Roxanne",
   "details":
     {"track": 3,
      "length": "4:10"}
  }
}

song.track #=> 3

It’s incredible how this new feature simplified our process to connect to the new Australian Post API – and, frankly, I feel a bit embarrassed I didn’t provide you guys with this feature earlier.

h3. Deep Nesting

The new @nested@ method turned out to be extremely useful for deeply nested “throw-away documents” that don’t need to be persistent.

For instance, here is a typical response from the Auspost API.

{"CreateArticleResponse":{
  "ArticleErrors":{
    "BusinessExceptions":{
      "NoOfErrors":1,
      "BusinessException":{
        "Code":102,"Description":"Internal Error, failed to process request to source"
      }
    }
  }
}}

To get to the actual error message, I need a 4-level deep hash access. The representer code before @::nested@ would look terrible – you’d spend half an hour on creating an object graph that maps to this document. Sucks.

Here’s how it looks now.

class ErrorsRepresenter
  include Representable::JSON

  self.representation_wrap = :CreateArticleResponse

  nested :ArticleErrors do
    nested :BusinessExceptions do
      nested :BusinessException do
        property :Description
      end
    end
  end
end

This is all defensive declarative code. If one of the keys is not found in the incoming document, representable will simply stop parsing that property.

To actually retrieve the error description I simply use a @Struct@.

err = Struct.new(:Description).new

err.extend(ErrorsRepresenter).
  from_json('{"CreateArticleResponse":{ ..')

err.Description #=> "Internal Error, ..."

What I love about this is: This code won’t break if the parsed document does _not_ contain any of the nested attributes. @err.Documents@ will simply return @nil@.

Imagining the nightmare I’d have conditionally parsing a 4-level deep hash I really feel like this is a good feature.

h3. Internals.

A fair side note. Internally, a @nested@ block “is implemented using a @Decorator@”:https://github.com/apotonick/representable/blob/4e01ec75ec4f0f629f7a8cd43d78388494e8d32d/lib/representable.rb#L170, even if you’re using a module representer for the original document. This doesn’t really affect you, however, if you add methods to the nested representer, make sure to use the right reference when you wanna access the model.

class SongRepresenter
  include Representable::JSON

  property :title

  nested :details do
    property :track
    property :length

    define_method :track do
      represented.track.to_f
    end
  end
end

Note that I use @represented@ instead of @self@ in the helper method.

Let me know what you think.
}}}

Advertisement

4 thoughts on “Better Nesting in API Documents With Representable 1.7.6

  1. Glad I found this article. I’m having trouble using `nested` from the other direction: consuming documents from an API that are nested and need to be pulled out and mapped to my ActiveRecord objects.

    My representer:

    require 'representable/hash'
    
    module Nhl::GameRepresenter
      include Representable::Hash
      include Representable::Hash::AllowSymbols
    
      property :stats_id, as: :eventId
    
      nested :venue do
        property :venue_id, as: :venueId
      end
    end
    

    My test:

    class Nhl::GameTest  1553247,
          venue: {
            :venueId=>28,
            :name=>"Air Canada Centre"
          }
        }
    
        game = Nhl::Game.new.extend(Nhl::GameRepresenter).from_hash(doc)
    
        assert_equal 1553247, game.stats_id
        assert_equal 28, game.venue_id
      end
    end
    

    stats_id is mapping correctly but not venue_id. Is there something I’m missing??

    Like

  2. In my above post the first part of my test got formatted incorrectly. The `doc` hash should look like this:

    doc = {
    :eventId => 1553247,
    venue: {
    :venueId=>28,
    :name=>”Air Canada Centre”
    }
    }

    Like

  3. I changed everything to JSON format instead of Hash and now it seems to work. Is this nested feature not meant to work with Representable::Hash?

    Like

  4. David: It is supposed to work with all engines (JSON, Hash, XML). Please open a discussion on Github as my blog is not the correct place for this, though. 😉

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s