Roar 1.0 Released With JSON-API Support!

Happy new year, dear friends! I hope your dreams come true this year, finally! Releasing Roar 1.0 has been a dream of mine and that’s how we kick off 2015.

Shorter Namespaces.

Releasing Roar 1.0 was a good occassion to introduce brief namespaces and constant names in Roar. Unsure about what Roar’s gonna be, I initially started with constant names such as Roar::Representer::Feature::Hypermedia a few years back.

Major version bumps allow you to change things without deprecating it – yay! Since the concept of a representer is found throughout the Roar gem we ditched the Representer namespace. The same happened to Feature. It is nonsense to prefix a feature module – modules are always features.

JSON-API Support.

Roar 1.0 comes with full JSON-API support. This is both, rendering and parsing JSON-API documents. Roar is the only gem presently that does both ways – all other gems are either pure client gems or can only render JSON-API documents, like ActiveModel::Serializer (AMS).

I am mentioning that because Roar constantly gets compared to AMS. And this is simply wrong. AMS is nothing more but an object-oriented rendering engine. Roar is a document framework that uses the same definition to render and to deserialise documents for further processing. This is a bit like comparing Haml with the JSON gem.

A Minimal JSON-API Representer.

Let’s start with the simplest representer for a JSON-API document. In this example, I use a Roar decorator, nevertheless, you are free to use a module representer in case you fancy the extend approach.

class SongDecorator < Roar::Decorator
  include Roar::JSON::JSONAPI
  type :songs

  property :id
end

By mixing JSONAPI into the representer you import semantics and DSL for this hypermedia format.

Rendering JSON-API.

Given you had a Song instance at hand, here’s how you render a JSON-API document.

SongDecorator.prepare(song).to_json
#=> "{"songs":{"id":"1"}}"

This is a singular document, representing an individual entity. JSON-API differentiates between singular and collection documents.

Personally, I dislike this decision as it makes it harder for both server and clients to handle documents. They always have to check whether it’s a singular or a collection document.

Anyway, here’s how you would render a collection of songs.

songs = [song, song2]
SongDecorator.for_collection.prepare(songs).to_json
#=> "{"songs":[{"id":"1"},{"id":"2"}]}"

The for_collection class method will return the collection representer. That one only accepts a collection of songs and renders a JSON array.

Parsing JSON-API.

As already noted, the reason I created Roar is to provide a framework to handle both ways of dealing with representational documents. Here’s how to parse a JSON-API document to a Ruby object.

song = Song.new
json = '{"songs":{"id":"1"}}'

SongRepresenter.prepare(song).from_json(json)

song.id #=> 1

Roar deserialises the properties back to a Ruby object. This happens by using public setter methods on the represented model, only.

The same works with a collection. Here, you need to provide a collection of new (or existing) songs to update, exactly as we did a minute ago.

Simple Attributes.

You can add as many resource attributes as you want using property.

class SongDecorator < Roar::Decorator
  #..

  property :id
  property :title
  property :track_number

By defining properties, Roar knows what to render and what to parse from incoming documents.

Relationship Links.

JSONAPI allows to globally link to related resources in a document. In Roar, you use link blocks to specify those relationships.

class SongDecorator < Roar::Decorator
  #..
  link "songs.album" do
    {
      type: "album",
      href: "http://example.com/albums/{songs.album}"
    }
  end

This will render global links into the document.

"songs" => {
  "id" => "1",
},
"links" => {
  "songs.album"=> {
    "href"=>"http://example.com/albums/{songs.album}", 
    "type"=>"album"
  }
},

Note that the DSL is not final, yet, as we’re still collecting user input.

To-One Relationships.

Representing associations for one object is called To-One relation in JSON-API. You can define that per document in Roar.

class SongDecorator < Roar::Decorator
  #..
  has_one :composer
  has_many :listeners

As you can see, Roar’s JSON-API implementation lets you define associations using has_one and has_many.

This will add links section to each represented object in the document.

{
  "songs" => {
  "id" => "1",
  "links" => {
    "composer"=>"10",
    "listeners"=>["8"]
  }
},

Depending on the type of association it renders an ID or an array of IDs. There is no magic to that: Roar simply calls song.composer and collects the IDs from each object.

Compound Documents.

The JSONAPI media format also allows embedding parts of other, associated resources into the document. This is called a compound document.

In Roar, the compound block acts like a sub-representer to specify the nested documents.

class SongDecorator < Roar::Decorator
  #..

  compound do
    property :album do
      property :title
    end

    collection :musicians do
      property :name
    end
  end

Again, this is pure Roar DSL and works exactly the way you nest representers in Roar/Representable using inline representers.

This renders associated documents into the linked section.

"songs" => {
  "id" => "1",
       
  "linked" => {
    "album"=> [{"title"=>"Eruption"}],
    "musicians"=> [
      {"name"=>"Eddie Van Halen"},
      {"name"=>"Greg Howe"}
     ]
  }
}

The implementation of JSONAPI in Roar is relatively simple and reuses a lot of existing mechanisms.

More Features? Of course!

The JSONAPI module also allows adding meta data and more. Check out the README for the complete DSL.

The way media formats are supported in Roar makes it straight-forward to try out different specifications without too much change in the representer. We support HAL-JSON and JSON-API out-of-the-box.

Please give the new JSON-API implementation a go and let me know what else you need!

Advertisements

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 )

Google+ photo

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

Connecting to %s