{{{
It has been a while since I last blogged about “representable”:https://github.com/apotonick/representable/ – Ruby’s mapping gem that helps you rendering and parsing representations. To be precise, it has been more than 4 ½ months of reflecting, testing and refactoring, and I am happy to finally announce great new features.
h3. Inline Representers
When nesting representations, you have to tell representable about which nested representer to use.
module AlbumRepresenter include Representable::JSON property :title collection :songs, extend: SongRepresenter end
This happens using the @:extend@ option. While this provides a great modularity for the @SongRepresenter@, it can feel clumsy when you don’t intend to reuse it anywhere else.
You can now *define it inline*.
module AlbumRepresenter include Representable::JSON property :title collection :songs do property :name property :track end end
Just pass the nested representer in a block.
Note that you still have to supply @:class@ when you use the representer for parsing.
collection :songs, class: Song do property :name end
And, even better, you can still use @:extend@ with the inline declaration to inherit from a base module.
property :cover_song, extend: SongRepresenter do property :original_composer end
This will inherit @SongRepresenter@’s properties into the inline block.
Inline representers work with both @::property@ and of course @::collection@.
h3. PUT Semantics: Sync Models When Parsing
Representers can also parse documents and create nested objects.
Let’s use the representer we just discussed.
module AlbumRepresenter include Representable::JSON collection :songs, class: Song do property :title end end
Now, representable gives us parsing for free, as long as we provide the @:class@ option.
album = Album.new.extend(AlbumRepresenter) album.from_json('{songs: [{title: "Eruption"}]}') album.songs.first.title #=> "Eruption"
Internally, what happens is that representable will create a @Song@ instance for each element in the collection.
It does the following per parsed song.
Song.new.extend(SongRepresenter). from_json('{title: ..}')
What if you *wanna _update_ an existing @Song@* instead of creating a new? Representable now comes with @:parse_strategy@ which allows exactly that.
module AlbumRepresenter include Representable::JSON collection :songs, parse_strategy: :sync Song do property :title end end
As we provide @:sync@, representable will no longer create an object but call @from_json@ on the existing item.
album = Album.find(1) album.songs.first #=> #
Note that the @Album@ instance contains one song already.
album.extend(AlbumRepresenter). from_json('{songs: [{title: "Eruption"}]}') album.songs.first #=> #
What happened is that representable used the existing song instance when parsing, resulting in the song being renamed from “Panama” to “Eruption”. Both great songs.
This behaviour roughly implements @PUT@ semantics in a REST service when updating an existing resource. And it works with properties and collections.
h3. Predictable Coercion
You can use the “virtus”:https://github.com/solnic/virtus gem with representable to have coercion when representing objects.
module SongRepresenter include Representable::JSON include Representable::Coercion property :title, type: String property :track, type: Integer
We used to mix in @Virtus@ directly into the represented object, which gave us virtus’ accessors for free, but that also resulted in unpredictable behaviour due to virtus’ dynamic nature.
Coercion is now handled in a separate object and only happens inside @to_/from_@ invocations. Also, you have to add accessors to your properties manually.
class Song attr_accessor :title, :track end
This is a bit more work for you but greatly reduces confusion in the representable gem (and virtus) and makes it predictable – which is what a good gem should be.
h3. What Happened On The Inside?
The “Binding”:https://github.com/apotonick/representable/blob/0eabb31323927add10752f6c54da567dd341394e/lib/representable/binding.rb class got way to big and static, I had to copy+paste code to make those features work, so I extracted “ObjectDeserializer”:https://github.com/apotonick/representable/blob/0eabb31323927add10752f6c54da567dd341394e/lib/representable/deserializer.rb and its brother @ObjectSerializer@, and some more classes.
Also, a lot of methods from the @Representable@ module itself got moved into a separate “Mapper”:https://github.com/apotonick/representable/blob/0eabb31323927add10752f6c54da567dd341394e/lib/representable/mapper.rb class.
This makes the entire architecture a lot more cleaner, simpler to follow through and easier to replace parts of it. The refactoring of representable will be a part of my “upcoming talk at Rubyshift”:http://rubyshift.org/ in the Ukraine this year.
You should come, it’s an awesome conf!
h3. Update!
I totally forgot, so I have to add it now: Representable 1.7 also allows overriding properties in inheriting representers.
module CoverSongRepresenter include Representable::JSON # defines property :title include SongRepresenter # overrides that definition. property :title, as: :known_as end
As you can see, consecutively calling @property :title@ will override the former definition. That’s exactly how “proper” inheritance with methods work.
}}}
IINM your code example for updating an existing song is slightly off.
collection :songs, parse_strategy: :sync Song do
should be
collection :songs, parse_strategy: :sync, class: Song do
yes?
Also, thanks for the post. It just so happens I was looking for a solution like this for an API I am building out, so this release / post is quite timely!
LikeLike
Soulcutter: No!!!1one The whole point about
parse_strategy: :sync
is not having to define the class for deserialization as nothing is instantiated! Representable just grabs the existing objects and updates them.LikeLike