TRB JOBS: Deutschsprachiger Ruby Developer in Hannover - Jaaaa!

Operation

Operation Representer

Last updated 19 January 2017 trailblazer v2.0 v1.1

Representers help to parse and render documents for JSON or XML APIs.

After defining a representer, it can either parse an incoming document into an object graph, or, the other way round, serialize a nested object into a document.

You can use representers from the Representable gem with your operations.

Check the → full example for a quick overview.

Parsing needs Representable >= 3.0.2.

Parsing: Introduction

In Trailblazer, the normal workflow is to deserialize (parse) incoming data into an object graph, then validate this data structure, then persist parts of it. Transforming the incoming data into one or many objects is done by a representer.

Normally, this happens internally in the Reform object, which receives the data in validate and then uses an automatically inferred representer to parse the data onto itself.

However, let’s discuss representers first and ignore the validation layer.

What the representer does and how it interacts with the contract (or any Ruby object) can be explained in a simple example.

Imagine the following incoming document from a form submission.

{
  "title" => "Let Them Eat War",
  "band"  => "Bad Religion"
}

In this case the document is a hash, but Representable allows parsing JSON, XML and YAML, too.

To process this document a representer must be defined.

class SongRepresenter < Representable::Decorator
  include Representable::Hash # the document format.

  property :title
  property :band
end

Representers are provided by the Representable and Roar gems. They look a lot like contracts, but define the structure of the documents, only, no semantics or validations.

When parsing, the representer simply traverses the document and writes every known attribute to the represented object. The latter can be any kind of object, it only has to expose property setters.

Song = Struct.new(:title, :band)

While you will usually use models (e.g. ActiveRecord) or contracts with representers, a simple struct is sufficient for explaining.

Parsing the document onto a Song instance is very straight-forward.

input = { "title" => "Let Them Eat War", "band" => "Bad Religion" }

song  = Song.new # this doesn't have to be an empty object.

SongRepresenter.new(song).from_hash(input)

In pseudo-code, the representer literally only walks through the hash and assigns values to the model.

# SongRepresenter#from_hash
  song.title = input["title"]
  song.band  = input["band"]

After the parsing, your represented model is populated with the values.

song.title #=> "Let Them Eat War"
song.band  #=> "Bad Religion"

Representers increasingly make sense with documents differing from your models, for complex media formats such as JSON API, and also for nested documents.

Parsing: Nesting

While one-level parsing might appear trivial and easily solvable using mechanisms such as update_attributes and Rails’ automatic parsed params hash, the deserialization process gets more complicated with nested fragments and models.

Let’s assume the band: property should now be a dedicated object. Here’s the new document.

{
  "title" => "Let Them Eat War",
  "band"  => {
    "name" => "Bad Religion"
  }
}

When parsing this document, a new Band object should be created, attached to the song, and assigned a name.

Again, the Band model could be provided by any ORM, or simply be a struct.

Band = Struct.new(:name)

The representer to implement parsing and creating looks as follows.

class SongRepresenter < Representable::Decorator
  include Representable::Hash # the document format.

  property :title
  property :band, class: Band do
    property :name
  end
end

The class: option is the easiest way to tell a representer to create an object for a nested fragment. This is called population and deserves its own documentation section as it exposes a few ways, such as find-or-create, instantiate, and so on.

Parsing the nested document will now result in a nested object graph.

input = { "title" => "Let Them Eat War", "band" => { "name" => "Bad Religion" } }

song  = Song.new # this doesn't have to be an empty object.

SongRepresenter.new(song).from_hash(input)

The representer will deserialize the nested fragment into its own model, the way you specified (or override) it.

song.title #=> "Let Them Eat War"
song.band  #=> #<struct Band name="Bad Religion">
song.band.name #=> "Bad Religion"

As you can see, all the representer does when parsing is following its specified schema, assign property values to the model or creating/finding nested models, which it recurses then onto.

TODO: link to populator docs.

Parsing with Contract

The aforementioned parsing also works against a Reform contract instead of a pure model. Consider the following contract.

class SongContract < Reform::Form
  property :title
  property :band

  validates :title, presence: true
  validates :band, presence: true
end

To validate this contract, you actually pass it a document.

input = { "title" => "Let Them Eat War", "band" => "Bad Religion" }

SongContract.new(song).validate(input)

The only real difference to the above examples is that Reform will validate itself after the deserialization. In other words: In validate, Reform uses a representer to deserialize the document to itself, then runs its validation logic.

→ Reform’s architecture docs talk about this in detail.

Reform infers this representer (or deserializer) automatically in validate and that is fine for most HTML forms where the contract schema and form layout are identical. In document APIs, whatsoever, the documents format often doesn’t match 1-to-1 with the contract’s schema. For example, when validating input in a JSON API system.

This is where you can specify your own representer to be used against the contract.

The idea is to decouple the validation (contract) from the document structure (representer). A contract shouldn't be aware of the environment it is being used in, and the representer mustn't have any knowledge about validations and the underlying persistence layer.

How that is done is discussed in the following sections.

Parsing: Explicit

Instead of using Reform’s automatic representer to deserialize the incoming document, the Contract::Validate macro allows you to specify a different representer.

This requires a representer class.

class MyRepresenter < Representable::Decorator
  include Representable::JSON
  property :id
end

While this representer could be used stand-alone, the operation helps you to leverage it for parsing.

