How does Kaminari paginate?

“Kaminari” means thunder in Japanese

Kaminari is a popular new gem that provides pagination behavior - to learn how to use it see the Railscast Pagination with Kaminari, or just refer to the Github readme page. For me Kaminari is a good example of “Rails magic...” somehow by just adding the gem to my Rails application all of my models get the Kaminari page method. I don’t have even have to type a single line of code in my model... it’s just automatically added for me. Then when I call page, it immediately works: returning just the records for a given page, even working in conjunction with other scopes I might have.

Today I’m going to take a closer look under the hood at how Kaminari actually works. For me the most beautiful part of the Ruby language is the way it allows talented open source developers like Akira Matsuda to make their code seem elegant, beautiful and magical.

The Kaminari page scope

Here’s what a call to the Kaminari page method looks like - your controller receives the page to display as an HTTP parameter: params[:page], and passes it into Kaminari. The per(5) call indicates you want to override the default and display five people per page:

def index 
  @people = Person.page(params[:page]).per(5)
end

One reason Kaminari became popular this year is that it cleanly leverages Rails 3 scopes to refine the result set to the specified page. Using ActiveRecord, for example, a call to Person.page(3).per(5) will return an ActiveRecord::Relation scope object that looks something like this:

page scope

An ActiveRecord::Relation object encapsulates some values, saved as instance variables, that will later be used to generate the SQL statement sent to your relational database server. This happens when your view code uses the ActiveRecord::Relation object as an array, for example when you call @people.each. In this diagram I’ve shown just two values that are used by Kaminari for pagination:

  • @limit_value: how many records to return in total (i.e. one page worth instead of all of them)
  • @offset_value: which record to start the result set with (i.e. the record at the top of the specified page)

For Person.page(3).per(5) Kaminari will set @limit_value to 5, the number of records per page, and the @offset_value to to 10, which is the zero-based index of the first record on the third page.

The nice thing about scopes is that you can chain them together to create a more complex, refined query. For example, I can display a paginated list of legal drinking age people like this:

Person.where("age >= ?", 21).page(params[:page]).per(5)

Here the where method first returns a scope containing the age filter:

filter scope

It turns out that filter options set by using the where method are saved as multiple values in an array. You can checkout the ActiveRecord::Relation source code for more information. Next the call to the page method combines the pagination options with that and a single, more complex scope is returned:

page scope 2

Inspecting pagination SQL statements in the console

The best way to understand how scopes work is to experiment with them in the console, and inspect the SQL statements generated by ActiveRecord using the to_sql method. Let’s give it a try. First, here’s the SQL produced by ActiveRecord and the MySQL database adapter for a call to Person.page(3).per(5):

$ rails c
Loading development environment (Rails 3.1.0)
ruby-1.8.7-p302 :001 > Person.page(3).per(5).to_sql
 => "SELECT  `people`.* FROM `people`  LIMIT 5 OFFSET 10"

Now you can see how the LIMIT and OFFSET values from the page scope are actually used in the SQL statement. Let’s try again but this time include the age filter

ruby-1.8.7-p302 :002 > Person.where("age > ?", 21).page(3).per(5).to_sql
 => "SELECT  `people`.* FROM `people`  WHERE (age > 21) LIMIT 5 OFFSET 10"

Here ActiveRecord has just combined the options from the two scopes together and produced a single SQL statement.

How does Kaminari create the page scope?

Finally, let’s take a quick look at how Akira Matsuda actually implemented this inside of Kaminari. You can find the code that implements the page method for ActiveRecord models in the active_record_model_extension.rb file inside of the Kaminari gem:

require File.join(File.dirname(__FILE__), 'active_record_relation_methods')
 
module Kaminari 
  module ActiveRecordExtension 
    extend ActiveSupport::Concern 
    included do 
      def self.inherited(kls) #:nodoc: 
        super 
 
        kls.class_eval do 
          include Kaminari::ConfigurationMethods 
 
          # Fetch the values at the specified page number 
          #   Model.page(5) 
          scope :page, Proc.new {|num|
            limit(default_per_page).offset(default_per_page * ([num.to_i, 1].max - 1))
          } do 
            include Kaminari::ActiveRecordRelationMethods 
            include Kaminari::PageScopeMethods 
          end
        end 
      end 
    end 
  end 
end

There’s a lot of metaprogramming going on here, so let’s take it one step at a time. The first thing to learn is that the Kaminari::ActiveRecordExtension module uses ActiveRecord::Concern, which provides a standard way to add behavior to some target Ruby class. This well written article does a nice job of explaining how ActiveSupport::Concern works in Rails 3. Elsewhere in the gem source code Kaminari::ActiveRecordExtension is included in ActiveRecord::Base, which becomes the target class in this case. The rest of the code reads something like this:

  • When Kaminari:: ActiveRecordExtension is included in ActiveRecord::Base...
  • Create a class method on ActiveRecord::Base called inherited...
  • Then this will be called when the Rails developer writes a new ActiveRecord model...
  • Then open the new ActiveRecord model class using class_eval, e.g. my Person model...
  • Include some new methods in it from Kaminari::ConfigurationMethods...
  • And finally call scope to add the page scope.

Here’s the most important line of code, which adds the Kaminari page scope to each new ActiveRecord::Base subclass:

scope :page, Proc.new {|num|
  limit(default_per_page).offset(default_per_page * ([num.to_i, 1].max - 1))
}

We call scope and pass in the symbol :page followed by a Proc, which is the scope definition. This means that in fact Kaminari does not actually implement a new method called page, it just creates a scope with that name and adds it to the ActiveRecord::Base class.

In other words, by calling scope :page Kaminari is doing exactly the same thing you would if you had written this code yourself:

class Person < ActiveRecord::Base 
  scope :page, Proc.new {|num|
        limit(default_per_page).offset(default_per_page * ([num.to_i, 1].max - 1))
      }
etc...

Finally, actually reading the Proc's definition, you can see that the limit value is set to the number of records per page default_per_page, the offset value is calculated based on the number of records per page, and the page number passed in num. Since the scope is a Proc, it is evaluated every time it is used, for the given value of num.