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!