• Stars
    star
    1,844
  • Rank 24,176 (Top 0.5 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 13 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Parse and render REST API documents using representers.

Roar

Resource-Oriented Architectures in Ruby.

Gitter Chat TRB Newsletter Build Status Gem Version

Table of Contents

Introduction

Roar is a framework for parsing and rendering REST documents. Nothing more.

Representers let you define your API document structure and semantics. They allow both rendering representations from your models and parsing documents to update your Ruby objects. The bi-directional nature of representers make them interesting for both server and client usage.

Roar comes with built-in JSON, JSON-HAL and XML support. JSON API support is available via the JSON API gem. Its highly modular architecture provides features like coercion, hypermedia, HTTP transport, client caching and more.

Roar is completely framework-agnostic and loves being used in web kits like Rails, Hanami, Sinatra, Roda, etc. If you use Rails, consider roar-rails for an enjoyable integration.

Representable

Roar is just a thin layer on top of the representable gem. While Roar gives you a DSL and behaviour for creating hypermedia APIs, representable implements all the mapping functionality.

If in need for a feature, make sure to check the representable API docs first.

Installation

The roar gem runs with all Ruby versions >= 1.9.3.

gem 'roar'

To use roar with Ruby versions < 2.2.0, add a version pin to your Gemfile:

gem 'sinatra', '~> 1.4'

Dependencies

Roar does not bundle dependencies for JSON and XML.

If you want to use JSON, add the following to your Gemfile:

gem 'multi_json'

If you want to use XML, add the following to your Gemfile:

gem 'nokogiri'

Defining Representers

Let's see how representers work. They're fun to use.

require 'roar/decorator'
require 'roar/json'

class SongRepresenter < Roar::Decorator
  include Roar::JSON

  property :title
end

API documents are defined using a decorator class. You can define plain attributes using the ::property method.

Now let's assume we'd have Song which is an ActiveRecord class. Please note that Roar is not limited to ActiveRecord. In fact, it doesn't really care whether it's representing ActiveRecord, Sequel::Model or just an OpenStruct instance.

class Song < ActiveRecord::Base
end

Rendering

To render a document, you apply the representer to your model.

song = Song.new(title: "Medicine Balls")

SongRepresenter.new(song).to_json #=> {"title":"Medicine Balls"}

Here, the song objects gets wrapped (or "decorated") by the decorator. It is treated as immutable - Roar won't mix in any behaviour.

Parsing

The cool thing about representers is: they can be used for rendering and parsing. See how easy updating your model from a document is.

song = Song.new(title: "Medicine Balls")

SongRepresenter.new(song).from_json('{"title":"Linoleum"}')
song.title #=> Linoleum

Unknown attributes in the parsed document are simply ignored, making half-baked solutions like strong_parameters redundant.

Module Representers

Module Representers are deprecated in Roar 1.1 and will be removed in Roar 2.0.

In place of inheriting from Roar::Decorator, you can also extend a singleton object with a representer module. Decorators and module representers actually have identical features. You can parse, render, nest, go nuts with both of them.

song = Song.new(title: "Fate")
song.extend(SongRepresenter)

song.to_json #=> {"title":"Fate"}

Here, the representer is injected into the actual model and gives us a new #to_json method.

This also works both ways.

song = Song.new
song.extend(SongRepresenter)

song.from_json('{"title":"Fate"}')
song #=> {"title":"Fate"}

It's worth noting though that many people dislike #extend due to well-known performance issues and object pollution. As such this approach is no longer recommended. In this README we'll use decorators to illustrate this library.

Collections

Roar (or rather representable) also allows mapping collections in documents.

class SongRepresenter < Roar::Decorator
  include Roar::JSON

  property :title
  collection :composers
end

Where ::property knows how to handle plain attributes, ::collection does lists.

song = Song.new(title: "Roxanne", composers: ["Sting", "Stu Copeland"])

SongRepresenter.new(song).to_json #=> {"title":"Roxanne","composers":["Sting","Stu Copeland"]}

And, yes, this also works for parsing: from_json will create and populate the array of the composers attribute.

Nesting

Now what if we need to tackle with collections of Songs? We need to implement an Album class.

class Album < ActiveRecord::Base
  has_many :songs
end

Another representer to represent.

class AlbumRepresenter < Roar::Decorator
  include Roar::JSON

  property :title
  collection :songs, extend: SongRepresenter, class: Song
end

Both ::property and ::collection accept options for nesting representers into representers.

The extend: option tells Roar which representer to use for the nested objects (here, the array items of the album.songs field). When parsing a document class: defines the nested object type.

Consider the following object setup.

album = Album.new(title: "True North")
album.songs << Song.new(title: "The Island")
album.songs << Song.new(title: "Changing Tide")

You apply the AlbumRepresenter and you get a nested document.

AlbumRepresenter.new(album).to_json #=> {"title":"True North","songs":[{"title":"The Island"},{"title":"Changing Tide"}]}

This works vice-versa.

album = Album.new

AlbumRepresenter.new(album).from_json('{"title":"Indestructible","songs":[{"title":"Tropical London"},{"title":"Roadblock"}]}')

puts album.songs[1] #=> #<Song title="Roadblock">

The nesting of two representers can map composed object as you find them in many many APIs.

In case you're after virtual nesting, where a nested block in your document still maps to the same outer object, check out the ::nested method.

Inline Representer

Sometimes you don't wanna create two separate representers - although it makes them reusable across your app. Use inline representers if you're not intending this.

class AlbumRepresenter < Roar::Decorator
  include Roar::JSON

  property :title

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

This will give you the same rendering and parsing behaviour as in the previous example with just one module.

Syncing Objects

Usually, when parsing, nested objects are created from scratch. If you want nested objects to be updated instead of being newly created, use parse_strategy:.

class AlbumRepresenter < Roar::Decorator
  include Roar::JSON

  property :title

  collection :songs, extend: SongRepresenter, parse_strategy: :sync
end

This will advise Roar to update existing songs.

album.songs[0].object_id #=> 81431220

AlbumRepresenter.new(album).from_json('{"title":"True North","songs":[{"title":"Secret Society"},{"title":"Changing Tide"}]}')

album.songs[0].title #=> Secret Society
album.songs[0].object_id #=> 81431220

Roar didn't create a new Song instance but updated its attributes, only.

We're currently working on better strategies to easily implement POST and PUT semantics in your APIs without having to worry about the nitty-gritties.

Coercion

Roar provides coercion with the dry-types gem.

require 'roar/coercion'
require 'roar/json'

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

  property :title
  property :released_at, type: Types::DateTime
end

The :type option allows to set a dry-types-compatible type.

song = Song.new

SongRepresenter.new(song).from_json('{"released_at":"1981/03/31"}')

song.released_at #=> 1981-03-31T00:00:00+00:00

More Features

Roar/representable gives you many more mapping features like renaming attributes, wrapping, passing options, etc. See the representable documentation for a detailed explanation.

Hypermedia

Roar comes with built-in support for embedding and processing hypermedia in your documents.

class SongRepresenter < Roar::Decorator
  include Roar::JSON
  include Roar::Hypermedia

  property :title

  link :self do
    "http://songs/#{title}"
  end
end

The Hypermedia feature allows declaring links using the ::link method. In the block, you have access to the represented model. When using representer modules, the block is executed in the model's context.

However, when using decorators, the context is the decorator instance, allowing you to access additional data. Use represented to retrieve model data.

class SongRepresenter < Roar::Decorator
  # ..
  link :self do
    "http://songs/#{represented.title}"
  end
end

This will render links into your representation.

SongRepresenter.new(song).to_json #=> {"title":"Roxanne","links":[{"rel":"self","href":"http://songs/Roxanne"}]}

Per default, links are pushed into the hash using the links key. Link blocks are executed in represented context, allowing you to call any instance method of your model (here, we call #title).

Also, note that roar-rails allows using URL helpers in link blocks.

Passing Options

Sometimes you need more data in the link block. Data that's not available from the represented model.

class SongRepresenter < Roar::Decorator
  include Roar::JSON

  property :title

  link :self do |opts|
    "http://#{opts[:base_url]}songs/#{title}"
  end
end

Pass this data to the rendering method.

representer = SongRepresenter.new(song)
representer.to_json(base_url: "localhost:3001/")

Any options passed to #to_json will be available as block arguments in the link blocks.

Specify Decorator

If you have a property that is a separate class or model, you can specify a decorator for that property. Suppose there is a separate Artist model for an album. When the album is eagerly loaded, the artist model could be represented along with it.

class ArtistRepresenter < Roar::Decorator
  property :name
end

class AlbumRepresenter < Roar::Decorator
  # ..
  property :artist, decorator: ArtistRepresenter
end

Consuming Hypermedia

Since we defined hypermedia attributes in the representer we can also consume this hypermedia when we parse documents.

representer.from_json('{"title":"Roxanne","links":[{"rel":"self","href":"http://songs/Roxanne"}]}')

representer.links[:self].href #=> "http://songs/Roxanne"

Reading link attributes works by using #links[] on the consuming instance.

This allows an easy way to discover hypermedia and build navigational logic on top.

Media Formats

While Roar comes with a built-in hypermedia format, there's official media types that are widely recognized. Roar currently supports HAL and JSON API.

Simply by including a module you make your representer understand the media type. This makes it easy to change formats during evaluation.

HAL-JSON

The HAL format is a simple media type that defines embedded resources and hypermedia.

require 'roar/json/hal'

class SongRepresenter < Roar::Decorator
  include Roar::JSON::HAL

  property :title

  link :self do
    "http://songs/#{title}"
  end
end

Documentation for HAL can be found in the API docs.

Make sure you understand the different contexts for links when using decorators.

Hypermedia

Including the Roar::JSON::HAL module adds some more DSL methods to your module. It still allows using ::link but treats them slightly different.

representer.to_json
#=> {"title":"Roxanne","_links":{"self":{"href":"http://songs/Roxanne"}}}

According to the HAL specification, links are now key with their rel attribute under the _links key.

Parsing works like-wise: Roar will use the same setters as before but it knows how to read HAL.

Nesting

Nested, or embedded, resources can be defined using the :embedded option.

class AlbumRepresenter < Roar::Decorator
  include Roar::JSON::HAL

  property :title

  collection :songs, class: Song, embedded: true do
    property :title
  end
end

To embed a resource, you can use an inline representer or use :extend to specify the representer name.

AlbumRepresenter.new(album).to_json

#=> {"title":"True North","_embedded":{"songs":[{"title":"The Island"},{"title":"Changing Tide"}]}}

HAL keys nested resources under the _embedded key and then by their type.

All HAL features in Roar are discussed in the API docs, including array links.

JSON API

Roar also supports JSON API via the Roar JSON API gem.

Client-Side Support

Being a bi-directional mapper that does rendering and parsing, Roar representers are perfectly suitable for use in clients, too. In many projects, representers are shared as gems between server and client.

Consider the following shared representer.

class SongRepresenter < Roar::Decorator
  include Roar::JSON
  include Roar::Hypermedia

  property :title
  property :id

  link :self do
    "http://songs/#{title}"
  end
end

In a client where you don't have access to the database it is common to use OpenStruct classes as domain objects.

require 'roar/client'
require 'roar/json'

class Song < OpenStruct
  include Roar::JSON
  include SongRepresenter
  include Roar::Client
end

HTTP Support

The Client module mixes all necessary methods into the client class, e.g. it provides HTTP support

song = Song.new(title: "Roxanne")
song.post(uri: "http://localhost:4567/songs", as: "application/json")

song.id #=> 42

What happens here?

  • You're responsible for initializing the client object with attributes. This can happen with in the constructor or using accessors.
  • post will use the included SongRepresenter to compile the document using #to_json.
  • The document gets POSTed to the passed URL.
  • If a document is returned, it is deserialized and the client's attributes are updated.

This is a very simple but efficient mechanism for working with representations in a client application.

Roar works with all HTTP request types, check out GET.

song = Client::Song.new
song.get(uri: "http://localhost:4567/songs/1", as: "application/json")

song.title #=> "Roxanne"
song.links[:self].href #=> http://localhost/songs/1

As GET is not supposed to send any data, you can use #get on an empty object to populate it with the server data.

HTTPS

Roar supports SSL connections - they are automatically detected via the protocol.

song.get(uri: "https://localhost:4567/songs/1")

Basic Authentication

The HTTP verbs allow you to specify credentials for HTTP basic auth.

song.get(uri: "http://localhost:4567/songs/1", basic_auth: ["username", "secret_password"])

Client SSL certificates

(Only currently supported with Net:Http)

song.get(uri: "http://localhost:4567/songs/1", pem_file: "/path/to/client/cert.pem", ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER)

Note: ssl_verify_mode is not required and will default to OpenSSL::SSL::VERIFY_PEER)

Request customization

All verbs yield the request object before the request is sent, allowing to modify it. It is a Net::HTTP::Request instance (unless you use Faraday).

song.get(uri: "http://localhost:4567/songs/1") do |req|
 req.add_field("Cookie", "Yumyum")
end

Error handling

In case of a non-2xx response status, #get and friends raise a Roar::Transport::Error exception. The original response can be accessed as follows.

  song.get(uri: "http://localhost/songs1") # not-existing.
rescue Roar::Transport::Error => exception
  exception.response.code #=> 404

XML

Roar also comes with XML support.

class SongRepresenter < Roar::Decorator
  include Roar::XML
  include Roar::Hypermedia

  property :title
  property :id

  link :self do
    "http://songs/#{title}"
  end
end

Include the Roar::XML engine and get bi-directional XML for your objects.

song = Song.new(title: "Roxanne", id: 42)

SongRepresenter.new(song).to_xml

Note that you now use #to_xml and #from_xml.

<song>
  <title>Roxanne</title>
  <id>42</id>
  <link rel="self" href="http://songs/Roxanne"/>
</song>

Please consult the representable XML documentation for all its great features.

Support

Questions? Need help? Free 1st Level Support on irc.freenode.org#roar ! We also have a mailing list, yiha!

License

Roar is released under the MIT License.

More Repositories

1

trailblazer

The advanced business logic framework for Ruby.
Ruby
3,380
star
2

cells

View components for Ruby and Rails.
Ruby
3,061
star
3

reform

Form objects decoupled from models.
Ruby
2,486
star
4

roar-rails

Use Roar's representers in Rails.
Ruby
233
star
5

trailblazer-rails

Trailblazer in Rails.
Ruby
174
star
6

reform-rails

Automatically load and include all common Rails form features.
Ruby
98
star
7

trailblazer-operation

Trailblazer's Operation implementation.
Ruby
86
star
8

formular

Form builder for Ruby. Fast, Furious, and Framework-Agnostic.
Ruby
81
star
9

trailblazer-activity

Model business workflows and run them.
Ruby
70
star
10

cells-rails

Rails support for Cells.
Ruby
53
star
11

roar-jsonapi

JSON API support for Roar.
Ruby
42
star
12

trailblazer-finder

Find, filter and aggregate via ActiveRecord or Sequel.
Ruby
37
star
13

trailblazer-cells

Trailblazer's file structure for Cells.
Ruby
26
star
14

rspec-trailblazer

RSpec Matchers for strong and non-verbose operation tests.
Ruby
24
star
15

trailblazer.github.io

The TRAILBLAZER.TO website. Please PR against the f6 branch. ๐Ÿ’‹
HTML
23
star
16

trailblazer-endpoint

Generic HTTP endpoints to deal with different results from your operations.
Ruby
21
star
17

guides

Finally! Learn everything about operations, Trailblazer, Reform and Cells with our guides.
Ruby
21
star
18

cells-slim

Ruby
17
star
19

trailblazer-loader

Require concept files, models and more without any autoloading magic.
Ruby
17
star
20

tamarama

The stack we fancy: Sinatra/Grape, Trailblazer, Cells and Sequel.
Ruby
13
star
21

trailblazer-developer

Debugging, tracing and workflow management tools for Trailblazer developers.
Ruby
13
star
22

trailblazer-activity-dsl-linear

Simple DSL to compile Trailblazer activities.
Ruby
11
star
23

trailblazer-compat

This gem provides a seamless-erโ„ข upgrade from TRB 1.1 to 2.x.
Ruby
10
star
24

trailblazer-generator

CLI generators for operations, contracts, cells, and much more.
Ruby
10
star
25

trailblazer-test

Super strong, non-verbose tests for your operations.
Ruby
10
star
26

trailblazer-macro

Ruby
9
star
27

cells-erb

ERB support for Cells.
Ruby
9
star
28

cells-hamlit

Hamlit integration for Cells
Ruby
7
star
29

trailblazer-macro-contract

Reform and Dry::Validation integration for Trailblazer's operation.
Ruby
6
star
30

cells-haml

Haml integration for Cells
Ruby
5
star
31

blog-example

A publishing workflow implemented with Trailblazer.
Ruby
5
star
32

api-docs

Website for Trailblazer >= 2.1.
JavaScript
5
star
33

tutorial

The code for our tutorials.
Ruby
4
star
34

trailblazer-story

Painless test factories. / ...like a mix of cucumber and traits - delicious!
Ruby
3
star
35

meta

Ruby
3
star
36

website

The trailblazer.to website
HTML
3
star
37

trailblazer-context

Argument-specific data structures for Trailblazer.
Ruby
3
star
38

trailblazer-transform

Transform, parse, coerce, validate, it's all just a chain of transformations. Replacement/extension for Reform.
Ruby
3
star
39

trailblazer-disposable

Domain object layer.
Ruby
2
star
40

tyrant

Authentication library.
Ruby
2
star
41

trailblazer-option

Generate callables in Trailblazer!
Ruby
2
star
42

cells-mailer

Provides mail functionality for the Cells gem.
Ruby
2
star
43

cfp-app

Ruby
1
star
44

trailblazer-declarative

Generic DSL providing schemas and inheritance.
Ruby
1
star
45

trailblazer-errors

Generic errors object used in operations and contracts.
Ruby
1
star
46

trailblazer-pro-rails

Rails support for TRB PRO.
Ruby
1
star
47

blog9

Blog application with approve/reject workflow.
Ruby
1
star
48

trailblazer-future

Bridge gem to upgrade from 2.0 to 2.1
Ruby
1
star
49

trailblazer-args

Scoped, mutable and immutable keyword arguments for callables.
Ruby
1
star