Released Roar 0.11.5 With HAL Link Arrays and Representable 1.2.9 With Polymorphism Support!

{{{
Time to catch up with some improvements in the “representable”:https://github.com/apotonick/representable gem and it’s bigger brother “roar”:https://github.com/apotonick/roar. Most of the work was done on the sunny island Fuerteventura in a surf camp *grin.

h3. Readable And Writeable Properties.

To make it a little bit easier to provide assignment security we added @:readable@ and @:writeable@ switches to @#property@. Both are @true@ per default.

I’ll use a song to demonstrate.

song = Song.new(:title => "You're Wrong", :track => 4)

For simplicity I’ll use the @Hash@ representer. However, the following code works just fine with JSON, XML, and YAML.

module SongRepresenter
  include Representable::Hash

  property :title, :readable => false
  property :track
end

Since the @title@ property is not readable – from an outer perspective – it is not serialized anymore.

song.extend(SongRepresenter).to_hash
#=> {"track"=>4}

Vice-versa, properties can be read-only, too.

module SongRepresenter
  include Representable::Hash

  property :title, :writeable => false
  property :track
end

The @title@ property is now ignored when consuming (or parsing) a document as it is _not_ writeable.

song = Song.new.extend(SongRepresenter)
song.from_hash({:title => "Fallout", :track => 1})
puts song #=> #

Those two switches allow a declarative access control for properties without having to override accessors. BTW, they also replace the mass-assignment problem and its poor successor strong_parameters in Rails.

h3. Polymorphic Extend.

Finally, something fuckin’ awesome. Until today, representable allowed you to specify the representer modules for parsing and rendering using the @:extend@ option.

  property :song, :extend => SongRepresenter

To make this dynamic, both @:class@ and @:extend@ now also accept a lambda to compute the class or module at runtime.

Let’s assume we not only have original songs but also cover songs.

class CoverSong < Song
end

Now we set up a composition where the @Album@ instance contains one @Song@ and one @CoverSong@ instance.

songs = [
 Song.new(title: "Weirdo", track: 5),
 CoverSong.new(title: "Truth Hits Everybody", track: 6, 
    copyright: "The Police")
]
album = Album.new(name: "Incognito", songs: songs)

Note that the covered song also points to the amazing band that originally wrote this song.

Two simple representers are needed to properly represent those songs.

module SongRepresenter
  include Representable::Hash

  property :title
  property :track
end

module CoverSongRepresenter
  include Representable::Hash
  include SongRepresenter

  property :copyright
end

I am using inheritance here – yay!

The album representer needs to distinguish between the different song types. That is why I use a dynamic @:extend@ here.

module AlbumRepresenter
  include Representable::Hash

  property :name
  collection :songs, :extend => lambda { |song| 
    song.is_a?(CoverSong) ? 
      CoverSongRepresenter : SongRepresenter }
end

Check out what we get when calling the rendering.

album.extend(AlbumRepresenter).to_hash
#=> {"name"=>"INCOGNITO", "songs"=>[
 {"title"=>"Weirdo", "track"=>5}, 
 {"title"=>"Truth Hits Everybody", "track"=>6, 
  "copyright"=>"The Police"}]}

Representable uses the respective representer module depending on the type of the object passed in. It is important to understand that @:extend@ works both ways and always receives a valid object into the lambda block.

  • When *rendering*, the object bound to the property (or collection) is extended using the provided module.
  • However, when *parsing*, representable uses the class from @:class@, instantiates a brand-new object, passes this into the block and then extends it.

Speaking about @:class@ brings us to the next feature!

h3. Dynamic Class Block For Parsing.

Now what if you need to compute the object’s class at runtime when parsing? Well, use a lambda!

module AlbumRepresenter
 include Representable::Hash

 property :name
 collection :songs, 
  :extend => lambda{ |song| ...,
  :class  => lambda{ |hsh| hsh.has_key?("copyright") ?
    CoverSong : Song }
end

The dynamic @:class@ lambda allows you to decide the class right when you’re parsing the document. If you wonder how the @hsh@ argument looks like, this would be something like

hsh #=> {"title"=>"Weirdo", "track"=>5}

Never ever complain about missing polymorphism in representable any more! Ha!

h3. MORE! Dynamic Object Creation For Parsing!

No, I’m not finished, yet! When working on roar and its HAL feature we encountered the problem that we needed to override the object creation when parsing – in an easy way. And here comes @:instance@.

module AlbumRepresenter
  include Representable::Hash

  property :name
  collection :songs, 
    :extend   => lambda { |song| ... },
    :instance => lambda do |hsh| 
      hsh.has_key?("copyright") ? 
        CoverSong.new : 
        Song.new(original: true)
    end
end

Instead of providing a class with the @:class@ option, I create the song instances myself using @:instance@. The returned value from the block will be extended and passed into the deserialize workflow where representable will call something like @from_hash@ on it.

Note how I assign additional data for the @Song@ object which will survive the parsing (line 10).

Isn’t that fantastic? The dynamic lambdas for @:extend@, @:class@ and @:instance@ allow you to have simple polymorphic representations without the pain of overriding representable’s internals.

BTW: all lambda blocks are executed in the represented instance context – which is the @Song@ object here.

h3. Last But Not Least: HAL Link Arrays In Roar.

This might sound funny, but we wrote all that in order to provide a 100% complete implementation of the “HAL/JSON standard”:http://tools.ietf.org/html/draft-kelly-json-hal-00, a cool universal media format for RESTful APIs.

What was missing was “support for arrays of links”:https://github.com/apotonick/roar/issues/33 in HAL. Here’s an example of what people wanted.

{"_links":{
 "next":{"href":"http://next"}, 
 "self":[{"lang":"en","href":"http://en.hit"},
         {"lang":"de","href":"http://de.hit"}]
}}

For some strange reasons I still don’t understand why the HAL standard allows having “single links” (no girlfriend) and arrays of links. I am sure Mike Kelly himself will soon explain why we need this – however, here is how roar handles this “since 0.11.5”:https://github.com/apotonick/roar/commit/367a4eb64e34315ab6d81bf70f8bf9ac9027ece0.

module SongRepresenter
  include Roar::Representer::JSON::HAL

  link :next do
    "http://next"
  end

  links :self do
    [{:lang => "en", :href => "http://en.hit"}, 
     {:lang => "de", :href => "http://de.hit"}]
  end
end

You can see, we still handle “single links” with the @::link@ class method. Arrays of links can be specified using the new @::links@ method which receives the rel attribute and a block. The block should return an array of link attribute hashes.

As always, that works for rendering _and_ parsing. Be careful when you work with a link array.

song.from_json(..)
song.links[:next] #=> #
song.links[:self] #=> [#, #]

Links defined with @::links@ will naturally contain an array of @Hyperlink@ instances after parsing. Does that help you, guys?

h3. More To Come.

That was quite some work. And there’s more coming. Let me know if anything could be improved or added. But now it’s time for a surf. Keep it real!
}}}

Advertisement

One thought on “Released Roar 0.11.5 With HAL Link Arrays and Representable 1.2.9 With Polymorphism Support!

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