Representable

  • Last updated 04 Jul 20

Representable maps objects to documents (rendering) and documents to objects (parsing) using representers. Representers define the document structure and the transformation to/from objects.

Representers can define deeply nested object graphs….

It is very popular amongst REST API developers as it tackles both sides of exposing APIs: rendering documents and deserializing incoming documents to object graphs using a very generic approach.

But it’s also very helpful as a generic data transformer. …

In case you’re looking towards implementing a REST API, check out Roar first, which adds hypermedia semantics, media formats and more to Representable

API

In Representable, we differentiate between three APIs.

The declarative API is how we define representers. You can learn how to use those representers by reading about the very brief public API. Representable is extendable without having to hack existing code: the function API documents how to use its options to achieve what you need.

Declarative API

To render objects to documents or parse documents to objects, you need to define a representer.

A representer can either be a class (called decorator) or a module (called representer module). Throughout the docs, we will use decorators as they are cleaner and faster, but keep in mind you can also use modules.

require 'representable/json'

class SongRepresenter < Representable::Decorator
  include Representable::JSON

  property :id
  property :title
end

A representer simply defines the fields that will be mapped to the document using property or collection. You can then decorate an object and render or parse. Here’s an example.

# Given a Struct like this
Song = Struct.new(:id, :title) #=> Song

# You can instantiate it with the following
song = Song.new(1, "Fallout") #=> #<struct Song id=1, title="Fallout">

# This object doesn't know how to represent itself in JSON
song.to_json #=> NoMethodError: undefined method `to_json'

# But you can decorate it with the above defined representer
song_representer = SongRepresenter.new(song)

# Relax and let the representer do its job
song_representer.to_json #=> {"id":1,"title":"Fallout"}

The details are being discussed in the public API section.

Representer Modules

Instead of using classes as representers, you can also leverage modules which will then get mixed into the represented object.

A representer module is also a good way to share configuration and logic across decorators.

module SongRepresenter
  include Representable::JSON

  property :id
  property :title
end

The API in a module representer is identical to decorators. However, the way you apply them is different.

song.extend(SongRepresenter).to_json #=> {"id":1,"title":"Fallout"}

There’s two drawbacks with this approach.

  1. You pollute the represented object with the imported representer methods (e.g. to_json).
  2. Extending an object at run-time is costly and with many extends there will be a notable performance decrease.

Throughout this documentation, we will use decorator as examples to encourage this cleaner and faster approach.

Collections

Not everything is a scalar value. Sometimes an object’s property can be a collection of values. Use collection to represent arrays.

class SongRepresenter < Representable::Decorator
  include Representable::JSON

  property :id
  property :title
  collection :composer_ids
end

The new collection composer_ids has to be enumeratable object, like an array.

Song = Struct.new(:id, :title, :composer_ids)
song = Song.new(1, "Fallout", [2, 3])

song_representer = SongRepresenter.new(song)
song_representer.to_json #=> {"id":1,"title":"Fallout","composer_ids":[2,3]}

Of course, this works also for parsing. The incoming composer_ids will override the old collection on the represented object.

Nesting

Representable can also handle compositions of objects. This works for both property and collection.

For example, a song could nest an artist object.

Song   = Struct.new(:id, :title, :artist)
Artist = Struct.new(:id, :name)

artist = Artist.new(2, "The Police")
song   = Song.new(1, "Fallout", artist)

Here’s a better view of that object graph.

#<struct Song
  id=1,
  title="Fallout",
  artist=#<struct Artist
    id=2,
    name="The Police">>

Inline Representer

The easiest way to nest representers is by using an inline representer.

class SongRepresenter < Representable::Decorator
  include Representable::JSON

  property :id
  property :title

  property :artist do
    property :id
    property :name
  end
end

Note that you can have any levels of nesting.

Explicit Representer

Sometimes you want to compose two existing, stand-alone representers.

class ArtistRepresenter < Representable::Decorator
  include Representable::JSON

  property :id
  property :name
end

To maximize reusability of representers, you can reference a nested representer using the :decorator option.

class SongRepresenter < Representable::Decorator
  include Representable::JSON

  property :id
  property :title

  property :artist, decorator: ArtistRepresenter
end

This is identical to an inline representer, but allows you to reuse ArtistRepresenter elsewhere.

Note that the :extend and :decorator options are identical. They can both reference a decorator or a module.

Nested Rendering

Regardless of the representer types you use, rendering will result in a nested document.

SongRepresenter.new(song).to_json
#=> {"id":1,"title":"Fallout","artist":{"id":2,"name":"The Police"}}

Nested Parsing