class Create < Trailblazer::Operation
  class MyContract < Reform::Form
    property :id
  end

  step Model( Song, :new )
  step Contract::Build( constant: MyContract )
  step Contract::Validate( representer: MyRepresenter ) # :representer
  step Contract::Persist( method: :sync )
end

In the Validate macro, the representer: option will set the specified representer for deserialization. Note that the contract can also be an inline contract.

You may now pass a JSON document instead of a hash into the operation’s call.

Create.({}, "document" => '{"id": 1}')

This parses the JSON document and the representer will assign the property values and objects to the contract. Afterwards, the contract validates itself with the normal mechanics.

In fact, the contract doesn’t even know its data was parsed from a JSON or XML document.

Parsing: Inline Representer

If you quickly want to try a representer or you’re facing a small amount of properties, only, you can use an inline representer.

class Create < Trailblazer::Operation
  class MyContract < Reform::Form
    property :id
  end

  extend Representer::DSL

  representer do
    property :id
  end

  step Model( Song, :new )
  step Contract::Build( constant: MyContract )
  step Contract::Validate( representer: self["representer.default.class"] )
  step Contract::Persist( method: :sync )
end

The behavior is identical to referencing the representer class constant.

Parsing: Infer

A representer can also be inferred from the contract’s schema. All you need to do is define the format, e.g. Representable::JSON.

class Create < Trailblazer::Operation
  class MyContract < Reform::Form
    property :id
  end

  step Model( Song, :new )
  step Contract::Build( constant: MyContract )
  step Contract::Validate( representer: Representer.infer(MyContract, format: Representable::JSON) )
  step Contract::Persist( method: :sync )
end

The Operation::Representer.infer method will return a representer class.

Parsing: Dependency Injection

You can override the parsing representer when calling the operation with dependency injection. This allows things like exchanging the representer to parse other document formats, such as XML.

require "representable/xml"

class MyXMLRepresenter < Representable::Decorator
  include Representable::XML
  property :id
end

The representer can be injected using Trailblazer’s well-defined injection interface.

result = Create.({},
  "document" => '<body><id>1</id></body>',
  "representer.default.class" => MyXMLRepresenter # injection
)

Note how the XML representer replaces the built-in JSON representer and can parse the XML document to the contract. The latter doesn’t know anything about the swapped documents.

Naming

Without a name specified, the representer will be named default.

class Create < Trailblazer::Operation
  extend Representer::DSL
  representer MyRepresenter
end

Create["representer.default.class"] #=> MyRepresenter

To maintain multiple representers per operation, you may name them.

representer :parse, MyRepresenter
representer :errors, ErrorsRepresenter

They are now accessible via their named path.

Create["representer.parse.class"] #=> MyRepresenter

Rendering: Introduction

In a document API system, after processing the operation, you usually want to render a response document. While you could use something like ActiveModel::Serializer for this, it makes sense to reuse a representer.

class SongRepresenter < Representable::Decorator
  include Representable::JSON # the document format.

  property :title
  property :band
end

Given the following model.

song  #=> #<struct Song title="Let Them Eat War" band="Bad Religion">

The mechanics when rendering are very similar to what happens when parsing.

SongRepresenter.new(song).to_json #=> '{"title":"Let Them Eat War","band": "Bad Religion"}'

In pseudo-code, the representer literally only walks through its schema, asks the model for the property values and serializes them into a document.

# SongRepresenter#to_json
  json = {}
  json[:title] = song.title
  json[:band]  = song.band
  json.to_json

Representers are very helpful when introducing media formats such as HAL or JSON API, or when having to render complex XML documents from a nested object graph. The only requirement they have are the model’s readers.

Go and read a bit about Representable to learn more about mapping, aliases, media formats, and more.

Rendering: Example

Rendering a document after the operation finished is part of the presentation layer, which should not happen inside the operation itself. Serializing a document is to happen where the operation was called, such as a controller.

However, you may use the result object to grab representers and models.

result = Create.({}, "document" => '{"id": 1}')
json   = result["representer.default.class"].new(result["model"]).to_json
json #=> '{"id":1}'

Luckily, Endpoint and respond in Rails controllers help you with this.

Full Example

Often, an operation will maintain multiple representers, e.g. for parsing, to render into a specific media format, and to handle error cases.

You could have a generic errors representer.

class ErrorsRepresenter < Representable::Decorator
  include Representable::JSON
  collection :errors
end

Using naming, the operation may then contain several representers.

class Create < Trailblazer::Operation
  extend Contract::DSL
  extend Representer::DSL

  contract do
    property :title
    validates :title, presence: true
  end

  representer :parse do
    property :title
  end

  representer :render do
    include Roar::JSON::HAL

    property :id
    property :title
    link(:self) { "/songs/#{represented.id}" }
  end

  representer :errors, ErrorsRepresenter # explicit reference.

  step Model( Song, :new )
  step Contract::Build()
  step Contract::Validate( representer: self["representer.parse.class"] )
  step Contract::Persist( method: :sync )
end

Note that you don’t even have to hook those representers into the operation class - this is a convention for structuring.

An exemplary controller method to handle both outcomes could look like the following snippet.

def create
  result = Create.(params, "document" => request.body.read)

  if result.success?
    result["representer.render.class"].new(result["model"]).to_json
  else
    result["representer.errors.class"].new(result["result.contract.default"]).to_json
  end
end

Make sure to check out Endpoint which bundles the most common outcomes for you and is easily extendable.