Many people use the Representable gem to render documents from Ruby objects, or to parse incoming documents into an object graph.
This is great for implementing document APIs with JSON or XML. Since Representable does both ways, rendering and deserializing, it gives you a great tool to cover huge parts of your API code.
What many people do not know is that Representable is also useful when transforming objects to other objects. This is particularly marvelous when decorating object graphs or customizing objects.
For example, we do that a lot in Reform, since a form object is mainly data transformations and pushing objects in different representations back and fourth.
Transforming Objects.
In versions before 1.2.6, we used to first transform the source object to a hash, and then apply that hash to the target object.
Consider the following object graph.
source = OpenStruct.new( name: "30 Years Live", songs: [ OpenStruct.new(id: 1, title: "Dear Beloved"), OpenStruct.new(id: 2, title: "Fuck Armageddon") ])
For simplicity, I’m using OpenStructs to implement an album composed of songs. Let’s assume we need to translate this object graph into objects that look like this.
Album = Struct.new(:name, :songs) Song = Struct.new(:title)
It is needless to say that the target classes could be ActiveRecord derivates or whatever you fancy. Here, Struct will help us to focus on what we need to do: Transform a graph of OpenStructs into Structs.
The Clumsy Way. Oh, and Slow.
In older versions, transformation to a differing object graph worked via an intermediate hash, using a representer.
class AlbumRepresenter < Representable::Decorator include Representable::Hash property :name collection :songs, class: Song do property :title end end
Here’s the transformation.
target = Album.new hash = AlbumRepresenter.new(source).to_hash AlbumRepresenter.new(target).from_hash(hash)
This will transform the OpenStruct graph into a tree of an Album
instance holding two Song
instances. Of course, this transformation doesn’t really make sense, but I hope to proof my point: This is incredible clumsy and slow, as this need two representers.
Representable::Object Helps!
I am surprised that I didn’t come up with that solution earlier, but here’s how it works now.
target = Album.new AlbumRepresenter.new(target).from_object(source)
Just one call to from_object
is required. Speaking of requirements: here’s how the representer changes.
require "representable/object" class AlbumRepresenter < Representable::Decorator include Representable::Object # .. the same as above. end
Note how the Hash
engine of Representable got replaced with Object
. And now, check out the simple transformation.
When running the representer, the exact same thing as above will happen, resulting in a target object graph as follows.
#<struct Album name="30 Years Live", songs= [#, #]>
The representer will simply traverse the source
object (the OpenStructs), deserialize necessary composite objects and map (“copy”) properties to the target
instance.
This Is Not The End.
My example was simple, probably too simple, but please keep in mind that the transformation can use all kinds of options as :instance
, renaming properties using :as
and allows an unlimited number of nestings. Also, runtime options like :exclude
and friends will work as well.
The new Object
representer engine’s a great tool and I started using it heavily in Reform and Disposable as it simplifies the code and speeds up about 20%. If you want to play with it, here’s the above code.
:exclude