When parsing, per default Representable will want to instantiate an object for every nested, typed fragment.

You have to tell Representable what object to instantiate for the nested artist: fragment.

class SongRepresenter < Representable::Decorator
  # ..
  property :artist, decorator: ArtistRepresenter, class: Artist
end

This happens via the :class option. Now, the document can be parsed and a nested Artist will be created by the parsing.

song = Song.new # nothing set.

SongRepresenter.new(song).
  from_json('{"id":1,"title":"Fallout","artist":{"id":2,"name":"The Police"}}')

song.artist.name #=> "The Police"

The default behavior is - admittedly - very primitive. Representable’s parsing allow rich mapping, object creation and runtime checks.

Document Nesting

Not always does the structure of the desired document map to your objects. The ::nested method allows structuring properties within a separate section while still mapping the properties to the outer object.

Imagine the following document. json_fragment = «END {“title”: “Roxanne”, “details”: {“track”: 3, “length”: “4:10”} } END

However, in the Song class, there’s no such concept as details.

Song = Struct.new(:title, :track, :length)

song = Song.new #=> #<struct Song title=nil, track=nil, length=nil>

Both track and length are properties of the song object itself. Representable gives you ::nested to map the virtual details section to the song instance.

class SongRepresenter < Representable::Decorator
  include Representable::JSON

  property :title

  nested :details do
    property :track
    property :length
  end
end

song_representer = SongRepresenter.new(song)
song_representer.from_json(json_fragment)

Accessors for the nested properties will still be called on the song object. And as always, this works both ways - for rendering and parsing.

Wrapping

You can automatically wrap a document.

class SongRepresenter < Representable::Decorator
  include Representable::JSON

  self.representation_wrap= :song

  property :title
  property :id
end

This will add a container for rendering and parsing.

SongRepresenter.new(song).to_json
#=> {"song":{"title":"Fallout","id":1}}

Setting self.representation_wrap = true will advice representable to figure out the wrap itself by inspecting the represented object class.

Note that representation_wrap is a dynamic function option.

self.representation_wrap = ->(user_options:) { user_options[:my_wrap] }

This would allow to provide the wrap manually.

song_representer.to_json(user_options: { my_wrap: "hit" })

Suppressing Nested Wraps

When reusing a representer for a nested document, you might want to suppress its representation_wrap= for the nested fragment.

Reusing SongRepresenter from the last section in a nested setup allows suppressing the wrap via the :wrap option.

class AlbumRepresenter < Representable::Decorator
  include Representable::JSON

  collection :songs,
    decorator: SongRepresenter, # SongRepresenter defines representation_wrap.
    wrap:      false            # turn off :song wrap.
end

The representation_wrap from the nested representer now won’t be rendered or parsed…

Album = Struct.new(:songs)
album = Album.new
album.songs = [song]
AlbumRepresenter.new(album).to_json

.. and will result in:

{"songs":[{"title":"Fallout","id":1}]}

Otherwise it would respect the representation_wrap= set in the nested decorator (SongRepresenter) and will render:

{"songs":[{"song":{"title":"Fallout","id":1}}]}

Note that this only works for JSON and Hash at the moment.

Inheritance

Properties can be inherited across representer classes and modules.

class SongRepresenter < Representable::Decorator
  include Representable::JSON

  property :id
  property :title
end

What if you need a refined representer to also add the artist. Use inheritance.

class SongWithArtistRepresenter < SongRepresenter
  property :artist do
    property :name
  end
end

All configuration from SongRepresenter will be inherited, making the properties on SongWithArtistRepresenter: id, title, and artist. The original SongRepresenter will stay as it is.

Artist         = Struct.new(:name)
SongWithArtist = Struct.new(:id, :title, :artist)

artist           = Artist.new("Ivan Lins")
song_with_artist = SongWithArtist.new(1, "Novo Tempo", artist)

# Using the same object with the two representers
song_representer             = SongRepresenter.new(song_with_artist)
song_with_artist_representer = SongWithArtistRepresenter.new(song_with_artist)

song_representer.to_json
#=> {"id":1,"title":"Novo Tempo"}

song_with_artist_representer.to_json
#=> {"id":1,"title":"Novo Tempo","artist":{"name":"Ivan Lins"}}

Composition

You can also use modules and decorators together to compose representers.

module GenericRepresenter
  include Representable::JSON

  property :id
end

This can be included in other representers and will extend their configuration.

class SongRepresenter < Representable::Decorator
  include GenericRepresenter

  property :title
end

As a result, SongRepresenter will contain the good old id and title property.

Overriding Properties

You might want to override a particular property in an inheriting representer. Successively calling property(name) will override the former definition - exactly as you know it from overriding methods in Ruby.

