Ruby on Rails
HowtoPagination

(deprecated and removed as of rails 2.0)

Pagination in the Controller

Simple Pagination

First, see if the Pagination Helper will suit your purposes. It currently allows for :include, :joins, :conditions, and :order. In fact, it does its work using Model.find(:all, *params). This should get you by in most cases.

“paginate” returns two things: a paginator object and a collection of the items on the current page. So it uses the double assignment syntax (x, y = paginate(z)). Don’t let that confuse you if you haven’t seen double assignment before.

Here’s an example:

def list
    @item_pages, @items = paginate(:items, 
                            :include     => [:parent_item], 
                            :conditions => ['parent_id = ?', @some_id], 
                            :order         => 'created_on ASC', 
                            :per_page     => 20)
end

The current page is determined by the params[:page] variable.

More detailed instructions for the Pagination Helper can be found at:
http://wiki.rubyonrails.com/rails/pages/PaginationHelper

paginate_by_sql

If the data you would like to paginate can be described by a custom sql query, consider using paginate_by_sql. This method method works with any database and only fetches the range of records needed, and you won’t need to write any custom code.

See the the code for paginate_by_sql for instructions how to add paginate_by_sql to your application.

Using paginate_by_sql is similiar to find_by_sql, except that it also takes an argument giving the number of items per page and returns both paginator and results object.

sql = "select j.id, j.name,c.name as company_name from jobs j" +
" inner join companies on j.company_id=c.id " +
" order by j.activated_date DESC" 

@job_pages, @jobs = paginate_by_sql Job, sql, 20

Custom Pagination

The built-in pagination method becomes limited when your finds become complex. If you are using find_by_SQL then you’ll definitely want to consider using the Paginator directly. It’s not much more complicated than the Pagination Helper and nothing to be afraid of.

You’ll just need to determine the total number of items in the found set, the number of items per page, and the current page. Then you can create an instance of Paginator with them.

Here you’ll also want to make a choice. Will it be more efficient for you to find ALL the matching records, count them, then display just a page of the found set? Or will it be more efficient to make two calls to the database: 1) an SQL COUNT of records that would match, 2) a find of only the current records being shown on the current page. The first approach will only hit your SQL database once, but will get a lot of information during that trip. The second approach will hit your SQL database twice, but will return two smaller pieces of information on each trip.

You’ll have to decide which is right for you. The answer to which is better could depend on a number of factors: the size of the data set you are working with, the speed of your server, the amount of database traffic, the efficiency of your other SQL calls, your database design, perhaps even the size of the database. I find that I can use the first method (finding them all) successfully with 1000 records—your mileage may vary. You can always start with the first approach and easily modify it later if it is too slow.

Here is the one-SQL-trip, get-everything method in 4 easy steps.

Kevin’s custom-pagination-in-four-easy-steps

def list
    # step 1: read and set the variables you'll need
    page = (params[:page] ||= 1).to_i
    items_per_page = 20
    offset = (page - 1) * items_per_page

    # step 2: do your custom find without doing any kind of limits or offsets
    #  i.e. get everything on every page, don't worry about pagination yet
    @items = Item.find_with_some_custom_method(@some_variable)

    # step 3: create a Paginator, the second variable has to be the number of ALL items on all pages
    @item_pages = Paginator.new(self, @items.length, items_per_page, page)

    # step 4: only send a subset of @items to the view
    # this is where the magic happens... and you don't have to do another find
    @items = @items[offset..(offset + items_per_page - 1)]

end

The two-trip method is almost as easy. You’ll just need two SQL methods. In step 2 you do a count of the records that would match and then in step 4 you retrieve only the records on the current page. Note that “offset” and “items_per_page” will need to be incorporated into your model’s custom find method as :offset and :limit parameter options. (View the RDoc documentation for find to learn how.)

def list
    # step 1: read and set the variables you'll need
    page = (params[:page] ||= 1).to_i
    items_per_page = 20
    offset = (page - 1) * items_per_page

    # step 2: instead of performing the full find, just find the record count
    @item_count = Item.find_record_count(@some_variable)

    # step 3: create a Paginator, the second variable has to be the number of ALL items on all pages
    @item_pages = Paginator.new(self, @item_count, items_per_page, page)

    # step 4: only find the requested subset of @items
    @items = Item.find_with_some_custom_method(@some_variable, offset, items_per_page)

end

Pagination in the View

Once your controller has assigned values to @item_pages (a Paginator instance) and @items (a collection of Active Record objects), you are ready to utilize them in your view. It’s not difficult to work with the paginator object. The only catch is to make sure that your other parameters are preserved and sent to the next page. (You’ll notice that the scaffold’s pagination does not do this by default.)

Previous Page and Next Page links

<%= link_to('previous page', {:params => params.merge('page' => @item_pages.current.previous)}) if @item_pages.current.previous %>
<%= ' | ' if @item_pages.current.previous and @item_pages.current.next %>
<%= link_to('next page', {:params => params.merge('page' => @item_pages.current.next)}) if @item_pages.current.next %>

One warning here: the params hash cannot be nested, because it will be flattened by link_to.

List of page number links

Pages: 
<% for page in @item_pages -%>
    <%= link_to_unless(params[:page].to_i == page.number, page.number, 
            {:params => params.merge('page' => page)}) %>&nbsp;
<% end -%><br />

<!-- Or make the unlinked version bold -->
<% for page in @item_pages
    p = params[:page].nil? ? 1 : params[:page]
 -%>
    <%= link_to_unless(p.to_i == page.number, page.number, 
            {:params => params.merge('page' => page)}) {|link_name| "<strong>#{link_name}</strong>"} %>&nbsp;
<% end -%>

<!-- Or give both links and non-links CSS class styles -->
<% for page in @item_pages -%>
    <%= link_to_unless(params[:page].to_i == page.number, page.number, 
            {:params => params.merge('page' => page)}, 
            {:class => 'linked_page'}) {|link_name| "<span class='unlinked_page'>#{link_name}</span>"} %>&nbsp;
<% end -%><br />

Information about the subset being displayed

Displaying <%= @item_pages.current_page.first_item %> through <%= @item_pages.current_page.last_item %> of <%= @item_pages.item_count %> items.

I hope this pagination guide helped. I’ll try to answer any questions or comments. Please feel free to make any suggestions and corrections.

—Kevin Skoglund


I posted a longer article Pagination in Rails on my blog. It gives a full how-to guide for simple, custom and plug-in pagination.

—Kevin Skoglund

Questions:


I found this script that paginate a collection ( Array and Hashes ): How To Paginate an Array



“Hey…Its one of the best way of paginating an array of values”