On Programming Well

by Javier Saldana

Pagination With ActiveResource

Recent versions of ActiveResource allow you to parse HTTP responses (without hacks).

For those unfamiliar, what is ActiveResource?

ActiveResource is a gem extracted from Rails. It is built on a standard JSON or XML format for requesting and submitting resources over HTTP.

The basic documentation is very well explained and can help you get started fairly fast. Unfortunately, once you begin to grow your application (and depend on ARes), you’ll notice that some simple tasks can quickly become rather cumbersome to figure out. Pagination is one of the first bumps I hit.

The solution

The first way to implement pagination, and my preferred choice, is to send pagination data along HTTP headers. This approach sends a clean response and follows API best practices. The downside to it is that it can be complex to implement.

Example with a Rails API and the will_paginate gem

1
2
3
4
5
6
7
8
9
# app/controllers/api/posts_controller.rb
class Api::PostsController < ApiController
  set_pagination_headers only: [:index]

  def index
    @posts = Post.paginate(:page => params[:page], :per_page => params[:per_page])
    respond_with(@posts)
  end
end

Nothing new here, the paginate method is provided by the will_paginate gem. However, note the set_pagination_headers method, which is defined in the ApiController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ApiController < ApplicationController
  respond_to :json, :xml

  private

  def self.set_pagination_headers(options = {})
    after_filter(options) do |controller|
      results = instance_variable_get("@#{controller_name}") # @posts
      headers["Pagination-Limit"] = results.limit_value.to_s
      headers["Pagination-Offset"] = results.offset_value.to_s
      headers["Pagination-TotalCount"] = results.total_count.to_s
    end
  end
end

See, limit_value, offset and total_count are provided by will_paginate, and I chose to send these values because I use the Kaminari gem with ActiveResource in my client. Kaminari has an extension for paginatable arrays, which requires precisely these variables to figure out pagination logic. The set up is done once in an after_filter, and we embed this data into our response headers.

Note: The best practice for custom HTTP headers was to prefix them with “X-“, but that approach has been deprecated recently.

That’s all for our API. Now we turn to the client.

ActiveResource 4.0.0.beta1 introduces ActiveResource::Collection, which (according to the documentation in the source code) is a wrapper to handle parsing index responses. A Post class can be set up to handle it with:

1
2
3
4
5
6
require 'active_resource/paginated_collection' # our custom parser

class Post < ActiveResource::Base
  self.site = "http://example.com"
  self.collection_parser = PaginatedCollection
end

The PaginatedCollection class will take the parsed elements. Here we can access the connection object, which holds the response headers we set in our API.

Remember: This class inherits from ActiveResource::Collection and there are certain methods we can override to customise our collection object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# lib/active_resource/paginated_collection.rb
class PaginatedCollection < ActiveResource::Collection

  # Our custom array to handle pagination methods
  attr_accessor :paginatable_array

  # The initialize method will receive the ActiveResource parsed result
  # and set @elements.
  def initialize(elements = [])
    @elements = elements
    setup_paginatable_array
  end

  # Retrieve response headers and instantiate a paginatable array
  def setup_paginatable_array
    @paginatable_array ||= begin
      response = ActiveResource::Base.connection.response rescue {}

      options = {
        limit: response["Pagination-Limit"].try(:to_i),
        offset: response["Pagination-Offset"].try(:to_i),
        total_count: response["Pagination-TotalCount"].try(:to_i)
      }

      Kaminari::PaginatableArray.new(elements, options)
    end
  end

  private

  # Delegate missing methods to our `paginatable_array` first,
  # Kaminari might know how to respond to them
  # E.g. current_page, total_count, etc.
  def method_missing(method, *args, &block)
    if paginatable_array.respond_to?(method)
      paginatable_array.send(method)
    else
      super
    end
  end
end

Note: You must install the Kaminari gem for this to work.

Now your ActiveResource collections can handle pagination:

1
2
3
4
5
6
7
# client - app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    @posts = Post.all(params: { page: params[:page], per_page: params[:per_page]})
    respond_with(@posts)
  end
end

And your views too (thanks to Kaminari):

1
2
3
4
5
6
7
8
<!-- client - app/views/posts/index.html.erb -->
<div id="posts">
  <h2>Posts</h2>
  <ul>
    <%= render @posts %>
  </ul>
  <%= paginate @posts %>
</div>

Other options

Another alternative is to send pagination data inside the body of the response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# GET /posts.json:
#   {
#     posts: [
#       {
#         title: "Foo",
#         body: "Lorem Ipsum"
#       }
#       { ... }
#     ]
#     next_page: "/posts.json?page=2"
#   }

@elements = parsed['posts']
@next_page = parsed['next_page']

You will have to make some modifications to the code above in order for this to work.

Final words

I encourage you to try the first approach. It embraces the principles of flexibility, standardization and loose coupling.

Final notes

  • You can use either will_paginate or Kaminari, or both (like in this example), or even another solution. As long as you can create custom paginatable arrays. I chose Kaminari for the client because it was easier and I read it handles arrays pretty well.
  • Solution built with ideas from this blog post and the active_resource source code
  • ActiveResource might not have the greatest README, but you can find everything you need in the comments inside the source code, and in the tests, so make sure to take a look there.

Learn to Focus

When you are excited about learning a new programming language or technology, there is so much you don’t know, and it’s easy to fall into trying to comprehend every topic you read in books or elsewhere.

I’m a Ruby and Rails advocate, and recently I’ve seen books aimed at “beginners” trying to cover too much.

I’ve seen people struggling with deployment techniques and file versioning systems, when they can’t even produce or understand half the code they write. While all this is valuable, it’s not important that you know the bells and whistles right away. Try to focus on what really matters.

Start big. Then tackle that big picture little by little. Every once in a while take a few steps back and review the whole, with refreshed, new eyes (new knowledge).

It’s easy to do this wrong, so beware. Things will get messy if you focus too much. Always remember the big picture.

Free Your Brain

Don’t waste your time and energy learning the exact syntax of functions, the order of their parameters or their dependencies. It is enough to be aware that such functions exist.

Invest your time in learning a good text editor and become familiar with navigating your system shell. The point is to have at your fingertips the power of the libraries and frameworks you use the most.

For example, I have a folder called “github” with clones of libraries I use a lot, and whenever I’m trying to remember “what was the name of that function” or “what class do I have to include for it to work?”, I use the command line or my text editor to quickly find out.

Go to the Source

This is probably one of the most powerful ways to improve the quality of your code. If you use this often you will save time and get a better grasp of the tools you use everyday.

The source code.

When in doubt, instead of using Google or StackOverflow, dive into the source code to find out exactly how things work and what tools you have available in a given scope. It’s not just code, though. Popular open source projects have a lot of comments (even with examples) and tests, which will shed light on trickier methods.

Give it a try, clone the repository of a tool you use every day: the next time you feel the urge, just go to the source.

Rant

What do you want to be, a better “googler” or a better coder?

Think about it, there’s nothing like the source code of a project and its official documentation to find the correct answer. Using Google to find a solution in order to save time is a really bad (and common) habit. Bad snippets of code will often appear in the first results and people who don’t know better will use them. You learn nothing valuable by doing this.

Reading code written by the best is a great way to become a better coder yourself.