class CoverSongRepresenter < SongRepresenter
  include Representable::JSON

  property :title, as: :name # overrides that definition.
end

Partly Overriding Properties

Instead of fully replacing a property, you can extend it with :inherit. This will add your new options and override existing options in case the one you provided already existed.

class SongRepresenter < Representable::Decorator
  include Representable::JSON

  property :title, as: :name, render_nil: true
end

You can now inherit properties but still override or add options.

class CoverSongRepresenter < SongRepresenter
  include Representable::JSON

  property :title, as: :songTitle, default: "n/a", inherit: true
end

Using the :inherit, this will result in a property having the following options.

property :title,
  as:         :songTitle, # overridden in CoverSongRepresenter.
  render_nil: true        # inherited from SongRepresenter.
  default:    "n/a"       # defined in CoverSongRepresenter.

The :inherit option works for both inheritance and module composition.

Inherit With Inline Representers

:inherit also works applied with inline representers.

class SongRepresenter < Representable::Decorator
  include Representable::JSON

  property :title
  property :artist do
    property :name
  end
end

You can now override or add properties within the inline representer.

class HitRepresenter < SongRepresenter
  include Representable::JSON

  property :artist, inherit: true do
    property :email
  end
end

Results in a combined inline representer as it inherits.

property :artist do
  property :name
  property :email
end

Naturally, :inherit can be used within the inline representer block.

Note that the following also works.

class HitRepresenter < SongRepresenter
  include Representable::JSON

  property :artist, as: :composer, inherit: true
end

This renames the property but still inherits all the inlined configuration.

Basically, :inherit copies the configuration from the parent property, then merges in your options from the inheriting representer. It exposes the same behaviour as super in Ruby - when using :inherit the property must exist in the parent representer.

Feature

If you need to include modules in all inline representers automatically, register it as a feature.

class AlbumRepresenter < Representable::Decorator
  include Representable::JSON
  feature Link # imports ::link

  link "/album/1"

  property :hit do
    link "/hit/1" # link method imported automatically.
  end
end

Nested representers will include the provided module automatically.

Execution Context

Readers and Writers for properties will usually be called on the represented object. If you want to change that, so the accessors get called on the decorator instead, use :exec_context.

class SongRepresenter < Representable::Decorator
  property :title, exec_context: :decorator

  def title
    represented.name
  end
end

Callable Options

While lambdas are one option for dynamic options, you might also pass a “callable” object to a directive.

class Sanitizer
  include Uber::Callable

  def call(represented, fragment, doc, *args)
    fragment.sanitize
  end
end

Note how including Uber::Callable marks instances of this class as callable. No respond_to? or other magic takes place here.

property :title, parse_filter: Santizer.new

This is enough to have the Sanitizer class run with all the arguments that are usually passed to the lambda (preceded by the represented object as first argument).

Read/Write Restrictions

Using the :readable and :writeable options access to properties can be restricted.

property :title, readable: false

This will leave out the title property in the rendered document. Vice-versa, :writeable will skip the property when parsing and does not assign it.

Coercion

If you need coercion when parsing a document you can use the Coercion module which uses virtus for type conversion.

Include Virtus in your Gemfile, first.

gem 'virtus', ">= 0.5.0"

Use the :type option to specify the conversion target. Note that :default still works.

class SongRepresenter < Representable::Decorator
  include Representable::JSON
  include Representable::Coercion

  property :recorded_at, type: DateTime, default: "May 12th, 2012"
end

Coercing values only happens when rendering or parsing a document. Representable does not create accessors in your model as virtus does.

Note that we think coercion in the representer is wrong, and should happen on the underlying object. We have a rich coercion/constraint API for twins.

Symbol Keys

When parsing, Representable reads properties from hashes using their string keys.

song.from_hash("title" => "Road To Never")

To allow symbol keys also include the AllowSymbols module.

class SongRepresenter < Representable::Decorator
  include Representable::Hash
  include Representable::Hash::AllowSymbols
  # ..
end

This will give you a behavior close to Rails’ HashWithIndifferentAccess by stringifying the incoming hash internally.

Defaults

The defaults method allows setting options that will be applied to all property definitions of a representer.

class SongRepresenter < Representable::Decorator
  include Representable::JSON

  defaults render_nil: true

  property :id
  property :title
end

This will include render_nil: true in both id and title definitions, as if you’d provided that option each time.

You can also have dynamic option computation at compile-time.

class SongRepresenter < Representable::Decorator
  include Representable::JSON

  defaults do |name, options|
    { as: name.camelize }
  end

