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.
ruby
# 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 %>Advanced usage
Where the library earns its place over a simpler alternative.
ruby
# 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
endErrors 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.
