#
#
How To
This page provides practical tips and examples to help you effectively use Pagy.
You can also ask the Pagy AI for instant answers to questions not covered on this page.
Check the list of paginators or click the Pagy AI button below and ask something like: "What are the available paginators for DBs and search platforms?"
- Fixed
- Use the
:limit
option to set the number of items to serve with each page.
- Use the
- Requestable
- Use the
limit
option combined with the:client_max_limit
option, allowing the client to request a variable:limit
up to the specified:client_max_limit
. - Additionally, you can use the limit_tag_js helper to provide a UI selector to the user.
- Use the
@pagy, @products = pagy(:offset, collection, limit: 10)
@pagy, @products = pagy(:offset, collection, limit: 10, client_max_limit: 1_000)
ActiveRecord limit
The :limit
option defined here will override any existing limit
set in ActiveRecord
collections.
See Common Options.
Pagy provides series_nav and series_nav_js helpers for displaying a pagination bar.
You can customize the number and position of page links in the navigation bar using:
- The :slots and :compact options.
- Overriding the
series
method for full control over the pagination bar
:page
Pagy retrieves the page from the 'page'
request query hash. To force a specific page number, pass it directly to the pagy
method. For example:
@pagy, @records = pagy(:offset, collection, page: 3) # force page #3
Pagy uses standard i18n dictionaries for string translations and supports overriding them.
See I18n.
You can customize the aria-label
attributes of any *nav*
helper by providing a :aria_label
string.
See the :aria_label option.
You can also replace the pagy.aria_label.nav
strings in the dictionary, as well as the pagy.aria_label.previous
and the
pagy.aria_label.next
.
See ARIA.
By default, Pagy retrieves the page from the request query hash and generates URLs using the "page"
key, e.g., ?page=3
.
- Set
page_key: 'custom_page'
to customize URL generation, e.g.,?custom_page=3
. - Set the
:limit_key
to customize thelimit
param the same way.
See Common Options
Enable jsonapi: true
, optionally providing :page_key
and :limit_key
:
# JSON:API nested query string: E.g.: ?page[number]=2&page[size]=100
@pagy, @records = pagy(:offset, collection, jsonapi: true, page_key: 'number', limit_key: 'size' R)
See the :querify Option
You can use the :fragment option.
Pagy includes different formats of stylesheets for customization, as well as styled nav
tags for
:bootstrap
and :bulma
.
You can also override the specific helper method.
The input_nav_js
and limit_tag_js
use inline style attributes. You can override these rules in your stylesheet files using the
[style]
attribute selector and !important
. Below is an example of overriding the width
of an input
element:
.pagy input[style] {
width: 5rem !important; /* just an useless example */
}
- Identify its path in the gem
lib
dir (e.g., 'pagy/...'). - Note the name of the module where it is defined (e.g.,
Pagy::...
).
If you need assistance, ask in the Q&A discussions.
Copy and paste the original method in the Pagy Initializer
require 'pagy/...' # path to the overridden method file
module MyOverridingModule # wrap it with your arbitrarily named module
def any_method # Edit or define your method with the identical name
# Custom logic here...
super
# Custom logic here...
end
end
# prepend your module to the overridden module
Pagy::AnyModule.prepend MyOverridingModule
Simply pass it as the collection: pagy(:offset, my_array, **options)
Pagy works seamlessly with ActiveRecord
collections, but certain collections may require specific handling:
- Grouped collections
- For better performance of grouped counts, you may want to use the :count_over option
- Decorated collections
- Do it in two steps:
- Get the page of records without decoration
- Decorate the retrieved records.
controller@pagy, records = pagy(:offset, Post.all) @decorated_records = records.decorate # or YourDecorator.method(records) whatever works
- Do it in two steps:
- Custom scope/count
- If the default pagy doesn't get the right count:
controller# pass the right count to pagy (that will directly use it skipping its own `collection.count(:all)`) @pagy, @records = pagy(:offset, custom_scope, count: custom_count) # Example implementation
- Ransack results
- Ransack's
result
method returns anActiveRecord
collection that is ready for pagination:
controllerq = Person.ransack(params[:q]) @pagy, @people = pagy(:offset, q.result)
- Ransack's
- PostgreSQL Collections
Explore the following options:
You can send selected @pagy
instance data to the client as JSON using the data_hash helper,
including pagination metadata in your JSON response.
See these paginators:
Use the :calendar paginator for pagination filtering by calendar time units (e.g., year, quarter, month, week, day).
When you need to paginate multiple collections in a single request, you need to explicitly differentiate the pagination objects. Here are some common methods to achieve this:
#
Override the request path
By default, Pagy generates links using the same path as the request path. To generate links pointing to a different controller or
path, explicitly pass the desired :path
. For example:
def index
@pagy_foos, @foos = pagy(:offset, Foo.all, path: '/foos')
@pagy_bars, @bars = pagy(:offset, Bar.all, path: '/bars')
end
<%== @pagy_foos.series_nav %>
<%== @pagy_bars.series_nav %>
<!-- Pagination links of `/foos?page=2` instead of `/dashboard?page=2` -->
<!-- Pagination links of `/bars?page=2` etc. -->
#
Use separate turbo frames actions
If you're using hotwire (turbo-rails being the Rails
implementation), another way of maintaining independent contexts is using separate turbo frames actions. Just wrap each
independent context in a turbo_frame_tag
and ensure a matching turbo_frame_tag
is returned:
<-- movies/index.html.erb -->
<-- movies#bad_movies -->
<%= turbo_frame_tag "bad_movies", src: bad_movies_path do %>
<%= render "movies_table", locals: {movies: @movies}%>
<%== @pagy.series_nav %>
<% end %>
<-- movies#good_movies -->
<%= turbo_frame_tag "good_movies", src: good_movies_path do %>
<%= render "movies_table", locals: {movies: @movies}%>
<%== @pagy.series_nav %>
<% end %>
# controller action
def good
@pagy, @movies = pagy(:offset, Movie.good, limit: 5)
end
def bad
@pagy, @movies = pagy(:offset, Movie.bad, limit: 5)
end
Consider Benito Serna's implementation of turbo-frames (on Rails) using search forms with the Ransack gem along with a corresponding demo app for a similar implementation of the above logic.
#
Use different page symbols
You can also paginate multiple model in the same request by simply using different :page_key
for each instance:
def index # controller action
@pagy_stars, @stars = pagy(:offset, Star.all, page_key: 'page_stars')
@pagy_nebulae, @nebulae = pagy(:offset, Nebula.all, page_key: 'page_nebulae')
end
See :max_pages option.
When your collection is already paginated and contains count and pagination metadata, you don't need any pagy*
controller
method.
For example this is a Tmdb API search result object, but you can apply the same principle to any other type of collection metadata:
#<Tmdb::Result page=1, total_pages=23, total_results=446, results=[#<Tmdb::Movie ..>,#<Tmdb::Movie...>,...]...>
As you can see, it contains the pagination metadata that you can use to set up the pagination with pagy:
# get the paginated collection
tobj = Tmdb::Search.movie("Harry Potter", page: params[:page])
# use its count and page to initialize the @pagy object
@pagy = Pagy::Offset.new(count: tobj.total_results, page: tobj.page, request: Pagy::Request.new(request))
# set the paginated collection records
@movies = tobj.results
Unlike other gems, Pagy does not decide for you that the nav of a single page of results must not be rendered. You may want it rendered... or maybe you don't. If you don't:
<%== @pagy.series_nav if @pagy.last > 1 %>
Check out these paginators:
- Consider the paginators:
- Consider the
series_nav_js
andinput_nav_js
helpers: they are a few orders of magnitute faster.- Add the
oj
gem to your gemfile
- Add the
Pagy output safe HTML, however being an agnostic pagination gem it does not use the specific html_safe
rails helper for its
output. That is noted by the Brakeman gem, that will raise a warning.
You can avoid the warning adding it to the brakeman.ignore
file. More details here
and here.
With the OFFSET pagination technique, it may happen that the users/clients paginate after the end of the collection (when one or a few records got deleted) and a user went to a stale page.
By default, Pagy doesn't raise any exceptions for requesting an out-of-range page. Instead, it does not retrieve any records and serves the navs as usual, so the user can visit a different page.
Sometimes you may want to take a diffrent action, so you can set the option raise_range_error: true
, rescue
it and do whatever
fits your app better. For example:
rescue_from Pagy::RangeError, with: :redirect_to_last_page
private
def redirect_to_last_page(exception)
redirect_to url_for(page: exception.pagy.last), notice: "Page ##{params[:page]} is out-of-range. Showing page #{exception.pagy.last} instead."
end
Rescue from Pagy::RangeError
first
All Pagy exceptions are subclasses of ArgumentError
, so if you need to rescue_from ArgumentError, ...
along with
rescue_from Pagy::RangeError, ...
then the Pagy::RangeError
line should go BEFORE the ArgumentError
line, or it will never
get rescued.
- Pagy has 100% test coverage.
- You only need to test pagy if you have overridden methods.
Warning!
The pagy nav helpers are not only a lot faster than templates, but accept dynamic arguments and comply with ARIA and I18n standards. Using your own templates is possible, but it's likely just reinventing a slower wheel.
If you really need to use your own templates, you absolutely can. Here is a static example that doesn't use any other helper nor dictionary file for the sake of simplicity, however feel free to add your dynamic options and use any helper and dictionary entries as you need:
<%# IMPORTANT: replace '<%=' with '<%==' if you run this in rails %>
<%# The a variable below is set to a lambda that generates the a tag %>
<%# Usage: anchor_tag = a_lambda.(page_number, text, classes: nil, aria_label: nil) %>
<% a_lambda = pagy.send(:a_lambda) %>
<nav class="pagy nav" aria-label="Pages">
<%# Previous page link %>
<% if pagy.previous %>
<%= a_lambda.(pagy.previous, '<', aria_label: 'Previous') %>
<% else %>
<a role="link" aria-disabled="true" aria-label="Previous"><</a>
<% end %>
<%# Page links (series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]) %>
<% pagy.send(:series).each do |item| %>
<% if item.is_a?(Integer) %>
<%= a_lambda.(item) %>
<% elsif item.is_a?(String) %>
<a role="link" aria-disabled="true" aria-current="page"><%= item %></a>
<% elsif item == :gap %>
<a role="separator" aria-disabled="true">…</a>
<% end %>
<% end %>
<%# Next page link %>
<% if pagy.next %>
<%= a_lambda.(pagy.next, '>', aria_label: 'Next') %>
<% else %>
<a role="link" aria-disabled="true" aria-label="Next"><</a>
<% end %>
</nav>
You can use it as usual: just remember to pass the :pagy
local set to the @pagy
object:
<%== render file: 'nav.html.erb', locals: {pagy: @pagy} %>
You may want to look at the actual output interactively by running:
pagy demo
...and point your browser to http://127.0.0.1:8000/template
For non-rack environments that don't respond to the request method, you should pass the :request option to the paginator.
pagy
outside controllers or views
The pagy
method needs to set a few options that depend on the availability of the self.request
method in the class/module
where you included it.
For example, if you call the pagy
method for a model (that included the Pagy::Method
), it would almost certainly not have the
request
method available.
The simplest way to make it work is as follows:
include Pagy::Method
def self.paginated(view, my_arg1, my_arg2, **)
collection = ...
view.instance_eval { pagy(:offset, collection, **) }
end
<% pagy, records = YourModel.paginated(self, my_arg1, my_arg2, **options) %>
You may need to POST a very complex search form and paginate the results. Pagy produces nav tags with GET links, so here is a simple way of handling it.
You can start the process with your regular POST request and cache the filtering data on the server. Then, using the regular GET pagination cycle, pass only the cache key as a param (which avoids sending the actual filters back and forth).
Here is a conceptual example using the session
:
require 'digest'
def filtered_action
options = {}
if params[:filter_key] # already cached
filters = session[params[:filter_key]]
else
# first request
filters = params[:filters]
key = Digest::SHA1.hexdigest(filters.sort.to_json)
session[key] = filters
options[:querify] = ->(qh) { qh.merge!(filter_key: key) }
end
collection = Product.where(**filters)
@pagy, @records = pagy(:offset, collection, **options)
end
Notice: ensure a server-side storage
If you use the session
for caching, configure it to use ActiveRecord
, Redis
, or any server-side storage
#
1. Find the pluralization rules for your language
- Locate the locale file you need in the list of pluralizations
- Check the pluralization rules required in it. For example, the name of the file required is
one_other
foren.rb
. In Pagy, that translates to'OneOther'
.- If you cannot find the pluralization in the link above, check the Unicode pluralization rules.
- Confirm that pagy already defines the pluralization rules of your dictionary file by checking the file in
the P18n directory.
- If the rules are not defined, you can either:
- Add a new module for the pluralization (by adapting the same pluralization from the corresponding rails-i18n file) and include tests for it;
- Or, create an issue requesting the addition of the pluralization entry and its tests.
- If the rules are not defined, you can either:
#
2. Duplicate an existing Pagy locale dictionary file and translate it into your language.
- See the lib/locales for existing dictionaries.
- Check that the
p11n
entry points to a module in the P18n directory. - The mandatory pluralized entries in the dictionary file are
aria_label.nav
anditem_name
. Please provide all the necessary plurals for your language. For example, if your language uses theEastSlavic
rule, you should provide the plurals forone
,few
,many
, andother
. If it usesOther
, you should only provide a single value. Check other dictionary files for examples, and ask if you have any doubts.
Feel free to ask for help in your Pull Request.
For major versions in the make, we may push a preview version to rubygems. You can check its existence with:
$ gem search pagy --pre
And install it with:
gem 'pagy', '43.0.0-pre.1' # Specific preview version