The options hash combines the user’s and Representable computed options.

property :id, skip: true

defaults do |name, options|
  options[:skip] ? { as: name.camelize } : {}
end

Note that the dynamic defaults block always has to return a hash.

Combining those two forms also works.

class SongRepresenter < Representable::Decorator
  include Representable::JSON

  defaults render_nil: true do |name|
    { as: name.camelize }
  end

All defaults are inherited to subclasses or including modules.

Standalone Hash

If it’s required to represent a bare hash object, use Representable::JSON::Hash instead of Representable::JSON.

This is sometimes called a lonely hash.

require "representable/json/hash"

class SongsRepresenter < Representable::Decorator
  include Representable::JSON::Hash
end

You can then use this hash decorator on instances of Hash.

hash = {"Nick" => "Hyper Music", "El" => "Blown In The Wind"}
SongsRepresenter.new(hash).to_json
#=> {"Nick":"Hyper Music","El":"Blown In The Wind"}

This works both ways.

A lonely hash starts to make sense especially when the values are nested objects that need to be represented, too. You can configure the nested value objects using the values method. This works exactly as if you were defining an inline representer, accepting the same options.

class SongsRepresenter < Representable::Decorator
  include Representable::JSON::Hash

  values class: Song do
    property :title
  end
end

You can now represents nested objects in the hash, both rendering and parsing-wise.

hash = {"Nick" => Song.new("Hyper Music")}
SongsRepresenter.new(hash).to_json

In XML, use XML::Hash. If you want to store hash attributes in tag attributes instead of dedicated nodes, use XML::AttributeHash.

Standalone Collection

Likewise, you can represent lonely collections, instances of Array.

require "representable/json/collection"

class SongsRepresenter < Representable::Decorator
  include Representable::JSON::Collection

  items class: Song do
    property :title
  end
end

Here, you define how to represent items in the collection using items.

Note that the items can be simple scalar values or deeply nested objects.

ary = [Song.new("Hyper Music"), Song.new("Screenager")]
SongsRepresenter.new(ary).to_json
#=> [{"title":"Hyper Music"},{"title":"Screenager"}]

Note that this also works for XML.

Standalone Collection: to_a

Another trick to represent collections is using a normal representer with exactly one collection property named to_a.

class SongsRepresenter < Representable::Decorator
  include Representable::JSON # note that this is a plain representer.

  collection :to_a, class: Song do
    property :title
  end
end

You can use this representer the way you already know and appreciate, but directly on an array.

ary = []
SongsRepresenter.new(ary).from_json('[{"title": "Screenager"}]')

In order to grab the collection for rendering or parsing, Representable will now call array.to_a, which returns the array itself.

Automatic Collection Representer

Instead of explicitly defining representers for collections using a “lonely collection”, you can let Representable do that for you.

You define a singular representer, Representable will infer the collection representer.

Rendering a collection of objects comes for free, using for_collection.

songs = Song.all
SongRepresenter.for_collection.new(songs).to_json
#=> '[{"title": "Sevens"}, {"title": "Eric"}]'

SongRepresenter.for_collection will return a collection representer class.

For parsing, you need to provide the class for the nested items. This happens via collection_representer in the representer class.

class SongRepresenter < Representable::Decorator
  include Representable::JSON
  property :title

  collection_representer class: Song
end

You can now parse collections to Song instances.

json  = '[{"title": "Sevens"}, {"title": "Eric"}]'

SongRepresenter.for_collection.new([]).from_json(json)

Note: the implicit collection representer internally is implemented using a lonely collection. Everything you pass to ::collection_representer is simply provided to the ::items call in the lonely collection. That allows you to use :populator and all the other goodies, too.

Automatic Singular and Collection

In case you don’t want to know whether or not you’re working with a collection or singular model, use represent.

# singular
SongRepresenter.represent(Song.find(1)).to_json
#=> '{"title": "Sevens"}'

# collection
SongRepresenter.represent(Song.all).to_json
#=> '[{"title": "Sevens"}, {"title": "Eric"}]'

represent figures out the correct representer for you. This works for parsing, too.

Public API

When decorating an object with a representer, the object needs to provide readers for every defined property - and writers, if you’re planning to parse.

Accessors

In our small SongRepresenter example, the represented object has to provide #id and #title for rendering.

Song = Struct.new(:id, :title)
song = Song.new(1, "Fallout")

Rendering

You can render the document by decorating the object and calling the serializer method.

SongRepresenter.new(song).to_json #=> {"id":1, title":"Fallout"}

