Collection+JSON Support In Roar!

{{{

h3. What Is Collection+JSON?

The “Collection+JSON”:http://amundsen.com/media-types/collection/format/ (Cj) specifies a hypermedia type for collections of items. It not only defines how single items or lists are represented in a document, it also comes with application semantics how to expose a standard CRUD behavior for items. In addition to that, embedded HATEOAS hyperlinks are one of the building blocks for this format.

And, hey, all that is done in lovely JSON!!!

Why not assume our music API would suddenly support the Cj media format? Here’s what I’d @GET@ for @http://songs/scarifice@.

{
 "collection" :
 {
  "version" : "1.0",
  "href" : "http://songs/",
  "items" :
  [
   {
    "href" : "http://songs/scarifice",
    "data" : [
     {"name": "title", "value": "Scarifice"},
     {"name": "band",  "value": "Racer X"}
    ],
    "links" : [
     {"rel": "band", "href": "http://bands/racer-x"}
    ]
   }
  ]
 }
}

The Cj format surprisingly always “speaks in” collections – even the single song is represented in a 1-item collection. The format provides a @version@, the hyperlink @href@ to the collection itself, and the actual @items@ we’re interested in (line 6).

Items in turn can have a @href@ pointing to itself, properties of the represented object are found in the @data@ attribute (line 10), and items may also contain additional links. Here, the song references the API endpoint of the band owning that very song (line 15).

Note that “real” collections have the same format, e.g. when grabbing @http://songs@ the @items@ array is be filled with millions of damn good song items.

h3. Application Semantics Of Cj.

Apart from the standardization of representing lists this is nothing special so far. The cool thing about Cj is that it comes with specifications about the CRUD behavior of collections. Here’s an example.

If we were to create another song *without knowing the API semantics* we were helpless! Not with Cj! Here, a collection usually comes with a @template@ telling us how to create or update particular items.

The @template@ is embedded in the collection itself, in the document we were just talking about.

{
 "collection" :
 {
  "version" : "1.0",
  "href" : "http://songs/",
  "items" : 
 ...
  "template" : {
   "data" : [
     {"name": "title", "value": ""},
     {"name": "band",  "value": ""}
   ]
  }
 }
}

Normally, you’d fill out a form and submit it to create something. That’s exactly what the @template@ does – it acts like a form which is to be @POST@ed to the collection’s @href@ URL. Remember, that was @http://songs/@ (line 5)?

The corresponding create request would be something like the following.

POST http://songs/
------------------
{
 "template" : {
  "data" : [
   {"name": "title", "value": "Parasite"},
   {"name": "band",  "value": "Frenzal Rhomb"}
  ]
 }
}

Cj comes with more, it contains a query specification for retrieving other collections, an error structure for exposing internal errors and more. I suggest you “check out the examples”:http://amundsen.com/media-types/collection/examples/ the creator Mike Amundsen provides on his site.

h3. Implementing Cj Services.

Too bad media formats only describe documents and how they might be used. It is still up to you to actually _implement_ those. To make your life simpler “the roar gem”:https://github.com/apotonick/roar now comes with a “Collection+JSON representer”:https://github.com/apotonick/roar/blob/b84aeab9bf8b003c0a5b15f42ed80077025a7547/lib/roar/representer/json/collection_json.rb

By the time of writing this roar gives you a representer and a bit of added DSL sugar to render and parse collection representations. I won’t go into detail in this posting as the API is not 100% stable and we’re waiting for your comments. Here’s how a full-blown representer for the songs domain would look like.

module SongCollectionRepresenter
  include Roar::Representer::JSON::CollectionJSON
  version "1.0"
  href { "//songs/" }

  link(:feed) { "//songs/feed" }

  items(:class => Song) do
    href { "//songs/scarifice" }

    property :title, :prompt => "Song title"
    property :length, :prompt => "Song length"

    link(:download) { "//songs/scarifice.mp3" }
    link(:stats) { "//songs/scarifice/stats" }
  end

  template do
    property :title, :prompt => "Song title"
    property :length, :prompt => "Song length"
  end

  queries do
    link :search do
      {:href => "//search", :data => [
        {:name => "q", :value => ""}]}
    end
  end
end

The DSL should be pretty self-explaining. However, suggestions welcome! I tried to use as much roar/representable language as possible. Those guys already familiar with roar will recognize the @property@ and @link@ semantics for sure. Semantics. I love this word. Although I don’t understand its semantic.

Having defined the Cj representer you may now render and parse documents.

[Song.find(1)].
  extend(SongCollectionRepresenter).
  to_json

Note that the collection representer works with enumerable objects, like arrays, only.

Parsing an incoming document, e.g. in a client is just as simple as the rendering.

[].
  extend(SongCollectionRepresenter).
  from_json("{collection: ...")

Please, play around with it! Since Rubygems is only partially working you should go with the “github master of roar”:https://github.com/apotonick/roar. Have fun!
}}}

7 thoughts on “Collection+JSON Support In Roar!

  1. Karlin: The Hyperlink class using OpenStruct shouldn’t be a performance killer but we can investigate on that. And, good news for you: the upcoming version of roar/representable supports a decorator strategy avoiding extend 🙂

    Like

  2. Awesome, thanks for adding Decorator. And thanks for working on Roar and Representable in general, they’re great!

    I tried out the new Roar::Decorator yesteday but ran into trouble using Roar::Representer::JSON::HAL (I decided to use HAL instead of Collection+JSON.) and the ‘link’ helper:

    link :self { “/widgets/#{represented.id}” }

    It was still trying to call ‘links’ on the represented object (let’s call it ‘Widget’) instead of on my Roar::Decorator subclass (‘WidgetsRepresenter’):

    undefined method `links’ for #

    It looks like some modules (like LinkCollectionRepresenter) in ‘lib/roar/representer/json/hal.rb’ are still assuming they should use ‘extend’? Any ideas?

    Like

Leave a comment