Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Resource-oriented architectures in Ruby. Roar focuses on object-oriented REST documents.

branch: master

Fetching latest commit…

Octocat-spinner-32-eaf2f5

Cannot retrieve the latest commit at this time

Octocat-spinner-32 lib
Octocat-spinner-32 test
Octocat-spinner-32 .gitignore
Octocat-spinner-32 CHANGES.markdown
Octocat-spinner-32 Gemfile
Octocat-spinner-32 README.textile
Octocat-spinner-32 Rakefile
Octocat-spinner-32 TODO.markdown
Octocat-spinner-32 roar.gemspec
README.textile

ROAR

Resource-Oriented Architectures in Ruby.

“Lets make documents suit our models and not models fit to documents.”

Questions? Need help? Free 1st Level Support on irc.freenode.org#roar !

Introduction

REST is about representations. Representations are documents. Documents are pushed back and forth between clients and REST services. Roar focuses on providing object-oriented representations.

Most REST gems provide a neat HTTP interface, automatic parsing of documents and a more or less convenient way for rendering representations. Roar is different. The central concept of Roar are representers – object-oriented documents suitable for parsing and rendering, extendable at runtime and with hypermedia support. The Representer concept is the answer to the missing REST abstraction layer in most frameworks.

Features

  • OOP access to documents.
  • Parsing and rendering of representations in one place.
  • Declaratively define document syntax and semantics.
  • Hypermedia support.
  • ActiveResource-like client support.
  • Useable in both client and server.
  • Framework agnostic, runs with sinatra, Rails, webmachine and friends.

Example

Say your webshop consists of two completely separated apps. The REST backend, a Sinatra app, serves articles and processes orders. The frontend, being browsed by your clients, is a rich Rails application. It queries the services for articles, renders them nicely and reads or writes orders with REST calls. That being said, the frontend turns out to be a pure REST client.

Representations

Representations are the pivotal elements of REST. Work in a REST system means working with representations, which can be put down to parsing or extracting representations and rendering the like.

Roar makes it easy to render and parse representations of resources after defining the formats.

Creating Representations

Why not GET a particular article, what about a good beer?

GET http://articles/lonestarbeer

It’s cheap and it’s good. The response of a GET is a representation of the requested resource. A representation is always a document. In this example, it’s a bit of JSON.

{ "article": {
  "title":  "Lonestar Beer",
  "id":     4711,
  "links":[
    { "rel":  "self", 
      "href": "http://articles/lonestarbeer"}
  ]}
}

In addition to boring article data, there’s a link embedded in the document. This is hypermedia, yeah! We will learn more about that shortly.

So, how did the service render that JSON document? It could use an ERB template, #to_json, or maybe another gem. The document could also be created by a representer.

Representers are the key ingredience in Roar, so let’s check them out!

Representers

Representers are most usable when defined in a module, and then mixed into a host class. In our example, the host class is the article.

class Article
  attr_accessor :title, :id
end

To render a representational document from the article, the backend service has to define a representer.

require 'roar/representer/json'
require 'roar/representer/feature/hypermedia'


module ArticleRepresenter
  include Roar::Representer::JSON
  include Roar::Representer::Feature::Hypermedia
  
  property :title
  property :id
  
  link :self do
    article_url(self)
  end 
end

Hooray, we can define plain properties and embedd links easily – and we can even use URL helpers (in Rails, using the roar-rails gem). There’s even more, nesting, collections, but more on that later!

Rendering Representations in the Service

In order to render an actual document, the backend service would have to do a few steps: creating a representer, filling in data, and then serialize it.

loney = Article.new.extend(ArticleRepresenter)
  loney.title = "Lonestar"
  loney.id    = 666
  loney.to_json # => "{\"article\":{\"id\":666, ...

Articles itself are useless, so they may be placed into orders. This is the next example.

Nesting Representations

What if we wanted to check an existing order? We’d GET http://orders/1, right?

{ "order": {
  "id":         1,
  "client_id":  "815",
  "articles":   [
    {"title": "Lonestar Beer",
    "id":     666,
    "links":[
      { "rel":  "self", 
        "href": "http://articles/lonestarbeer"}
    ]}
  ],
  "links":[
    { "rel":  "self", 
      "href": "http://orders/1"},
    { "rel":  "items", 
      "href": "http://orders/1/items"}
  ]}
}

The order model is simple.

class Order
  attr_accessor :id, :client_id, :articles
end

Since orders may contain a composition of articles, how would the order service define its representer?

module OrderRepresenter
  include Roar::Representer::JSON
  include Roar::Representer::Feature::Hypermedia

  property :id
  property :client_id
  
  collection :articles, :as => Article
  
  link :self do
    order_url(represented)
  end
  
  link :items do
    items_url
  end 
