It has been a while since I blogged about Roar and it’s representers – beautiful South Africa kept me from doing my homework. Nevertheless, in the meantime, the new roar-rails gem emerged bringing together Rails and Roar in a very easy going way. I’d like to describe how this gem works by implementing a fruit and fruit bowl backend service following the examples from the last posts.

Oh, and before I forget mentioning, the Rails example app is available on github where I will keep adding examples from the Ruby On REST blog series.

Setting It Up…

To make things happen it is necessary adding the roar-rails gem to your Gemfile.

gem 'roar-rails'

As we wanna use Rails’ URL helpers in our representers we have to add one line to the environment.

  config.representer.default_url_options = 
    {:host => "yum.my"}

Rendering Fruits, Done Right.

For simplicity let’s assume our Fruit model has the following underlying, highly complex database scheme.

  create_table "fruits" do |t|
    t.string   "title"
  end

Tough stuff, isn’t it?

Clients might want to retrieve fruit representations, that’s why we start with a functional test for handling GET requests on the FruitsController.

class FruitsControllerTest < ActionController::TestCase
  include Roar::Rails::TestCase
 
  test "GET returns fruit document" do
    get :show, :id => 1, :format => :json
    assert_body "{\"title\":\"Apple\",\"links\":[
      {\"rel\":\"self\",
       \"href\":\"http://yum.my/fruits/Apple\"}]}"
  end

Including the Roar::Rails::TestCase module into the test gives us a couple of new or changed methods. One of those is assert_body and all it does it testing equality of the passed string and the last returned document. This test verifies our REST representations are rendered correctly.

Here’s the handling code in the FruitsController.

class FruitsController < ApplicationController
  include Roar::Rails::ControllerAdditions
  respond_to :json
 
  def show
    fruit = Fruit.find_by_id(params[:id])
    respond_with fruit
  end

This is all very familiar code! But, wait… what happens here behind the scenes? The inclusion of the ControllerAdditions module activates a new responder which will take care of the JSON-rendering that is invoked inside respond_with. So, behind the curtain, the following is executed.

fruit.
  extend(FruitRepresenter).
  to_json

The roar-rails gem infers the representer name used for rendering.

The Fruit Representer

Now, where is that representer? It lives in the app/representers directory and its implementation is quite simple.

module FruitRepresenter
  include Roar::Representer::JSON
 
  property :title
 
  link :self do
    fruit_url(title)
  end
end

All it does is defining the title property and the self link. Please note that we can simply use URL helpers here, making it pretty easy to include hypermedia in our representations.

Amazing! This is all the code needed for rendering fruit documents, including hot hypermedia and handling GET requests. Now, what do we need to consume incoming documents, in, let’s say a POST request?

Eat More Fruits!

To create new fruits in our backend service we’d need to accept POST requests. Test it.

class FruitsControllerTest < ActionController::TestCase
  # ...
 
  test "POST creates fruit" do
    post :create, "{\"title\":\"Orange\"}", 
      :format => :json
 
    assert_body "{\"title\":\"Orange\",\"links\":[
      {\"rel\":\"self\",
      \"href\":\"http://yum.my/fruits/Orange\"}]}"
  end

Again, we use assert_body to assure the resource returns the freshly created representation of the orange. Now, check the post: The second argument is the actual JSON document sent by the client. This is an API change. We no longer pass hashes to the “POST”, but use real documents to test our services.

The consuming controller action is hilariously simple as well.

class FruitsController < ApplicationController
  # ...
  def create
    fruit = Fruit.new.
      extend(FruitRepresenter).
      from_json(request.body.string).
      save
 
    respond_with fruit
  end

It creates a new Fruit instance, mixes in the respective representer, parses the incoming JSON document and updates the fruit’s attributes in the #from_json method and then renders the created model.

In the upcoming version of roar-rails, this code can also be written like:

class FruitsController < ApplicationController
  # ...
  def create
    fruit = consume(Fruit).
      save
 
    respond_with fruit
  end

The consume method provided by roar-rails is still under discussion – feel free to add comments to this post if you have a groundbreaking idea how to simplify the consumption steps!

Fruits On This Planet – Unite!

If that was to easy for you, and I bet it was, why not implement the fruit bowl as well. Consider this model class.

class Bowl < ActiveRecord::Base
  has_and_belongs_to_many :fruits
end

Instead of wasting time with the rendering test and parsing code – which is identical to the fruit code – we jump directly into the parsing test.

class BowlsControllerTest < ActionController::TestCase
  include Roar::Rails::TestCase
 
  test "POST creates bowl with fruits" do
    post :create, 
      "{\"fruits\":[{\"title\":\"Orange\"}]}", 
      :format => :json
 
    bowl = Bowl.find(:last)
    assert_body "{\"fruits\":[
      {\"title\":\"Orange\",\"links\":[
        {\"rel\":\"self\",
        \"href\":\"http://yum.my/fruits/Orange\"}]}],
      \"links\":[
        {\"rel\":\"self\",
        \"href\":\"http://bowls/#{bowl.id}\"}]}"
  end

Sometimes I wish my blog was less narrow.

In the test, an initial bowl document is POSTed to the resource. We expect a fresh bowl representation containing fruits as the response.

Let’s look at the controller action to understand how representers work in a nested setup. Speaking of nesting, here’s the representer responsible for rendering and parsing bowls.

module BowlRepresenter
  include Roar::Representer::JSON
 
  property :location
 
  collection :fruits, 
    :class => Fruit, 
    :extend => FruitRepresenter
 
  link :self do
    bowl_url(id)
  end
end

In addition to the location property the collection defines a nesting fruits. The BowlRepresenter now knows that the collection contains Fruit instances that must be represented with the according FruitRepresenter. We already discussed that in one of the last posts.

Here’s the controller action using that representer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class BowlsController < ApplicationController
  # ...
 
  def create
    bowl = consume(Bowl)
 
    # puts bowl.fruits.inspect
    bowl.fruits = bowl.fruits.collect do |f|  
      Fruit.find_by_title(f.title)
    end
 
    bowl.save
 
    respond_with bowl
  end

I simply assume the consume call already works. Then, what is that loop? Well, when parsing the incoming document, the bowl representer creates a new Fruit instance for every fruit in the collection. If line 7 would be uncommented, it would output the following.

[#<Fruit id: nil, title: "Orange">]

That is, before we reset the collection manually. The representer doesn’t know anything about the database – it just maps a fruit representation in the document to a fresh Fruit instance. This is why there is no id set, yet. We have to do that ourselves (lines 8-10). What might look clumsy to you is simplicity – it’s up to you how to map objects to database records.

Conclusion – For Now?!

Come on, it is easy to use representers in your Rails backends using the roar-rails gem. It helps you rendering documents and makes it quite easy to have object-oriented access to a parsed incoming representation – under Rails-conditions.

And, as always, this is just a start. Open source lives from discussion and criticism, so feel free to use the comments form below. In the next post I’ll discuss writing paginated collections using representers. Have a nice day!

Tags: , ,

3 Responses to “Ruby on REST 4: Using Representers in Rails”


  1. xeLL

    awesome, thanks, write more!

  2. [...] Ruby on REST 4: Using Representers in Rails [...]

  3. Very good example of the represented pattern in rails. The only thing I would change would be to not have to escape all of the quotes in your examples.

Leave a Reply