{{{
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!
}}}
Very good source code with OOP concept and worked with JSON, XML, and YAML.
LikeLike