Representable 1.7 Is Out With Syncing Support, Inline Representers And More!

{{{
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.

}}}

Advertisement

2 thoughts on “Representable 1.7 Is Out With Syncing Support, Inline Representers And More!

  1. 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!

    Like

  2. 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.

    Like

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