Skip to content

Kaminari

Data & AnalyticsDataRuby

What it is

Kaminari is a pagination library for Rails and ActiveRecord, with scope-based paging, customisable views and support for non-ActiveRecord collections.

page and per are scopes, so they compose with filters and ordering. View helpers render the page links.

Installation

gem 'kaminari'

Getting started

The smallest useful thing you can do with it, and what each part means.

Paging and rendering
# Composes with any other scope.
@books = Book.includes(:author)
             .where(published: true)
             .order(year: :desc)
             .page(params[:page])
             .per(20)

# JSON APIs
render json: {
  data: @books,
  meta: {
    current_page: @books.current_page,
    total_pages:  @books.total_pages,
    total_count:  @books.total_count,
  }
}

# In an ERB view
<%= paginate @books %>
<%= page_entries_info @books %>
Because page is a scope, it applies LIMIT and OFFSET in SQL rather than loading everything and slicing in Ruby — the mistake that makes naive pagination useless at scale.

Advanced usage

Where the library earns its place over a simpler alternative.

Avoiding the expensive count
# total_count runs SELECT COUNT(*), which is slow on very large tables.
# without_count skips it — you lose total_pages but gain the query back.
@books = Book.page(params[:page]).per(20).without_count

# <%= link_to_next_page @books, 'Next' %>

# Paginate a plain array.
@items = Kaminari.paginate_array(results).page(params[:page]).per(10)

# Global defaults
Kaminari.configure do |config|
  config.default_per_page = 25
  config.max_per_page = 100      # stop ?per_page=100000
end
max_per_page is a small but important setting: without it, a client can request a million rows per page and turn pagination into a denial-of-service vector.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

undefined method page for Array
page is an ActiveRecord scope. Wrap plain collections with Kaminari.paginate_array.
Pagination is slow on deep pages
Large OFFSET values scan and discard rows. Use keyset pagination on an indexed column instead.

Best practices

  • Set max_per_page so a client cannot request an unbounded page size.
  • Use without_count on very large tables where COUNT(*) dominates the query time.
  • Keep pagination as a scope so it composes with filters rather than slicing in Ruby.
  • Consider cursor-based pagination for deep pages; OFFSET grows linearly slower.

Background

Why it exists, and what it was reacting to.

Kaminari made pagination a composable scope rather than a controller concern, so it chains with other scopes and works with any Enumerable.