end

Representers don’t have to be in modules, but can be

The declarative #collection method lets us define compositions of representers.

Parsing Documents in the Service

Rendering stuff is easy: Representers allow defining the layout and serializing documents for us. However, representers can do more. They work bi-directional in terms of rendering outgoing and parsing incoming representation documents.

If we were to implement an endpoint for creating new orders, we’d allow POST to http://orders/. Let’s explore the service code for parsing and creation.

  post "/orders" do
    order = Order.new.extend(OrderRepresenter)
    order.from_json(request.body.string)
    order.to_json
  end

Look how the #from_json method helps extracting data from the incoming document and, again, #to_json returns the freshly created order’s representation. Roar’s representers are truely working in both directions, rendering and parsing and thus prevent you from redundant knowledge sharing.

Representers in the Client

The new representer abstraction layer seems complex and irritating first, where you used params[] and #to_json is a new OOP instance now. But… the cool thing is: You can package representers in gems and distribute them to your client layer as well. In our example, the web frontend can take advantage of the representers, too.

Using HTTP

Communication between REST clients and services happens via HTTP – clients request, services respond. There are plenty of great gems helping out, namely Restfulie, HTTParty, etc. Representers in Roar provide support for HTTP as well, given you mix in the HTTPVerbs feature module!

To create a new order, the frontend needs to POST to the REST backend. Here’s how this could happen using a representer on HTTP.

  order = Order.new(:client_id => current_user.id)
  order.post("http://orders/")

A couple of noteworthy steps happen here.

  1. Using the constructor a blank order document is created.
  2. Initial values like the client’s id are passed as arguments and placed in the document.
  3. The filled-out document is POSTed to the given URL.
  4. The backend service creates an actual order record and sends back the representation.
  5. In the #post call, the returned document is parsed and attributes in the representer instance are updated accordingly,

After the HTTP roundtrip, the order instance keeps all the information we need for proceeding the ordering workflow.

  order.id #=> 42

Discovering Hypermedia

Now that we got a fresh order, let’s place some items! The system’s API allows adding articles to an existing order by POSTing articles to a specific resource. This endpoint is propagated in the order document using hypermedia.

Where and what is this hypermedia?

First, check the JSON document we get back from the POST.

{ "order": {
  "id":         42,
  "client_id":  1337,
  "articles":   [],
  "links":[
    { "rel":  "self", 
      "href": "http://orders/42"},
    { "rel":  "items", 
      "href": "http://orders/42/items"}
  ]}
}

Two hypermedia links are embedded in this representation, both feature a rel attribute for defining a link semantic – a “meaning” – and a href attribute for a network address. Isn’t that great?

  • The self link refers to the actual resource. It’s a REST best practice and representations should always refer to their resource address.
  • The items link is what we want. The address http://orders/42/items is what we have to refer to when adding articles to this order. Why? Cause we decided that!

Using Hypermedia

Let the frontend add the delicious “Lonestar” beer to our order, now!

beer = Article.new(:title => "Lonestar Beer")
beer.post(order.links[:items])

That’s all we need to do.

  1. First, we create an appropriate article representation.
  2. Then, the #links method helps extracting the items link URL from the order document.
  3. A simple POST to the respective address places the item in the order.

The order instance in the frontend is now stale – it doesn’t contain articles, yet, since it is still the document from the first post to http://orders/.

order.items #=> []

To update attributes, a GET is needed.

order.get!(order.links[:self])

Again, we use hypermedia to retrieve the order’s URL. And now, the added article is included in the order.

Note: If this looks clumsy – It’s just the raw API for representers. You might be interested in the upcoming DSL that simplifys frequent workflows as updating a representer.]

order.to_attributes #=> {:id => 42, :client_id => 1337, 
  :articles => [{:title => "Lonestar Beer", :id => 666}]}

This is cool, we used REST representers and hypermedia to create an order and fill it with articles. It’s time for a beer, isn’t it?

Using Accessors

What if the ordering API is going a different way? What if we had to place articles into the order document ourselves, and then PUT this representation to http://orders/42? No problem with representers!

Here’s what could happen in the frontend.

beer = Article.new(:title => "Lonestar Beer")
order.items << beer
order.post(order.links[:self])

This was dead simple since representations can be composed of different documents in Roar.

Current Status

Please note that Roar is still in conception, the API might change as well as concepts do.

What is REST about?

Making that system RESTful basically means

  1. The frontend knows one single entry point URL to the REST services. This is http://orders.
  2. Do not let the frontend compute any URLs to further actions.
  3. Showing articles, creating a new order, adding articles to it and finally placing the order – this all requires further URLs. These URLs are embedded as hypermedia in the representations sent by the REST backend.
Something went wrong with that request. Please try again.