When rendering, the document fragment is read from the represented object using the getter (e.g. Song#id).

Since we use Representable::JSON the serializer method is #to_json.

For other format engines the serializer method will have the following name.

  • Representable::JSON#to_json
  • Representable::JSON#to_hash (provides a hash instead of string)
  • Representable::Hash#to_hash
  • Representable::XML#to_xml
  • Representable::YAML#to_yaml

Parsing

Likewise, parsing will read values from the document and write them to the represented object.

song = Song.new
SongRepresenter.new(song).from_json('{"id":1, "title":"Fallout"}')
song.id    #=> 1
song.title #=> "Fallout"

When parsing, the read fragment is written to the represented object using the setter (e.g. Song#id=).

For other format engines, the deserializing method is named analogue to the serializing counterpart, where to becomes from. For example, Representable::XML#from_xml will parse XML if the format engine is mixed into the representer.

User Options

You can provide options when representing an object using the user_options: option.

song_representer.to_json(user_options: { is_admin: true })

Note that the :user_options will be accessible on all levels in a nested representer. They act like a “global” configuration and are passed to all option functions.

Here’s an example where the :if option function evaluates a dynamic user option.

property :id, if: ->(options) { options[:user_options][:is_admin] }

This property is now only rendered or parsed when :is_admin is true.

Using Ruby 2.1’s keyword arguments is highly recommended - to make that look a bit nicer.

property :id, if: ->(user_options:, **) { user_options[:is_admin] }

Nested User Options

Representable also allows passing nested options to particular representers. You have to provide the property’s name to do so.

song_representer.to_json(artist: { user_options: { is_admin: true } })

This will pass the option to the nested artist, only. Note that this works with any level of nesting.

Include and Exclude

Representable supports two top-level options.

:include allows defining a set of properties to represent. The remaining will be skipped.

song_representer.to_json(include: [:id])  #=> {"id":1}

The other, :exclude, will - you might have guessed it already - skip the provided properties and represent the remaining.

song_representer.to_json(exclude: [:id, :artist])
#=> {"title":"Fallout"}

As always, these options work both ways, for rendering and parsing.

Note that you can also nest :include and :exclude.

song_representer.to_json(artist: { include: [:name] })
#=> {"id":1, "title":"Fallout", "artist":{"name":"Sting"}}

to_hash and from_hash

Function API

Both rendering and parsing have a rich API that allows you to hook into particular steps and change behavior.

If that still isn’t enough, you can create your own pipeline.

Overview

Function option are passed to property.

property :id, default: "n/a"

Most options accept a static value, like a string, or a dynamic lambda.

property :id, if: ->(options) { options[:fragment].nil? }

The options hash is passed to all options and has the following members. {doc: doc, options: options, represented: represented, decorator: self}

options[:doc] When rendering, the document as it gets created. When parsing, the entire document.
options[:fragment] When parsing, this is the fragment read from the document corresponding to this property.
options[:input] When rendering, this is the value read from the represented object corresponding to this property.
options[:represented] The currently represented object.
options[:decorator] The current decorator instance.
options[:binding] The current binding instance. This allows to access the currently used definition, e.g. options[:binding][:name].
options[:options] All options that have been passed into the render or parse method.
options[:user_options] The :user_options for the current representer. These are only the nested options from the user, for a particular representer.

In your option function, you can either receive the entire options hash and use it in the block.

if: ->(options) { options[:fragment].nil? }

Or, and that is the preferred way, use Ruby’s keyword arguments.

if: ->(fragment:, **) { fragment.nil? }

Options

Here’s a list of all available options.

:as Renames property
:getter Custom getter logic for rendering
:setter Custom setter logic after parsing
:if Includes property when rendering/parsing when evaluated to true
:reader Overrides entire parsing process for property
:writer Overrides entire rendering process for property
:skip_parse Skips parsing when evaluated to true
:skip_render Skips rendering when evaluated to true
:parse_filter Pipeline to process parsing result
:render_filter Pipeline to process rendering result
:deserialize Override deserialization of nested object
:serialize Override serialization of nested object
:extend Representer to use for parsing or rendering
:prepare Decorate the represented object
:class Class to instantiate when parsing nested fragment
:instance Instantiate object directly when parsing nested fragment
:render_nil Render nil values
:render_empty Render empty collections

As

If your property name doesn’t match the name in the document, use the :as option.

property :title, as: :name

This will render using the :as value. Vice-versa for parsing

song.to_json #=> {"name":"Fallout","track":1}

Getter

When rendering, Representable calls the property’s getter on the represented object. For a property named :id, this will result in represented.id to retrieve the value for rendering.

You can override this, and instead of having Representable call the getter, run your own logic.

property :id, getter: ->(represented:, **) { represented.uuid.human_readable }

In the rendered document, you will find the UUID now where should be the ID.

decorator.to_json #=> {"id": "f81d4fae-7dec-11d0-a765-00a0c91e6bf6"}

As helpful as this option is, please do not overuse it. A representer is not a data mapper, but a document transformer. If your underlying data model and your representers diverge too much, consider using a twin to simplify the representer.

Setter

After parsing has happened, the fragment is assigned to the represented object using the property’s setter. In the above example, Representable will call represented.id=(fragment).

Override that using :setter.

property :id,
  setter: ->(fragment:, represented:, **) { represented.uuid = fragment.upcase }

Again, don’t overuse this method and consider a twin if you find yourself using :setter for every property.

If

You can exclude properties when rendering or parsing, as if they were not defined at all. This works with :if.

property :id, if: ->(user_options:,**) { user_options[:is_admin] }

When parsing (or rendering), the id property is only considered when is_admin has been passed in.

This will parse the id field.

decorator.from_json('{"id":1}', user_options: {is_admin: true})

Reader

To override the entire parsing process, use :reader. You won’t have access to :fragment here since parsing hasn’t happened, yet.

property :id,
  reader: ->(represented:,doc:,**) { represented.payload = doc[:uuid] || "n/a" }

With :reader, parsing is completely up to you. Representable will only invoke the function and do nothing else.

Writer

To override the entire rendering process, use :writer. You won’t have access to :input here since the value query to the represented object hasn’t happened, yet.

property :id,
  writer: ->(represented:,doc:,**) { doc[:uuid] = represented.id }

With :writer, rendering is completely up to you. Representable will only invoke the function and do nothing else.

Skip Parse

To suppress parsing of a property, use :skip_parse.

property :id,
  skip_parse: ->(fragment:,**) { fragment.nil? || fragment=="" }

No further processing happens with this property, should the option evaluate to true.

Skip Render

To suppress rendering of a property, use :skip_render.

property :id,
  skip_render: ->(represented:,**) { represented.id.nil? }

No further processing happens with this property, should the option evaluate to true.

Parse Filter

Use :parse_filter to process the parsing result.

property :id,
  parse_filter: ->(fragment, options) { fragment.strip }

Just before setting the fragment to the object via the setter, the :parse_filter is called.

Note that you can add multiple filters, the result from the last will be passed to the next.

Render Filter

Use :render_filter to process the rendered fragment.

property :id,
  render_filter: ->(input, options) { input.strip }

Just before rendering the fragment into the document, the :render_filter is invoked.

Note that you can add multiple filters, the result from the last will be passed to the next.

Deserialize

When deserializing a nested fragment, the default mechanics after decorating the represented object are to call represented.from_json(fragment).

Override this step using :deserialize.

property :artist,
  deserialize: ->(input:,fragment:,**) { input.attributes = fragment }

The :input option provides the currently deserialized object.

Serialize

When serializing a nested object, the default mechanics after decorating the represented object are to call represented.to_json.

Override this step using :serialize.

property :artist,
  serialize: ->(represented:,**) { represented.attributes.to_h }

Extend

Alias: :decorator.

When rendering or parsing a nested object, that represented object needs to get decorated, which is configured via the :extend option.

You can use :extend to configure an explicit representer module or decorator.

property :artist, extend: ArtistRepresenter

Alternatively, you could also compute that representer at run-time.

For parsing, this could look like this.

property :artist,
  extend: ->(fragment:,**) do
    fragment["type"] == "rockstar" ? RockstarRepresenter : ArtistRepresenter
  end

For rendering, you could do something as follows.

property :artist,
  extend: ->(input:,**) do
    input.is_a?(Rockstar) ? RockstarRepresenter : ArtistRepresenter
  end

This allows a dynamic polymorphic representer structure.

Prepare

The default mechanics when representing a nested object is decorating the object, then calling the serializer or deserializer method on it.

You can override this step using :prepare.

property :artist,
  prepare: ->(represented:,**) { ArtistRepresenter.new(input) }

Just for fun, you could mimic the original behavior.

property :artist,
  prepare: ->(represented:,binding:,**) { binding[:extend].new(represented) }

Class

When parsing a nested fragment, Representable per default creates an object for you. The class can be defined with :class.

property :artist,
  class: Artist

It could also be dynamic.

property :artist,
  class: ->(fragment) { fragment["type"] == "rockstar" ? Rockstar : Artist }

Instance

Instead of using :class you can directly instantiate the represented object yourself using :instance.

property :artist,
  instance: ->(fragment) do
    fragment["type"] == "rockstar" ? Rockstar.new : Artist.new
  end

Render Nil

In Representable, false values are considered as a valid value and will be rendered into the document or parsed.

If you want nil values to be included when rendering, use the :render_nil option.

property :title,
  render_nil: true

Render Empty

Per default, empty collections are rendered (unless they’re nil). You can suppress rendering.

collection :songs,
  render_empty: false

XML

If you’re enjoying the pleasure of working with XML, Representable can help you. It does render and parse XML, too, with an almost identical declarative API.

require "representable/xml"

class SongRepresenter < Representable::Decorator
  include Representable::XML

  property :title
  collection :composers
end

Note that you have to include the Representable::XML module.

The public API then gives you to_xml and from_xml.

Song = Struct.new(:title, :composers)
song = Song.new("Fallout", ["Stewart Copeland", "Sting"])
SongRepresenter.new(song).to_xml
<song>
  <title>Fallout</title>
  <composers>Stewart Copeland</composers>
  <composers>Sting</composers>
</song>

Tag Attributes

You can also map properties to tag attributes in Representable. This works only for the top-level node, though (seen from the representer’s perspective).

class SongRepresenter < Representable::Decorator
  include Representable::XML

  property :title, attribute: true
  collection :composers
end

SongRepresenter.new(song).to_xml
<song title="Fallout">
  <composers>Stewart Copeland</composers>
  <composers>Sting</composers>
</song>

Naturally, this works both ways.

Mapping Content

The same concept can also be applied to content. If you need to map a property to the top-level node’s content, use the :content option. Again, top-level refers to the document fragment that maps to the representer.

class SongRepresenter < Representable::Decorator
  include Representable::XML

  property :title, content: true
end

SongRepresenter.new(song).to_xml
<song>Fallout</song>

Wrapping Collections

It is sometimes unavoidable to wrap tag lists in a container tag.

class AlbumRepresenter < Representable::Decorator
  include Representable::XML

  collection :songs, as: :song, wrap: :songs
end

Album = Struct.new(:songs)
album = Album.new(["Laundry Basket", "Two Kevins", "Wright and Rong"])

album_representer = AlbumRepresenter.new(album)
album_representer.to_xml

Note that :wrap defines the container tag name.

<album>
  <songs>
    <song>Laundry Basket</song>
    <song>Two Kevins</song>
    <song>Wright and Rong</song>
  </songs>
</album>

Namespace

Namespaces in XML allow the use of different vocabularies, or set of names, in one document. Read this great article to share our fascination about them.

Where’s the EXAMPLE CODE?

The Namespace module is available in Representable >= 3.0.4. It doesn’t work with JRuby due to Nokogiri’s extremely complex implementation. Please wait for Representable 4.0 where we replace Nokogiri.

For future-compat: Namespace only works in decorator classes, not modules.

Namespace: Default

You can define one namespace per representer using ::namespace to set the section’s default namespace.

{{ “test/xml_namespace_test.rb:simple-class:../representable” tsnippet }}

Nested representers can be inline or classes (referenced via :decorator). Each class can maintain its own namespace.

Without any mappings, the namespace will be used as the default one.

{{ “test/xml_namespace_test.rb:simple-xml:../representable” tsnippet }}

Namespace: Prefix

After defining the namespace URIs in the representers, you can map them to a document-wide prefix in the top representer via ::namespace_def.

{{ “test/xml_namespace_test.rb:map-class:../representable” tsnippet }}

Note how you can also use :namespace to reference a certain differing prefix per property.

When rendering or parsing, the local property will be extended, e.g. /library/book/isbn will become /lib:library/lib:book/lib:isbn.

{{ “test/xml_namespace_test.rb:map-xml:../representable” tsnippet }}

The top representer will include all namespace definitions as xmlns attributes.

Namespace: Parse

Namespaces also apply when parsing an XML document to an object structure. When defined, only the known, prefixed tags will be considered.

{{ “test/xml_namespace_test.rb:parse-call:../representable” tsnippet }}

In this example, only the /lib:library/lib:book/lib:character/hr:name was parsed.

{{ “test/xml_namespace_test.rb:parse-res:../representable” tsnippet }}

If your incoming document has namespaces, please do use and specify them properly.

Namespace: Remove

If an incoming document contains namespaces, but you don’t want to define them in your representers, you can automatically remove them.

class AlbumRepresenter < Representable::Decorator
  include Representable::XML

  remove_namespaces!
end

This will ditch the namespace prefix and parse all properties as if they never had any prefix in the document, e.g. lib:author becomes author.

Removing namespaces is a Nokogiri hack. It’s absolutely not recommended as it defeats the purpose of XML namespaces and might result in wrong values being parsed and interpreted.

YAML

Representable also comes with a YAML representer. Like XML, the declarative API is almost identical.

Flow Style Lists

A nice feature is that #collection also accepts a :style option which helps having nicely formatted inline (or “flow”) arrays in your YAML - if you want that!

require 'representable/yaml'

class SongRepresenter < Representable::Decorator
  include Representable::YAML

  property :title
  property :id
  collection :composers, style: :flow
end

Public API

To render and parse, you invoke to_yaml and from_yaml.

Song = Struct.new(:title, :id, :composers)
song = Song.new("Fallout", 1, ["Stewart Copeland", "Sting"])
SongRepresenter.new(song).to_yaml
---
title: Fallout
id: 1
composers: [Stewart Copeland, Sting]

Debugging

Representable is a generic mapper using recursions, pipelines and things that might be hard to understand from the outside. That’s why we got the Debug module which will give helpful output about what it’s doing when parsing or rendering.

You can extend objects on the run to see what they’re doing.

SongRepresenter.new(song).extend(Representable::Debug).from_json("..")
SongRepresenter.new(song).extend(Representable::Debug).to_json

It’s probably a good idea not to do this in production.

Upgrading Guide

We try to make upgrading as smooth as possible. Here’s the generic documentation, but don’t hesitate to ask for help on Gitter.

2.4 to 3.0

  • The 3.0 line runs with Ruby >2.0, only. This is to make extensive use of keyword arguments.
  • All deprecations from 2.4 have been removed.

to_hash(user_options: {}) ->(options) { options[:user_options] } ->(user_options:,**) { user_options }

2.3 to 2.4

The 2.4 line contains many new features and got a major internal restructuring. It is a transitional release with deprecations for all changes.

Breakage

:render_filter => lambda { |val, options| "#{val.upcase},#{options[:doc]},#{options[:options][:user_options]}" }

Deprecations

Once your code is migrated to 2.4, you should upgrade to 3.0, which does not have deprecations anymore and only supports Ruby 2.0 and higher.

If you can’t upgrade to 3.0, you can disable slow and annoying deprecations as follows.

Representable.deprecations = false

Positional Arguments

For dynamic options like :instance or :getter we used to expose a positional API like instance: ->(fragment, options) where every option has a slightly different signature. Even worse, for collections this would result in a differing signature plus an index like instance: ->(fragment, index, options).

From Representable 2.4 onwards, only one argument is passed in for all options with an identical, easily memoizable API. Note that the old signatures will print deprecation warnings, but still work.

For parsing, this is as follows (:instance is just an example).

property :artist, instance: ->(options) do
  options[:input]
  options[:fragment] # the parsed fragment
  options[:doc]      # the entire document
  options[:result]   # whatever the former function returned,
                     # usually this is the deserialized object.
  options[:user_options] # options passed into the parse method (e.g. from_json).
  options[:index]    # index of the currently iterated fragment (only with collection)
end

We highly recommend to use keyword arguments if you’re using Ruby 2.1+.

property :artist, instance: ->(fragment:, user_options:, **) do

User Options

When passing dynamic options to to_hash/from_hash and friends, in older version you were allowed to pass in the options directly.

decorator.to_hash(is_admin: true)

This is deprecated. You now have to use the :user_options key to make it compatible with library options.

decorator.to_hash(user_options: { is_admin: true })

Pass Options

The :pass_options option is deprecated and you should simply remove it, even though it still works in < 3.0. You have access to all the environmental object via options[:binding].

In older version, you might have done as follows.

property :artist, pass_options: true,
  instance: ->(fragment, options) { options.represented }

Runtime information such as represented or decorator is now available via the generic options.

property :artist, instance: ->(options) do
  options[:binding]              # property Binding instance.
  options[:binding].represented  # the represented object
  options[:user_options]         # options from user.
end

The same with keyword arguments.

property :artist, instance: ->(binding:, user_options:, **) do
  binding.represented  # the represented object
end

Parse Strategy

The :parse_strategy option is deprecated in favor of :populator. Please replace all occurrences with the new populator style to stay cool.

If you used a :class proc with :parse_strategy, the new API is class: ->(options). It used to be class: ->(fragment, user_options).

Class and Instance

In older versions you could use :class and :instance in combination, which resulted in hard-to-follow behavior. These options work exclusively now.

SkipRender

skip_render: lambda { |options|
# raise options[:represented].inspect
        options[:user_options][:skip?] and options[:input].name == "Rancid"

Binding

The :binding option is deprecated and will be removed in 3.0. You can use your own pipeline and replace the WriteFragment function with your own.