Ruby On REST 6: Pagination With Roar

{{{
Yo! How’s it folks? Let’s do some more REST today. I’d like to show how easy it is to have paginated REST documents with “Roar”:https://github.com/apotonick/roar while using a nifty feature introduced “in version 0.10.2”:https://github.com/apotonick/roar/commit/46fb88d1a79338188a9d05d353fc721d6a058e4e.

h3. What’s In That Fruit Salad, Sir?

Since we keep having fruit salads “in the last posts”:http://nicksda.apotomo.de/2012/03/ruby-on-rest-5-learn-hypermedia-by-making-fruit-salad/ I wanna write a service to display the ingredients of a particular fruit bowl in a list. Not just a list of fruits, a _paginated_ list of fruits.

Let’s GET it.

POST http://bowls/1/fruits?page=1

Considering two fruit items per page this document will be returned.

{"total_entries":5,
 "links":[
  {"rel": "self",
   "href":"http://bowls/1/fruits?page=1"},
  {"rel": "next",
   "href":"http://bowls/1/fruits?page=2"}],
 "items":[
  {"title":"Apple",
   "links":[{"rel":"self",
              "href":"http://fruits/Apple"}]},
  {"title":"Orange",
   "links":[{"rel":"self",
             "href":"http://fruits/Orange"}]}
  ]
}

Ignore the @items@ for now, the interesting part here are the pagination elements in the first lines. What we have here is:

  • The number of total ingredients in the bowl (line 1).
  • The obligatory @self@ link (line 3-4).
  • A link guiding us to the next page (line 5-6).

h3. Wow! Code?

The backing code in this service endpoint could look like the following snippet. As always, the code used to create this example “can be found on github”:https://github.com/apotonick/ruby-on-rest/commit/ed55c56c73f97a2c8f58257659b4dc223bd17f3e.

bowl = Bowl.find(1)
page = bowl.fruits.paginate(:page => 1, 
  :per_page => 2)

page.extend(BowlPageRepresenter)
page.to_json(:bowl => @bowl)

First task is to retrieve the viewed bowl (line 1). I use a static id in this example, feel free to replace it with something like @params[:id]@ in your code. Next, I use @paginate@ from the famous “will_paginate”:https://github.com/mislav/will_paginate gem to get a paged subset of the included fruits. Again, the @:page@ parameter should be refering to a variable, whatever (line 2-3).

Then I simply extend the collection (line 5) since it is a valid Ruby object and call @to_json@ to render the list (line 6). Keep in mind that we pass the @:bowl@ instance _into_ the render method as an argument.

h3. The Bowl Representer Can Do Pagination

To fully understand that example we need to look at the representer now.

module BowlPageRepresenter
  include Roar::Representer::JSON
  include Roar::Representer::Feature::Hypermedia
  
  property :total_entries
  collection :items, :extend => FruitRepresenter
  
  link :self do |opts|
    bowl_url(opts[:bowl], :page => current_page)
  end
  
  link :next do |opts|
    bowl_url(opts[:bowl], :page => next_page) 
      if next_page
  end
  
  link :previous do |opts|
    bowl_url(opts[:bowl], :page => previous_page) 
      if previous_page
  end
  
  def items
    self
  end
end

Instead of refering to line number I’ll use code excerpts now. Do you like that better?

  property :total_entries

In a paginated collection we got the @total_entries@ method “as described in the will_paginate API docs”:http://rubydoc.info/github/mislav/will_paginate/93e7b446900853d22e89/WillPaginate/Collection#total_entries-instance_method. To give our REST consumer a hint about the total amount of ingredients we can simply define a property after that.

  collection :items, :extend => FruitRepresenter

To render the actual items in the doc we extend each fruit with the @FruitRepresenter@ as we learned in “an older post”:http://nicksda.apotomo.de/2012/01/ruby-on-rest-3-one-model-multiple-representations/. Note that Roar will use the @items@ method to retrieve the collection of fruits.

  def items
    self
  end

Since we are already a collection all we have to do is return self – roar will iterate the paginated collection, extend each element with the @FruitRepresenter@ and render the items.

h3. A Cool New Feature!

The links to the next pages are defined using Roar’s hypermedia feature.

  link :next do |opts|
    bowl_url(opts[:bowl], :page => next_page) 
      if next_page
  end

Two things happen here. First, note that this link is conditional. If @next_page@, another API method for a will_paginate collection, is evaluated to false, this link won’t be rendered.

Second, we use a new feature of Roar here to access variables passed into @to_json@. Remember how we called the render method?

page.to_json(:bowl => @bowl)

Right, we pass in some values from the outside since we don’t have access to the actual bowl instance within the represented collection. These parameters are accessible in the @link@ block parameters. Isn’t that nice? I like it.

These few lines of code make it easy to render a paginated collection into a valid REST document.

h3. Writing A Generic Pagination Representer

Now that all paginated documents share attributes (total entries, next and previous link, the self link, etc) why not abstract that into a generic representer? Most of the code can be reused.

module PaginationRepresenter
  include Roar::Representer::JSON
  include Roar::Representer::Feature::Hypermedia
    
  property :total_entries
  
  link :self do |opts|
    page_url(opts[:model], :page => current_page)
  end
  
  link :next do |opts|
    page_url(opts[:model], :page => next_page) 
      if next_page
  end
  
  link :previous do |opts|
    page_url(opts[:model], :page => previous_page) 
      if previous_page
  end
  
  def items
    self
  end
  
  def page_url(*)
    raise "Implement me."
  end
end

All I did was calling a generic @page_url@ method that must be implemented by the using representer. Also, I no longer use the @:bowl@ keyword but a more generic @:model@, ok?

Nothing in this abstract representer module is related to stinky fruit salads anymore. Man, this thing could even represent a sixpack of beers (a domain I do prefer over fruits).

To inherit we just include the abstract into the concrete representer.

module BowlPageRepresenter
  include Roar::Representer::JSON
  include PaginationRepresenter
  
  collection :items, :extend => FruitRepresenter
  
  def page_url(*args)
    bowl_url(*args)
  end
end

That is cool. All we have to do is defining the concrete items collection and how to compute the pagination URLs. Come on guys, that is easy!

Remember, we changed the incoming parameter, so the rendering call must change, too.

page.to_json(:model => @bowl)

h3. Cheers!

It is Friday eve, have a wonderful weekend and let me know how it was!
}}}

Advertisement

5 thoughts on “Ruby On REST 6: Pagination With Roar

  1. I haven’t been able to get the example code working… I’m working with Rails 3.2.1, and will_paginate 3.0.3.

    I create a bowl, and paginate the fruits. Everything working there, page.to_json returns fine. Then I extend BowlPageRepresenter and .to_json gives me this error:

    ActionController::RoutingError: No route matches {:action=>”show”, :controller=>”bowls”, :page=>page 1}

    Might be a noob question, but, why is it routing anyway?
    Other methods like to_xml work fine… probably because in this example they’re not overridden by ROAR like to_json. New methods like .page_url are also giving me similar errors.

    Any clues? Thanks in advance!

    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