{{{
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.
}}}
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:
My test:
stats_id is mapping correctly but not venue_id. Is there something I’m missing??
LikeLike
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”
}
}
LikeLike
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?
LikeLike
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. 😉
LikeLike