Ruby on REST 4: Using Representers in Rails

{{{
It has been a while since I blogged about “Roar”:https://github.com/apotonick/roar and it’s representers – “beautiful South Africa”:http://rubyfuza.org/speakers?page=2 kept me from doing my homework. Nevertheless, in the meantime, the new “roar-rails gem”:https://github.com/apotonick/roar-rails 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”:http://nicksda.apotomo.de/2012/01/ruby-on-rest-3-one-model-multiple-representations/.

Oh, and before I forget mentioning, the Rails example app “is available on github”:https://github.com/apotonick/ruby-on-rest where I will keep adding examples from the _Ruby On REST_ blog series.

h3. 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"}

h3. 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  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.

h3. 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?

h3. Eat More Fruits!

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

class FruitsControllerTest  :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!

h3. 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  :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 @POST@ed 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”:http://nicksda.apotomo.de/2011/12/ruby-on-rest-2-representers-and-the-dci-pattern/.

Here’s the controller action using that representer.

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.

[#]

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.

h3. Conclusion – For Now?!

Come on, it _is_ easy to use representers in your Rails backends using the “roar-rails gem”:https://github.com/apotonick/roar-rails. 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!
}}}

Advertisement

3 thoughts on “Ruby on REST 4: Using Representers in Rails

  1. 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.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s