# Technical Documentation: ddnexus/pagy

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/ddnexus/pagy)

### Overview (`README.md`)

<span>[](https://rubygems.org/gems/pagy)</span> <span>
[](https://endoflife.date/ruby)</span> <span>
[](https://github.com/ddnexus/pagy/actions/workflows/api-test.yml)</span> <span>
[](https://github.com/ddnexus/pagy/actions/workflows/e2e-test.yml)</span> <span>
</span> <span>
</span> <span>
[](http://opensource.org/licenses/MIT)</span> <span>
[](https://github.com/ddnexus/pagy/commits/master/)</span> <span>
[](https://rubygems.org/gems/pagy)</span> <span>
[](https://github.com/ddnexus/pagy/stargazers)</span> <span>
[](https://gurubase.io/g/pagy)</span>

#

<h2><img src="assets/images/pagy-the-frog.png" alt="💚" width="75" style="vertical-align:bottom;"><br/>Pagy the leaping gem!</h2>

_Agnostic pagination in plain Ruby_

<br/>

> [!TIP]
> ⭐ If you like Pagy, please support it with a Star! ⭐

<br/>




### <span style="font-size: .65em; vertical-align: middle">💚</span> v3 was already quite snappy...

[<img src="assets/images/ips-chart.png" title="~40x Faster!" width="500">](https://ddnexus.github.io/pagination-comparison/gems.html#ips-benchmark)<br>
[<img src="assets/images/memory-chart.png" title="~36x Lighter!" width="500">](https://ddnexus.github.io/pagination-comparison/gems.html#memory-profile)<br>
[<img src="assets/images/objects-chart.png" title="~35x Simpler!" width="500">](https://ddnexus.github.io/pagination-comparison/gems.html#memory-profile)<br>
[<img src="assets/images/resource-consumption-chart.png" title="1,410x More Efficient!" width="500">](https://ddnexus.github.io/pagination-comparison/gems.html#efficiency-ratio)

<br/>

### ... and now it's more, with less.

- Compatible with all environments and collection types
- It can use OFFSET, COUNTISH, COUNTLESS, KEYSET, KEYNAV, SEARCH, and CALENDAR pagination techniques
- It supports server-side rendering or faster client-side rendering for popular CSS frameworks and APIs
- It autoloads ONLY the methods that you actually use, with almost zero configuration
- It boasts 100% test coverage for Ruby, HTML, and JavaScript end-to-end (E2E)

<br/>

### <span style="font-size: .65em; vertical-align: middle">💚</span> Examples

##### Pagination code

```rb
# Include pagy in your code (usually application_controller.rb)
include Pagy::Method

# Offset-based pagination
@pagy, @records = pagy(:offset, Product.all)

# Keyset-based pagination (fastest technique)
@pagy, @records = pagy(:keyset, Product.order(my_order).all)

# Paginate your collection with one of several paginators
@pagy, @records = pagy(...)
```

_See all the available [paginators](https://ddnexus.github.io/pagy/toolbox/paginators/#paginators)_

##### JSON:API pagination

```ruby
# JSON:API nested query string. E.g.: ?page[number]=2&page[size]=100
@pagy, @records = pagy(:offset, Product.all, jsonapi: true)
@pagy, @records = pagy(:keyset, Product.order(my_order).all, jsonapi: true)
render json: { links: @pagy.urls_hash, data: @records }
```

##### JSON-client pagination

```ruby
render json: { pagy: @pagy.data_hash, data: @records }
```

##### Search server pagination

Available paginators: `:elasticsearch_rails`, `:meilisearch`, `:rails_active_search`, `:searchkick`, `:typesense_rails`

```rb
# Extend your models (e.g. application_record.rb)
extend Pagy::Search

# Paginate with pagy:
search           = Product.pagy_search(params[:q])
@pagy, @response = pagy(:a_search_paginator, search)

# Or get pagy from paginated results:
@results = Product.search(params[:q])
@pagy    = pagy(:a_search_paginator, @results)
```

##### Calendar pagination

Combine `time-range` and `offset` pagination.

_(Available time units: year, quarter, month, week, and day)_

<img src="assets/images/calendar-app.png" width="450"><br/>

```ruby
@calendar, @pagy, @records = pagy(:calendar, collection, year: {}, month: {}, offset: {})
```

```ERB
<%== @calendar[:year].series_nav(:bootstrap) %>
<%== @calendar[:month].series_nav(:bootstrap) %>
<%== @pagy.info_tag %>
...
<%== @pagy.series_nav(:bootstrap) %>
```

##### Server side rendering

##### `series_nav`

Default `:pagy`, `:bootstrap` and `:bulma` styles shown.

<img src="assets/images/pagy-series_nav.png" width="310"><br/>
<img src="assets/images/bootstrap-series_nav.png" width="255"><br/>
<img src="assets/images/bulma-series_nav.png" width="342"><br/>

```erb

<%== @pagy.series_nav %> 
<%== @pagy.series_nav(:bootstrap) %>
<%== @pagy.series_nav(:bulma) %>
```

##### Client side rendering

```rb
# pagy.rb initializer
javascript_dir = Rails.root.join('app/javascript')
Pagy.sync_javascript(javascript_dir, 'pagy.mjs') if Rails.env.development?
```

##### `series_nav_js`

**Faster and responsive**

Dynamically fills the container width.

<img src="assets/images/pagy-series_nav_js-7.png" width="312"><br/>
<img src="assets/images/pagy-series_nav_js-9.png" width="390"><br/>

```erb

<%== @pagy.series_nav_js %> 
<%== @pagy.series_nav_js(:bootstrap) %>
<%== @pagy.series_nav_js(:bulma) %>
```

##### `input_nav_js`

**Fastest!** Combines navigation and info in minimum space.

<img src="assets/images/pagy-input_nav_js.png" width="229"><br/>

```erb

<%== @pagy.input_nav_js %> 
<%== @pagy.input_nav_js(:bootstrap) %>
<%== @pagy.input_nav_js(:bulma) %>
```

<br/>

### <span style="font-size: .65em; vertical-align: middle">💚</span> Dev Tools

Integrate pagy with your app's themes interactively ([watch demo](https://youtu.be/GhhNRrVG9jg)), and ask specific questions to the Pagy AI __right in your app__! You need only a single line in your page/layout `head` to get the Pagy Wand and the Pagy AI:

```erb
<%== Pagy.dev_tools %>
```

<img src="assets/images/dev-tools.png" width="450"><br/>

See [Dev Tools](https://ddnexus.github.io/pagy/sandbox/dev_tools/)

<br/>

### <span style="font-size: .65em; vertical-align: middle">💚</span> Support and Docs

- [Quick Start](https://ddnexus.github.io/pagy/guides/quick-start)
- [How To (quick recipes)](https://ddnexus.github.io/pagy/guides/how-to/)
- [Upgrade to 43](https://ddnexus.github.io/pagy/guides/upgrade-guide) (from version 9 or earlier)
- [Migrate from WillPaginate and Kaminari](https://ddnexus.github.io/pagy/guides/migration-guide) (practical guide)
- [Discussions](https://github.com/ddnexus/pagy/discussions/categories/q-a)
- [Issues](https://github.com/ddnexus/pagy/issues)
- [Changelog](https://ddnexus.github.io/pagy/changelog)

<br/>

> [!TIP]
> ⭐ If you like Pagy, please support it with a Star! ⭐

<br/>

### Top 🏆 Adopters



[<img src="assets/images/top-adopters/docusealco-docuseal.svg" title="Docuseal ⭐ 18,753&#10;Open source DocuSign alternative. Create, fill, and sign digital documents ✍️">](https://github.com/docusealco/docuseal)[<img src="assets/images/top-adopters/opf-openproject.svg" title="Openproject ⭐ 16,459&#10;OpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alternative with agile planning, issue tracking, roadmaps, Gantt charts, time tracking, collaboration features, and more. Available on premises or in the cloud. ⭐ Star us on GitHub">](https://github.com/opf/openproject)[<img src="assets/images/top-adopters/spree-spree.svg" title="Spree ⭐ 15,763&#10;Open Source Platform for DTC, B2B Commerce, Marketplaces &amp; Omnichannel. REST APIs, TypeScript SDKs, and production-ready Next.js storefront. Self-host it. Own your data. No vendor lock-in. Zero platform fees.">](https://github.com/spree/spree)[<img src="assets/images/top-adopters/freika-dawarich.svg" title="Dawarich ⭐ 10,695&#10;Your favorite self-hostable alternative to Google Timeline (Google Location History)">](https://github.com/Freika/dawarich)[<img src="assets/images/top-adopters/we-promise-sure.svg" title="Sure ⭐ 10,581&#10;The personal finance app for everyone (by everyone)">](https://github.com/we-promise/sure)[<img src="assets/images/top-adopters/antiwork-gumroad.svg" title="Gumroad ⭐ 9,967&#10;See what sticks">](https://github.com/antiwork/gumroad)[<img src="assets/images/top-adopters/theodinproject-theodinproject.svg" title="Theodinproject ⭐ 4,659&#10;Main website for The Odin Project">](https://github.com/TheOdinProject/theodinproject)[<img src="assets/images/top-adopters/octobox-octobox.svg" title="Octobox ⭐ 4,488&#10;📮 Untangle your GitHub Notifications">](https://github.com/octobox/octobox)[<img src="assets/images/top-adopters/blackcandy-org-blackcandy.svg" title="Blackcandy ⭐ 4,428&#10;A self hosted music streaming server">](https://github.com/blackcandy-org/blackcandy)[<img src="assets/images/top-adopters/pglombardo-passwordpusher.svg" title="PasswordPusher ⭐ 3,225&#10;🔐   Securely share sensitive information with automatic expiration &amp; deletion after a set number of views or duration.  Track who, what and when with full audit logs.">](https://github.com/pglombardo/PasswordPusher)[<img src="assets/images/top-adopters/caninehq-canine.svg" title="Canine ⭐ 2,938&#10;A developer friendly PaaS for your Kubernetes">](https://github.com/CanineHQ/canine)[<img src="assets/images/top-adopters/rubygems-rubygems.org.svg" title="Rubygems.org ⭐ 2,439&#10;The Ruby community&#39;s gem hosting service.">](https://github.com/rubygems/rubygems.org)[<img src="assets/images/top-adopters/otwcode-otwarchive.svg" title="Otwarchive ⭐ 2,271&#10;The Organization for Transformative Works (OTW) - Archive Of Our Own (AO3) Project">](https://github.com/otwcode/otwarchive)[<img src="assets/images/top-adopters/manyfold3d-manyfold.svg" title="Manyfold ⭐ 2,208&#10;A self-hosted digital asset manager for 3d print files.">](https://github.com/manyfold3d/manyfold)[<img src="assets/images/top-adopters/greasyfork-org-greasyfork.svg" title="Greasyfork ⭐ 2,132&#10;An online repository of user scripts.">](https://github.com/greasyfork-org/greasyfork)[<img src="assets/images/top-adopters/openjournals-joss.svg" title="Joss ⭐ 1,927&#10;The Journal of Open Source Software">](https://github.com/openjournals/joss)[<img src="assets/images/top-adopters/bullet-train-co-bullet_train.svg" title="Bullet Train ⭐ 1,890&#10;The Open Source Ruby on Rails SaaS Template">](https://github.com/bullet-train-co/bullet_train)[<img src="assets/images/top-adopters/avo-hq-avo.svg" title="Avo ⭐ 1,811&#10;The Essential Toolkit for building Internal Tools and admin panels with Ruby on Rails">](https://github.com/avo-hq/avo)[<img src="assets/images/top-adopters/ossf-best-practices-badge.svg" title="Best Practices Badge ⭐ 1,370&#10;🏆Open Source Security Foundation (OpenSSF) Best Practices Badge (formerly Core Infrastructure Initiative (CII) Best Practices Badge)">](https://github.com/ossf/best-practices-badge)[<img src="assets/images/top-adopters/shopify-maintenance_tasks.svg" title="Maintenance Tasks ⭐ 1,313&#10;A Rails engine for queueing and managing data migrations.">](https://github.com/Shopify/maintenance_tasks)[<img src="assets/images/top-adopters/openfoodfoundation-openfoodnetwork.svg" title="Openfoodnetwork ⭐ 1,294&#10;Connect suppliers, distributors and consumers to trade local produce.">](https://github.com/openfoodfoundation/openfoodnetwork)[<img src="assets/images/top-adopters/circuitverse-circuitverse.svg" title="CircuitVerse ⭐ 1,282&#10;CircuitVerse Primary Code Base">](https://github.com/CircuitVerse/CircuitVerse)[<img src="assets/images/top-adopters/inertiajs-inertia-rails.svg" title="Inertia Rails ⭐ 1,244&#10;The Rails adapter for Inertia.js.">](https://github.com/inertiajs/inertia-rails)[<img src="assets/images/top-adopters/drhenner-ror_ecommerce.svg" title="Ror Ecommerce ⭐ 1,208&#10;Ruby on Rails Ecommerce platform, perfect for your small business solution.">](https://github.com/drhenner/ror_ecommerce)[<img src="assets/images/top-adopters/tarteelai-quranic-universal-library.svg" title="Quranic Universal Library ⭐ 1,073&#10;A comprehensive collection of Quran resources">](https://github.com/TarteelAI/quranic-universal-library)

…and [13,500+ more public repos](https://github.com/ddnexus/pagy/network/dependents) on GitHub.



<br/>

### Top 💯 Contributors



[<img src="https://avatars.githubusercontent.com/u/100721?v=4" width="40" title="@ddnexus: 621845 lines in 1881 commits">](https://github.com/ddnexus/pagy/commits?author=ddnexus)[<img src="https://avatars.githubusercontent.com/u/15097447?v=4" width="40" title="@benkoshy: 1725 lines in 84 commits">](https://github.com/ddnexus/pagy/commits?author=benkoshy)[<img src="https://avatars.githubusercontent.com/u/11367?v=4" width="40" title="@grosser: 1426 lines in 11 commits">](https://github.com/ddnexus/pagy/commits?author=grosser)[<img src="https://avatars.githubusercontent.com/u/132?v=4" width="40" title="@sunny: 817 lines in 3 commits">](https://github.com/ddnexus/pagy/commits?author=sunny)[<img src="https://avatars.githubusercontent.com/u/17091381?v=4" width="40" title="@djpremier: 627 lines in 3 commits">](https://github.com/ddnexus/pagy/commits?author=djpremier)[<img src="https://avatars.githubusercontent.com/u/26239269?v=4" width="40" title="@enzinia: 433 lines in 3 commits">](https://github.com/ddnexus/pagy/commits?author=enzinia)[<img src="https://avatars.githubusercontent.com/u/235048?v=4" width="40" title="@molfar: 404 lines in 3 commits">](https://github.com/ddnexus/pagy/commits?author=molfar)[<img src="https://avatars.githubusercontent.com/u/417475?v=4" width="40" title="@lfidnl: 319 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=lfidnl)[<img src="https://avatars.githubusercontent.com/u/2815199?v=4" width="40" title="@pedrocarmona: 226 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=pedrocarmona)[<img src="https://avatars.githubusercontent.com/u/9843321?v=4" width="40" title="@workgena: 222 lines in 4 commits">](https://github.com/ddnexus/pagy/commits?author=workgena)[<img src="https://avatars.githubusercontent.com/u/7076736?v=4" width="40" title="@renshuki: 177 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=renshuki)[<img src="https://avatars.githubusercontent.com/u/579103?v=4" width="40" title="@7a6163: 143 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=7a6163)[<img src="https://avatars.githubusercontent.com/u/13472945?v=4" width="40" title="@yshmarov: 130 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=yshmarov)[<img src="https://avatars.githubusercontent.com/u/1169363?v=4" width="40" title="@daniel-rikowski: 109 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=daniel-rikowski)[<img src="https://avatars.githubusercontent.com/u/2749593?v=4" width="40" title="@berniechiu: 107 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=berniechiu)[<img src="https://avatars.githubusercontent.com/u/22333?v=4" width="40" title="@bquorning: 83 lines in 3 commits">](https://github.com/ddnexus/pagy/commits?author=bquorning)[<img src="https://avatars.githubusercontent.com/u/2051199?v=4" width="40" title="@rbngzlv: 82 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=rbngzlv)[<img src="https://avatars.githubusercontent.com/u/1328587?v=4" width="40" title="@amenon: 73 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=amenon)[<img src="https://avatars.githubusercontent.com/u/1345993?v=4" width="40" title="@lumikcz: 73 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=lumikcz)[<img src="https://avatars.githubusercontent.com/u/3427854?v=4" width="40" title="@747: 66 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=747)[<img src="https://avatars.githubusercontent.com/u/82187643?v=4" width="40" title="@bruno-costanzo: 60 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=bruno-costanzo)[<img src="https://avatars.githubusercontent.com/u/71972054?v=4" width="40" title="@ShyamBorsaniya: 57 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=ShyamBorsaniya)[<img src="https://avatars.githubusercontent.com/u/35310?v=4" width="40" title="@kelso: 48 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=kelso)[<img src="https://avatars.githubusercontent.com/u/9436230?v=4" width="40" title="@Davidzhu001: 48 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=Davidzhu001)[<img src="https://avatars.githubusercontent.com/u/4191494?v=4" width="40" title="@jeffse: 46 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=jeffse)[<img src="https://avatars.githubusercontent.com/u/8354012?v=4" width="40" title="@ikanade: 41 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=ikanade)[<img src="https://avatars.githubusercontent.com/u/195636?v=4" width="40" title="@tersor: 38 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=tersor)[<img src="https://avatars.githubusercontent.com/u/500826?v=4" width="40" title="@tiagotex: 38 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=tiagotex)[<img src="https://avatars.githubusercontent.com/u/3071529?v=4" width="40" title="@kobusjoubert: 38 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=kobusjoubert)[<img src="https://avatars.githubusercontent.com/u/50970645?v=4" width="40" title="@sabljak: 37 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=sabljak)[<img src="https://avatars.githubusercontent.com/u/556268?v=4" width="40" title="@tagliala: 36 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=tagliala)[<img src="https://avatars.githubusercontent.com/u/32258?v=4" width="40" title="@espen: 34 lines in 3 commits">](https://github.com/ddnexus/pagy/commits?author=espen)[<img src="https://avatars.githubusercontent.com/u/29891001?v=4" width="40" title="@jyuvaraj03: 34 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=jyuvaraj03)[<img src="https://avatars.githubusercontent.com/u/9826538?v=4" width="40" title="@LuukvH: 34 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=LuukvH)[<img src="https://avatars.githubusercontent.com/u/1100176?v=4" width="40" title="@thomasklemm: 30 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=thomasklemm)[<img src="https://avatars.githubusercontent.com/u/6059188?v=4" width="40" title="@sliminas: 30 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=sliminas)[<img src="https://avatars.githubusercontent.com/u/8125726?v=4" width="40" title="@simonneutert: 29 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=simonneutert)[<img src="https://avatars.githubusercontent.com/u/30351533?v=4" width="40" title="@sk8higher: 26 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=sk8higher)[<img src="https://avatars.githubusercontent.com/u/421488?v=4" width="40" title="@petergoldstein: 25 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=petergoldstein)[<img src="https://avatars.githubusercontent.com/u/143771?v=4" width="40" title="@ollym: 24 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=ollym)[<img src="https://avatars.githubusercontent.com/u/147130?v=4" width="40" title="@AbelToy: 22 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=AbelToy)[<img src="https://avatars.githubusercontent.com/u/13119624?v=4" width="40" title="@yhk1038: 22 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=yhk1038)[<img src="https://avatars.githubusercontent.com/u/83706?v=4" width="40" title="@MrMoins: 22 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=MrMoins)[<img src="https://avatars.githubusercontent.com/u/6220668?v=4" width="40" title="@YutoYasunaga: 22 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=YutoYasunaga)[<img src="https://avatars.githubusercontent.com/u/3188392?v=4" width="40" title="@cseelus: 21 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=cseelus)[<img src="https://avatars.githubusercontent.com/u/2506436?v=4" width="40" title="@jivko-chobanov: 21 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=jivko-chobanov)[<img src="https://avatars.githubusercontent.com/u/12526288?v=4" width="40" title="@benjaminwols: 20 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=benjaminwols)[<img src="https://avatars.githubusercontent.com/u/5347394?v=4" width="40" title="@cellvinchung: 20 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=cellvinchung)[<img src="https://avatars.githubusercontent.com/u/752766?v=4" width="40" title="@jonasCyberdude: 20 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=jonasCyberdude)[<img src="https://avatars.githubusercontent.com/u/58137134?v=4" width="40" title="@muhammadnawzad: 20 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=muhammadnawzad)[<img src="https://avatars.githubusercontent.com/u/7021119?v=4" width="40" title="@thomaschauffour: 19 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=thomaschauffour)[<img src="https://avatars.githubusercontent.com/u/94952098?v=4" width="40" title="@KinWang-2013: 18 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=KinWang-2013)[<img src="https://avatars.githubusercontent.com/u/6527665?v=4" width="40" title="@t0ch1k: 18 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=t0ch1k)[<img src="https://avatars.githubusercontent.com/u/6125?v=4" width="40" title="@Cosmo: 18 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=Cosmo)[<img src="https://avatars.githubusercontent.com/u/14981592?v=4" width="40" title="@Earlopain: 17 lines in 4 commits">](https://github.com/ddnexus/pagy/commits?author=Earlopain)[<img src="https://avatars.githubusercontent.com/u/4824537?v=4" width="40" title="@yenshirak: 16 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=yenshirak)[<img src="https://avatars.githubusercontent.com/u/17851143?v=4" width="40" title="@leonvogt: 16 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=leonvogt)[<img src="https://avatars.githubusercontent.com/u/462701?v=4" width="40" title="@borama: 16 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=borama)[<img src="https://avatars.githubusercontent.com/u/1176145?v=4" width="40" title="@leoplct: 15 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=leoplct)[<img src="https://avatars.githubusercontent.com/u/17837825?v=4" width="40" title="@kursataktas: 14 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=kursataktas)[<img src="https://avatars.githubusercontent.com/u/62210?v=4" width="40" title="@geoffreymcgill: 12 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=geoffreymcgill)[<img src="https://avatars.githubusercontent.com/u/112558900?v=4" width="40" title="@wimdavies: 10 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=wimdavies)[<img src="https://avatars.githubusercontent.com/u/2780?v=4" width="40" title="@andycroll: 8 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=andycroll)[<img src="https://avatars.githubusercontent.com/u/174391530?v=4" width="40" title="@mikepayready: 8 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=mikepayready)[<img src="https://avatars.githubusercontent.com/u/37790?v=4" width="40" title="@gamafranco: 7 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=gamafranco)[<img src="https://avatars.githubusercontent.com/u/67093?v=4" width="40" title="@excid3: 6 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=excid3)[<img src="https://avatars.githubusercontent.com/u/361323?v=4" width="40" title="@snkashis: 6 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=snkashis)[<img src="https://avatars.githubusercontent.com/u/583995?v=4" width="40" title="@JasonBarnabe: 6 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=JasonBarnabe)[<img src="https://avatars.githubusercontent.com/u/10395940?v=4" width="40" title="@ethancrawford: 6 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=ethancrawford)[<img src="https://avatars.githubusercontent.com/u/412056?v=4" width="40" title="@rogermarlow: 6 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=rogermarlow)[<img src="https://avatars.githubusercontent.com/u/10076?v=4" width="40" title="@claudiob: 4 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=claudiob)[<img src="https://avatars.githubusercontent.com/u/101501?v=4" width="40" title="@rainerborene: 4 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=rainerborene)[<img src="https://avatars.githubusercontent.com/u/10906059?v=4" width="40" title="@achmiral: 4 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=achmiral)[<img src="https://avatars.githubusercontent.com/u/42350151?v=4" width="40" title="@branson-simplethread: 4 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=branson-simplethread)[<img src="https://avatars.githubusercontent.com/u/16821802?v=4" width="40" title="@jonathanloos: 4 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=jonathanloos)[<img src="https://avatars.githubusercontent.com/u/1530175?v=4" width="40" title="@thedanbob: 4 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=thedanbob)[<img src="https://avatars.githubusercontent.com/u/437470?v=4" width="40" title="@lostapathy: 4 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=lostapathy)[<img src="https://avatars.githubusercontent.com/u/93160109?v=4" width="40" title="@ranvold: 4 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=ranvold)[<img src="https://avatars.githubusercontent.com/u/8194848?v=4" width="40" title="@connie-feng: 4 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=connie-feng)[<img src="https://avatars.githubusercontent.com/u/12479464?v=4" width="40" title="@ashmaroli: 3 lines in 2 commits">](https://github.com/ddnexus/pagy/commits?author=ashmaroli)[<img src="https://avatars.githubusercontent.com/u/22420?v=4" width="40" title="@smoothdvd: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=smoothdvd)[<img src="https://avatars.githubusercontent.com/u/1012014?v=4" width="40" title="@okuramasafumi: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=okuramasafumi)[<img src="https://avatars.githubusercontent.com/u/12237543?v=4" width="40" title="@olieidel: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=olieidel)[<img src="https://avatars.githubusercontent.com/u/54139019?v=4" width="40" title="@pranavbabu: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=pranavbabu)[<img src="https://avatars.githubusercontent.com/u/43936240?v=4" width="40" title="@PedroAugustoRamalhoDuarte: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=PedroAugustoRamalhoDuarte)[<img src="https://avatars.githubusercontent.com/u/891109?v=4" width="40" title="@peter50216: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=peter50216)[<img src="https://avatars.githubusercontent.com/u/632682?v=4" width="40" title="@RyanTG: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=RyanTG)[<img src="https://avatars.githubusercontent.com/u/73866?v=4" width="40" title="@jgarber623: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=jgarber623)[<img src="https://avatars.githubusercontent.com/u/19877847?v=4" width="40" title="@KieraninTehdas: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=KieraninTehdas)[<img src="https://avatars.githubusercontent.com/u/1038959?v=4" width="40" title="@mcauser: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=mcauser)[<img src="https://avatars.githubusercontent.com/u/1322208?v=4" width="40" title="@fkmy: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=fkmy)[<img src="https://avatars.githubusercontent.com/u/86243?v=4" width="40" title="@denny: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=denny)[<img src="https://avatars.githubusercontent.com/u/482867?v=4" width="40" title="@artinboghosian: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=artinboghosian)[<img src="https://avatars.githubusercontent.com/u/24856?v=4" width="40" title="@creativetags: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=creativetags)[<img src="https://avatars.githubusercontent.com/u/7660738?v=4" width="40" title="@rafaelmontas: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=rafaelmontas)[<img src="https://avatars.githubusercontent.com/u/7662492?v=4" width="40" title="@AliOsm: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=AliOsm)[<img src="https://avatars.githubusercontent.com/u/43855653?v=4" width="40" title="@PyrinAndrii: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=PyrinAndrii)[<img src="https://avatars.githubusercontent.com/u/2381771?v=4" width="40" title="@KevinColemanInc: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=KevinColemanInc)[<img src="https://avatars.githubusercontent.com/u/7241024?v=4" width="40" title="@AngelGuerra: 2 lines in 1 commit">](https://github.com/ddnexus/pagy/commits?author=AngelGuerra)


<br/>

### <span style="font-size: .65em; vertical-align: middle">💚</span> Credits

Special thanks to:

- [JetBrains](http://www.jetbrains.com?from=https%3A%2F%2Fgithub.com%2Fddnexus%2Fpagy) for their free open source license.
- [Ben Koshy](https://github.com/benkoshy) for his contributions to the documentation, user support and interaction with external frameworks.
- [The Stargazers](https://github.com/ddnexus/pagy/stargazers) for their support.

<br/>

### <span style="font-size: .65em; vertical-align: middle">💚</span> Repository Info

<details>

<summary>How to contribute</summary>

See [Contributing](https://github.com/ddnexus/pagy/blob/master/.github/CONTRIBUTING.md)

</details>

<details>

<summary>Versioning</summary>

- Pagy follows the [Semantic Versioning 2.0.0](https://semver.org/). Please check the [Changelog](https://ddnexus.github.io/pagy/changelog) for breaking changes introduced by major versions. Using [pessimistic version constraint](https://guides.rubygems.org/patterns/#pessimistic-version-constraint) in your Gemfile will ensure smooth upgrades.

</details>

<details>

<summary>Branches</summary>

- The `master` branch is the latest rubygem-published release. It also contains docs and comment changes that don't affect the published code. It is never force-pushed.
- The `dev` branch is the development branch with the new code that will be merged in the next release. It could be force-pushed.
- Expect any other branch to be internal, experimental, force-pushed, rebased and/or deleted even without merging.

</details>

<br/>

### <span style="font-size: .65em; vertical-align: middle">💚</span> License

[MIT](https://opensource.org/licenses/MIT)

---

### Overview (`test/README.md`)

# Ruby Test Environment

Ruby is tested with [`minitest`](https://github.com/minitest/minitest) through `thor`. It also uses [`holdify`](https://github.com/ddnexus/holdify) as a snapshot system.

## Prerequisite

- Install the gems: `bundle install`
- E2E tests use your local Chrome

No other setup required.

## Testing

```
❯ bundle exec thor list
test
----
thor test:all                # Test Rubocop + API + Coverage + E2E
thor test:api:all            # Test current and next API
thor test:api:coverage       # Test API coverage
thor test:api:current        # Test current API
thor test:api:next           # Test next API
thor test:e2e:apps [APP]     # Test all E2E apps (or just APP)
```

## Run all checks

To get green light for PRs, just run them all in ~10secs...

`bundle exec thor test:all`

---

### Overview (`scripts/hooks/README.md`)

# Git Hooks

A hook to ensure consistency and automation. Please setup the hook if you contribute to pagy with PRs.

See also the brief description inside the hook file.

## Setup

### Symlink

If your system support symlinks: symlink the hook into the `.git/hooks` dir:

```shell
# from the repo root
ln -s ../../scripts/hooks/pre-commit .git/hooks
```

### Copy

Only for filesystem that don't support symlinks: copy the hooks into the `.git/hooks` dir:

```shell
# from the repo root
cp scripts/hooks/pre-commit .git/hooks
```

---

### CHANGELOG (`docs/CHANGELOG.md`)

---
icon: versions
---

#

## :icon-versions:&nbsp;&nbsp;CHANGELOG

---

### Release Policy

Pagy follows the [Semantic Versioning 2.0.0](https://semver.org/), and introduces BREAKING CHANGES only for MAJOR versions.

We release any new version (MAJOR, MINOR, PATCH) as soon as it is ready for release, regardless of any time constraint, frequency
or duration.

We rarely deprecate elements (releasing a new MAJOR version is just simpler and more efficient). However, when we do, you can
expect the old/deprecated functionality to be supported ONLY during the current MAJOR version.

### Recommended Version Constraint

Given a version number `MAJOR.MINOR.PATCH` (e.g. `43.8.1`):

The `gem 'pagy', '~> 43.0'` Gemfile entry (without the PATCH number) ensures that the `bundle update` command will update pagy to
the most recent version WITHOUT BREAKING CHANGES.

Increment the MAJOR version in your Gemfile ONLY when you are ready to handle the BREAKING CHANGES.

### Breaking Changes

Follow the [Upgrade to 43 Guide](https://ddnexus.github.io/pagy/guides/upgrade-guide).

If you upgrade from version `< 9.0.0` see the following:

- [Breaking changes in version 9.0.0](https://ddnexus.github.io/pagy/changelog_legacy#version-900)
- [Breaking changes in version 8.0.0](https://ddnexus.github.io/pagy/changelog_legacy#version-800)
- [Breaking changes in version 7.0.0](https://ddnexus.github.io/pagy/changelog_legacy#version-700)
- [Breaking changes in version 6.0.0](https://ddnexus.github.io/pagy/changelog_legacy#version-600)
- [Breaking changes in version 5.0.0](https://ddnexus.github.io/pagy/changelog_legacy#version-500)
- [Breaking changes in version 4.0.0](https://ddnexus.github.io/pagy/changelog_legacy#version-400)
- [Breaking changes in version 3.0.0](https://ddnexus.github.io/pagy/changelog_legacy#version-300)
- [Breaking changes in version 2.0.0](https://ddnexus.github.io/pagy/changelog_legacy#version-200)
- [Breaking changes in version 1.0.0](https://ddnexus.github.io/pagy/changelog_legacy#version-100)

> [!TIP]
> If you need to update through multiple versions, reimplementing the updated pagination
> from scratch might be faster.

### Deprecations

- `Pagy.options`: Use `Pagy::OPTIONS` directly.
- `Pagy.sync_javascript(...)`: Use `Pagy.sync(:javascript, ...)` instead.
- `:max_pages` option: [follow this method](https://ddnexus.github.io/pagy/guides/how-to/#paginate-only-max-records) instead.<br>
  **IMPORTANT**: The [Issue #890](https://github.com/ddnexus/pagy/issues/890) still affect the `:max_pages` option, so stop using it ASAP.
- `:client_max_limit` and `:max_limit` options: use `:client_limit` instead.
<hr>

#### Version 43.8.1

- Fix Active Record connection checkout in keyset pagination (#931)

#### Version 43.8.0

- Add Romanian localization (#930)

#### Version 43.7.1

- Fix the database error on an out-of-range page with a huge page number (#929)

#### Version 43.7.0

- Refactor the Search::Arguments and Searcher classes to allow more flexibility in chaining queres.
- Fix the non-standard "previous" link relation in headers_hash and JSON:API urls_hash
  - headers_hash emits the conventional rel="prev" in the link header
  - urls_hash returns the :prev key with jsonapi: true, as the JSON:API specification requires
  - urls_hash keeps the :previous key otherwise
- Fix series_nav_js ignoring the :slots and :compact helper options
  - The :slots and :compact options passed to the series_nav_js helpers were silently ignored
  - The :steps option overrides the :slots option, while the :compact option applies to all the steps
- Fix the playground doc URLs and make the playground apps consistent
- Add :rails_active_search paginator, test and docs

#### Version 43.6.3

- Deprecate the :max_limit in favor of the more descriptive :client_limit option.

#### Version 43.6.2

- Fix nil records returned for Array collections on page overflow (#920) (Fix #919)

#### Version 43.6.1

- HTML escape URL strings in href attributes (Fix #914)

#### Version 43.6.0

- Add the max_result_window option to the searchkick and elasticsearch_rails paginators to get an accurate count (#910)

#### Version 43.5.6

- Validate I18n locale input, coerce dev_tools wand_scale, add input-safety docs (#908)
  * Coerce dev_tools wand_scale to a float
  * Validate the I18n locale against a BCP 47 pattern
  * Update docs and comments about safety

#### Version 43.5.5

- Rescue malformed page request param in Pagy::Keyset and KeynavJsPaginator (#907)
  *  Rescue malformed page request param in Pagy::Keyset and KeynavJsPaginator
  * Simplify decoding logic

#### Version 43.5.4

- Fix SERIES_SLOTS loading error (close #902)

#### Version 43.5.3

- Autoload series, a_lambda and page_label
- Add Hungarian localization file for Pagy (#896)

#### Version 43.5.2

- Add type validation for page and limit keys type (close #895)
- Simplify series_nav_js removing "pagy-rjs" CSS class (Fix #894)

#### Version 43.5.1

- Remove ghost code from cli; improve tests

#### Version 43.5.0

- Update javascripts according to es-linting
- Update min ruby version to 3.3 (remove EOL 3.2)
- Implement the NEW `pagy/next` entrypoint to run the NEXT version now
- Refactor deprecations:
  - Deprecated :client_max_limit in favor of :max_limit; moved code into deprecation.rb
  - Move deprecated options out of class code, to keep the code clean
- Remove RBS resources. Not worth the maintenance effort.

#### Version 43.4.4

- Make typecasting in Keyset based classes safer and more efficient

#### Version 43.4.3

- Deprecate the :max_pages option (close #890)

#### Version 43.4.2

- Fix edge-case for queries with multiple orderings on same column (#888)

#### Version 43.4.1

- Normalize deprecations

#### Version 43.4.0

- Improve stylesheets and docs
- Improve JavaScript:
  - Add sync method and task
  - Deprecate sync_javascript
  - Simplify build, configuration and docs

#### Version 43.3.3

- Fix Request#resolve_page with jsonapi, limit, and missing page param (#885)
- Fix pagy-tailwind.css inconsistencies
- Improve ts/js build process and wand help
- Move the next_tag into the Pagy class

#### Version 43.3.2

- Implement NumericUI module to avoid including the numeric helpers in keynav classes
- Improve offset accessors and update docs
- Add basic RBS

#### Version 43.3.1

- Update assets for a few apps
- Fix pagy.ts /.js input_nav update

#### Version 43.3.0

- Add :typesense_rails paginator (See typesense/typesense-rails#17)
- Accept a variable number of search arguments
- Improve resilience of internal pagy nav method
- Use Pagy::OPTIONS instead of Pagy.options for efficiency
- Simplify assign options

#### Version 43.2.10

- Add failing test and revert code that caused empty aria label
- Avoid invalid limit param (similar to #862)
- Add thread flag to /bin/pagy
- Add uri gem dependency to the gemspec
- Freeze the @order in key* apps
- Replace require with require_relative in Pagy::CLI
- Make Pagy::OPTIONS thread-safe

#### Version 43.2.9

- Fix NoMethodError with tampered params (#872)

#### Version 43.2.8

- Add fallback to 'en' for unknown locale. Implement #868.
- Fix resolve_page with empty page in classes with non-integer page. Close #863
- Simplify keyset syntax

#### Version 43.2.7

- Add a hint predicate for DB optimizers, to multi-column keysets
- Improve code readability and style
- Refactor anchor tag helpers

#### Version 43.2.6

- Refactor i18n:
  - Raise exceptions for missing 'pagy' and 'p11n' keys in the dictionary
  file
  - Improve efficiency and readability

#### Version 43.2.5

- Fix Pagy::I18n.locale to ensure to_s. Close #861.
- Reduce endless methods to very short, paramless ones
- Refactor the in_range? method:
  - Remove implicit assignation to empty page variable
- Enforce freezing the Pagy::Request object and params
- Refactor calendar:
  - Remove the marshaling
  - Improve use and naming of locals
  - Improve readability
- Refactor linkable:
  - Improve use of locals
  - Extend conditional sub to fragment
  - Improve readability
- Improve simplicity and readability

#### Version 43.2.4

- Fix anchor_string option not being read from @options in a_lambda. Close #857
- Fix page '0' raising an exception

#### Version 43.2.3

- Remove rerun
- Improved direction handling in CSSs

#### Version 43.2.2

- Refactor bin/pagy to use the Pagy::CLI class
- Replace optimist with the optparse standard lib
- Simplify apps by using SQLite :memory:

#### Version 43.2.1

- Fix the shallow cloning of root_key params. Close #851.
- Implements support for easy overriding
- Add :current_url alias to :page_url
- Fix limit_tag_js not respecting the passed :client_max_limit option
- Fix data_hash returning keys with nil values, or URLs without page; add :current_url alias to :page_url
- Remove warning when fit_time
- Fix respond_to_missing definition arguments in Search::Arguments

#### Version 43.2.0

- Implement :countish paginator
- Simplify Pagy::Linkable

#### Version 43.1.8

- Refactoring of elasticsearch_rails paginator:
  - Fix "response" shadowing. Close #842.
  - Simplify code
  - Improve readability: rename variables and methods in more natural
  and unambiguous language

#### Version 43.1.7

- Fix and improve search paginators:
  - Add support for elasticsearch_rails v8
  - Prioritize elasticsearch_rails response over raw_response in
  total_count (causing multiple query executions)
  - Fix the ignored :search_method paginator option
  - Close #837, close #838, close #839.

#### Version 43.1.6

- Ensure the request GET and POST merge into a proper params Hash
- Simplify code efficiency and readability

#### Version 43.1.5

- Fix and improve the composition of the page url. Close #835

#### Version 43.1.4

- Add full support for legacy countless params, url composition and (limited) behavior

#### Version 43.1.3

- Improve the page options handling for countless:
  - Replace the crippled handling of legacy last-less links params
  introduced in 43.1.2 by gracefully starting the pagination from
  the beginning
  - Remove the compose_page_param method (it would need a deep refactoring
   for compatibility with the legacy handling)
  - Improve testing
- Improve the info message for empty collection in countless paginations

#### Version 43.1.2

- Support easy countless page param overriding (for legacy param and behavior). See #816
- Handle legacy page param for :countless paginator. Close #832
- Improve mock collections
- Improve code comments

#### Version 43.1.1

- Makes keynav pagination compatible with nested params:
  - Improve the URL composing and unescaping
  - Adds the keynav+root_key.ru showcase app
- Simplify the request code and arguments

#### Version 43.1.0

- Translate the aria_label.nav entry in the id, ja, kn, sw locales. Close #588, close #590, close #591, close #603.
- Improve the Pagy AI panel
- Simplify the paginators code
- Allow nested :page and :limit request params and URLs:
  - Add the :root_key option:
  - Simplify the :jsonapi handling as a special case of nested params
- Fix and simplify the pagy console code

#### Version 43.0.7

- Add compatibility for searchkick 6. Close #831
- Fix the search paginators request in passive mode. Close #830.

#### Version 43.0.6

- Improve Pagy AI Widget scripts

#### Version 43.0.5

- Add console loader for easier usage
- Add comment for the calendar usage. Close #827.

#### Version 43.0.4

- Remove the Pagy::OPTIONS from the Calendar configuration. Close #825

#### Version 43.0.3

- Update tr.yml (#824)

#### Version 43.0.2

- Ensure the Pagy::Request#params are the original params sent with the request. Close #821

#### Version 43.0.1

- Reimplement reading params from POST requests; rename internal variables. Close #821
- Fix AI widget problem for apps. Close #817.
- Improve I18n documentation. Close #811
- Link to documentation website's CHANGELOG page (#804)

#### Version 43.0.0

We needed a leap version to unequivocally signal that it's not just a major version: it's a complete redesign of the legacy
code at all levels, usage and API included.

**Why 43?** Because it's exactly one step beyond _"The answer to the ultimate question of life, the Universe, and everything."_ 😉

### Changes

- **The Countless pagination remembers the last page**
  - Pagination navs now allow jumping forward after navigating back a few pages.
- **Javascript refactoring**
  - The new `Pagy.sync_javascript` function used in the `pagy.js` initializer, avoids complicated configurations.
  - Added the plain `pagy.js` and relative source map files.
- **I18n refactoring**
  - No setup required: the locales and their pluralization are autoloaded when your app uses them.
  - The locale files are easier to override with `Pagy::I18n.pathnames << my_dictionaries`.
- **HTML and CSS refactoring**
  - Stylesheets are now based on CSS properties and calculations, for easier customization.
  - The new PagyWand interactive tool generates the CSS Override for your custom styles and provides live feedback right in your
    app.
- **Playground apps**
  - Better usability and styles
- **Bootstrap and Bulma**
  - Fixed a few style glitches.

[LEGACY CHANGELOG >>>](https://ddnexus.github.io/pagy/changelog_legacy)

---

### CHANGELOG LEGACY (`docs/CHANGELOG_LEGACY.md`)

---
icon: versions
layout: page
visibility: hidden
---

# LEGACY CHANGELOG

> [!WARNING]
> Many links on this page may refer to old resources and may return HTTP 404 "Not Found"

### Version 9.3.4

- Fix method visibility in JsonApiExtra (#765)

### Version 9.3.3

- Add test for locales - to find problematic keys (#752)
- Update locales: zh-CN, zh-HK, zh-TW  (#751) (fix #608, fix #609, fix #610)
  - Remove :other from :aria_label key and code comment
  - Change :item_name which had :one_other keys to default to the :other key
  - Fix comment

### Version 9.3.2

- Improve gapped series computation by a few milliseconds

### Version 9.3.1

- Added qualified column names to the keyset query

### Version 9.3.0

- Remove the :typecast_latest variable
- Add the :jsonify_keyset_attributes variable to override the encoding (#749)

### Version 9.2.2

- Replace inline templates with template block in sinatra apps
- Replace the rails calendar app with a sinatra app
- Add PagyApps::INDEX

### Version 9.2.1

- Improve bin/pagy to dynamically find apps and descriptions
- Apps refactoring:
  - Update rails to 8.0
  - Replace rails AR keyset apps with sinatra apps
  - Improve consistency

### Version 9.2.0

- Simplify the keyset API:
  - Deprecate the :after_latest variable in favour of :filter_newest
  - Add the keyset argument to the :filter_newest lambda
  - Rename protected after_latest_query > filter_newest_query

### Version 9.1.1

- Simplify calendar code

### Version 9.1.0

- Add Dzongkha (dz locale) (#741)

### Version 9.0.9

- Set freeze flag to false (#737)
- Fix: Prefix DEFAULT with Pagy:: (#736)

### Version 9.0.8

- Keep offset and limit variables untouched by the overflow extra in :empty_page mode (closes #733)

### Version 9.0.7

- Absolutize generic define? to fix module resolution

### Version 9.0.6

- Fix docs (closes #732)
- Replace pluck with pick

### Version 9.0.5

- Fix for missing variables passed to pagy_anchor from pagy_nav (closes #728)

### Version 9.0.4

- Fix groupdate timezone error on certain machines/config
- Fix gem containing tmp files

### Version 9.0.3

- Remove legacy and unused javascript files
- Improve pagy_get_page with force_integer option
- Fix jsonapi with keyset
- Complete the internal refactoring of version 9:
  - Remove the pagy*_get_vars methods now useless
  - Use to_i on the page variable for the search extras

### Version 9.0.2

- Rename and document the link header to pagy_link_header
- Add first and next url helpers to the keyset extra; add the keyset section to config/pagy.rb
- Fix nil page in keyset URL not overriding the params page
- Extracted shared method

### Version 9.0.1

- Fix countless executing the count query
- Rename row_comparison > tuple_comparison; ignore mixed orders

### Version 9.0.0

### Breaking Changes

#### Dropped support for deprecated stuff

- The `foundation`, `materialize`, `semantic` and `uikit` CSS extras have been removed:
  (See the [reasons](https://github.com/ddnexus/pagy/discussions/672#discussioncomment-9212328))
- **Javascript renamed files**
  - `pagy.js`: use `pagy.min.js`
  - `pagy-module.js`: use `pagy.mjs`
  - `pagy-dev.js`: use the `pagy.min.js`with the `pagy.min.js.map`
  - `pagy-module.d.ts`: use `pagy.d.ts`
- The Array type for the `:size` (e.g. `size: [1, 4, 4, 1]`) that generates the classic bar is not supported anymore: use the
  `:size` set to an integer with the `ends: true` variable (which are the default since 8.4.6). If a legacy bar remains REALLY a
  requirement, add `require 'pagy/extras/size` to your `pagy.rb` initalizer. (See the
  [size extra](https://ddnexus.github.io/pagy/docs/extras/size))

#### Simple renaming

We used `items` for too many things that confused even maintainers. The "items" are now referencing only the records/elements
fetched from a collection (plural entity), so they are kept as before (e.g. `pagy_*get_items` methods). The "number of items" (
singular entity), are now unmistakably referenced as the `limit` everywhere (code, API, files, extras, docs, etc.). The logic
didn't change so you have just to globally search 'items' and replace with `limit` and you should be done... or use the detailed
table below:

| Type              | Search                   | Replace                      |
|-------------------|--------------------------|------------------------------|
| Pagy Variable     | `:items`                 | `:limit`                     |
| Extra Path        | `pagy/extras/items`      | `pagy/extras/limit`          |
| Extra Variable    | `:items_param`           | `:limit_param`               |
| Default URL param | `items`                  | `limit`                      |
| Extra Variable    | `:items_extra`           | `:limit_extra`               |
| Extra Variable    | `:max_items`             | `:limit_max`  (now inverted) |
| Extra Method      | `pagy_items_selector_js` | `pagy_limit_selector_js`     |
| Extra Variable    | `:headers[:items]`       | `:headers[limit]`            |

#### Code changes

- The `DEFAULT[:anchor_string]` was useless and has been dropped.
- The `:anchor_string`and the `:fragment` are not instance variables anymore, but keyword arguments for all the helpers, because
  it is frontend code (see the [discussion](https://github.com/ddnexus/pagy/discussions/719)). Instead of passing them to the
  `pagy*` method in the controller, pass it to any `pagy_*nav` method in the view.
- A general internal revamp has changed the positional argument for the Pagy::* objects and constructors methods to keyword
  arguments. If you get a `wrong number of arguments (given 1, expected 0) (ArgumentError)`, just use a double splat `**`.

#### Possibly breaking overrides

- The internal Pagy protected methods have been renamed and refactored. If you use custom Pagy classes, you may need to search
  into the code.

## Changes

- Improve Keyset::Sequel and docs
- BREAKING: Rename :max_limit > :limit_max
- BREAKING: Rename variable, param, accessor, extra and helper "items" to "limit"
- Add playground keyset_ar.ru and keyset_s.ru apps and integration with the rest of the gems
- Add keyset pagination base files
  - Pagy::Keyset API
  - ActiveRecord and Sequel adapters
- BREAKING: Transform the vars positional hash argument in keyword arguments (double splat); internal renaming of setup/assign
  methods
- Refactor pagy_get_vars in various backend extras
- BREAKING: Refactor the fragment url
- BREAKING: Refactor the anchor_string system
- BREAKING: Drop the support for 8+ deprecations

### Version 8.6.3

- Add missing DEFAULT[:max_pages] hint to the config/pagy.rb
- Improve activerecord handling in playground apps
- Fix the missing "ar.pagy.aria_label.nav.other" (closes #577)

### Version 8.6.2

- Fix the legacy size in code and test
- Improve code readability and size check in series
- Fix the old terminology in the demo.ru app

### Version 8.6.1

- Update playground apps and e2e tests
- Update pagy.rb initializer

### Version 8.6.0

- Add translated pluralized aria_label.nav for "ar" locale (close #577)
- Deprecate the legacy bar. Insert first and last pages and gaps when needed into the simple bar

### Version 8.5.0

- Improve pagy playground launcher
- Refactor calendar class structure
- Remove automatic skipping of bundle install in playground apps
- Update ruby calendar test
- Update cypress calendar test
- Refactor calendar test environment to use activerecord
- Add code for calendar counts
- Remove redundant Warning
- Convert calendar.ru to calendar_rails.ru

### Version 8.4.5

- Fix pluralization rule link on locale files (#716)
- Install gems in pagy CI
- Indentation changes
- Remove :cycle false default
- Fill aria_label.nav ca pluralized entry (#715) (Fixes #581)
- Fix typos (#710)

### Version 8.4.4

- Update eslint: new configuration, stricter rules and javascript code

### Version 8.4.3

- Deprecate/rename javascript files keeping copies of old files to avoid production breaking changes; updates playground apps

### Version 8.4.2

- Limit the playground --rerun option to linux platforms
- Simplify and improve the js environment by using bun

### Version 8.4.1

- Fix pagy.in in pagy_get_items method  introduced in 8.4.0 (see #696) (closes #704) (closes #708) (#707)
- Fix renamed Frontend Helpers to JS Tools and typo `next_a` "aria-label" (#700)
- Fix rubocop

### Version 8.4.0

- Retrieve only @in items:
  - improve the performance of the last page in
    particular storage conditions (see #696)
- Improve pagy launcher for pagy devs

### Version 8.3.0

- Discontinue foundation materialize, semantic and uikit CSS extras
- Improve playground:
  - Add install option (automated in pagy development)
  - Fix HTML validation for all apps
  - Remove unused styles from the demo app
- Hardcode version in pagy.gemspec

### Version 8.2.2

- Add nav translation for ko (closes #592) (#690)

### Version 8.2.1

- Fix empty page param raising error (closes #689)

### Version 8.2.0

- Fix the '#pagy_url_for' method for calendar pagination (#688)
- Extend the use of pagy_get_page to the arel, array and countless extras
- Add the pagy_get_count method to the backend

### Version 8.1.2

- Added "da" locale for aria_label.nav (closes #583)

### Version 8.1.1

- Fixed broken aria-label for disabled links in Foundation (#685)
- Simplification of input variables and defaults: params and request_path are not instance variables

### Version 8.1.0

- Implement max_pages to limit the pagination regardless the actual count
- Improve efficiency of params in pagy_url_for
- Remove nil variables from DEFAULT
- Removed redundant @pages, aliased with @last

### Version 8.0.2

- Minor change in rails app and RM run config
- Fix canonical gem root:
  - Correct script.build: "NODE_PATH="$(bundle show 'pagy')/javascripts"
  - Move pagy.gemspec inside the gem root dir
- Fix for Turbo not intercepting changes in window.location
- Use require_relative for gem/lib files
- Complete translation of aria.nav for "ru" locale (close #599)
- Docs improvement and fixes

### Version 8.0.1

- Reorganize the gem root dir: it was the lib dir (containing everything), now is the gem dir (containing lib and everything
  else).
- Fix broken link in README

### Version 8.0.0

### Breaking changes

- Renamed/removed the following arguments for all the helpers:
  - Search `pagy_id:`, replace with `id:`
  - Search `nav_aria_label:`, replace with`aria_label:`
  - The `nav_i18n_key` has been removed: pass the interpolated/pluralized value as the `aria_label:` argument
  - The `item_i18n_key` has been removed: pass the interpolated/pluralized value as the `item_name:` argument
  - The `link_extra:` has been removed: its cumulative mechanism was confusing and error prone. The `:anchor_string` pagy
    variable substitutes it, however it's not an helper argument anymore, so you can assign it as the `DEFAULT[:anchor_string]`
    and/or pass it as any other pagy variable at object construction. (
    See [customize the link attributes](https://ddnexus.github.io/pagy/docs/how-to/#customize-the-link-attributes))
- HTML structure, classes and internal methods have been changed: they may break your views if you used custom stylesheets,
  templates or helper overrides. See the complete changes below if you notice any cosmetic changes or get some exception.
- The `navs` and `support` extras has been merged into the new [pagy extra](https://ddnexus.github.io/pagy/docs/extras/pagy).
  Search for `"extras/navs"` and
  `"extras/support"` and replace with `"extras/pagy"` (remove the duplicate if you used both)
- The `"extras/frontend_helpers"` has been renamed to `"extras/js_tools"`
- The build path for javascript builders has been updated to the canonical paths for gems, and has moved from the `lib` to
  the gem root. Notice that the correct setup in `package json` was still wrongly wrapped in the `gem` dir for 8.0.0-8.0.1, and it
  has finally been fixed in 8.0.2 (sorry for that):
  - 8.0.0-8.0.1 only: `build: "NODE_PATH=\"$(bundle show 'pagy')/gem/javascripts\" <your original command>"`
  - 8.0.2+: `build: "NODE_PATH=\"$(bundle show 'pagy')/javascripts\" <your original command>"`

### Changes

- Streamlined HTML and CSS helper structure. You may want to look at the actual output by running
  the [pagy demo](https://ddnexus.github.io/pagy/playground.md#3-demo-app)
  - The `pagy_nav` and `pagy_nav_js` helpers output a series of `a` tags inside a wrapper `nav` tag (nothing else)
  - The disabled links are so because they are missing the `href` attributes. (They also have the `role="link"`
    and `aria-disabled="true"` attributes)
  - The `current` and `gap` classes are assigned to the specific `a` tags
  - HTML changes
    - All the pagy helper root classes have been changed according to the following rule. For example:
      - `"pagy-nav"` > `"pagy nav"`
      - `"pagy-bootstrap-nav-js"` > `"pagy-bootstrap series-nav-js"`
      - and so on for all the helpers
    - The `active` class of the `*nav`/`*nav_js` links as been renamed as `current`
    - The `disabled`, `prev`, `next` and `pagy-combo-input` link classes have been removed (see
      the [stylesheets](https://ddnexus.github.io/pagy/docs/api/stylesheets/#pagy-scss) for details)
    - The `rel="prev"` and  `rel="next"` attributes have been dropped (they are obsolete)
    - The `<label>`/`</label>` and `<b>`/`</b>` wrappers in the dictionary files have been removed
- The `pagy_link_proc` method (only used internally or in your custom overriding) has been renamed to `pagy_anchor` and it works
  slightly differently:
  - The `link_extra:` key argument has been removed
    - The `extra` positional argument of the returned lambda has been removed
    - The `classes:` and `aria_label:` keyword arguments have been added to the returned lambda
- The `nav_aria_label_attr` method has been renamed as `nav_aria_label`
- The internal `prev_aria_label_attr` and `next_aria_label_attr` methods have been removed
- The `gap` in the nav bars is a disabled anchor element (`a` tag without a `href` attribute)
- The `pagy_prev_html` and `pagy_next_html` have been renamed as `pagy_prev_a` and `pagy_next_a`
- The `pagy_prev_link_tag` and `pagy_next_link_tag` have been renamed as `pagy_prev_link` and `pagy_next_link`
- The `*combo_nav_js` and `pagy_items_selector_js` helpers use a more efficient code
- The `src/pagy.ts` and relative built javascript files have been adapted to the above changes
- The [stylesheets](https://ddnexus.github.io/pagy/docs/api/stylesheets/) are a lot simpler as a consequence of the changes above
- All the `*combo-nav_js` of the framework extras use simpler structure and improve the look and feel consistently with their
  respective frameworks
- All the frontend extra have been normalized and are totally consistent with each other; a few may add the `classes:`
  argument to a few components, when the framework allows it.
- Created the [pagy playground](https://ddnexus.github.io/pagy/playground) system of apps working with the `pagy` executable.
- Internal renaming `FrontendHelpers` > `JSTools`
- Fix broken link of pagy.rb in docs (closes #668, #669)
- Docs Improvements
- Better code issue template

### Version 7.0.11

- Fix jsonapi prev and next keys for unavailable links (#665)
- Docs fixes

### Version 7.0.10

- Added name attribute to combo and items input tags; removed pagy_marked_link and refactored js input action

### Version 7.0.9

- Improve all pagy apps
- Normalized bootstrap, bulma, foundation, navs extra

### Version 7.0.8

- Update gems and fix a rubocop bug
- Add all styles to pagy_styles.ru
- Better pagy stylesheets
- Fix for uikit extra prev and next link duplicating chevrons
- Change aria_Label to aria_label for Arabic locale (#657)

### Version 7.0.7

- Fix for retype excluding linked files and showing category images
- Fix for first *nav_js page not active with trim (introduced with the #656 fix 7e2f118)
- Normalize pagy apps; implement pagy_styles.ru

### Version 7.0.6

- Internal renaming of frontend constants
- Fix for disabled links and missing or extra ARIA attributes in frontend extras
- Bootstrap fix for current page link; pagy.js fix for trim of current page (closes #656)

### Version 7.0.5

- Updated gems, npm modules, contributors
- Added the pagy stylesheets to the gem, updated apps, docs and manifest
- Added note for the metrics and compacted the chart section (closes #652)
- Docs: fix formatting, grammar in README.md (#654)
- Add note about PR base branches to readme (#653)

### Version 7.0.4

- Tailwind styles integrated with the pagy-items-selector-js (#646)
- Deprecated the "pagination" CSS class, use the "pagy" CSS class that has been added to all the interactive pagy helper outputs
- Fix indentation of cs locale (#648); add "pagy.aria_label.nav.few" entry, duplicating the "other" pluralization
- Update cs translations (#648)
- Expand/Correct changes about `pagy.prev` and `pagy.next` (#649)

### Version 7.0.3

- Remove extra space in `pagy_nav`, `pagy_nav_js` and `.pagy-combo-input`
- Refactor of tailwind styles and docs (closes #646)
- Add `pagy_tailwind_app.ru` (#646)
- Add missing CSS breaking change to the CHANGELOG (#646)

### Version 7.0.2

- Fix for missing to fetch count_args default (close #645)
- Non-code improvements

### Version 7.0.1

- Updates ckb translations to be complaint with ARIA in v7.x.x (#643)

### Version 7.0.0

### Breaking changes

- Dropped old rubies support: Pagy follows the [ruby end-of-life](https://endoflife.date/ruby) supported rubies now.
- Renamed `:i18n_key` > `:item_i18n_key`
- Refactored `support` extra
  - Renamed `pagy_prev_link` to `pagy_prev_html` to avoid confusion with pagy_prev_link_tag
  - Removed `pagy_next_link` to `pagy_next_html` to avoid confusion with pagy_next_link_tag
- Rack 3 breaking changes:
  - The `headers` extra produces all lowercase headers, regardless how you set
    them [see rack issue](https://github.com/rack/rack/issues/1592)
  - Removed `:escaped_html` option from `pagy_url_for` (only breaking if you override the method or use the option directly)
- Dictionary structure changes: (affects only app with custom helper/templates/dictionary entries)
  - The `nav` entry has been flattened: `pagy.nav.*` entries are now `pagy.*`:
    - If you have custom helpers/templates: search the keys that contain `'.nav.'` and replace them with `'.'`
    - If you have custom dictionary entries (overrides): remove the `'nav:'` line and unindent its block
  - A few labels used as `aria-label` have been added: you may want to add/use them to your custom helper/templates/dictionaries
    for ARIA compliance.
    - `pagy.aria_label.nav` Pluralized entry: used in the `nav` element
    - `pagy.aria_label.prev`, `pagy.aria_label.next` Single entry: used in the prev/next `a` link elements

### Default changes (possibly breaking test/views)

- Changed `Pagy::DEFAULT[:size]` variable defaults from `[1, 4, 4, 1]` to `7`. You can explicitly set/restore it in the
  initializer, if your app was relying on it.
- Added sensible `:size` defaults in Calendar Unit subclasses. You can explicitly set it in the initializer, if your app was
  relying on it.
  - `Pagy::Calendar::Day::DEFAULT[:size]` `31`
  - `Pagy::Calendar::Month::DEFAULT[:size]` `12`
  - `Pagy::Calendar::Quarter::DEFAULT[:size]` `4`
  - `Pagy::Calendar::Year::DEFAULT[:size]` `10`
- Changed a few `format` defaults in Calendar Unit subclasses. You can explicitly set it in the initializer, if your app was
  relying on it.
  - `Pagy::Calendar::Day::DEFAULT[:format]` from `'%Y-%m-%d'` to `'%d'`
  - `Pagy::Calendar::Month::DEFAULT[:format]` from `'%Y-%m'` to `'%b'`
  - `Pagy::Calendar::Quartr::DEFAULT[:format]` from `'%Y-Q%q'` to `'Q%q'`

### Visual changes (possibly breaking test/views)

- The ARIA label compliance required the refactoring of all the nav helpers that might look slightly different now:
- The text for `"Prev"` and `"Next"` is now used for the `aria-label` (actually as `"Previous"` and `"Next"`) and has been
  replaced in the UI as `<` and `>`. You can edit the dictionary entries `pagy.prev` and `pagy.next` if you want to revert it to
  the previous default (`&lsaquo;&nbsp;Prev` and `Next&nbsp;&rsaquo;`)

### CSS changes (possibly looking different/broken)

- The HTML of the current page in `pagy_nav` and `pagy_nav_js` has been changed from simple text (e.g. `5`) to a
  disabled link (e.g. `<a role="link" aria-disabled="true" aria-current="page">5</a>`). That affects your CSS rules and
  the old tailwind examples targeting the page links, now overreaching the current page.
  - You may fix eventual problems either by replacing the affected `a` rules with narrower `a[href]` selectors however if you use
    Tailwind we recommend to use the [improved tailwind style](https://ddnexus.github.io/pagy/docs/extras/tailwind/), that you can
    adapt in no time to anything you need.
- The extra spaces added between pages of `pagy_nav` and `pagy_nav_js` have been removed
- The `pagy-combo-nav-js .pagy-combo-input` internal element x-margins have been removed

### Internal renaming of private methods (unlikely to break anything)

You should not have used any of the private methods, but if you did so, you will get a `NoMethodError`
(undefined method...) very easy to fix by simply renaming, because there are no changes in the logic.

### Changes

- Added `:count_args` variable passed to the `collection.count(...)` statement (avoids overriding of `pagy-gets-vars` and
  expands count capabilities)
- [ARIA compliance](https://ddnexus.github.io/pagy/docs/api/aria/)
- Removed the pagy templates: they were a burden for maintenance with very limited usage,
  still [you can use them](http://ddnexus.github.io/pagy/docs/how-to/#using-your-pagination-templates)
- Added a simpler and faster nav without gaps (just pass an integer to the `:size`)
- Internal renaming of private frontend methods
- Updated code and tests for latest gem and npm module versions
- Internal improvements of automation scripts

### Version 6.5.0

- Add ckb: "pagination" entry (#641)

### Version 6.4.4

- Adapt pagy-ci workflow to run on current gemfile (no-failure for new gems enabled in leading version)
- Fix for wrong arguments types in meilisearch pagy_search extension

### Version 6.4.3

- Exclude coverage for prepend conditional, ruby < 3.0 syntax for prepend

### Version 6.4.2

- Better module overrides in jsonapi
- Replaced the is_a?(Hash) check for jsonapi reserved :page param with respond_to?(:Fetch) and prepended to the Frontend
- Docs improvements and fixes

### Version 6.4.1

- Remove dependency on base64 (#618)Ruby 3.3 prints a warning if base64 is used without specifying it in the gemfile.
  Ruby 3.4 will error

### Version 6.4.0

- Implement JSON:API specifications
- Added simpler nav generation, triggered by setting the size variable to a positive Integer
- Fix for pagy_calendar_app.ru

### Version 6.3.0

- Calendar improvements:
  - Added the :fit_time option to page_at and pagy_calendar_url_at methods. It avoids the OutOfRangeError by returning the first or last page
  - Added starting_time_for and page_offset_at feedback methods to the Calendar base class
  - Prepended the pagy_calendar_url_at to the Frontend and Backend
  - Added calendar showtime
- Updated node modules (dev on node 20)
- Updated Gemfile and fixed new rubocop complaints

### Version 6.2.0

- Add Belarusian locale (#567)
- Reordered RubyMine tasks (fix #541)

### Version 6.1.0

- Add Vietnamese locale (#550)
- Maintenance (docs, test, gems and node modules updates) fixes and improvements

### Version 6.0.4

- Updated gems and npm modules
- fix: Extras::Trim - fix trimming first page (#516)
- Fix for new rubocop

### Version 6.0.3

- Updated Gemfile and npm modules
- Improved Danish translation (#502)

### Version 6.0.2

- Fix: foundation combo helper (#469)

### Version 6.0.1

- Fix for pagy_bootstrap_combo_nav_js disabled input in certain bootstrap versions
- Fix for wrong module in standalone extra
- Fix for broken links in code comments and docs

### Version 6.0.0

### Breaking changes

Removed support for the deprecation of `5.0`:

- The `pagy_massage_params` method: use the `:params` variable set to a lambda `Proc` that does the same (but per instance). See [How to customize the params](https://ddnexus.github.io/pagy/docs/how-to#customize-the-params).
- The `activesupport` core dependency is now an optional requirement if you use the calendar: you must add `gem 'activesuport'` to your Gemfile if your app doesn't use rails.
- The plain `Time` objects in the `:period` variable: use only `ActiveSupport::TimeWithZone` objects.
- The `:offset` variable used by the `Pagy::Calendar::Week`: set the `Date.beginning_of_week` variable to the symbol of the first day of the week (e.g. `Date.beginning_of_week = :sunday`). Notice the default is `:monday` consistently with the ISO-8601 standard (and Rails).
- The `Pagy::DEFAULT[:elasticsearch_rails_search_method]`: use `Pagy::DEFAULT[:elasticsearch_rails_pagy_search]` instead.
- The `Pagy::DEFAULT[:searchkick_search_method]`: use `Pagy::DEFAULT[:searchkick_pagy_search`] instead.
- The `Pagy::DEFAULT[:meilisearch_search_method]`: use `Pagy::DEFAULT[:meilisearch_pagy_search]` instead.

#### Suggestions for a smooth removal of deprecations from pagy 5

- Upgrade to the latest version of pagy 5 (5.10.1)
- Run your tests or app
- Check the log for any deprecations message starting with '[PAGY WARNING]'
- Update your code as indicated by the messages
- Ensure that the log is now free from warnings
- If you are overriding the `pagy_url_for` method ensure you [add the extra parameters required.](https://github.com/ddnexus/pagy/discussions/424).
- Upgrade to pagy 6

### Changes

- Added new doc changes
- Replaced the gitter support with the Q&A discussion links
- reorganized cypress tests
- Updated run configurations
- Fix for bump.sh
- Add Meilisearch finite pagination by default in the meilisearch extra (#417)
- Allowing users to override the request path (#404)(squashed)
- Fix the args in the Pagy::Countless#series override (#411)(fixes #410)
- update: pagy_bare_rails script: for db testing (#379)
- Fix rubocop glitch
- added specific typescript installation to pagy-ci
- Chore: update github action versions to latest
- Reorganized pnpm setup and updated pagy.js to the new parcel default
- Reduced the error level for eslint rules
- Add Dependabot for GitHub Actions (#392)
- Fix NoMethodError when page param is hash (#390)
- Removed deprecated variables for the search extras
- Removed deprecated :offset variable for Calendar::Week
- Removed deprecated support for Time instances
- Removed activesupport hard dependency
- Removed support for deprecated pagy_massage_params
- fixup .gitignore
- Dropped docker env support
- Calendar code cleanup and normalization
- Implementation of calendar extra feature: pagy_calendar_url_at; refactoring and testing of the calendars files
- Pagy::Calendar::*: Simpler code and improved readability
- Added Pagy::Calendar#page_for(time) helper
- Fix for ignored pnpm files from other branch
- Updated cypress action
- Updated node modules and cypress 12
- Updated gems and rubocop config
- Remove aria-label="pager" (#412)
- Update korean translation (#413)
- Fixed small typo in German (#409)
- fix: Added Norwegian Nynorsk translation (#408)* Added Norwegian Nynorsk translation
- Fix Pagy configuration and documentation for renamed "pagy/extras/shared" to "pagy/extras/frontend_helpers" (#399)
- update link: to referenced rails apps (#384)
- Added reference to avoid N+1 queries when using `includes` (closes #378)
- Fix for broken links in docs
- Added note about overriding the `pagy_headers` method in the `headers` extra
- Bundle and npm updates
- Add: documentation changes (#376)
- Added post link in README; fix for another example in how-to.md
- Fix for example in how-to.md (closes #373)
- Added missing info
- Fix: docs - typo, minimise ambiguity (#372)

### Version 5.10.1

- Changed default for :meilisearch_search method to always defined :ms_search method

### Version 5.10.0

- Adjusted Psych::VERSION condition to avoid deprecations
- Improvement of search extras: (closes #367) (closes #369)
  - Added DEFAULT[:*_search] variables used to call the original :search method
  - Deprecated and renamed DEFAULT[:*_search_method] to DEFAULT[:*_pagy_search]
  - Updated config, docs and CHANGELOG deprecations
- Fix for wrong calculation of :page in Pagy.new_from_meilisearch (closes #368)
- Faster assignment of javascript path
- Simplified webpack config, added rollup config and normalized doc
- Better javascript doc (closes #365)
- Added specific webpack javascript configuration info; a few minor doc adjustments
- Added specific webpack javascript configuration info
- Update javascript.md (#362)

### Version 5.9.3

- Fix for too generalized html_escape in pagy_url_for. (closes #363)
- Docs updates

### Version 5.9.2

- Added info about using pagy-module.js with importmap
- Bundle and npm update
- Fix for non escaped query strings in links
- Small improvements in src files
- Added legacy methods to use the javascript files
- Added missing require in docs
- Updated docs with the proper way to use pagy with esbuild
- Docs improvements
- Improved the javascript files docs and moved into the lib/javascripts/README.md file

### Version 5.9.1

- Updated build.sh to set window.Pagy in pagy-dev.js
- Update pagy.md (#360)
- Minor - fix syntax highlighting (#361)
- Updated info about the current way to use pagy.js with jsbuilder-rails/esbundle

### Version 5.9.0

- Npm and bundle update
- Fix and refactoring of pagy calendar calculation of local time (closes #358):
  - The `Pagy::Calendar::*` classes require ActiveSupport (temporarily added activesupport dependency that will e replaced in 6.0)
  - The `:period` items can be `Time` or `TimeWithZone` objects (which work across DST ranges)
  - Deprecated use of `:offset` to set the first weekday, in favour of `Date.beginning_of_week` (default to `:monday`)
  - Added DST specific tests
  - Fix for calculation of label quarter
  - Updated docs and tests
- Fix for wrong offset in gearbox extra (closes #356)
- Reduced the public interface of pagy.js and relative files to just `version` and `init()`
- Add Ruby 3.1 to CI (#354)

### Version 5.8.1

- Shorter warning messages and minified pagy.js
- Fix for wrong decoding of non latin charsets

### Version 5.8.0

- The JSON in data-pagy attributes is base64 encoded: it is smaller than HTML escaped and it avoids crawlers to follow look-alike links
- Removed redundant PagyJSON type

### Version 5.7.6

- Updated Gemfile.lock and package-lock.json
- Broader browserslist and better doc for javascript
- Updated build.sh
- Improvement of pagy.ts:
  - Moved warn() out of init()
  - More inclusive try block
  - Simplified rjsObserver function
  - Replaced getOwnPropertyNames with keys
- Fix for missing entry in CHANGELOG.md

### Version 5.7.5

- Fix for unwanted offset in source map data

### Version 5.7.4

- Added parcel-plugin-nuke-dist
- Renamed pagy.mjs -> pagy-module.js; added TypeScript declaration file and build.sh

### Version 5.7.3

- npm update
- Simper pagy-dev.js; added pagy.mjs module
- Updated .eslintrc.json and package.json files

### Version 5.7.2

- Simplified keyword naming
- Better compilation and development support for pagy.js. Replaced babel with parcel.

### Version 5.7.1

- Simpler syntax for nav js helpers
- Added input e2e test file
- Simpler abstraction of input based js helpers

### Version 5.7.0

- Bundle and npm update
- TypeScript/Javascript improvements:
  - More efficient handling of responsive *nav_js helpers
  - Replaced window resize event listener with ResizeObserver
  - More robust input based js helpers (catching possible invalid user input)
  - Improved typing, function naming
  - Added error catching

### Version 5.6.10

- Improvement and fixes for client-side related structure
- Replaced e2e/cy script with a couple of npm scripts backed by the start-server-and-test
- Added trim test for items_selector_js; cleanup of e2e dir
- Normalized json files indentation
- Added trim to e2e tests
- Reorganized cypress tests: faster and more accurate
- Simpler pagy.ts

### Version 5.6.9

- Better doc for e2e related commands in docker/README.md
- Updated package-lock.json and improved ci-cache.sh
- Used better syntax and code style in typescript files
- Updated copyright year
- Refactoring of typescript related files and dirs:
  - Moved e2e dir in the project root
  - Added typescript linting plugins and configuration
  - Converted all the e2e test to typescript
- Updated Docker, VSC and .idea configurations

### Version 5.6.8

- Reorganized TypeScript/JavaScript with npm workspaces and better script naming
- Fix typo in gemspec
- Better typescript configuration with source maps and declarations

### Version 5.6.7

- GitHub Actions: added quotes to version ruby 3.0
- Added typescript + babel environment for better pagy.js
- Updated e2e environment
- Updated gemfiles
- Fix for support doc snippets and other typos
- Minor update of gemfiles and docs
- Added more gemspec metadata entries (closes #351)

### Version 5.6.6

- Docs improvements
- Added **_ to series and sequels
- Updated rematch and gemfiles
- Updated release-gem.yml workflow

### Version 5.6.5

- Improved a few code comments; added post link in README
- Full name for translate, aliased as t
- Added check for no calendar units in pagy_calendar configuration
- Updated gemfiles
- Small docs layout adjustment

### Version 5.6.4

- Updated RM run configurations
- Fix for missing innerHTML reset, unintentionally committed during a cleanup (closes #350)
- Updated gemfiles, docs and comments

### Version 5.6.3

- Improved readability and efficiency of calendar files
- Fix for English spelling in local variable name

### Version 5.6.2

- Updated pagy.manifest for Tamil locale
- Add Tamil (ta) translation (#349)
- Internal changes in calendar files:
  - Simpler calculations for month mixin
  - Normalized naming of non-api methods
  - Better comments
- Pagy::I18n: small performance improvement
- Docs reorganization
- Update paths_ignore for skip CI and docs

### Version 5.6.1

- Updated cypress and bundler
- Improved efficiency of unit labelling and support for custom calendar unit sub formats
- Added missing initializer default for calendar quarter and missing doc for custom units

### Version 5.6.0

- Updated gemfiles
- Updated docs
- Added calendar quarter tests
- Internal calendar refactoring to allow custom units; added quarter unit

### Version 5.5.1

- Docs updates
- upgrade bootstrap template navs: call pagy_link_proc with link_extra key (#348)
- Renamed internal #time_for -> #start_for
- Docs fixes and improvements

### Version 5.5.0

- Updated cypress and related packages
- **Calendar API: FINAL breaking changes** (stable from now on):
  - Refactoring of calendar classes and variables:
  - Moved calendar defaults from `Pagy::DEFAULT` to class-specific `Pagy::Calendar::*::DEFAULT`
  - Renamed variables:
    - `:minmax` -> `:period`
    - `:time_order` -> `:order`
    - `:week_offset` -> `:offset`
    - `:*_format` -> `:format`
  - Returning local time instead of UTC time for the utc accessors, now renamed:
    - `#utc_from` -> `#from` (use `from.utc` if you need it)
    - `#utc_to` -> `#to` (use `to.utc` if you need it)
  - Inverted the logic for the `:skip` key in the `#pagy_calendar` conf, now renamed:
    - `:skip` -> `:active`
  - Renamed methods:
    - `#pagy_calendar_minmax` -> `#pagy_calendar_period`
    - `#pagy_calendar_filtered` -> `#pagy_calendar_filter`
  - Added alternative way to delegate the localization to i18n without the i18n extra
  - Updated `pagy_calendar_app.ru`
  - Fix for wrong reordering in `:desc` order
  - Documentation fixes and improvements
  - Removed the warning for the API changes: the API is stable after these changes

### Version 5.4.0

- **Calendar API breaking changes** for refactoring of `Pagy::Calendar` and calendar extra:
  - Added complete compatibility with all the backend extras
  - Simpler usage with automatic handling of pagy objects
  - Less variables and simpler requirements for the methods to implement
- Updated gemfiles
- The localize method overridden by the i18n extra must receive a format
- Series and sequels use keyword arguments and pagy_*nav methods accepts a size keyword argument
- Docs improvements
- Removed unnecessary empty section in calendar docs
- Fixes for typos and misalignment

### Version 5.3.1

- Added screenshot to the calendar extra (closes #346)
- Added bump.sh script to bump the version in multiple files; check for consistency and optionally commit the changes
- Minor fixes
- Changelog improvements
- Reversed CHANGELOG (closes #345)
- Calendar I18n small internal renaming and docs improvements

### Version 5.3.0

- Implemented localization of time labels through the i18n extra delegation
- Renamed internal module and files of SharedExtra to FrontendHelpers
- Added support for `*nav_js` to Calendar
- Simplified page labelling, moved into the pagy classes and removed frontend methods
- Deprecated `pagy_massage_params`: use the :params variable set to a Proc that does the same, but per instance
- Added apps README
- Completed overflow fix for pagy Countless

### Version 5.2.3

- Fix for overflow :empty_page in regular Pagy instances, not returning an empty page
- docs: add tutorial, simplify header (#343)
- Refactoring of rails_inline_input.rb (include and close #342)

### Version 5.2.2

- Fix for missing defined?(Calendar) checks; small simplification in headers extra
- Calendar docs improvements and fixes

### Version 5.2.1

- Reorganization of mock collection classes; enabled rubocop layout in tests
- Small refactoring of the overview extra
- A few improvements for the Calendar pagination; added the current_page_label method

### Version 5.2.0

- Small changes in code; updated gemfiles, tests and docs
- Implemented calendar extra to paginate a Time periods by unit (year, month, week or day)
- Added pagy_labeler frontend method overridable for changing the link text from a simple page number to any arbitrary string
- Enabled rubocop Style/Documentation cop
- Updated npm modules and gemfiles
- Docker better mounts
- Used gem-generic release-gem action

### Version 5.1.3

- Added single action standalone rails_inline_output.rb
- Small improvements in code and docs
- Fix for rails problem with internal params in pagy URL (closes #341)
- Documentation improvements
- A few details tag in the README should be opened by default

### Version 5.1.2

- Refactoring of pagy_url_for and relative test:
  - Fix for ignoring the items_extra variable
  - Replaced request.GET with request.params to enable POST pagination
  - Refactoring of Mock test classes for better handling of params

### Version 5.1.1

- This reverts commit 1d77e672d5b7813108b40c13ca93fdec045f4c03.
  Generating the URL by using the application params method breaks rails apps because it requires manual changes in the apps.

### Version 5.1.0

- Fix and refactoring of pagy_url_for and relative test:
  - Fix for ignoring the params not coming from the request
  - Fix for ignoring the items_extra variable
  - Refactoring of Mock test classes for better handling of params
- Improved code comments, formatting and docs fixes
- Countless extra: simplified code, internal renaming of locals and docs update

### Version 5.0.1

- Updated docs and issue templates
- Added cypress-dark theme to e2e test
- Refactoring of coverage to include 100% of line and condition branches covered
- Simplification of metadata extra
- Refactoring of exceptions
- Added CHANGELOG_LEGACY

### Version 5.0.0

### Breaking changes - 1. Code update

Pagy 4 dropped the compatibility for old ruby versions `>2.5` and started to refactor the code using more modern syntax and paradigms and better performance. It deprecated the legacy ones, printing deprecation warnings and upgrading instruction in the log, but still supporting its legacy API. Pagy 5.0.0 cleans up and removes all that transitional support code.

The changes for upgrading your app cannot be fixed with simple search and replace, but fear not! Fixing them should just take a few minutes with the following steps:

- Upgrade to the latest version of pagy 4
- Run your tests or app
- Check the log for any deprecations message starting with '[PAGY WARNING]'
- Update your code as indicated by the messages
- Ensure that the log is now free from warnings
- Upgrade to pagy 5

FYI: Here is the list of the deprecations that are not supported anymore:

#### Removed support for deprecated variables

- `Pagy::VARS[:anchor]` is now `Pagy::DEFAULT[:fragment]`

#### Removed support for deprecated arguments order

- The argument order in `pagy_url_for(page, pagy)` is now inverted: `pagy_url_for(pagy, page)`

#### Removed support for deprecated positional arguments

The following optional positional arguments are passed with keywords arguments in all the pagy helpers:

- The `id` html attribute string with the `id` keyword
- The `url|absolute` flag with the `absolute` keyword
- The `item_name` string with the `item_name` keyword
- The `extra|link_extra` string with the `link_extra` keyword
- The `text` string with the `text` keyword

### Breaking changes - 2. Simple search and replace

There are a few renaming that have not been deprecated in previous versions because they are extremely easy to fix with simple search and replace (while implementing deprecations would have been detrimental to performance and complex for no reason)

#### Consistency renaming

A few elements have been renamed: you code may or may not contain them. Just search and replace the following strings:

- Rename `Pagy::VARS` to `Pagy::DEFAULT`
- Rename `enable_items_extra` to `items_extra`
- Rename `enable_trim_extra` to `trim_extra`
- Rename `Pagy::Helpers` to `Pagy::UrlHelpers`
- Rename `pagy_get_params` to `pagy_massage_params`

#### Items accessor

The items accessor does not adjust for the actual items in the last page anymore. This should not affect normal usage, so you can ignore this change unless you build something on that assumption.

If your code is relying on the actual number of items **in** the page, then just replace `@pagy.items` with `@pagy.in` wherever you meant that.

FYI: The `@pagy.items` is now always equal to `@pagy.vars[:items]` (i.e. the requested items), while the `@pagy.in` returns the actual items in the page (which could be less than the `items` when the page is the last page)

### Changes

- Removed support for deprecations
- Refactoring of Pagy and Pagy::Countless classes, I18n, and url helpers
- Refactoring of the docker environment, addition of ready to use VSCode setup
- Changed general module structure (use of prepend instead of re-opening modules)
- Added gearbox extra for geared pagination
- Added configuration files for a full working VSCode devcontainer environment
- Added Run Configurations for RubyMine
- Improved the usage of e2e tests
- Updated doc, gemfiles and github workflow
- Other minor fixes and improvements in code and doc

### Version 4.11.0

### Changes

- added countless_minimal feature
- added missing deprecation to countless class
- doc improvements

### Commits

- [5103ee9](http://github.com/ddnexus/pagy/commit/5103ee9): added countless_minimal feature
- [ed6bc0b](http://github.com/ddnexus/pagy/commit/ed6bc0b): added missing deprecation to countless class
- [bedb2d6](http://github.com/ddnexus/pagy/commit/bedb2d6): Fix - link directly to docs [ci-skip] (#328)
- [74aa300](http://github.com/ddnexus/pagy/commit/74aa300): Remove minor extra parenthesis in trim docs [ci-skip] (#326)
- [f8b96eb](http://github.com/ddnexus/pagy/commit/f8b96eb): docs - add links directly to docs page [ci-skip] (#324)

### Version 4.10.2

### Changes

- improve the customization of url when using the trim extra
- better tailwind rules
- doc fixes and improvements

### Commits

- [9e7fd48](http://github.com/ddnexus/pagy/commit/9e7fd48): improve the customization of url when using the trim extra (closes #325)
- [0d48b5f](http://github.com/ddnexus/pagy/commit/0d48b5f): better handling of extra docker-compose run files
- [f5ed8f6](http://github.com/ddnexus/pagy/commit/f5ed8f6): added note about the possible workaround for bundler/inline bug
- [2715660](http://github.com/ddnexus/pagy/commit/2715660): doc fixes
- [7187101](http://github.com/ddnexus/pagy/commit/7187101): Javascript doc improvements
- [24b7e78](http://github.com/ddnexus/pagy/commit/24b7e78): release workflow refactoring
- [20d1d8e](http://github.com/ddnexus/pagy/commit/20d1d8e): chore: add github action - publish & tag versions (#322)
- [922fd7f](http://github.com/ddnexus/pagy/commit/922fd7f): better tailwind rules
- [28983e6](http://github.com/ddnexus/pagy/commit/28983e6): added doc for using Searchkick.pagy_search (closes #319)

### Version 4.10.1

### Changes

- fixes for meilisearch extra

### Commits

- [ec748c2](http://github.com/ddnexus/pagy/commit/ec748c2): Fixes for Meilisearch extra (#318)

### Version 4.10.0

### Changes

- added arabic locale

### Commits

- [9bc548e](http://github.com/ddnexus/pagy/commit/9bc548e): added missing keys for Arabic (#317) (closes #295)
- [1b95674](http://github.com/ddnexus/pagy/commit/1b95674): Add arabic locale, pluralization and tests; alpha ordered tests

### Version 4.9.0

### Changes

- added meilisearch extra
- docs fixes

### Commits

- [572ed78](http://github.com/ddnexus/pagy/commit/572ed78): updated pagy-ci.yml
- [8fe1d19](http://github.com/ddnexus/pagy/commit/8fe1d19): Fix documents for searchkick (#315)
- [744717f](http://github.com/ddnexus/pagy/commit/744717f): Meilisearch extra (#316)
- [fb65260](http://github.com/ddnexus/pagy/commit/fb65260): updated issue templates
- [1c3d283](http://github.com/ddnexus/pagy/commit/1c3d283): Added Pagy::Console links to README

### Version 4.8.1

### Changes

- fix for deprecation and test improvements

### Commits

- [0f1a7f2](http://github.com/ddnexus/pagy/commit/0f1a7f2): improved deprecation_tests (see #311)
- [7e9c9cd](http://github.com/ddnexus/pagy/commit/7e9c9cd): fix(pagy): deprecated_var is a class method (#311)

### Version 4.8.0

### Changes

- Lowered minimal ruby version requirement to 2.5+
- Simpler tests using the `rematch` gem
- Docker environment improvements

### Commits

- [b5b500a](http://github.com/ddnexus/pagy/commit/b5b500a): Merge branch 'backport-to-2.5' into dev
- [001f22f](http://github.com/ddnexus/pagy/commit/001f22f): fix for different handling of module prepend in ruby <3
- [d6d71a5](http://github.com/ddnexus/pagy/commit/d6d71a5): fix for frozen error handled differently on ruby <3
- [8a2cf76](http://github.com/ddnexus/pagy/commit/8a2cf76): replaced syntax not available in ruby 2.5
- [14b3a0c](http://github.com/ddnexus/pagy/commit/14b3a0c): lowered the minimum ruby version requirement to 2.5
- [8c76943](http://github.com/ddnexus/pagy/commit/8c76943): updated Gemfile.lock
- [934f602](http://github.com/ddnexus/pagy/commit/934f602): added comment in doc example about the inverted order of arguments in previous versions
- [acd59f5](http://github.com/ddnexus/pagy/commit/acd59f5): moved command out of default docker-compose to its own file in order to avoid error before first bundle install
- [527a14f](http://github.com/ddnexus/pagy/commit/527a14f): Gemfile update
- [842de05](http://github.com/ddnexus/pagy/commit/842de05): better tests and stricter rubocop
- [d82e95c](http://github.com/ddnexus/pagy/commit/d82e95c): Gemfile updated and used for CI
- [675f587](http://github.com/ddnexus/pagy/commit/675f587): Gemfile: added gemspec and updated rematch
- [b0a6603](http://github.com/ddnexus/pagy/commit/b0a6603): Updated to rematch 1.0 and copyright year
- [8bda6a9](http://github.com/ddnexus/pagy/commit/8bda6a9): added rematch gem and reset rematch stores
- [f62fa88](http://github.com/ddnexus/pagy/commit/f62fa88): added e2e tests to ci
- [04755aa](http://github.com/ddnexus/pagy/commit/04755aa): updated puma
- [4ec03f1](http://github.com/ddnexus/pagy/commit/4ec03f1): added HTML validation for all the helpers and styles
- [87b2f62](http://github.com/ddnexus/pagy/commit/87b2f62): refactoring e2e: replaced snapshot plugin; updated to cypress 7.3.0

### Version 4.7.1

### Changes

- Fixed a couple of HTML validity issues with uikit and materialize combo_nav_js
- Improved rematch testing
- Updated the documentation

### Commits

- [8cb4193](http://github.com/ddnexus/pagy/commit/8cb4193): fixed and simplified uikit combo nav
- [f762aca](http://github.com/ddnexus/pagy/commit/f762aca): used chip class for materialized combo nav
- [064a2b5](http://github.com/ddnexus/pagy/commit/064a2b5): rematched tests
- [2a45321](http://github.com/ddnexus/pagy/commit/2a45321): expanded the combo nav label to the whole text
- [37c65a8](http://github.com/ddnexus/pagy/commit/37c65a8): fixed and simplified materialize combo nav
- [789773b](http://github.com/ddnexus/pagy/commit/789773b): removed faulty PAGY_REMATCH variable function; added rematch_all rake task
- [eb2df6d](http://github.com/ddnexus/pagy/commit/eb2df6d): updated docs [skip ci]

### Version 4.7.0

### Changes

- Refactoring of pagy-json tags into data attributes
- Added the rematch testing system to ease maintenance of tests
- Added Ukrainian locale
- updated Gemfile and doc

### Commits

- [887c783](http://github.com/ddnexus/pagy/commit/887c783): better doc for E2E testing
- [738fd93](http://github.com/ddnexus/pagy/commit/738fd93): updated manifest
- [0110e3a](http://github.com/ddnexus/pagy/commit/0110e3a): Ukrainian locale (#310)
- [9265cd2](http://github.com/ddnexus/pagy/commit/9265cd2): extended must_rematch to other tests
- [d217965](http://github.com/ddnexus/pagy/commit/d217965): refactoring of pagy_json_tag to pagy_json_attr: from script tag to data attribute
- [57e6f91](http://github.com/ddnexus/pagy/commit/57e6f91): added rematch test to ease maintenance of tests
- [94561a0](http://github.com/ddnexus/pagy/commit/94561a0): updated puma

### Version 4.6.0

### Changes

- Added labels to all input tags for improved usability and compliance
- Fixed a few HTML typos and redundant or illegal attributes
- Improved Sinatra app usable for experiment with pagy and providing support for ode issues
- Documentation improvements
- Added E2E testing for all pagy helpers
- GitHub management improvements

### Commits

- [0fe2e49](http://github.com/ddnexus/pagy/commit/0fe2e49): added helpers and navbar tests for all the styles; updated docker readme
- [26b57a8](http://github.com/ddnexus/pagy/commit/26b57a8): Added fix for Cypress bug affecting the snapshots plugin, improved cypress dockerfiles; added cypress studio and basic try
- [7693b3e](http://github.com/ddnexus/pagy/commit/7693b3e): cypress: updated images; better open-cypress.yml with persistent configuration settings; added snapshots plugin
- [94e262f](http://github.com/ddnexus/pagy/commit/94e262f): added label tag to the pagy inputs
- [f5e3482](http://github.com/ddnexus/pagy/commit/f5e3482): removed redundant role="navigation" attribute from nav tags; removed illegal aria-label="pager" from semantic div
- [0467d32](http://github.com/ddnexus/pagy/commit/0467d32): fixes for html typos in semantic, foundation and materialize helpers
- [3f2c9f0](http://github.com/ddnexus/pagy/commit/3f2c9f0): doc small fixes
- [bff8c5f](http://github.com/ddnexus/pagy/commit/bff8c5f): refactoring of sinatra apps; added quick-start doc section
- [b0baaa5](http://github.com/ddnexus/pagy/commit/b0baaa5): simplified standalone_app.ru
- [5442b34](http://github.com/ddnexus/pagy/commit/5442b34): fix broken link [ci skip]
- [17b1faf](http://github.com/ddnexus/pagy/commit/17b1faf): improved issue template [ci skip]
- [f933cd7](http://github.com/ddnexus/pagy/commit/f933cd7): Added issue template and standalone_app to use as a scaffold for reproducing issues [ci skip]
- [c8e41e3](http://github.com/ddnexus/pagy/commit/c8e41e3): added superails screencast; resized thumbnails [skip ci]
- [6116c32](http://github.com/ddnexus/pagy/commit/6116c32): added a few references in the how_to doc [skip ci]

### Version 4.5.1

### Changes

- Better Pagy::Console usability

### Commits

- [4ee8bbf](http://github.com/ddnexus/pagy/commit/4ee8bbf): pagy console extracted in its own module

### Version 4.5.0

### Changes

- Added E2E infrastructure for development and testing
- Refactoring of Javascript and docker environment

### Commits

- [1b7b014](http://github.com/ddnexus/pagy/commit/1b7b014): Add: clarity, further instructions to run pagy tests [skip ci] (#304)
- [7c723ba](http://github.com/ddnexus/pagy/commit/7c723ba): added basic structure to run cypress tests and open the cypress IDE

### Version 4.4.0

### Changes

- Passing positional arguments (besides `@pagy`) to all the helpers is deprecated and it will be supported only until pagy 5.0
- All the helpers accept more optional keyword arguments variables, for example:
  - `pagy*_nav(@pagy, id: 'my-id', link-extra: '...')`
  - `pagy*_nav_js(@pagy, id: 'my-id', link-extra: '...', steps: {...})`
  - `pagy*_combo_nav_js(@pagy, id: 'my-id', link-extra: '...')`
  - `pagy_items_selector_js(pagy, id: 'my-id', item_name: '...', i18n_key: '...', link_extra: '...')`
  - `pagy_info(@pagy, id: 'my-id', item_name: '...', i18n_key: '...')`
  - `pagy_prev_link(@pagy, text: '...', link_extra: '...')`
  - `pagy_next_link(@pagy, text: '...', link_extra: '...')`
  - `pagy_link_proc(@pagy, link_extra: '...')`
  - `pagy_url_for(pagy, page, absolute: nil)` (notice the inverted page/pagy order with the legacy`pagy_url_for(page, pagy, url=nil)`)
- deprecated `:anchor` variable in favor of `:fragment`
- Refactoring of rake tasks, tests, items extra, javascript helpers
- Gems and documentation updates and fixes
- Added standalone extra

### Commits

- [b69a2f2](http://github.com/ddnexus/pagy/commit/b69a2f2): Merge branch 'dev'
- [9611eac](http://github.com/ddnexus/pagy/commit/9611eac): updated CHANGELOG
- [d977767](http://github.com/ddnexus/pagy/commit/d977767): better links in extras.md
- [aa83126](http://github.com/ddnexus/pagy/commit/aa83126): added standalone extra
- [e22c9c3](http://github.com/ddnexus/pagy/commit/e22c9c3): Merge branch 'args-refactoring' into dev
- [d791a04](http://github.com/ddnexus/pagy/commit/d791a04): deprecated :anchor variable in favor of :fragment
- [e91e4da](http://github.com/ddnexus/pagy/commit/e91e4da): [skip ci] update javascript documentation (#305)
- [034afe8](http://github.com/ddnexus/pagy/commit/034afe8): config/pagy.rb: moved pagy variables block at the beginning
- [bd0bb6c](http://github.com/ddnexus/pagy/commit/bd0bb6c): renamed :trim variable to the more explicit :enable_trim_extra; removed redundant tests
- [76096e7](http://github.com/ddnexus/pagy/commit/76096e7): simplified the items extra, and added its features to the array and arel extras
- [86fb20e](http://github.com/ddnexus/pagy/commit/86fb20e): fixed pagy_semantic_nav hellips
- [2382482](http://github.com/ddnexus/pagy/commit/2382482): added link_extra keyword arg to pagy_items_selector_js
- [627a0dc](http://github.com/ddnexus/pagy/commit/627a0dc): also pagy_link_proc uses keyword arguments
- [8ff2665](http://github.com/ddnexus/pagy/commit/8ff2665): completed args conversion also for pagy_url_for and pagy_metadata
- [6a8fbb4](http://github.com/ddnexus/pagy/commit/6a8fbb4): added "pagy-info" and "pagy-items-selector-js" classes to helpers output
- [ad350e1](http://github.com/ddnexus/pagy/commit/ad350e1): pagy_info wrapped into span tag, and added id keyword arg
- [2faca63](http://github.com/ddnexus/pagy/commit/2faca63): small improvement to pagy_url_for
- [2acbac6](http://github.com/ddnexus/pagy/commit/2acbac6): added "pagy-njs" class to all pagy*_nav_js helpers
- [670f5a5](http://github.com/ddnexus/pagy/commit/670f5a5): updated documentation
- [64347f8](http://github.com/ddnexus/pagy/commit/64347f8): changed the argument order from pagy_url_for(page, pagy) > pagy_url_for(pagy, page) for consistency with all the other helpers; deprecation warning and support for pagy < 5
- [393d216](http://github.com/ddnexus/pagy/commit/393d216): updated gems
- [d2891fb](http://github.com/ddnexus/pagy/commit/d2891fb): refactoring and additions of tests
- [1538f24](http://github.com/ddnexus/pagy/commit/1538f24): updated tests with keyword arguments
- [6ce0c1d](http://github.com/ddnexus/pagy/commit/6ce0c1d): helpers: deprecated positional id argument and added keyword arguments
- [9fd7930](http://github.com/ddnexus/pagy/commit/9fd7930): removed id method and added id arg to the pagy*_nav helpers
- [ec74e4f](http://github.com/ddnexus/pagy/commit/ec74e4f): removed the mandatory id arg passed to pagy_json_tag; updated tests; simplified pagy.js using previousSibling
- [3f13014](http://github.com/ddnexus/pagy/commit/3f13014): added resource post and relative link [skip ci]
- [7927f37](http://github.com/ddnexus/pagy/commit/7927f37): added manifest:check to CI for master and dev and to the default rake task
- [01f8f1c](http://github.com/ddnexus/pagy/commit/01f8f1c): refactoring of Rakefile now split in different task files

### Version 4.3.0

### Changes

- Improved efficiency of #series algorithm
- Added new features to the trim extra
- Added rubocop compliance (additional rubocop-packaging plugin)
- Many test tasks improvements
- CI transition from Travis to Github Action

### Commits

- [80cef45](http://github.com/ddnexus/pagy/commit/80cef45): updated Gemfile
- [3cc464b](http://github.com/ddnexus/pagy/commit/3cc464b): added coverage_summary at the end of the default task; added possibility of creating a coverage report
- [0424b71](http://github.com/ddnexus/pagy/commit/0424b71): docs and comment additions/fixes [skip ci]
- [b6115e7](http://github.com/ddnexus/pagy/commit/b6115e7): Added explicit pagy.manifest to comply with rubocop-packaging (see also #297)
- [ef1980e](http://github.com/ddnexus/pagy/commit/ef1980e): refactoring of CI from Travis to Github Actions
- [9fb1195](http://github.com/ddnexus/pagy/commit/9fb1195): more verbose condition
- [fe565eb](http://github.com/ddnexus/pagy/commit/fe565eb): removed RUN_RUBOCOP
- [60be542](http://github.com/ddnexus/pagy/commit/60be542): fix missing codecov formatter/circular reference problem
- [8c4ccfc](http://github.com/ddnexus/pagy/commit/8c4ccfc): rubocop: added rubocop-packaging plugin; expanded inclusion of files
- [487eb70](http://github.com/ddnexus/pagy/commit/487eb70): small code normalization
- [3ea2d27](http://github.com/ddnexus/pagy/commit/3ea2d27): codecov gets already required in travis on ruby 3.0 (but not locally)
- [e17d294](http://github.com/ddnexus/pagy/commit/e17d294): improved series algorithm efficiency (now up to ~5x faster and ~2.3x lighter than the previous one)
- [1051e6d](http://github.com/ddnexus/pagy/commit/1051e6d): trim feature: added :trim variable to opt-out to trim even with the trim extra loaded, or invert the logic to out-in by setting Pagy::VARS[:trim] to false
- [ece4daa](http://github.com/ddnexus/pagy/commit/ece4daa): minor code restyling
- [0e3a09d](http://github.com/ddnexus/pagy/commit/0e3a09d): added :ide_development bundle group; small README changes
- [5e6f749](http://github.com/ddnexus/pagy/commit/5e6f749): simpler test tasks
- [e5ee4e7](http://github.com/ddnexus/pagy/commit/e5ee4e7): CI: Remove Bundler 1.x workaround (#296)The "github" source now has that form, already.

### Version 4.2.0

### Changes

- Fix for `Pagy::Frontend::I18n` conflicting with rthe `I18n` gem namespace after inclusion and safer renaming of other modules
- Fix the simplecov setup reporting less coverage than what's actually covered
- Updated Tailwind styles
- Included the test dir in the coverage check, refactoring and additions of tests
- Big code restyling following ruby 3.0 syntax and cops; tried to make the code simpler, more readable and verbose with almost negligible performance loss.

### Commits

- [a62ae94](http://github.com/ddnexus/pagy/commit/a62ae94): Updated README
- [9efb908](http://github.com/ddnexus/pagy/commit/9efb908): removed ENABLE_OJ from tests and travis config
- [dede255](http://github.com/ddnexus/pagy/commit/dede255): removed RUN_SIMPLECOV option
- [fc614bd](http://github.com/ddnexus/pagy/commit/fc614bd): fix/refactoring for simplecov setup reporting less coverage than what's actually covered
- [5e6ecf1](http://github.com/ddnexus/pagy/commit/5e6ecf1): refactoring of Rakefile
- [aea25a1](http://github.com/ddnexus/pagy/commit/aea25a1): code-restyling: test
- [cb67353](http://github.com/ddnexus/pagy/commit/cb67353): code-restyling: extras
- [b2dc35a](http://github.com/ddnexus/pagy/commit/b2dc35a): code-restyling: locales, config
- [b638764](http://github.com/ddnexus/pagy/commit/b638764): code-restyling: root files and Pagy Core
- [aac54aa](http://github.com/ddnexus/pagy/commit/aac54aa): Renaming prepended modules with more specific and safe naming convention (see Issue #290 PR #293)
- [3eef450](http://github.com/ddnexus/pagy/commit/3eef450): Simpler test for I18n namespace conflict
- [b2aaa01](http://github.com/ddnexus/pagy/commit/b2aaa01): README: fix typo (#289) [ci-skip]
- [3a9e70f](http://github.com/ddnexus/pagy/commit/3a9e70f): Fix extra i18n namespacing (#293)* Add a test that looks at i18n working in Modules included (fixes #290)
- [8cd25d2](http://github.com/ddnexus/pagy/commit/8cd25d2): renamed docker > pagy-on-docker
- [a8060b1](http://github.com/ddnexus/pagy/commit/a8060b1): fixed doc typos
- [f0e0d41](http://github.com/ddnexus/pagy/commit/f0e0d41): removed require for minitest/reporters
- [c178c6f](http://github.com/ddnexus/pagy/commit/c178c6f): docker README fixes [ci-skip]
- [ac988fd](http://github.com/ddnexus/pagy/commit/ac988fd): docs fixes
- [4d20d34](http://github.com/ddnexus/pagy/commit/4d20d34): Update Tailwind styles (#285)
- [9a14400](http://github.com/ddnexus/pagy/commit/9a14400): Small README addition [ci-skip]
- [9b40cdd](http://github.com/ddnexus/pagy/commit/9b40cdd): Update frontend.md (#284) [ci-skip]
- [2bbf204](http://github.com/ddnexus/pagy/commit/2bbf204): added Mike Rogers' screencast [ci-skip]
- [b24b86d](http://github.com/ddnexus/pagy/commit/b24b86d): Added note for brakeman false positive warnings (closes #243) [ci-skip]
- [df3c8d6](http://github.com/ddnexus/pagy/commit/df3c8d6): small syntax normalization
- [618d226](http://github.com/ddnexus/pagy/commit/618d226): docker README fix [ci-skip]

### Version 4.1.0

### Changes

- added Bosnian, Croatian and Serbian locales

### Commits

- [8da0e06](http://github.com/ddnexus/pagy/commit/8da0e06): Add Serbian locale (#283)
- [dcfc659](http://github.com/ddnexus/pagy/commit/dcfc659): Add locales for Bosnian and Croatian language (#281)

### Version 4.0.0

### Breaking Changes

- renamed `Pagy::Search` as `Pagy::Searchkick` or `Pagy::ElasticsearchRails`

### Changes

- Dropped support for all jruby versions and ruby version <3.0 (use pagy 3.x instead)
- Dropped deprecation for old/renamed locales
- Fixed error in searchkick extra with ruby 3
- Various refactorings taking advantage of the dropped support for old ruby versions

### Commits

- [4d48d33](http://github.com/ddnexus/pagy/commit/4d48d33): refactoring of pagy_json_tag
- [745e68f](http://github.com/ddnexus/pagy/commit/745e68f): minor fixes for docker
- [296f060](http://github.com/ddnexus/pagy/commit/296f060): added pagy_search name customization to elasticsearch extras
- [98a5254](http://github.com/ddnexus/pagy/commit/98a5254): refactoring of the elasticsearch_rails extra matching the actual params of the search method
- [6966fe3](http://github.com/ddnexus/pagy/commit/6966fe3): refactoring of elasticsearch extras
- [aecb565](http://github.com/ddnexus/pagy/commit/aecb565): normalization of i18n naming
- [b3f78b8](http://github.com/ddnexus/pagy/commit/b3f78b8): refactoring of items extra using module prepend instead alias method chaining and module_eval; refactoring of tests (countless was masking failures of elasticsearch extras)
- [8ec7893](http://github.com/ddnexus/pagy/commit/8ec7893): refactoring of trim extra using module prepend instead alias method chaining
- [05d9e92](http://github.com/ddnexus/pagy/commit/05d9e92): used ruby 3.0 syntax
- [f1d89a5](http://github.com/ddnexus/pagy/commit/f1d89a5): refactoring of I18n extra using module prepend instead alias method chaining
- [df8607e](http://github.com/ddnexus/pagy/commit/df8607e): refactoring of overflow extra using module prepend instead alias method chaining
- [c361293](http://github.com/ddnexus/pagy/commit/c361293): Gemfile: uncommented performance group
- [66e29d1](http://github.com/ddnexus/pagy/commit/66e29d1): fixes for the docker README
- [2a343ce](http://github.com/ddnexus/pagy/commit/2a343ce): added README note about versions
- [dadd2f1](http://github.com/ddnexus/pagy/commit/dadd2f1): ruby 3.0 syntax
- [ce1d7ec](http://github.com/ddnexus/pagy/commit/ce1d7ec): removed legacy I18n compatibility
- [6751412](http://github.com/ddnexus/pagy/commit/6751412): added a few env variables in order to control the verbosity of tests; added test for negative page number
- [3eac56b](http://github.com/ddnexus/pagy/commit/3eac56b): removed unused Hash additional methods
- [24ab5c6](http://github.com/ddnexus/pagy/commit/24ab5c6): simpler travis setup
- [ba100dc](http://github.com/ddnexus/pagy/commit/ba100dc): updated documentation
- [5d53043](http://github.com/ddnexus/pagy/commit/5d53043): emptied CHANGELOG and dropped LEGACY_CHANGELOG
- [33c467a](http://github.com/ddnexus/pagy/commit/33c467a): removed MARK constant
- [49040fb](http://github.com/ddnexus/pagy/commit/49040fb): removed EMPTY constant used for legacy compatibility with frozen_string_literal
- [b826f61](http://github.com/ddnexus/pagy/commit/b826f61): updated rubocop and fixed offending lines
- [f075337](http://github.com/ddnexus/pagy/commit/f075337): removed deprecated locales
- [a70881a](http://github.com/ddnexus/pagy/commit/a70881a): updated version to 4.0.0
- [4676788](http://github.com/ddnexus/pagy/commit/4676788): updated docker environment for pagy 4
- [4a85efe](http://github.com/ddnexus/pagy/commit/4a85efe): updated gemfile and gemspec for pagy 4
- [fcaab88](http://github.com/ddnexus/pagy/commit/fcaab88): updated gemfile and gemspec
- [467b985](http://github.com/ddnexus/pagy/commit/467b985): added docker development environment

### Version 3.11.0

### Changes

- added Swahili localization

### Commits

- [fbc322f](http://github.com/ddnexus/pagy/commit/fbc322f): Add Swahili (sw) to pagy (#276)
- [ebbac6b](http://github.com/ddnexus/pagy/commit/ebbac6b): Fix: broken links (#272) [ci-skip]
- [2178436](http://github.com/ddnexus/pagy/commit/2178436): restored file removed by mistake

### Version 3.10.0

### Changes

- added Czeck localization

### Commits

- [316183f](http://github.com/ddnexus/pagy/commit/316183f): minor adjustments to p11n.rb; removed local file
- [2e7fa1b](http://github.com/ddnexus/pagy/commit/2e7fa1b): Update with 'cs' (#269)added lambda for 'west_slavic' and 'hash for 'cs' language
- [70760c6](http://github.com/ddnexus/pagy/commit/70760c6): Czech localization (#268)

### Version 3.9.0

### Changes

- added link tags helpers to support extra

### Commits

- [a66491b](http://github.com/ddnexus/pagy/commit/a66491b): added link tags helpers to support extra
- [2ef08a9](http://github.com/ddnexus/pagy/commit/2ef08a9): @pagy_locale explicitly initialized to avoid warning
- [261c6a1](http://github.com/ddnexus/pagy/commit/261c6a1): reorganized gemfiles for travis, removed jruby 9.2 tests and added CAVEATS and removed redundant ||=nil
- [619d7d1](http://github.com/ddnexus/pagy/commit/619d7d1): updated gems
- [fc861d8](http://github.com/ddnexus/pagy/commit/fc861d8): added .gitattributes to fix github linguist [ci-skip]
- [287b94f](http://github.com/ddnexus/pagy/commit/287b94f): added Raul Palacio screencast link to README
- [52c65a3](http://github.com/ddnexus/pagy/commit/52c65a3): added "Contributions" section in the README
- [f09a80a](http://github.com/ddnexus/pagy/commit/f09a80a): Add tutorial link to readme (#259)
- [86bf3dc](http://github.com/ddnexus/pagy/commit/86bf3dc): Fix: typo [ci-skip] (#257)
- [bd3bcc7](http://github.com/ddnexus/pagy/commit/bd3bcc7): Update copyright notice to 2020 (#256) [ci-skip]
- [01e4a14](http://github.com/ddnexus/pagy/commit/01e4a14): [ci-skip] Add note re: html safe helpers (#254)Fixes discussion noted in: #6
- [1d812be](http://github.com/ddnexus/pagy/commit/1d812be): doc improvements [ci-skip]

### Version 3.8.3

### Changes

- Portuguese translation
- Fixes and documentation improvements

### Commits

- [f64a1d6](http://github.com/ddnexus/pagy/commit/f64a1d6): added pagy reference to jetbrains link
- [8a4596f](http://github.com/ddnexus/pagy/commit/8a4596f): Add translation for Portuguese (#250) [ci-skip]
- [515de9b](http://github.com/ddnexus/pagy/commit/515de9b): Adds parentheses (#249) [ci-skip]
- [734f30c](http://github.com/ddnexus/pagy/commit/734f30c): added basic sinatra app
- [c7aa44d](http://github.com/ddnexus/pagy/commit/c7aa44d): updated gems
- [62bd4ac](http://github.com/ddnexus/pagy/commit/62bd4ac): added tailwind extra styles
- [c2e8eef](http://github.com/ddnexus/pagy/commit/c2e8eef): added "Customizing CSS styles" how to section
- [614a3e1](http://github.com/ddnexus/pagy/commit/614a3e1): fix for extra "-js" in "pagy-combo-nav-js"
- [8673ca4](http://github.com/ddnexus/pagy/commit/8673ca4): a couple of how_to additions

### Version 3.8.2

### Changes

- Fixes, improvements and gems updates

### Commits

- [a1a7962](http://github.com/ddnexus/pagy/commit/a1a7962): gems update
- [0a70110](http://github.com/ddnexus/pagy/commit/0a70110): item_name can be overridden by passing an already pluralized string to pagy_info
- [28913d6](http://github.com/ddnexus/pagy/commit/28913d6): Use proper attributes for "previous" links (#244)

### Version 3.8.1

### Changes

- Various fixes ad gems update

### Commits

- [1aa1fa7](http://github.com/ddnexus/pagy/commit/1aa1fa7): gems update
- [289d38a](http://github.com/ddnexus/pagy/commit/289d38a): added conditional definition of pagy_t_with_i18n to avoid ruby 2.7 "last argument as keyword parameters" deprecation warning (closes #241)
- [d571719](http://github.com/ddnexus/pagy/commit/d571719): added raw_response method check for pagy_elasticsearch_rails (#234)
- [7a09bb9](http://github.com/ddnexus/pagy/commit/7a09bb9): fix typo in documentation
- [d7a9f7a](http://github.com/ddnexus/pagy/commit/d7a9f7a): Fixes boundary character escaping in regex in js trim function (#239)
- [3561885](http://github.com/ddnexus/pagy/commit/3561885): gems update

### Version 3.8.0

### Changes

- Khmer translation
- Added support for elasticsearch < 6
- Documentation fixes

### Commits

- [bba1bca](http://github.com/ddnexus/pagy/commit/bba1bca): used ternary conditional
- [a9cee7d](http://github.com/ddnexus/pagy/commit/a9cee7d): Support ES5 on elasticsearch rails (#234)
- [d58a39d](http://github.com/ddnexus/pagy/commit/d58a39d): Khmer translation for pagy (#235)
- [7ced96c](http://github.com/ddnexus/pagy/commit/7ced96c): fix for missing suffix fr method reference in doc
- [73600be](http://github.com/ddnexus/pagy/commit/73600be): improved consistency in searchkick and elasticsearch_rails documentation examples (#231)

### Version 3.7.5

### Changes

- Danish translation
- Re-enabled legacy jruby travis config
- Added ruby 2.7 travis testing and rubocop config

### Commits

- [721d01f](http://github.com/ddnexus/pagy/commit/721d01f): more documentation fixes and improvements
- [246fca7](http://github.com/ddnexus/pagy/commit/246fca7): Danish translation for Pagy (#232)
- [e57215f](http://github.com/ddnexus/pagy/commit/e57215f): added ruby 2.7 travis testing and rubocop target version
- [280164a](http://github.com/ddnexus/pagy/commit/280164a): re-enabled travis testing for old jruby (travis fixed the jruby images issue)

### Version 3.7.4

### Changes

- Locales normalization and deprecations
- Bundle update

### Commits

- [c424466](http://github.com/ddnexus/pagy/commit/c424466): Merge branch 'locales-normalization' into dev
- [032615c](http://github.com/ddnexus/pagy/commit/032615c): added Pagy::I18n deprecation tests
- [ebd6151](http://github.com/ddnexus/pagy/commit/ebd6151): added deprecation remapping
- [e1bd70c](http://github.com/ddnexus/pagy/commit/e1bd70c): renamed pt-br -> pt-BR
- [a77dfcf](http://github.com/ddnexus/pagy/commit/a77dfcf): fix for sv and sv-SE pluralization
- [6a31db6](http://github.com/ddnexus/pagy/commit/6a31db6): Swedish locale: add sv-SE, rename se -> sv (#226)
- [00bd312](http://github.com/ddnexus/pagy/commit/00bd312): bundle update (closes #208, closes #223, closes #224)
- [133da16](http://github.com/ddnexus/pagy/commit/133da16): Add tutorial link (#222) [ci skip]

### Version 3.7.3

### Commits

- [a2b915e](http://github.com/ddnexus/pagy/commit/a2b915e): reorganization of Javascript documentation
- [fead55c](http://github.com/ddnexus/pagy/commit/fead55c): Remove duplicated pagination class for Bootstrap (#212)

### Version 3.7.2

### Changes

- fix for numeric headers in the headers extra

### Commits

- [6a93b97](http://github.com/ddnexus/pagy/commit/6a93b97): headers must be strings (closes #211)

### Version 3.7.1

### Changes

- fix for arel count

### Commits

- [cbe4a97](http://github.com/ddnexus/pagy/commit/cbe4a97): Type cast pagy_arel_count value to integer (#207)
- [973f4fe](http://github.com/ddnexus/pagy/commit/973f4fe): updated link [ci skip]

### Version 3.7.0

### Changes

- updated French and Spanish translations
- added Italian translation
- internal renaming `Pagy::Frontend::MARK` > `Pagy::PAGE_PLACEHOLDER`

### Commits

- [434912b](http://github.com/ddnexus/pagy/commit/434912b): updated rubyzip gem
- [b627e55](http://github.com/ddnexus/pagy/commit/b627e55): add italian translation (#202)
- [3341dcc](http://github.com/ddnexus/pagy/commit/3341dcc): created ITEMS_PLACEHOLDER constant
- [7775155](http://github.com/ddnexus/pagy/commit/7775155): replaced Pagy::Frontend::MARK with Pagy::PAGE_PLACEHOLDER (MARK is still defined for backward compatibility)
- [439676e](http://github.com/ddnexus/pagy/commit/439676e): Fixed previous translation for Spanish (#199)
- [ee1b1f0](http://github.com/ddnexus/pagy/commit/ee1b1f0): Update fr.yml (#195)
- [8b6f81f](http://github.com/ddnexus/pagy/commit/8b6f81f): added pagy-cursor link in README
- [c290dca](http://github.com/ddnexus/pagy/commit/c290dca): update incorrect tutorial link (#198)
- [77c045d](http://github.com/ddnexus/pagy/commit/77c045d): add: tutorial link in ReadMe - complex search forms (#194)
- [846eece](http://github.com/ddnexus/pagy/commit/846eece): fix in README.md
- [224748a](http://github.com/ddnexus/pagy/commit/224748a): fix for resurrected-by-mistake line in README

### Version 3.6.0

### Changes

- added arel extra
- updated Gemfile for development environment
- docs fixes and improvements

### Commits

- [af0f189](http://github.com/ddnexus/pagy/commit/af0f189): updated minitest and minitest-reporter gems and removed deprecation
- [6a62de5](http://github.com/ddnexus/pagy/commit/6a62de5): updated Gemfile
- [4856782](http://github.com/ddnexus/pagy/commit/4856782): Arel Extra (#189)
- [2a2d9b0](http://github.com/ddnexus/pagy/commit/2a2d9b0): Revert "Added digital-climate-strike banner"This reverts commit 07f7d9256ef9b1cef1c1eaf6da56ebb03daa19d5.
- [b5c2809](http://github.com/ddnexus/pagy/commit/b5c2809): docs fixes
- [f8e4f42](http://github.com/ddnexus/pagy/commit/f8e4f42): replaced markdown file link for documentation link in extras
- [6a5c271](http://github.com/ddnexus/pagy/commit/6a5c271): updated github-pages gem
- [60d243c](http://github.com/ddnexus/pagy/commit/60d243c): small doc improvement [ci skip]
- [2feb451](http://github.com/ddnexus/pagy/commit/2feb451): docs changes
- [07f7d92](http://github.com/ddnexus/pagy/commit/07f7d92): Added digital-climate-strike banner
- [281da85](http://github.com/ddnexus/pagy/commit/281da85): update grammar (#187)
- [739f032](http://github.com/ddnexus/pagy/commit/739f032): Update how-to: Correct to require header for API (#188)

### Version 3.5.1

### Changes

- added support for elasticsearch 7

### Commits

- [ca736cc](http://github.com/ddnexus/pagy/commit/ca736cc): Adding elasticsearch7 support (#185) Fixes #184
- [13b7314](http://github.com/ddnexus/pagy/commit/13b7314): correct typos in docs and initializer (#186) [ci skip]
- [9734ed6](http://github.com/ddnexus/pagy/commit/9734ed6): Docs style changes [ci skip]
- [fb8bcf4](http://github.com/ddnexus/pagy/commit/fb8bcf4): doc fix for missing :partial key in rendering example (closes #180)
- [7160a18](http://github.com/ddnexus/pagy/commit/7160a18): Minor docs changes [ci skip]

### Version 3.5.0

### Changes

- Added Korean locale
- Added Catalan locale
- Added metadata extra
- Docs fixes and improvements

### Commits

- [2813973](http://github.com/ddnexus/pagy/commit/2813973): Docs:
  - added detailed Quick Start steps (closes #176, closes #177)
  - added specific sections for API clients and javascript frameworks
- [43349a8](http://github.com/ddnexus/pagy/commit/43349a8): added metadata extra
- [261e6e6](http://github.com/ddnexus/pagy/commit/261e6e6): Add Catalan localization (#178)
- [30ed505](http://github.com/ddnexus/pagy/commit/30ed505): temporarily removed jruby 1.7 and 9.0 rvm from travis - waiting travis to fix their rvms or suggest some work around [see issue](https://travis-ci.community/t/jruby-1-7-and-9-0-fail/4703)
- [2863c55](http://github.com/ddnexus/pagy/commit/2863c55): added minitest-reporters gem
- [0b42e5c](http://github.com/ddnexus/pagy/commit/0b42e5c): Add translation ko.yml for korean (#172)
- [da3fc37](http://github.com/ddnexus/pagy/commit/da3fc37): minor doc fixes [ci skip]

### Version 3.4.1

### Changes

- Improvements for searchkick extra
- Added javascript Pagy.version

### Commits

- [0ac5c27](http://github.com/ddnexus/pagy/commit/0ac5c27): refactoring of pagy_search and its usage in order to allow optional term argument for searchkick (#169)
- [389755b](http://github.com/ddnexus/pagy/commit/389755b): small docs editing [ci skip]

### Version 3.4.0

### Changes

- Added UIkit nav, nav_js and combo_js helpers and templates.

### Commits

- [1eb25ec](http://github.com/ddnexus/pagy/commit/1eb25ec): Implement UIkit pagination (#164)

### Version 3.3.2

### Changes

- Fix for #163
- Javascript improvements

### Commits

- [aff8cbb](http://github.com/ddnexus/pagy/commit/aff8cbb): better handling of resize listeners; added render function to the pagy element
- [c699fe5](http://github.com/ddnexus/pagy/commit/c699fe5): fix for javascript/turbolink nav.render firing when element not found (#163)

### Version 3.3.1

### Changes

Namespacing of all exceptions, still backward compatible with legacy rescues

### Commits

- [530cb24](http://github.com/ddnexus/pagy/commit/530cb24): all the exceptions are namespaced and can return more information for easier rescue of special cases

### Version 3.3.0

### Changes

- Added Bulgarian locale

### Commits

- [2c56428](http://github.com/ddnexus/pagy/commit/2c56428): added pluralization comment to bg.yml dictionary
- [7111cec](http://github.com/ddnexus/pagy/commit/7111cec): Bulgarian language yml file. (#162)
- [a46f6b5](http://github.com/ddnexus/pagy/commit/a46f6b5): improved suggestion for headers usage

### Version 3.2.1

### Commits

- [b403a35](http://github.com/ddnexus/pagy/commit/b403a35): improved trim regex: simpler, safer and more tested
- [bd3be09](http://github.com/ddnexus/pagy/commit/bd3be09): normalized aliasing with alias_method
- [3c60d9b](http://github.com/ddnexus/pagy/commit/3c60d9b): minor doc fixes
- [9aa1bea](http://github.com/ddnexus/pagy/commit/9aa1bea): fix for missing "partial" in examples for template usage (#160)
- [405b4d2](http://github.com/ddnexus/pagy/commit/405b4d2): reorganization and renaming of test helpers
- [459edaa](http://github.com/ddnexus/pagy/commit/459edaa): improved notes about preventing crawlers to follow look-alike links
- [01c73d3](http://github.com/ddnexus/pagy/commit/01c73d3): added search box to docs

### Version 3.2.0

### Commits

- [2341a83](http://github.com/ddnexus/pagy/commit/2341a83): docs fixes and additions
- [96ad00f](http://github.com/ddnexus/pagy/commit/96ad00f): extended trim extra support to all the *_js helpers:
  - refactored trim extra
  - simplified MARK strings
  - use of RegExp patterns in pagy.js
  - added and improved tests

### Version 3.1.0

### Commits

- [1732dee](http://github.com/ddnexus/pagy/commit/1732dee): refactoring of marker and hidden links in *_js helpers:
  - MARKER renamed to MARK is simpler, static and hardcoded in pagy.js as well
  - removed hidden links to bogus urls that were triggering crawlers to follow (#158)
  - pagy_items_selector now works also with trim and in combination with other *nav_js helpers
  - updated tests
- [aa140e1](http://github.com/ddnexus/pagy/commit/aa140e1): doc improvement
- [d0e4ba6](http://github.com/ddnexus/pagy/commit/d0e4ba6): a few fixes and doc improvements
- [c1ddbd9](http://github.com/ddnexus/pagy/commit/c1ddbd9): Fix typo in docs example (#156)
- [e0b2a2b](http://github.com/ddnexus/pagy/commit/e0b2a2b): removed duplication in LEGACY_CHANGELOG
- [ecb6822](http://github.com/ddnexus/pagy/commit/ecb6822): fix typo in README

### Version 3.0.0

### Breaking Changes

#### Refactoring of i18n

- The `zh-cn` and `zh-hk` were named incorrectly. They have been properly renamed as `zh-CN` and `zh-HK`. If you used them, you should update the `Pagy::I18n.load` statement.
- Removed the plural keys inconsistent with the locale: now the dictionary files are 100% compatible with the I18n gem. If you use custom dictionaries, you should update their entries.
- The `:item_path` variable has been renamed as `:i18n_key`: you should search and replace it.

#### Renamed nav helper methods and files

The javascript-powered helpers (`pagy*_responsive_nav`, `pagy*_compact_nav` and `items_selector`) have been renamed in order to be simpler and more descriptive of their arguments, features and requirements. They have been improved internally, but you need only to search and replace because their usage has not been changed.

#### Bootstrap Extra

| v2.0+                                          | v3.0+                            |
|:-----------------------------------------------|:---------------------------------|
| `pagy_bootstrap_responsive_nav` `:breakpoints` | `pagy_bootstrap_nav_js` `:steps` |
| `pagy_bootstrap_compact_nav`                   | `pagy_bootstrap_combo_nav_js`    |

#### Bulma Extra

| v2.0+                                      | v3.0+                        |
|:-------------------------------------------|:-----------------------------|
| `pagy_bulma_responsive_nav` `:breakpoints` | `pagy_bulma_nav_js` `:steps` |
| `pagy_bulma_compact_nav`                   | `pagy_bulma_combo_nav_js`    |

#### Foundation Extra

| v2.0+                                           | v3.0+                             |
|:------------------------------------------------|:----------------------------------|
| `pagy_foundation_responsive_nav` `:breakpoints` | `pagy_foundation_nav_js` `:steps` |
| `pagy_foundation_compact_nav`                   | `pagy_foundation_combo_nav_js`    |

#### Materialize Extra

| v2.0+                                            | v3.0+                              |
|:-------------------------------------------------|:-----------------------------------|
| `pagy_materialize_responsive_nav` `:breakpoints` | `pagy_materialize_nav_js` `:steps` |
| `pagy_materialize_compact_nav`                   | `pagy_materialize_combo_nav_js`    |

#### Plain > Navs Extra

| v2.0+                                      | v3.0+                        |
|:-------------------------------------------|:-----------------------------|
| `pagy_plain_nav` (`pagy_nav` alias)        | - removed -                  |
| `pagy_plain_responsive_nav` `:breakpoints` | `pagy_nav_js` `:steps`       |
| `pagy_plain_compact_nav`                   | `pagy_combo_nav_js`          |
| `require "pagy/extras/plain"`              | `require "pagy/extras/navs"` |

#### Semantic Extra

| v2.0+                                         | v3.0+                           |
|:----------------------------------------------|:--------------------------------|
| `pagy_semantic_responsive_nav` `:breakpoints` | `pagy_semantic_nav_js` `:steps` |
| `pagy_semantic_compact_nav`                   | `pagy_semantic_combo_nav_js`    |

#### Renamed Pagy::VARS

`Pagy::Vars[:breakpoints]` has been renamed as `Pagy::VARS[:steps]`

#### Renamed items_selector

`items_selector` has been renamed as `items_selector_js`. It is also possible to use the `i18n_key` variable to customize the item name.

#### Renamed classes and params in helpers

**Notice**: You can skip the following sections if you did not override any nav helper.

#### CSS nav classes

The `nav` CSS classes follow the same renaming rules of the helper methods:

| v2.0+                             | v3.0+                           |
|:----------------------------------|:--------------------------------|
| `pagy-bootstrap-responsive-nav`   | `pagy-bootstrap-nav-js`         |
| `pagy-bootstrap-compact-nav`      | `pagy-bootstrap-combo-nav-js`   |
| `pagy-bulma-responsive-nav`       | `pagy-bulma-nav-js`             |
| `pagy-bulma-compact-nav`          | `pagy-bulma-combo-nav-js`       |
| `pagy-foundation-responsive-nav`  | `pagy-foundation-nav-js`        |
| `pagy-foundation-compact-nav`     | `pagy-foundation-combo-nav-js`  |
| `pagy-materialize-responsive-nav` | `pagy-materialize-nav-js`       |
| `pagy-materialize-compact-nav`    | `pagy-materialize-combo-nav-js` |
| `pagy-plain-responsive-nav`       | `pagy-nav-js`                   |
| `pagy-plain-compact-nav`          | `pagy-combo-nav-js`             |
| `pagy-semantic-responsive-nav`    | `pagy-semantic-nav-js`          |
| `pagy-semantic-compact-nav`       | `pagy-semantic-combo-nav-js`    |

#### CSS JSON tag classes

Renamed first param passed to the `pagy_json_tag`:

| v2.0+         | v3.0+             |
|:--------------|:------------------|
| `:responsive` | `:nav`            |
| `:compact`    | `:combo_nav`      |
| `:items`      | `:items_selector` |

#### Javascript functions

| v2.0+             | v3.0+                 |
|:------------------|:----------------------|
| `Pagy.responsive` | `Pagy.nav`            |
| `Pagy.compact`    | `Pagy.combo_nav`      |
| `Pagy.items`      | `Pagy.items_selector` |

#### Dropped marginal methods

Dropped `pagy_serialized`, `pagy_apply_init_tag` and `PagyInit` javascript namespace. They were too basic to be useful as support for javascript powered custom component.

### Commits

- [df3ebf2](http://github.com/ddnexus/pagy/commit/df3ebf2): updated performance assets for v3
- [260e7d4](http://github.com/ddnexus/pagy/commit/260e7d4): refactoring of i18n:
  - I18n gem compliant (removed plural keys inconsistent with the locale)
  - added tests for all the pluralization procs
  - simplified and normalized Pagy dictionary file
  - renamed :items_Path to :collection_key
  - added :collection_key lookup for the items_selector_js helper
- [8ebb1ab](http://github.com/ddnexus/pagy/commit/8ebb1ab): refactoring of test coverage:
  - replaced SingleCov with SimpleCov for CodeCov integration
  - simplified Travis setup
  - separate controls of RuboCop, SimpleCov, Oj and CodeCov with dedicated ENV variables
  - completed coverage at 100%
- [ae3e4bc](http://github.com/ddnexus/pagy/commit/ae3e4bc): removed overridden method and undefined instance variable warnings
- [aa1aad9](http://github.com/ddnexus/pagy/commit/aa1aad9): renamed multi_series > sequels and relative js arguments and variables; renamed :sizes > :steps; updated nav test cases
- [901f779](http://github.com/ddnexus/pagy/commit/901f779): compact > combo and other changes
- [f7425a5](http://github.com/ddnexus/pagy/commit/f7425a5): renamed assets according to helper names; reordered and improved nav docs
- [ad100eb](http://github.com/ddnexus/pagy/commit/ad100eb): renamed plain extra to navs; removed the "plain_" qualifier for the helpers
- [54f4a7e](http://github.com/ddnexus/pagy/commit/54f4a7e): updated docs and comments
- [25470dd](http://github.com/ddnexus/pagy/commit/25470dd): renamed items_selector > items_selector_js
- [d4be9dd](http://github.com/ddnexus/pagy/commit/d4be9dd): replaced responsive with nav_js; reordered methods and test normalization
- [ec7d754](http://github.com/ddnexus/pagy/commit/ec7d754): removed warning for locales zh-cn and zh-hk, and properly renamed as zh-CN and zh-HK
- [2548557](http://github.com/ddnexus/pagy/commit/2548557): simplified support extra (dropped pagy_serialized and pagy_apply_init_tag)
- [f82b2e7](http://github.com/ddnexus/pagy/commit/f82b2e7): fix typo
- [704a4df](http://github.com/ddnexus/pagy/commit/704a4df): add french translation (#154)
- [2f67dfa](http://github.com/ddnexus/pagy/commit/2f67dfa): UPDATE pagy locales id (#151)
- [15732da](http://github.com/ddnexus/pagy/commit/15732da): fix for broken migration link (#150)

### Version 2.1.5

### Changes

- Fix for `pagy_url_for` returning duplicated params
- Added "zh-TW" locale
- Deprecated incorrect zh-cn and zh-hk locales in favor of zh-CN and zh-HK locales

### Commits

- [62ee172](http://github.com/ddnexus/pagy/commit/62ee172): fixes for pagy_url_for full url gets duplicated page param (#149)
  - used concatenated request.
    base_url instead of request.url
  - added better tests
  - fixed overriding of pagy_url_for in items extra
- [0bca4af](http://github.com/ddnexus/pagy/commit/0bca4af): fix for rubocop offense
- [6b7fe77](http://github.com/ddnexus/pagy/commit/6b7fe77): deprecated zh-cn and zh-hk locales, normalized as zh-CN and zh-HK
- [c28d21a](http://github.com/ddnexus/pagy/commit/c28d21a): Added locale zh-TW (#147)
- [95cdf61](http://github.com/ddnexus/pagy/commit/95cdf61): docs improvements
- [310c804](http://github.com/ddnexus/pagy/commit/310c804): Update lib/locales/README.md (#148)

### Version 2.1.4

### Changes

- Fix for responsive javascript not working with IE

### Commits

- [3cce19a](http://github.com/ddnexus/pagy/commit/3cce19a): javascript responsive refactoring:
  - replaced problematic loop functions with plain for loops
  - better naming in responsive function
  - shortened rendering time
  - simpler doc
- [3eacc8d](http://github.com/ddnexus/pagy/commit/3eacc8d): better doc examples
- [5c92187](http://github.com/ddnexus/pagy/commit/5c92187): added missing event listeners to auto-incremental javascript example
- [119ba78](http://github.com/ddnexus/pagy/commit/119ba78): fix for support.md auto-incremental javascript example
- [1f376d4](http://github.com/ddnexus/pagy/commit/1f376d4): Update support.md (#146) Fix incremental/infinite scroll examples

### Version 2.1.3

### Changes

- added Swedish locale

### Commits

- [f0dcac6](http://github.com/ddnexus/pagy/commit/f0dcac6): docs update
- [9dc8c05](http://github.com/ddnexus/pagy/commit/9dc8c05): removed tap from Pagy#series
- [e237ea6](http://github.com/ddnexus/pagy/commit/e237ea6): add translation for Swedish (#145)
- [7d71699](http://github.com/ddnexus/pagy/commit/7d71699): lighter README and a few docs improvements
- [f25b5f5](http://github.com/ddnexus/pagy/commit/f25b5f5): updated support extra examples with pagy_countess
- [6fa1d03](http://github.com/ddnexus/pagy/commit/6fa1d03): comments and docs updates
- [9f057e6](http://github.com/ddnexus/pagy/commit/9f057e6): small improvement for the headers extra doc
- [2c53e76](http://github.com/ddnexus/pagy/commit/2c53e76): a few docs changes for the migration guide
- [2a90e6a](http://github.com/ddnexus/pagy/commit/2a90e6a): stop following the GitFlow conventions (extra complexity for no advantage for this project; too many merge commits; history difficult to follow)
- [198883c](http://github.com/ddnexus/pagy/commit/198883c): Fix legacy changelog url (#144)

### Version 2.1.2

### Changes

- Added "Current-Page" header to headers extra

### Commits

- [016b377](http://github.com/ddnexus/pagy/commit/016b377): added Current-Page default header to headers extra
- [4086181](http://github.com/ddnexus/pagy/commit/4086181): fix compilant > compliant typos in docs
- [5d6edf6](http://github.com/ddnexus/pagy/commit/5d6edf6): reconciliation between test strings

### Version 2.1.1

### Changes

- Fix for multiple pagy get wrong url (#143)
- Better documentation for responsive breakpoints (#140)

### Commits

- [e9f467a](http://github.com/ddnexus/pagy/commit/e9f467a): fix for pagy_url_for not working with multiple pagy in the same request (#143)
- [75329b4](http://github.com/ddnexus/pagy/commit/75329b4): better configuration example and documentation for responsive breakpoints (#140)

### Version 2.1.0

### Changes

- Added headers extra
- Added support for overflow :last_page to searchkick and elasticsearch_rails extras
- Added zh-hk locale
- Better docs

### Commits

- [4e83993](http://github.com/ddnexus/pagy/commit/4e83993): updated docs
- [8bd19d6](http://github.com/ddnexus/pagy/commit/8bd19d6): added headers extra (#141)
- [9a26b04](http://github.com/ddnexus/pagy/commit/9a26b04): added support for overflow :last_page to searchkick and elasticsearch_rails extras (#138)
- [c5524fc](http://github.com/ddnexus/pagy/commit/c5524fc): Add zh-hk language yml file (#139)
- [6d5841b](http://github.com/ddnexus/pagy/commit/6d5841b): update travis setup
- [5bd7c0a](http://github.com/ddnexus/pagy/commit/5bd7c0a): updated docs notes for the searchkick and elasticsearch_rails extras
- [0ca5539](http://github.com/ddnexus/pagy/commit/0ca5539): updated comments in config/pagy.rb (#138)

### Version 2.0.1

### Changes

- Fix for missing require in config/pagy.rb and fix for pt-br translation typos
- docs fixes and improvements

### Commits

- [4e7b30f](http://github.com/ddnexus/pagy/commit/4e7b30f): update Repository Info section in README
- [835e8d2](http://github.com/ddnexus/pagy/commit/835e8d2): Fix for missing require in config/pagy.rb and fix for pt-br translation typos (#134)
- [8cdffa1](http://github.com/ddnexus/pagy/commit/8cdffa1): updated consumption chart in README
- [91aa417](http://github.com/ddnexus/pagy/commit/91aa417): replaced efficiency table with resource-consumption chart; other minor fixes
- [964ab24](http://github.com/ddnexus/pagy/commit/964ab24): fix for wrong params in searchkick synopsis example
- [71baf56](http://github.com/ddnexus/pagy/commit/71baf56): added paginate-responder link to the HTTP header in the How to doc (#132)
- [05e218c](http://github.com/ddnexus/pagy/commit/05e218c): fix for indents in searchkick doc
- [7061086](http://github.com/ddnexus/pagy/commit/7061086): added README comments for the docs/update branch
- [b93259c](http://github.com/ddnexus/pagy/commit/b93259c): fix typos in searchkick readme (#129)
- [4156121](http://github.com/ddnexus/pagy/commit/4156121): added how-to for custom count for custom scopes (#130); other minor adjustments
- [4c7b50a](http://github.com/ddnexus/pagy/commit/4c7b50a): docs fixes and additions (#128)

### Version 2.0.0

### Breaking Changes

#### Renamed methods and files (already deprecated)

The following methods have been renamed. You only need to search and replace, because the functionality has not been changed.

#### Bootstrap Extra

| Legacy Name                     | New Name                        |
|:--------------------------------|:--------------------------------|
| `pagy_nav_bootstrap`            | `pagy_bootstrap_nav`            |
| `pagy_nav_compact_bootstrap`    | `pagy_bootstrap_compact_nav`    |
| `pagy_nav_responsive_bootstrap` | `pagy_bootstrap_responsive_nav` |

#### Bulma Extra

| Legacy Name                 | New Name                    |
|:----------------------------|:----------------------------|
| `pagy_nav_bulma`            | `pagy_bulma_nav`            |
| `pagy_nav_compact_bulma`    | `pagy_bulma_compact_nav`    |
| `pagy_nav_responsive_bulma` | `pagy_bulma_responsive_nav` |

#### Foundation Extra

| Legacy Name                      | New Name                         |
|:---------------------------------|:---------------------------------|
| `pagy_nav_foundation`            | `pagy_foundation_nav`            |
| `pagy_nav_compact_foundation`    | `pagy_foundation_compact_nav`    |
| `pagy_nav_responsive_foundation` | `pagy_foundation_responsive_nav` |

#### Materialize Extra

| Legacy Name                       | New Name                          |
|:----------------------------------|:----------------------------------|
| `pagy_nav_materialize`            | `pagy_materialize_nav`            |
| `pagy_nav_compact_materialize`    | `pagy_materialize_compact_nav`    |
| `pagy_nav_responsive_materialize` | `pagy_materialize_responsive_nav` |

#### Navs Extra

| Legacy Name                  | New Name                            |
|:-----------------------------|:------------------------------------|
|                              | `pagy_plain_nav` (`pagy_nav` alias) |
| `pagy_nav_compact`           | `pagy_plain_compact_nav`            |
| `pagy_nav_responsive`        | `pagy_plain_responsive_nav`         |
| `require "pagy/extras/navs"` | `require "pagy/extras/plain"`       |

#### Semantic Extra

| Legacy Name                    | New Name                       |
|:-------------------------------|:-------------------------------|
| `pagy_nav_semantic`            | `pagy_semantic_nav`            |
| `pagy_nav_compact_semantic`    | `pagy_semantic_compact_nav`    |
| `pagy_nav_responsive_semantic` | `pagy_semantic_responsive_nav` |

#### Renamed classes and params in helpers

**Notice**: You can skip this section if you did not override any nav helper.

#### CSS nav classes

The `nav` CSS classes follow the same renaming rules of the helper methods. For example:

`pagy-nav-responsive-bootstrap` is now `pagy-bootstrap-responsive-nav`

#### CSS JSON tag classes

All the JSON tag classes are now `pagy-json`, and the qualifier of the json (e.g. `compact`, `responsive`, ...) is now passed as the first argument in the array content. For example:

`<script type="application/json" class="pagy-responsive-json">["#{id}", ...`

is now

`<script type="application/json" class="pagy-json">["responsive", "#{id}", ...`

However the tags have been refactored to use the shared `pagy_json_tag` helper. For example:

`script = pagy_json_tag(:responsive, id, tags,  responsive[:widths], responsive[:series])`

#### Pagy I18n

- The `Pagy::Frontend::I18N` is now `Pagy::I18n`
- The `Pagy::I18n.load` method accepts params in different format: see the [doc](https://ddnexus.github.io/pagy/api/frontend#i18n)

#### Searchkick Extra

- The `searchkick` extra has been refactored and its methods work in a different way: see the [doc](https://ddnexus.github.io/pagy/extras/searchkick)

#### Elasticsearch Rails Extra

- The `elasticsearch_rails` extra has been refactored and its methods work in a different way: see the [doc](https://ddnexus.github.io/pagy/extras/elasticsearch_rails)

#### Pagy Initializer

The pagy initializer has changed: please replace/update it: [pagy.rb](https://github.com/ddnexus/pagy/blob/master/lib/config/pagy.rb)

### Commits

- [a03ff0b](http://github.com/ddnexus/pagy/commit/a03ff0b): updated README and docs
- [7bb34e3](http://github.com/ddnexus/pagy/commit/7bb34e3): refactoring of searchkick extra
- [22afe14](http://github.com/ddnexus/pagy/commit/22afe14): refactoring of elasticsearch_rails extra
- [fc608e9](http://github.com/ddnexus/pagy/commit/fc608e9): setup for ruby 1.9+ and jruby 1.7+
- [7948394](http://github.com/ddnexus/pagy/commit/7948394): updated magic comments
- [d8ce15f](http://github.com/ddnexus/pagy/commit/d8ce15f): updated Gemfile
- [8ad362d](http://github.com/ddnexus/pagy/commit/8ad362d): conditional SingleCov
- [14a7c30](http://github.com/ddnexus/pagy/commit/14a7c30): small change in pagy_bootstrap_compact_nav style
- [8550b67](http://github.com/ddnexus/pagy/commit/8550b67): removed fork from tests
- [532743d](http://github.com/ddnexus/pagy/commit/532743d): simpler overflow module structure (backward compatible)
- [9c1212b](http://github.com/ddnexus/pagy/commit/9c1212b): i18n refactoring:
  - added multi-language
  - simpler I18N format
  - faster pagy_t method
- [1f3c86f](http://github.com/ddnexus/pagy/commit/1f3c86f): fixed warnings and a few backward incompatible statements
- [a6a9371](http://github.com/ddnexus/pagy/commit/a6a9371): replaced +'...' syntax with EMPTY + '...' syntax for backward compatibility
- [32984a3](http://github.com/ddnexus/pagy/commit/32984a3): removed empty string assignation (simpler and more efficient)
- [e61b1e6](http://github.com/ddnexus/pagy/commit/e61b1e6): Removed deprecations:
  - remove deprecation of method extras and extra CSS class in navs in modules
  - remove deprecated method from `extras/shared`
  - remove extra CSS class in tests and templates
  - remove the extras/navs.rb and DEPRECATIONS.md files
  - remove support for overridden legacy helper in `pagy.js`
  - add a note in the GoRails section in the README
- [ca18374](http://github.com/ddnexus/pagy/commit/ca18374): Cache gemset for faster Travis builds (#126)
- [71a3e5f](http://github.com/ddnexus/pagy/commit/71a3e5f): Always return the same object as Pagy.root (#125)* Update copyright notice to 2019 (#121)

### Version 1.3.3

### Commits

- [11944e4](http://github.com/ddnexus/pagy/commit/11944e4): Fix typo in class name (#122)
- [40ef36d](http://github.com/ddnexus/pagy/commit/40ef36d): travis badge links to branches
- [78141a3](http://github.com/ddnexus/pagy/commit/78141a3): Improve default German translation (#120)
- [c0fe0df](http://github.com/ddnexus/pagy/commit/c0fe0df): minor typo corrections (#119)

### Version 1.3.2

### Changes

- fixed responsive javascript error

### Commits

- [332c6a2](http://github.com/ddnexus/pagy/commit/332c6a2): fix for responsive javascript error in slow loading pages (#115)

### Version 1.3.1

### Changes

- fixed issue with items extra rendered after pagy_nav

### Commits

- [3ead159](http://github.com/ddnexus/pagy/commit/3ead159): fix for temp items variable in items extra (#117)

### Version 1.3.0

### Changes

- Added Dutch locale
- Refactoring of plurals
- Javascript refactoring: fixes and improvements

### Commits

- [70796d3](http://github.com/ddnexus/pagy/commit/70796d3): Add Dutch language (#116)Added Dutch dictionary
- [b112f30](http://github.com/ddnexus/pagy/commit/b112f30): plurals.rb:
  - added frozen variables
  - added explicit 'zero' for :zero_one_few_may_other
  - added :pl pluralization (#111)
  - added README.md
- [6cf86af](http://github.com/ddnexus/pagy/commit/6cf86af): added docs notes
- [b05db4e](http://github.com/ddnexus/pagy/commit/b05db4e): added optional target element to Pagy.init for AJAX support
- [e35a9ae](http://github.com/ddnexus/pagy/commit/e35a9ae): refactoring of default element ids for helpers and deprecation:
  - default ids are now constant also in different processes and compliant also with older HTML versions
  - simpler deprecation code
- [cef823c](http://github.com/ddnexus/pagy/commit/cef823c): pagy.js responsive refactoring (#115):
  - moved handling of window event listeners into the responsive function
  - added resize delay to Pagy.responsive
- [a64a8bd](http://github.com/ddnexus/pagy/commit/a64a8bd): fix for items test
- [e9ab4a1](http://github.com/ddnexus/pagy/commit/e9ab4a1): renamed argument items > fetched; removed redundant assignation

### Version 1.2.1

### Changes

- Improved efficiency for count in backend and extras

### Commits

- [01178cb](http://github.com/ddnexus/pagy/commit/01178cb): replaced enumerable #count for #size (suggested in #112)
- [0723228](http://github.com/ddnexus/pagy/commit/0723228): count (and page) in *get_variables methods are set only when nil (alternative implementation of #112)

### Changes

- Deprecation of frontend helpers (see [Deprecations](https://github.com/ddnexus/pagy/blob/master/DEPRECATIONS.md))
- Internal refactoring of javascript
- added `support` extra for features like: incremental, infinite, auto-scroll pagination

### Commits

- [2bdb1e5](http://github.com/ddnexus/pagy/commit/2bdb1e5): added support extra
- [055af34](http://github.com/ddnexus/pagy/commit/055af34): added :cycle variable to Pagy
- [c1680ec](http://github.com/ddnexus/pagy/commit/c1680ec): add JSON/javascript deprecations
- [263c12d](http://github.com/ddnexus/pagy/commit/263c12d): better internal javascript naming
- [910facc](http://github.com/ddnexus/pagy/commit/910facc): updated tests
- [6e8cde8](http://github.com/ddnexus/pagy/commit/6e8cde8): refactoring of json tags:
  - single "pagy-json" class for all json tags
  - removed prefix used for all pagy element ids
  - extracted pagy_json_tag from frontend extras
- [98ce8e7](http://github.com/ddnexus/pagy/commit/98ce8e7): automatic ids based on hash must be strings
- [cd8256e](http://github.com/ddnexus/pagy/commit/cd8256e): added DEPRECATIONS file
- [6e3e1e4](http://github.com/ddnexus/pagy/commit/6e3e1e4): deprecated and renamed navs helpers in code, tests and docs; renamed navs extension as plain; added :pagy_plain_nav alias of :pagy_nav
- [4bc72af](http://github.com/ddnexus/pagy/commit/4bc72af): deprecated and renamed semantic navs in code, tests and docs
- [a9c3822](http://github.com/ddnexus/pagy/commit/a9c3822): deprecated and renamed materialize navs in code, tests and docs
- [5481f40](http://github.com/ddnexus/pagy/commit/5481f40): renamed foundation templates and updated docs
- [f984f83](http://github.com/ddnexus/pagy/commit/f984f83): deprecated and renamed foundation navs in code, tests and docs
- [95045c3](http://github.com/ddnexus/pagy/commit/95045c3): renamed bulma templates and updated docs
- [dbad41a](http://github.com/ddnexus/pagy/commit/dbad41a): deprecated and renamed bulma navs in code, tests and docs
- [e4f859f](http://github.com/ddnexus/pagy/commit/e4f859f): renamed bootstrap templates and updated docs
- [35078ab](http://github.com/ddnexus/pagy/commit/35078ab): deprecated and renamed bootstrap navs in code, tests and docs
- [04ec860](http://github.com/ddnexus/pagy/commit/04ec860): added Pagy.deprecate method

### Version 1.1.0

### Changes

- added `Pagy::Countless` support subclass
- added `countless` extra integrated with the other extras

### Commits

- [fa547ad](http://github.com/ddnexus/pagy/commit/fa547ad): reworded the doc notice about :pagy_countless_get_items returning an Array; fix typo in comment
- [8368a99](http://github.com/ddnexus/pagy/commit/8368a99): Synopsys > Synopsis
- [1b8a080](http://github.com/ddnexus/pagy/commit/1b8a080): a few fixes for the countless tests
- [0bf2f46](http://github.com/ddnexus/pagy/commit/0bf2f46): Typo fix
- [2f93227](http://github.com/ddnexus/pagy/commit/2f93227): Test coverage for `Pagy::Countless` (#108)
- [c2a25b6](http://github.com/ddnexus/pagy/commit/c2a25b6): Add some tests for Countless extra (#108)
- [04fa7a2](http://github.com/ddnexus/pagy/commit/04fa7a2): added countless support to the items extra
- [f272faf](http://github.com/ddnexus/pagy/commit/f272faf): added countless support to the overflow extra
- [7b25165](http://github.com/ddnexus/pagy/commit/7b25165): added countless sub-class and extra code
- [37cf51a](http://github.com/ddnexus/pagy/commit/37cf51a): series can return an empty array if size is empty
- [7bf9d14](http://github.com/ddnexus/pagy/commit/7bf9d14): fix for a few missing lines in the extras docs

### Version 1.0.0

### Breaking Changes

- The "Out Of Range" concept has been redefined as "Overflow" resulting in a few renaming. Since there are no changes in the logic, you can update by just searching and replacing (if present) a few string in your code:
  - `"/out_of_range"` > `"/overflow"`
  - `"OutOfRangeError"` > `"OverflowError"`
  - `":out_of_range_mode"` > `":overflow"`
  - `"out_of_range?` > `"overflow?"`

### Commits

- [431e4db](http://github.com/ddnexus/pagy/commit/431e4db): redefined the "Out Of Range" concept as "Overflow" (#103):
  - files: out_of_range* > overflow*
  - extra name: out_of_range > overflow
  - error class: OutOfRangeError > OverflowError
  - module: OutOfRange > Overflow
  - VARS: :out_of_range_mode > :overflow
  - instance variable: @out_of_range > @overflow
  - method: out_of_range? > overflow?
- [a0b411a](http://github.com/ddnexus/pagy/commit/a0b411a): added README note about semantic versioning

### Version 0.23.1

### Commits

- [e8a2251](http://github.com/ddnexus/pagy/commit/e8a2251): fix for ArgumentError feedback in Pagy constructor should show the originally passed offending value (#104)

### Version 0.23.0

### Changes

- Added German locale

### Commits

- [f41d465](http://github.com/ddnexus/pagy/commit/f41d465): German (#101)
- [b6149ec](http://github.com/ddnexus/pagy/commit/b6149ec): docs fixes
- [408cc32](http://github.com/ddnexus/pagy/commit/408cc32): fix for typo in CHANGELOG

### Version 0.22.0

### Changes

- Added Norwegian locale

### Commits

- [59064c2](http://github.com/ddnexus/pagy/commit/59064c2): Added Norwegian locale (#100)

### Version 0.21.0

### Changes

- Added Elasticsearch Rails extra

### Commits

- [e0acbf6](https://github.com/ddnexus/pagy/commit/e0acbf6): fix for robocop offenses (#96)
- [d082729](https://github.com/ddnexus/pagy/commit/d082729): Added Elasticsearch Rails extra (#96)
- [dd0575a](https://github.com/ddnexus/pagy/commit/dd0575a): fix doc typo
- [d3510a7](https://github.com/ddnexus/pagy/commit/d3510a7): updated comments in plurals.rb
- [5ed458a](https://github.com/ddnexus/pagy/commit/5ed458a): added README disclaimer
- [02b0a74](https://github.com/ddnexus/pagy/commit/02b0a74): Update link to Semantic-UI pagination

### Version 0.20.0

### Breaking Changes

- I18n refactoring
  - Combo helpers and item selectors have been refactored: you should update your overridden helpers accordingly
  - The i18n structure has changed, update your custom i18n locale file

### Commits

- [bab3bf8](https://github.com/ddnexus/pagy/commit/bab3bf8): Merge pull request #93 from ddnexus/i18n
- [f8017c3](https://github.com/ddnexus/pagy/commit/f8017c3): updated config/pagy.rb
- [c461885](https://github.com/ddnexus/pagy/commit/c461885): added ru plural proc
- [5680628](https://github.com/ddnexus/pagy/commit/5680628): fix for wrong indentation in zh-cn
- [6998932](https://github.com/ddnexus/pagy/commit/6998932): pagy.yml properly split into separate locale files
- [e3048f2](https://github.com/ddnexus/pagy/commit/e3048f2): improvements for ja tr and zh-cn
- [c1aec4c](https://github.com/ddnexus/pagy/commit/c1aec4c): removed marginal pagy.nav.current key used only by foundation an fixed consistency in foundation helpers and templates
- [15e413b](https://github.com/ddnexus/pagy/commit/15e413b): refactoring of the composition of i18n strings for compact helpers and items selector

### Version 0.19.4

### Changes

- added Japanese, Brazilian and Indonesian translations

### Commits

- [67f1bb9](https://github.com/ddnexus/pagy/commit/67f1bb9): reordered translations
- [4ab3438](https://github.com/ddnexus/pagy/commit/4ab3438): adding pt-br translation (#87)
- [d1f8674](https://github.com/ddnexus/pagy/commit/d1f8674): alpha-ordered translations
- [0311c85](https://github.com/ddnexus/pagy/commit/0311c85): Added Indonesian locale (#91)
- [ce68624](https://github.com/ddnexus/pagy/commit/ce68624): add japanese translation (#89)
- [5e4f93d](https://github.com/ddnexus/pagy/commit/5e4f93d): corrected chinese translation (#88)
- [26654d4](https://github.com/ddnexus/pagy/commit/26654d4): updated README

### Version 0.19.3

### Changes

- added Chinese locale

### Commits

- [ebc0ed5](https://github.com/ddnexus/pagy/commit/ebc0ed5): added Chinese locale
- [31823f9](https://github.com/ddnexus/pagy/commit/31823f9): fix for compact nav in bootstrap 3 (#86)
- [ac2a76c](https://github.com/ddnexus/pagy/commit/ac2a76c): docs fixes

### Version 0.19.2

### Changes

- added Russian locale

### Commits

- [099731b](https://github.com/ddnexus/pagy/commit/099731b): small improvement in i18n docs
- [ef77ae2](https://github.com/ddnexus/pagy/commit/ef77ae2): Added ru translation (#84)
- [d4b49d6](https://github.com/ddnexus/pagy/commit/d4b49d6): Fixed a typo in spanish locale. (#85)
- [a91b72a](https://github.com/ddnexus/pagy/commit/a91b72a): small description change for items selector UI
- [a62b222](https://github.com/ddnexus/pagy/commit/a62b222): small refactoring of the trim extra
- [c23505a](https://github.com/ddnexus/pagy/commit/c23505a): fix for doc typo

### Version 0.19.1

### Changes

- added Spanish locale

### Commits

- [6d498a1](https://github.com/ddnexus/pagy/commit/6d498a1): Added Spanish locale (#82)
- [65db877](https://github.com/ddnexus/pagy/commit/65db877): general docs improvements
- [5862b3f](https://github.com/ddnexus/pagy/commit/5862b3f): updated README
- [6414395](https://github.com/ddnexus/pagy/commit/6414395): added GoRails screencast link to README
- [a8ad399](https://github.com/ddnexus/pagy/commit/a8ad399): docs fixes and improvements
- [435e849](https://github.com/ddnexus/pagy/commit/435e849): updated docs

### Version 0.19.0

### Important Changes

- added Semantic extra

### Commits

- [b65a905](https://github.com/ddnexus/pagy/commit/b65a905): fix for page_link string and conflicts, added compact and responsive navs tests and docs (#73)
- [f147cca](https://github.com/ddnexus/pagy/commit/f147cca): Semantic UI nav helper (#73)
- [429ac14](https://github.com/ddnexus/pagy/commit/429ac14): updated pagy.js requirements info

### Version 0.18.0

### Breaking Changes

- The extras dir has been reorganized:
  - the helpers in the `compact` and `responsive` extras have been integrated into the respective frontend extras (`bootstrap`, `bulma`, `foundation`, `materialize` and `navs`), so the `compact` and `responsive` extras are gone. You should remove them from the [pagy.rb](https://github.com/ddnexus/pagy/blob/master/lib/config/pagy.rb) initializer, eventually adding the `navs` extra if you use its plain helpers.
  - The `templates` and `javascripts` dirs have been moved to `lib`. If you use rails and the `pagy.js` you should update the `assets.paths` in the [pagy.rb](https://github.com/ddnexus/pagy/blob/master/lib/config/pagy.rb) initializer.

### Commits

- [8865fdb](https://github.com/ddnexus/pagy/commit/8865fdb): updated docs
- [f093c7e](https://github.com/ddnexus/pagy/commit/f093c7e): reorganization of tests
- [cd11c4f](https://github.com/ddnexus/pagy/commit/cd11c4f): reorganization of extras dir

### Version 0.17.0

### Important Changes

- added Foundation extra

### Commits

- [ba7d046](https://github.com/ddnexus/pagy/commit/ba7d046): added fixes and improvements for foundation extra:
- [d78f85d](https://github.com/ddnexus/pagy/commit/d78f85d): Foundation extra (#79)
- [13f37a0](https://github.com/ddnexus/pagy/commit/13f37a0): restyling of extras in README, docs and initializer example

### Version 0.16.0

### Important Changes

- added Searchkick extra

### Commits

- [6143f5f](https://github.com/ddnexus/pagy/commit/6143f5f): docs fixes
- [5227bd1](https://github.com/ddnexus/pagy/commit/5227bd1): added searchkick references in docs, README and initializer example (#75)
- [b9acf0c](https://github.com/ddnexus/pagy/commit/b9acf0c): Searchkick extra (#75)

### Version 0.15.1

### Important Changes

- small bugs and compatibility fixes

### Commits

- [1436833](https://github.com/ddnexus/pagy/commit/1436833): added Post and Tutorials links and Credits section
- [4e400c6](https://github.com/ddnexus/pagy/commit/4e400c6): items are adjusted only for non-empty pages
  (avoid sequel error for limit 0)
- [3abcc11](https://github.com/ddnexus/pagy/commit/3abcc11): fix for sinatra: page_param must be converted to string
- [ad8c311](https://github.com/ddnexus/pagy/commit/ad8c311): fix for trim extra missing when current page=1
- [6f774d7](https://github.com/ddnexus/pagy/commit/6f774d7): added missing doc menu for trim extra
- [5f6de56](https://github.com/ddnexus/pagy/commit/5f6de56): added a few tests for the trim extra

### Version 0.15.0

### Important Changes

- Added Trim extra

### Commits

- [868be25](https://github.com/ddnexus/pagy/commit/868be25): added trim extra
- [fb48976](https://github.com/ddnexus/pagy/commit/fb48976): chained method for extra items overrides

### Version 0.14.0

### Important Changes

- Added Materialize extra

### Commits

- [b0e959b](https://github.com/ddnexus/pagy/commit/b0e959b): added stars github button to docs
- [1dd38c9](https://github.com/ddnexus/pagy/commit/1dd38c9): added materialize extra (#44)
- [30c1a57](https://github.com/ddnexus/pagy/commit/30c1a57): updated docs

### Version 0.13.1

### Important Changes

- Improvements and fixes for the out_of_range extra

### Commits

- [419af23](https://github.com/ddnexus/pagy/commit/419af23): better comments and error message for unknown out_of_range_mode variable
- [48fa056](https://github.com/ddnexus/pagy/commit/48fa056): use case (#71)
- [36761a8](https://github.com/ddnexus/pagy/commit/36761a8): fix :last_page mode in out_of_range extra (#70)
- [5958dc5](https://github.com/ddnexus/pagy/commit/5958dc5): out_of_range extra improvements (#68)
- [7177cd9](https://github.com/ddnexus/pagy/commit/7177cd9): added note about the out_of_range exta to the README

### Version 0.13.0

### Important Changes

- Added Out Of Range extra

### Commits

- [c52db2a](https://github.com/ddnexus/pagy/commit/c52db2a): added out_of_range extra (#68)
- [37119ee](https://github.com/ddnexus/pagy/commit/37119ee): fixes for bulma extra in initializer_example.rb and docs
- [05729f6](https://github.com/ddnexus/pagy/commit/05729f6): removed redundant condition in Pagy initialize
- [2e75247](https://github.com/ddnexus/pagy/commit/2e75247): small fixes in CHANGELOG
- [3d732dd](https://github.com/ddnexus/pagy/commit/3d732dd): docs for empty page OutOfRangeError (#69)
- [a3be52e](https://github.com/ddnexus/pagy/commit/a3be52e): Fixed screenshot name for compact bulma extra (#67)

### Version 0.12.0

### Important Changes

- Added Bulma extra
- Added Turkish language in pagy dictionary
- Improved consistence and compatibility in extras

### Commits

- [8fc91df](https://github.com/ddnexus/pagy/commit/8fc91df): fix for "area" typos in bulma extra (#62) and consistency improvement across responsive helpers
- [bb0060f](https://github.com/ddnexus/pagy/commit/bb0060f): fix for window event listeners persisted by turbolinks (#64)
- [d759f91](https://github.com/ddnexus/pagy/commit/d759f91): Bulma extra (#62)
- [9f09837](https://github.com/ddnexus/pagy/commit/9f09837): Fixed typo in some extras usage (#63)
- [2ce572a](https://github.com/ddnexus/pagy/commit/2ce572a): Added Turkish locales (#58)
- [dd361e1](https://github.com/ddnexus/pagy/commit/dd361e1): replaced Array.from in pagy.js for extended compatibility
- [bb1243d](https://github.com/ddnexus/pagy/commit/bb1243d): fix typos
- [da34b94](https://github.com/ddnexus/pagy/commit/da34b94): fix typo (#61)
- [cdab354](https://github.com/ddnexus/pagy/commit/cdab354): added better I18n comments in initializer_example.rb

### Version 0.11.2

### Commits

- [76fc273](https://github.com/ddnexus/pagy/commit/76fc273): used merge! in place of **

### Version 0.11.1

### Important Changes

- All tests has been converted to the spec syntax

### Commits

- [b14f446](https://github.com/ddnexus/pagy/commit/b14f446): converted tests to spec syntax
- [0422568](https://github.com/ddnexus/pagy/commit/0422568): cleanup of i81n plurals in code and docs
- [5a2db16](https://github.com/ddnexus/pagy/commit/5a2db16): added a few missing references to docs
- [6028980](https://github.com/ddnexus/pagy/commit/6028980): enabled rubocop Naming/UncommunicativeMethodParamName
- [1de470b](https://github.com/ddnexus/pagy/commit/1de470b): small improvements and fixes for docs
- [228109e](https://github.com/ddnexus/pagy/commit/228109e): fix for typos
- [0a1ed94](https://github.com/ddnexus/pagy/commit/0a1ed94): updated initializer_example.rb

### Version 0.11.0

### Breaking Changes

- The `pagy_nav_bootstrap_compact` and `pagy_nav_bootstrap_responsive` helpers have been renamed as `pagy_nav_compact_bootstrap` and `pagy_nav_responsive_bootstrap` to keep the consistency with the extras structure (and support the consistency of future framework additions). Please, rename them accordingly.
- The `Pagy::Frontend::I18N.load_file` has been renamed as `Pagy::Frontend::I18N.load` and expects a hash as the argument. See the [I18n doc](https://ddnexus.github.io/pagy/api/frontend#i18n).
- The seldom used `Pagy::Frontend::I18N[plurals]` has been renamed as `Pagy::Frontend::I18N[plural]`. See the [I18n doc](https://ddnexus.github.io/pagy/api/frontend#i18n).
- Please, update your initializer file to the new [initializer_example.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/initializer_example.rb)

### Commits

- [826798b](https://github.com/ddnexus/pagy/commit/826798b): refactoring of I18N constant: added support for multiple static languages and plural rules
- [b78b71e](https://github.com/ddnexus/pagy/commit/b78b71e): better naming of json classes
- [ea7c22b](https://github.com/ddnexus/pagy/commit/ea7c22b): breaking change for compact and responsive extras: renaming of pagy_nav_bootstrap_* helpers to pagy_nav_*_bootstrap, consistent with extras structure
- [7d1a573](https://github.com/ddnexus/pagy/commit/7d1a573): internal consistency renaming of local/test variables; minor fixes and improvements

### Version 0.10.1

### Commits

- [8ca5013](https://github.com/ddnexus/pagy/commit/8ca5013): doc improvements
- [99081ee](https://github.com/ddnexus/pagy/commit/99081ee): small performance improvement for #51 and updated doc
- [5863ceb](https://github.com/ddnexus/pagy/commit/5863ceb): add support for counting grouped collections (#51)

### Version 0.10.0

### Breaking Changes

- The javascript used by the `compact`, `items` and `responsive` extras has been refactored in order to avoid any usafe-inline vulnerability. The javascript files have been replaced by one shared file, and require to set an event listener or run a function on window load. See the commit below and the [javascript doc](https://ddnexus.github.io/pagy/extras#javascript) for details.

### Commits

- [50e304b](https://github.com/ddnexus/pagy/commit/50e304b): javascript refactoring in order to avoid any usafe-inline vulnerability (#52):
  - removed inline scripts from all extras
  - one single pagy.js file shared among all the extras
  - the Pagy.init function should be executed at document load
  - updated tests and docs
- [32d0afc](https://github.com/ddnexus/pagy/commit/32d0afc): doc fix: UI selector > selector UI

### Version 0.9.2

### Important Changes

- 100% test coverage for core code and extras

### Commits

- [c11dfcc](https://github.com/ddnexus/pagy/commit/c11dfcc): updated README
- [f8728e7](https://github.com/ddnexus/pagy/commit/f8728e7): added responsive extra tests
- [14ea356](https://github.com/ddnexus/pagy/commit/14ea356): added compact extra tests
- [2ac743c](https://github.com/ddnexus/pagy/commit/2ac743c): added bootstrap extra test
- [159b65e](https://github.com/ddnexus/pagy/commit/159b65e): added i18n extra tests
- [4449c95](https://github.com/ddnexus/pagy/commit/4449c95): normalization of indentation and empty lines
- [9afa9d9](https://github.com/ddnexus/pagy/commit/9afa9d9): Default Frozen Strings for Ruby Files (#49)
- [036c87f](https://github.com/ddnexus/pagy/commit/036c87f): Fix typo in :page_param configuration (#48)

### Version 0.9.1

### Important Changes

- Improved the Pagy::OutOfRangeError exception, now storing the failed Pagy object

### Commits

- [9817659](https://github.com/ddnexus/pagy/commit/9817659): improved OutOfRangeError exception (storing the failed Pagy object)
- [7195229](https://github.com/ddnexus/pagy/commit/7195229): a few minor consistency fixes for the initializer_example.rb
- [00cd5bb](https://github.com/ddnexus/pagy/commit/00cd5bb): Fix typo (#45)
- [8b3a733](https://github.com/ddnexus/pagy/commit/8b3a733): fix for header level in README

### Version 0.9.0

### Important Changes

- Improvements for compact extra
- Refactoring of test structure and tasks
- Added items extra: Allow the client to request a custom number of items per page with an optional selector UI

### Commits

- [45dd700](https://github.com/ddnexus/pagy/commit/45dd700): added array extra tests
- [623ce9e](https://github.com/ddnexus/pagy/commit/623ce9e): Added items extra and tests
- [5902e8a](https://github.com/ddnexus/pagy/commit/5902e8a): passed instance variables are not deleted from the vars hash
- [e2efb89](https://github.com/ddnexus/pagy/commit/e2efb89): refactoring of test structure
- [1cc335b](https://github.com/ddnexus/pagy/commit/1cc335b): documentation updates
- [42ba053](https://github.com/ddnexus/pagy/commit/42ba053): improved pagy-compact.js
- [8c20fa2](https://github.com/ddnexus/pagy/commit/8c20fa2): Enable support for return key inside Pagy compact input (#43)
- [d4b751c](https://github.com/ddnexus/pagy/commit/d4b751c): Documentation updates
- [9f0dec9](https://github.com/ddnexus/pagy/commit/9f0dec9): fix for broken links in docs
- [c41e823](https://github.com/ddnexus/pagy/commit/c41e823): additions and normalization of documentation and comments
- [38f9a49](https://github.com/ddnexus/pagy/commit/38f9a49): fix for broken links in readme

### Version 0.8.6

### Commits

- [0db9125](https://github.com/ddnexus/pagy/commit/0db9125): minor documentation improvements
- [8a0a5a8](https://github.com/ddnexus/pagy/commit/8a0a5a8): broader parallel assignment in methods (improved performance)
- [18baa8b](https://github.com/ddnexus/pagy/commit/18baa8b): small optimization (used a few less objects)
- [1d16e3d](https://github.com/ddnexus/pagy/commit/1d16e3d): documentation additions
- [62c420d](https://github.com/ddnexus/pagy/commit/62c420d): updated doc and charts

### Version 0.8.5

### Important Changes

- Fix for I18n exception on startup

### Commits

- [31b815d](https://github.com/ddnexus/pagy/commit/31b815d): fix for I18n exception on startup (#41)

### Version 0.8.4

### Important Changes

- Improved readability and memory allocation
- All the core methods are tested
- Better documentation

### Commits

- [c25493a](https://github.com/ddnexus/pagy/commit/c25493a): added backend tests
- [48dbe17](https://github.com/ddnexus/pagy/commit/48dbe17): docs small fixes and improvements
- [5e9b7d4](https://github.com/ddnexus/pagy/commit/5e9b7d4): add Pagy::VARS[:item_path] empty default
- [ce8d09a](https://github.com/ddnexus/pagy/commit/ce8d09a): add Pagy::VARS[:anchor] empty default
- [8789edd](https://github.com/ddnexus/pagy/commit/8789edd): improve readability of pagy_link_proc (#40):
  - remove conditions to avoid extra spaces inside the html tags
  - add Pagy::VARS[:link_extra] empty default
- [2f0d384](https://github.com/ddnexus/pagy/commit/2f0d384): docs additions

### Version 0.8.3

### Important Changes

- Refactoring of `I18N` to fix #39

### Breaking Changes

- `Pagy::I18N` has been moved to `Pagy::Frontend::I18N`: you should update the [initializer](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/initializer_example.rb) in case you set any of the `Pagy::I18N` variable.

### Commits

- [d85791e](https://github.com/ddnexus/pagy/commit/d85791e): Refactoring I18N to fix #39:
  - moved I18N from Pagy to Pagy::Frontend - I18N_DATA moved to Pagy::Frontend::I18N[:data]
  - I18N[:file] removed
  - added I18N.load_file method
  - updated initializer and doc
- [504b943](https://github.com/ddnexus/pagy/commit/504b943): added missing tests to frontend_test.rb
- [6168df3](https://github.com/ddnexus/pagy/commit/6168df3): use actionable code coverage (#29)

### Version 0.8.2

### Important Changes

- Fix for PostgreSQL: `collection.count(:all)`

### Commits

- [8a204dc](https://github.com/ddnexus/pagy/commit/8a204dc): fix for count in Pagy::Backend (#36)
- [29ed221](https://github.com/ddnexus/pagy/commit/29ed221): added test_check_variable_defaults
- [1263540](https://github.com/ddnexus/pagy/commit/1263540): indentation fixes for tests
- [a927d57](https://github.com/ddnexus/pagy/commit/a927d57): improve testability by sorting tests into files that mirror lib + group by method (#37)
- [5a5178b](https://github.com/ddnexus/pagy/commit/5a5178b): docs markdown normalization and README editing
- [11fbc49](https://github.com/ddnexus/pagy/commit/11fbc49): examples was using responsive instead of i18n (#35)
- [b9517d3](https://github.com/ddnexus/pagy/commit/b9517d3): docs small fixes and improvements
- [8bc6900](https://github.com/ddnexus/pagy/commit/8bc6900): fix for typos and omissions in initializer_example.rb
- [a09558e](https://github.com/ddnexus/pagy/commit/a09558e): avoid uncoverable code by not using gemspec in Gemfile (#33)
- [a585fe6](https://github.com/ddnexus/pagy/commit/a585fe6): clean up gemspec (#32)
- [ae48f13](https://github.com/ddnexus/pagy/commit/ae48f13): test on all supported ruby versions and split out rubocop (#30)

### Version 0.8.1

### Important Changes

- Added `:params` and `:anchor` variables to control the generation of arbitrary URLs per pagy instance

### Commits

- [d185461](https://github.com/ddnexus/pagy/commit/d185461): added :params and :anchor variables to control the generation of arbitrary URLs per pagy instance
- [62fd6c8](https://github.com/ddnexus/pagy/commit/62fd6c8): better forward improvements with pagy_url_for receiving the pagy instance instead of the single pagy_param
- [748de0d](https://github.com/ddnexus/pagy/commit/748de0d): readme and changelog improvement

### Version 0.8.0

### Important Changes

- Added an [initializer_example.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/initializer_example.rb) that you can use as an easy template for Pagy configuration
- Integrated extras from `pagy-extras` gem into Pagy, now optionally required in the initializer
- Refactored I18n: simpler code and simpler configuration now based on the `I18n` extra
- Reorganized documentation and added a few more how-to topics
- Added the `:page_param` variable used to get the page number from and create the URL for the page links

### Breaking Changes

- Extra are now integrated in Pagy. The `pagy-extras` gem has been discontinued: you should remove it and update your code as indicated [here](https://github.com/ddnexus/pagy-extras)
- Pagy I18n has been refactored and it's simpler to use. The main change is that the `Pagy::I18N[:gem]` variable has been removed, so if you want to use the `I18n` gem in place of the internal Pagy implementation you need just to `require 'pagy/extras/i18n'` in your initializer. (see the [I18n doc](https://ddnexus.github.io/pagy/api/frontend.md#i18n))
- `pagy_url_for` now requires the pagy argument and it's called with 2 params: if you have overridden it, you should add the extra param.
- `pagy_get_vars` now requires the vars argument and it's called with 2 params: if you have overridden it, you should add the extra param.
- `pagy_array_get_vars` now requires the vars argument and it's called with 2 params: if you have overridden it, you should add the extra param.

### Commits

- [3e2ad90](https://github.com/ddnexus/pagy/commit/3e2ad90): added CHANGELOG.md with the latest few versions (#23)
- [f42811d](https://github.com/ddnexus/pagy/commit/f42811d): implemented :page_param (#20)
- [9040593](https://github.com/ddnexus/pagy/commit/9040593): added doc about skipping single pages navs
- [605169a](https://github.com/ddnexus/pagy/commit/605169a): added initializer_example.rb
- [daa2c1b](https://github.com/ddnexus/pagy/commit/daa2c1b): I18n refactoring:
  - simplified the pagy_t definition
  - simplified the configuration of I18n now using the I18n extra to override the pagy_t
  - modules are now required and not autoloaded
  - updated doc
- [9b0d420](https://github.com/ddnexus/pagy/commit/9b0d420): style cleanup (#24)
- [dbc5a05](https://github.com/ddnexus/pagy/commit/dbc5a05): Add prev/next links to compact Bootstrap nav and improve HTML semantics (#21)
- [f764cc1](https://github.com/ddnexus/pagy/commit/f764cc1): refactoring extras:
  - moved pagy-extra files into the extras dir - moved the templates dir into the extras dir
  - updated the documentation
- [befe92d](https://github.com/ddnexus/pagy/commit/befe92d): Fixes Travis Badge (#25)
- [3862d26](https://github.com/ddnexus/pagy/commit/3862d26): Travis (#22)
- [7050ee0](https://github.com/ddnexus/pagy/commit/7050ee0): basic rubocop setup (#18)
- [7aee689](https://github.com/ddnexus/pagy/commit/7aee689): small docs improvements

### Version 0.7.2

### Commits

- [527cd8e](https://github.com/ddnexus/pagy/commit/527cd8e): simplification of pagy_url_for
- [144c997](https://github.com/ddnexus/pagy/commit/144c997): URIs are always joined with forward slash (#11)

### Version 0.7.1

### Commits

- [92260b5](https://github.com/ddnexus/pagy/commit/92260b5): docs and comments refactoring, also updated for pagy-extras v0.2.0
- [46f235e](https://github.com/ddnexus/pagy/commit/46f235e): Remove a couple of Range allocations (#10)
- [39c5470](https://github.com/ddnexus/pagy/commit/39c5470): Only use and/or keywords for control flow (#12)
- [26bb29c](https://github.com/ddnexus/pagy/commit/26bb29c): fix for last page items adjusted to 0 (#3)
- [860c579](https://github.com/ddnexus/pagy/commit/860c579): small improvement: to_i called only once in core variables set
- [b18f254](https://github.com/ddnexus/pagy/commit/b18f254): Merge pull request #5 from artinboghosian/patch-1
- [03d6ee8](https://github.com/ddnexus/pagy/commit/03d6ee8): Update README.md
- [358a806](https://github.com/ddnexus/pagy/commit/358a806): a couple of links added to the doc
- [fac1f34](https://github.com/ddnexus/pagy/commit/fac1f34): docs small fixes and improvements

### Version 0.7.0

### Commits

- [e480d04](https://github.com/ddnexus/pagy/commit/e480d04): better handling of blank core variables
- [b56ef9e](https://github.com/ddnexus/pagy/commit/b56ef9e): docs fixes and improvements
- [987be15](https://github.com/ddnexus/pagy/commit/987be15): I18N[:plurals] proc uses symbols and returns a frozen string
- [6ea7a48](https://github.com/ddnexus/pagy/commit/6ea7a48): changes for PR #2: - use of local variable zero_one instead of constant I18N_PLURALS - VARS cannot be frozen
- [fb8975b](https://github.com/ddnexus/pagy/commit/fb8975b): Freeze class immutable constant variable at first

### Version 0.6.0

### Commits

- [ec8e268](https://github.com/ddnexus/pagy/commit/ec8e268): created pagy-extras gem:
  - moved pagy_nav_bootstrap method and templates
  - updated docs files (fixes and use of relative links)
- [facc4c3](https://github.com/ddnexus/pagy/commit/facc4c3): unescaped notation for all erb usage
- [26c068f](https://github.com/ddnexus/pagy/commit/26c068f): moved require yaml in Frontend
- [f5782d8](https://github.com/ddnexus/pagy/commit/f5782d8): added css classes to pagy helper navs
- [cee02c6](https://github.com/ddnexus/pagy/commit/cee02c6): helper tags are strings
- [4788c3c](https://github.com/ddnexus/pagy/commit/4788c3c): grouped the variables :initial, :before, :after and :final into one single :size array; #series can work with different sizes
- [e610b82](https://github.com/ddnexus/pagy/commit/e610b82): minor docs fixes

---

### Retype.Yml (`docs/retype.yml`)

start:
  pro: true
  open: false

input: .
output: .public
url: https://ddnexus.github.io/pagy/

include:
  - "/assets/nav.html.erb"
  - "/gem/apps/enable_rails_page_segment.rb"
  - "/gem/config/pagy.rb"
  - "/gem/locales/en.yml"
  - "/gem/stylesheets/pagy.css"
  - "/gem/stylesheets/pagy-tailwind.css"

exclude:
  - "CLAUDE*.md"

branding:
  title: Pagy
  logo: assets/images/pagy-the-frog.png
  label: 43.8.1
  colors:
    label:
      text: "#ffffff"
      background: "#1f7a1f"

meta:
  title: " 💚 Pagy"

links:
  - text: GitHub
    link: https://github.com/ddnexus/pagy
    icon: mark-github
    target: blank
  - text: Issues
    link: https://github.com/ddnexus/pagy/issues
    icon: issue-opened
    target: blank
  - text: Q&A
    link: https://github.com/ddnexus/pagy/discussions/categories/q-a
    icon: comment-discussion
    target: blank
  - text: Discussions
    link: https://github.com/ddnexus/pagy/discussions
    icon: comment-discussion
    target: blank

search:
  mode: full

backlinks:
  enabled: false

breadcrumb:
  home: Pagy
  separator: ":icon-arrow-right:"

theme:
  base:
    base-color: "#008000"
  dark:
    base-color: "#7cb342"

lastUpdated:
  date:
    enabled: true
    source: committer

nav:
  mode: stack
  icons:
    mode: pages

nextprev:
  mode: exclude

footer:
  copyright: "&copy; 2017-{{ date.now.year }} • [Domizio Demichelis](https://github.com/ddnexus) • [MIT License](https://opensource.org/licenses/MIT)"

---

### Guides Choose Right (`docs/guides/choose-right.md`)

---
label: Choose Right
icon: thumbsup
order: 95
---

#

## :icon-thumbsup:&nbsp;&nbsp;Choose the right tool

---

Pagination just means retrieving a (potentially big) collection of items in small sequential chunks (pages).

Pagy offers four different pagination techniques, each implementing different types of paginators. Choose the right one to ensure the best performance and workflow.

### Pagination Types

>>> :icon-move-to-end:&nbsp;&nbsp;OFFSET {#offset}

The most common pagination technique is counting the collection items (COUNT DB query), using the count to calculate where the specific pages start (i.e., OFFSET) and retrieving only the LIMIT items for that page (OFFSET+LIMIT DB query).

It is straightforward to understand and set up and very versatile for the UI, but it also has a few shortcomings.

==- Shortcomings

- **DB Performance**
  - Counting records might be quite expensive in terms of execution time and DB load. The standard OFFSET pagination causes the DB to count twice per page: once for the total records and another for the records to skip.
- **Data Shift**
  - If records are created or deleted during browsing, the calculated OFFSET may become inconsistent. The user may see previous records again or miss records entirely.
  - Notice that querying the precise count for each page DOES NOT fix the data-shift problem.

!!!tip For better DB performance with OFFSET...
- Use the [:countish](#offset-paginators) or [:countless](#offset-paginators) paginators to improve the DB performance **up to two times**.
- [Paginate only MAX records](/guides/how-to/#paginate-only-max-records) when possible.
!!!

==- Paginators&nbsp;&nbsp;[!badge variant="contrast" size="xs" corners="pill" text="3"] {#offset-paginators}

<br/>

{.list-icon}
- [:icon-move-to-end:&nbsp;:offset](/toolbox/paginators/offset.md)
  - :icon-check-circle: **Best for**: Standard App (Small Data)
  - :icon-thumbsup:     **Pros**: Simple setup, full UI support
  - :icon-thumbsdown:   **Cons**: Slow on big tables (two queries per page), data-shift

{.list-icon}
- [:icon-move-to-end:&nbsp;:countish](/toolbox/paginators/countish.md)
  - :icon-check-circle: **Best for**: Standard App (Large Data)
  - :icon-thumbsup:     **Pros**: Same as `:offset`, but far better performance than `:offset` (memoizes the total count)
  - :icon-thumbsdown:   **Cons**: Same as `:offset`, the `count` may become stale (likely a negligible side effect)

{.list-icon}
- [:icon-move-to-end:&nbsp;:countless](/toolbox/paginators/countless.md)
  - :icon-check-circle: **Best for**: API, Infinite Scroll
  - :icon-thumbsup:     **Pros**: Fastest in the offset family (only one query per page)
  - :icon-thumbsdown:   **Cons**: Same as `:offset`, the `count` is always `nil`, UI support with a few limitations

===

>>> :icon-key:&nbsp;&nbsp;KEYSET {#keyset}

The KEYSET pagination technique allows the fastest and lightest DB performance. It does not count the (ordered) collection (which makes it faster), nor does it calculate any numeric page pointers in advance (which avoids the data-shift during browsing). It just uses the values in the last record of the page to retrieve the next page.

==- Shortcomings

- It knows only the current page and the pointer to the next page.
- Page pointers are encoded strings and the count is not known.
- It supports only APIs and infinite scrolling.

!!!tip For UI support with KEYSET pagination...
Use the [:keynav_js](#keyset-paginators) paginator.
!!!

==- Paginators&nbsp;&nbsp;[!badge variant="contrast" size="xs" corners="pill" text="2"] {#keyset-paginators}

<br/>

{.list-icon}
- [:icon-key:&nbsp;:keyset](/toolbox/paginators/keyset.md)
  - :icon-check-circle: **Best for**: API, Infinite Scroll
  - :icon-thumbsup:     **Pros**: Fastest paginator, no data-shift, fastest single query per page
  - :icon-thumbsdown:   **Cons**: Very limited UI support, appropriate DB indices required

{.list-icon}
- [:icon-key:&nbsp;:keynav_js](/toolbox/paginators/keynav_js.md)
  - :icon-check-circle: **Best for**: Standard App (Large Data)
  - :icon-thumbsup:     **Pros**: All the pros of `:keyset`+`:countless`, numeric pages
  - :icon-thumbsdown:   **Cons**: Same as `:countless`, requires [JavaScript Support](/resources/javascript) (or it falls back to the `:countless` paginator)

===

>>> :icon-calendar:&nbsp;&nbsp;TIME-RANGE {#time-range}

This hybrid technique filters by a specific time period (Year, Month, Day, etc.) and applies the offset paginator within that period.

==- Paginator&nbsp;&nbsp;[!badge variant="contrast" size="xs" corners="pill" text="1"]

{.list-icon}
- [:icon-calendar:&nbsp;:calendar](/toolbox/paginators/calendar.md)
  - :icon-check-circle: **Best for**: Time-series, log collections
  - :icon-thumbsup:     **Pros**: Natural navigation for date-based data
  - :icon-thumbsdown:   **Cons**: The setup for the UI is more involved

===

>>> :icon-search:&nbsp;&nbsp;SEARCH {#search}

Pagy supports `ElasticsearchRails`, `Meilisearch`, `ActiveSearch`, `Searchkick`, and `TypesenseRails`.

The search paginators get the count, limit and results provided by the search platform. Pagy acts as an interface to these underlying gems, using the [OFFSET](#offset) technique, without its shortcomings.

==- Paginators&nbsp;&nbsp;[!badge variant="contrast" size="xs" corners="pill" text="5"]

{.list-icon}
- [:icon-search:&nbsp;:elasticsearch_rails](/toolbox/paginators/elasticsearch_rails.md)&nbsp;&nbsp;
[:icon-search:&nbsp;:meilisearch](/toolbox/paginators/meilisearch.md)&nbsp;&nbsp;
[:icon-search:&nbsp;:rails_active_search](/toolbox/paginators/rails_active_search.md)&nbsp;&nbsp;
[:icon-search:&nbsp;:searchkick](/toolbox/paginators/searchkick.md)&nbsp;&nbsp;
[:icon-search:&nbsp;:typesense_rails](/toolbox/paginators/typesense_rails.md)
  - :icon-check-circle: **Best for**: Search Results
  - :icon-thumbsup:     **Pros**: Leverages the engine's native response
  - :icon-thumbsdown:   **Cons**: None (simple interface with the search platform gems)

===

>>>

---

### Guides How To (`docs/guides/how-to.md`)

---
label: How To
order: 90
icon: tools
---

#

## :icon-tools:&nbsp;&nbsp;How To

---

This page provides practical tips and examples to help you use Pagy efficiently.

==- Choose the right pagination technique

Check the [Choose Right Guide](choose-right)

==- Control the items per page

Fixed
: Set the `:limit` option to the number of items to serve with each page.

Requestable
: Set the `:client_limit` option to allow the client to request up to the `:client_limit`. If the client omits to request a specific limit, pagy will fall back to its regular behavior. See also [Customize the URL keys](#customize-the-url-keys) below.

Interactive
: Use the [limit_tag_js](/toolbox/helpers/limit_tag_js) helper to provide a UI selector to the user.

==- Customize the URL keys

- Set `page_key: 'custom_page'` to customize URL generation, e.g., `?custom_page=3`.
- Set the `:limit_key` to customize the `limit` param the same way. (Only useful if `:client_limit` is allowed).

See [URL Options](/resources/urls#options)

==- Control the pagination bar

You can customize the number and position of page links of the [series_nav](/toolbox/helpers/series_nav) and [series_nav_js](/toolbox/helpers/series_nav_js) navigation bars using:

- The [:slots and :compact options](/toolbox/helpers/series_nav#options).
- Overriding the `series` method for full control over the pagination bar.

==- Force the `:page`

Pagy retrieves the page from the `'page'` request params hash. To force a specific page number, pass it directly to the `pagy` method. For example:

```ruby controller
@pagy, @records = pagy(:offset, collection, page: 3) # force page #3
```

==- Handle user input

See [Safety](/guides/safety)

==- Customize the ARIA labels

You can customize the `aria-label` attributes of any `*nav*` helper by passing the `:aria_label` option to the helper.

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](/resources/ARIA).

==- Paginate with JSON:API nested URLs

Enable `jsonapi: true`, optionally providing `:page_key` and `:limit_key`:

```ruby
# 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')
```

==- Customize the URL query

See the [:querify Option](/resources/urls#options)

==- Add a URL fragment

See the [URL Options](/resources/urls#options)

==- Add HTML attributes to the anchor tags (links)

Pass the `:anchor_string` option to the helper. It's especially useful for adding `data-turbo-*` or `data-*` Stimulus attributes.

==- Customize CSS styles

Pagy includes different formats of [stylesheets](/resources/stylesheets) for customization, as well as styled `nav` tags for `:bootstrap` and `:bulma`.

You can also override the specific helper method.

:::
==- Override the element "style" attribute

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:

```css
.pagy input[style] {
  width: 5rem !important; /* just a useless example */
}
```
:::

==- Override pagy methods

- Identify the method file's path in the gem `lib` dir (e.g., 'pagy/...').
- Note the name of the module where it is defined (e.g., `Pagy::...`).

Copy and paste the original method in the [Pagy Initializer](/toolbox/configuration/initializer)

```ruby pagy.rb (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
```

If you need assistance, ask in the [Q&A discussions](https://github.com/ddnexus/pagy/discussions/categories/q-a).

==- Paginate an array

Simply pass it as the collection: `pagy(:offset, my_array, **options)`

==- Paginate ActiveRecord collections

Pagy works seamlessly with `ActiveRecord` collections, but certain collections may require specific handling:

:::

==- :icon-database:&nbsp; Grouped collections

For better performance of grouped counts, you may want to use the [:count_over](/toolbox/paginators/offset#options) option.

==- :icon-database:&nbsp; Decorated collections

Do it in two steps:

>>> Get the page of records without decoration

```ruby controller
@pagy, records = pagy(:offset, Post.all)
```
>>> Decorate the retrieved records

```ruby controller
@decorated_records = records.decorate # or YourDecorator.method(records), whatever works
```

>>>

==- :icon-database:&nbsp; Custom scope/count

If the default pagy doesn't get the right count:

```ruby 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
```

==- :icon-database:&nbsp; Ransack results

Ransack's `result` method returns an `ActiveRecord` collection that is ready for pagination:

```ruby controller
q = Person.ransack(params[:q])
@pagy, @people = pagy(:offset, q.result)
```

==- :icon-database:&nbsp; PostgreSQL collections

[Always ensure your collections are ordered!](/toolbox/paginators#troubleshooting)

:::

==- Paginate for generic API clients

Explore the following options:

- [:keyset paginator](/toolbox/paginators/keyset)
- [headers_hash helper](/toolbox/helpers/headers_hash)
- `:client_limit` paginator option
- `:jsonapi` paginator option

==- Paginate for JavaScript frameworks

You can send selected `@pagy` instance data to the client as JSON using the [data_hash](/toolbox/helpers/data_hash) helper, including pagination metadata in your JSON response.

==- Paginate search platform results

See these paginators:

- [elasticsearch_rails](/toolbox/paginators/elasticsearch_rails)
- [meilisearch](/toolbox/paginators/meilisearch)
- [rails_active_search](/toolbox/paginators/rails_active_search)
- [searchkick](/toolbox/paginators/searchkick)
- [typesense_rails](/toolbox/paginators/typesense_rails)

==- Paginate by date

Use the [:calendar](/toolbox/paginators/calendar) paginator for pagination filtering by calendar time units (e.g., year, quarter, month, week, day).

==- Paginate multiple independent collections

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:

:::
==- :icon-light-bulb:&nbsp; Override the request path

<br/>

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:

```rb Controller
def index
  @pagy_foos, @foos = pagy(:offset, Foo.all, path: '/foos')
  @pagy_bars, @bars = pagy(:offset, Bar.all, path: '/bars')
end
```

```erb
<%== @pagy_foos.series_nav %>
<%== @pagy_bars.series_nav %>


```

==- :icon-light-bulb:&nbsp; Use separate Turbo Frame actions

<br/>

If you're using [hotwire](https://hotwired.dev/) ([turbo-rails](https://github.com/hotwired/turbo-rails) being the Rails implementation), another way of maintaining independent contexts is using separate Turbo Frame actions. Just wrap each independent context in a `turbo_frame_tag` and ensure a matching `turbo_frame_tag` is returned:

```erb
<%# 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 %>
```

```rb Controller
def good
  @pagy, @movies = pagy(:offset, Movie.good, limit: 5)
end

def bad
  @pagy, @movies = pagy(:offset, Movie.bad, limit: 5)
end
```

==- :icon-light-bulb:&nbsp; Use the root_key option

<br/>

By default, pagy creates flat URLs for its links. If you need to handle multiple pagy instances in the same request, you can nest the `:page` and (if you use it) the `:limit` params by passing the `:root_key` option to the paginator:

```rb Controller
def index
  @pagy_stars, @stars     = pagy(:offset, Star.all, root_key: 'stars')
  @pagy_nebulae, @nebulae = pagy(:offset, Nebula.all, root_key: 'nebulae')
end
```

==- :icon-light-bulb:&nbsp; Use different page keys

<br/>

You can also paginate multiple models in the same request by simply using a different `:page_key` for each instance:

```rb Controller
def index
  @pagy_stars, @stars     = pagy(:offset, Star.all, page_key: 'pagy_stars')
  @pagy_nebulae, @nebulae = pagy(:offset, Nebula.all, page_key: 'pagy_nebulae')
end
```

:::

==- Paginate only MAX records

You may want to limit the availability of your records either for speeding up the DB queries (especially useful with [OFFSET](/guides/choose-right/#offset) paginators with big tables), or simply to avoid exposing all your data to scrapers.

The best way to ensure it is to create a limited collection using an ActiveRecord Virtual Table:

```rb
max_records     = 10_000
collection      = Product.where(...).limit(max_records)   # Add the max_records limit to your collection
limited         = collection.from(collection, :products)  # Create a limited collection using the :products Virtual Table
@pagy, @records = pagy(:offset, limited, **options)       # Paginate the limited collection
```

!!!success
- It works with all [OFFSET](/guides/choose-right/#offset) and [KEYSET](/guides/choose-right/#keyset) paginators.
- It produces faster COUNT and OFFSET queries, limiting the table scan.
- It enforces strict control over the quantity of records you expose.
!!!

==- Paginate collections with metadata

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:

```rb
#<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:

```ruby controller
# 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: request))
# set the paginated collection records
@movies = tobj.results
```

==- Skip single page navs

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:

```erb
<%== @pagy.series_nav if @pagy.last > 1 %>
```

==- Maximize performance

- Consider the paginators:
  - [:countish](/toolbox/paginators/countish)
  - [:countless](/toolbox/paginators/countless)
  - [:keyset](/toolbox/paginators/keyset)
  - [:keynav_js](/toolbox/paginators/keynav_js)
- Consider the helpers:
  - [series_nav_js](/toolbox/helpers/series_nav_js)
  - [input_nav_js](/toolbox/helpers/input_nav_js)
- When possible:
  - [Paginate only MAX records](#paginate-only-max-records)

==- Handle Pagy exceptions

:::

==- :icon-stop:&nbsp; `Pagy::OptionError`

It is a subclass of `ArgumentError` that offers information to rescue invalid options. For example: with `rescue Pagy::OptionError => e` you can get access to a few readers:

- `e.pagy` the pagy object
- `e.option` the offending option symbol (e.g. `:page`)
- `e.value` the value of the offending option (e.g. `-3`)

==- :icon-stop:&nbsp; `Pagy::RangeError`

With the [OFFSET](/guides/choose-right/#offset) pagination technique, users/clients may request a page beyond the end of the collection (e.g., when some records got deleted and a user follows a stale page link).

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 different action, so you can set the option `raise_range_error: true`, `rescue` it and do whatever fits your app better. For example:

```ruby controller
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
```
:::

==- Test with Pagy

- Pagy has 100% test coverage.
- You only need to test pagy if you have overridden methods.

==- Use your own pagination templates

!!!warning Warning!
The pagy nav helpers are not only a lot faster than templates, but they also 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 or 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:

:::code source="/assets/nav.html.erb" :::

You can use it as usual: just remember to pass the `:pagy` local set to the `@pagy` object:

```erb
<%== render file: 'nav.html.erb', locals: {pagy: @pagy} %>
```

!!!
You may want to look at the actual output interactively by running:

```sh
pagy demo
```

...and point your browser to <http://127.0.0.1:8000/template>.
!!!

==- Use Pagy with non-rack apps

For non-rack environments that don't respond to the `request` method, you should pass the `:request` option to the paginator.

==- Use `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:

```ruby YourModel
include Pagy::Method

def self.paginated(view, my_arg1, my_arg2, **)
  collection = ...
  view.instance_eval { pagy(:offset, collection, **) }
end
```

```ERB view
<% pagy, records = YourModel.paginated(self, my_arg1, my_arg2, **options) %>
```

==- Handle POST requests (server side)

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`:

```ruby
require 'digest'

def filtered_action
  pagy_options = {}
  if params[:filter_key] # retrieve already cached filters
    filters = session[params[:filter_key]]
  else # store new filters
    filters      = params[:filters] # your filter hash
    key          = Digest::SHA1.hexdigest(filters.sort.to_json)
    session[key] = filters

    pagy_options[:querify] = ->(query_hash) { query_hash.merge!(filter_key: key) }
  end
  collection      = Product.where(**filters)
  @pagy, @records = pagy(:offset, collection, **pagy_options)
end
```

!!!success Notice: ensure a server-side storage
If you use the `session` for caching, configure it to use `ActiveRecord`, `Redis`, or any server-side storage.
!!!

===

{{ include "snippets/ask-for-support" }}

---

### Guides Index.Yml (`docs/guides/index.yml`)

label: Guides
icon: north-star
order: 100
expanded: true

---

### Guides Migration Guide (`docs/guides/migration-guide.md`)

---
label: Migrate Legacy Pagination
icon: paper-airplane
order: 80
---

#

## :icon-paper-airplane:&nbsp;&nbsp;Migrate WillPaginate/Kaminari

---

This page covers most of the standard changes you need to make in order to migrate from legacy pagination.

### Steps

The Pagy API is quite different from other pagination gems; however, if you split the process into the following general steps, it should be quite simple.

>>> Remove the old code

==- {{ include "snippets/mini-step" step: "1" }} Preparation

- Uninstall the legacy gem and replace it with `gem "pagy"` in the `Gemfile`.
- Add the `include Pagy::Method` statement to the application controller.

==- {{ include "snippets/mini-step" step: "2" }} Application-wide search and replace

- Search for the class name of the legacy gem, for example `WillPaginate` or `Kaminari`. You should find most of the code related to the global gem configuration or monkey patching.
- Remove all the legacy settings of the old gem.

==- {{ include "snippets/mini-step" step: "3" }} Clean up the models

Look for terms like `per_page`, `per`, and similar, as these are configuration settings. Include them in the appropriate paginator call in the controller (e.g., `pagy(:offset, collection, limit: 10)`) or globally in the Pagy initializer (e.g., `Pagy::OPTIONS[:limit] = 10`).

If the app uses the `page` scope in model methods or scopes, remove it, along with the argument used to pass the page number, while preserving the rest of the scope. Locate where the app applies this paginated scope in the controllers and replace it with the scope in a paginator statement. For example:

```ruby Controller
#@records = Product.paginated_scope(params[:page])
@pagy, @records = pagy(:offset, Product.non_paginated_scope)
```

==- {{ include "snippets/mini-step" step: "4" }} Search and replace in the controllers

In controllers, legacy pagination statements generally map directly to Pagy, making them straightforward to convert.

Search for statements with keywords like `page` and `paginate` and use the `pagy(:offset, ...)` paginator instead. For example:

```ruby Controller
#@records = Product.some_scope.page(params[:page])
#@records = Product.paginate(:page => params[:page])

@pagy, @records = pagy(:offset, Product.my_scope)

#@records = Product.some_scope.page(params[:page]).per(15)
#@records = Product.some_scope.page(params[:page]).per_page(15)
#@records = Product.paginate(page: params[:page], per_page: 15)

@pagy, @records = pagy(:offset, Product.all, limit: 15)
```

==- {{ include "snippets/mini-step" step: "5" }} Search and replace in the views

Similarly, in views, legacy pagination statements typically correspond directly to Pagy, simplifying conversion.

Search for statements with keywords like `will_paginate` and `paginate` and use one of the `series_nav` methods. For example:

```erb View
<%= will_paginate @records %>
<%= paginate @records %>

<%== @pagy.series_nav %>
```

===

>>> Find the remaining code

If the app has tests, it's time to run them. If not, start the app and navigate through its pages.

If any legacy code remains, it will raise an exception. Remove the old code and retry until no exceptions occur.

>>> Fine-tuning

Once the app displays pagination correctly, customize Pagy as needed _(see [Stylesheets](/resources/stylesheets))_.

If the previous pagination used custom elements (e.g., custom params, URLs, links, HTML elements, etc.), adjustments may be required for compatibility. Take a look at the topics in the [How To](how-to) docs: they should cover most of your custom needs.

!!!tip
If the app uses pagination from `Bootstrap` or `Bulma` frameworks, the existing CSS should function seamlessly with Pagy navigation helpers.
!!!

>>>

{{ include "snippets/ask-for-support" }}

---

### Guides Pagy Next (`docs/guides/pagy-next.md`)

---
label: Use Pagy NEXT
order: 60
icon: zap
nav:
  badge:
    text: "≈ 44"
    variant: info
---

#

## :icon-zap:&nbsp;&nbsp;Pagy NEXT

---

!!!tip You can use the NEXT version NOW!

Just edit the Gemfile...

```diff Gemfile (diff)
- gem 'pagy', '~> 43.8'                         # MINOR version restriction
+ gem 'pagy', '~> 43.8.1', require: 'pagy/next' # PATCH version restriction + pagy/next entry point
```

As an alternative _(without Gemfile changes)_, ensure the environment variable `PAGY_NEXT=true` is set BEFORE `pagy` is required.

```rb IRB
$ PAGY_NEXT=true irb
>> require 'pagy'
=> true
>> Pagy::VERSION
=> "43.8.1.next"
```

!!!

### How does it work?

Pagy NEXT is the code that will be released as the next MAJOR version. It is already implemented and available in the current version; however, it is overridden with the legacy code and deprecation warnings to respect the [SemVer](https://semver.org/) contract.

Pagy NEXT provides an opt-in mechanism to bypass the legacy layer entirely. By using `require: 'pagy/next'` or setting `PAGY_NEXT=true`, you effectively run the next MAJOR version's code today.

### How should you maintain it?

This "early access" mode is lighter and faster, but **requires immediate adherence to the latest API changes**.

!!!warning Adjust the gem update policy!

- Use a stricter PATCH version restriction (e.g., replace `~> 43.8` with `~> 43.8.1`).
- Follow the [Deprecations Instructions](/changelog/#deprecations) after `bundle update` involving MINOR or MAJOR releases.
!!!

With the PATCH version restriction, Bundler will automatically update **only** PATCH releases, ensuring your NEXT code won't break on update. However, remember to manually check `bundle outdated` more often to avoid missing MINOR or MAJOR releases.

---

### Guides Quick Start (`docs/guides/quick-start.md`)

---
label: Quick Start
order: 100
icon: rocket
---

#

## :icon-rocket:&nbsp;&nbsp;Quick Start

---

!!!question Migrating from another pagination gem?
Check the [Migration Guide](migration-guide) first.
!!!

### Steps

>>> Install it...

```ruby Gemfile
gem 'pagy', '~> 43.8' # MINOR version restriction, to avoid breaking changes
```

>>> Try it...

{{ include "snippets/run-app" app: "demo" }}

>>> Use it...

=== {{ include "snippets/mini-step" step: "1" }} Include the `pagy` method where you are going to use it _(usually ApplicationController)_:

```ruby
include Pagy::Method
```

=== {{ include "snippets/mini-step" step: "2" }} Use it to paginate any collection with any technique:

```ruby
@pagy, @records = pagy(:offset, Product.some_scope, **options) # :offset paginator
@pagy, @records = pagy(:keyset, Product.some_scope, **options) # :keyset paginator
@pagy, @records = pagy(...)
```

_Read the [Choose Right](choose-right) guide to pick the right [paginators](/toolbox/paginators#paginators) for your app_

=== {{ include "snippets/mini-step" step: "3" }} Render navigation tags and other helpers with the `@pagy` instance methods:

```erb
<%# Render navigation bar helpers with various types and styles %>
<%== @pagy.series_nav %>
<%== @pagy.series_nav_js(:bootstrap) %>
<%== @pagy.input_nav_js(:bulma) %>
<%== @pagy.info_tag %>
```

_See all the available [@pagy helpers](/toolbox/helpers)_

===

>>>

---

### Guides Safety (`docs/guides/safety.md`)

---
label: Safety
icon: shield
order: 98
---

#

## :icon-shield:&nbsp;&nbsp;Safety

---

!!!success

Pagy handles the input of **its own URL params safely**, and assumes that all the configuration, options and arguments you set in your code are safe to use verbatim in its helpers.

!!!

!!!warning WARNING!

If you use **any other raw end-user input** in your Pagy configuration, options, or arguments, you must sanitize or escape it first in your own code.

!!!

---

### Guides Upgrade Guide (`docs/guides/upgrade-guide.md`)

---
label: Upgrade to 43
icon: check-circle
order: 70
nav:
  badge:
    icon: arrow-right
    text: "43"
    variant: info
---

#

## :icon-check-circle:&nbsp;&nbsp;Upgrade to 43

---

Pagy version 43 is a complete redesign of the legacy code. Its improvements make pagination a lot simpler and more powerful, but require a quite different way to use it.

!!!success Follow this guide to upgrade your app in just a few minutes.
Cherry-pick only what applies to your app: you can safely skip all the rest.
!!!

### Steps

>>> Replace the `pagy.rb` config file...

- Rename your `pagy.rb` initializer as `pagy-old.rb`.
- Add the new [pagy.rb](/toolbox/configuration/initializer) initializer in its place.
- Cut/remove the `Pagy::DEFAULT[...]` lines from `pagy-old.rb` and paste/add them to `pagy.rb`.
- Replace all the `Pagy::DEFAULT[...]` entries with `Pagy::OPTIONS[...]` in `pagy.rb`.

_In the next steps we will use the `pagy-old.rb` as the blueprint to guide most of the changes, and we will edit the new `pagy.rb` as needed._

>>> Remove your used extras...

The new version doesn't use the extras anymore. They got integrated into the core code, and a few have been discontinued.

- Search for any active `require 'pagy/extras/*'` in the `pagy-old.rb` file...
- When you find one, follow the specific section below to upgrade your code.
- As you proceed, remove each entry from the `pagy-old.rb`.

==- Extras

:::
==- `array`

{.compact}

| Search (old)      | Replace with (new)  |
|-------------------|---------------------|
| `pagy_array(...)` | `pagy(:offset,...)` |

==- `arel`

{.compact}

| Search (old)     | Replace with (new)                     |
|------------------|----------------------------------------|
| `pagy_arel(...)` | `pagy(:offset, count_over: true, ...)` |

==- `pagy`

- All the old helpers are now `@pagy` instance methods with more explicit names.

{.compact}

| Search (old)                                    | Replace with (new)                                  |
|-------------------------------------------------|-----------------------------------------------------|
| `pagy_nav_js(@pagy, ...)`                       | `@pagy.series_nav_js(...)`                          |
| `pagy_combo_nav_js(@pagy, ...)`                 | `@pagy.input_nav_js(...)`                           |
| `pagy_limit_selector_js(@pagy, ...)`            | `@pagy.limit_tag_js(...)`                           |
| `pagy_prev_url(@pagy, ...)`                     | `@pagy.page_url(:previous, ...)`                    |
| `pagy_next_url(@pagy, ...)`                     | `@pagy.page_url(:next, ...)`                        |
| `pagy_prev_a(@pagy, ...)`                       | `@pagy.previous_tag(...)`                           |
| `pagy_next_a(@pagy, ...)`                       | `@pagy.next_tag(...)`                               |
| `pagy_prev_link(@pagy, ...)`                    | discontinued: implement manually                    |
| `pagy_next_link(@pagy, ...)`                    | discontinued: implement manually                    |
| `size: ...`<br/>`Pagy::OPTIONS[:size] = ...`    | `slots: ...`<br/>`Pagy::OPTIONS[:slots] = ...`       |
| `ends: false`<br/>`Pagy::OPTIONS[:ends] = false` | `compact: true`<br/>`Pagy::OPTIONS[:compact] = true` |

==- `bootstrap`

- All the old helpers are now `@pagy` instance methods with more explicit names.

{.compact}

| Search (old)                                    | Replace with (new)                                  |
|-------------------------------------------------|-----------------------------------------------------|
| `pagy_bootstrap_nav(@pagy, ...)`                | `@pagy.series_nav(:bootstrap, ...)`                 |
| `pagy_bootstrap_nav_js(@pagy, ...)`             | `@pagy.series_nav_js(:bootstrap, ...)`              |
| `pagy_bootstrap_combo_nav_js(@pagy, ...)`       | `@pagy.input_nav_js(:bootstrap, ...)`               |
| `size: ...`<br/>`Pagy::OPTIONS[:size] = ...`    | `slots: ...`<br/>`Pagy::OPTIONS[:slots] = ...`       |
| `ends: false`<br/>`Pagy::OPTIONS[:ends] = false` | `compact: true`<br/>`Pagy::OPTIONS[:compact] = true` |

- **FYI**: The redundant `pagy-bootstrap` class has been removed from the `input_nav_js` body.

==- `bulma`

- All the old helpers are now `@pagy` instance methods with more explicit names.

{.compact}

| Search (old)                                    | Replace with (new)                                  |
|-------------------------------------------------|-----------------------------------------------------|
| `pagy_bulma_nav(@pagy, ...)`                    | `@pagy.series_nav(:bulma, ...)`                     |
| `pagy_bulma_nav_js(@pagy, ...)`                 | `@pagy.series_nav_js(:bulma, ...)`                  |
| `pagy_bulma_combo_nav_js(@pagy, ...)`           | `@pagy.input_nav_js(:bulma, ...)`                   |
| `size: ...`<br/>`Pagy::OPTIONS[:size] = ...`    | `slots: ...`<br/>`Pagy::OPTIONS[:slots] = ...`       |
| `ends: false`<br/>`Pagy::OPTIONS[:ends] = false` | `compact: true`<br/>`Pagy::OPTIONS[:compact] = true` |

- **FYI**: The `is-centered` CSS class has been removed.
- **FYI**: The previous/next links have been moved to the beginning and end of the pagination.

==- `countless`

{.compact}

| Search (old)                                                             | Replace with (new)                                    |
|--------------------------------------------------------------------------|-------------------------------------------------------|
| `pagy_countless(...)`                                                    | `pagy(:countless, ...)`                               |
| `countless_minimal: true`<br/>`Pagy::OPTIONS[:countless_minimal] = true` | `headless: true`<br/>`Pagy::OPTIONS[:headless] = true` |

==- `calendar`

{.compact}

| Search (old)                      | Replace with (new)     |
|-----------------------------------|------------------------|
| `pagy_calendar(...)`              | `pagy(:calendar, ...)` |
| `Pagy::Calendar::OutOfRangeError` | `Pagy::RangeError`     |
| flag: `active: true`              | `disabled: false`      |
| config: `pagy: ...`               | `offset: ...`          |

- If your `pagy-old.rb` file contains any localization configuration, then uncomment and customize the following line in the `pagy.rb` initializer: `Pagy::Calendar.localize_with_rails_i18n_gem(*your_locales)`.
  - _Note: In non-Rails applications, calendar localization requires adding `rails-i18n` to your Gemfile._
- Remove any existing `Pagy::Calendar::*::DEFAULT`. Pass the options to each unit when you paginate.

==- `elasticsearch_rails`

- Active and passive modes are now handled by the same `pagy` method:

{.compact}

| Search (old)                                                                             | Replace with (new)                                            |
|------------------------------------------------------------------------------------------|---------------------------------------------------------------|
| `pagy_elasticsearch_rails(...)`                                                          | `pagy(:elasticsearch_rails, ...)`                             |
| `Pagy.new_from_elasticsearch_rails(...)`                                                 | `pagy(:elasticsearch_rails, ...)`                             |
| `elasticsearch_rails_search: ...`<br/>`Pagy::OPTIONS[:elasticsearch_rails_search] = ...` | `search_method: ...`<br/>`Pagy::OPTIONS[:search_method] = ...` |

- **Customization of the `pagy_search` method name has been discontinued:**
  - Remove any existing `:elasticsearch_rails_pagy_search` variable from your code.
  - Replace your custom method name with the standard `pagy_search` method.

==- `meilisearch`

- Active and passive modes are now handled by the same `pagy` method:

{.compact}

| Search (old)                                                             | Replace with (new)                                            |
|--------------------------------------------------------------------------|---------------------------------------------------------------|
| `pagy_meilisearch(...)`                                                  | `pagy(:meilisearch, ...)`                                     |
| `Pagy.new_from_meilisearch(...)`                                         | `pagy(:meilisearch, ...)`                                     |
| `meilisearch_search: ...`<br/>`Pagy::OPTIONS[:meilisearch_search] = ...` | `search_method: ...`<br/>`Pagy::OPTIONS[:search_method] = ...` |

- **Customization of the `pagy_search` method name has been discontinued:**
  - Remove any existing `:meilisearch_pagy_search` variable from your code.
  - Replace your custom method name with the standard `pagy_search` method.

==- `searchkick`

- Active and passive modes are now handled by the same `pagy` method:

{.compact}

| Search (old)                                                           | Replace with (new)                                            |
|------------------------------------------------------------------------|---------------------------------------------------------------|
| `pagy_searchkick(...)`                                                 | `pagy(:searchkick, ...)`                                      |
| `Pagy.new_from_searchkick(...)`                                        | `pagy(:searchkick, ...)`                                      |
| `searchkick_search: ...`<br/>`Pagy::OPTIONS[:searchkick_search] = ...` | `search_method: ...`<br/>`Pagy::OPTIONS[:search_method] = ...` |

- **Customization of the `pagy_search` method name has been discontinued:**
  - Remove any existing `:searchkick_pagy_search` variable from your code.
  - Replace your custom method name with the standard `pagy_search` method.

==- `headers`

{.compact}

| Search (old)                                       | Replace with (new)                                        |
|----------------------------------------------------|-----------------------------------------------------------|
| `pagy_headers(...)`                                | `@pagy.headers_hash(...)`                                 |
| `pagy_headers_merge`                               | `response.headers.merge!(@pagy.headers_hash)`             |
| `headers: ...`<br/>`Pagy::OPTIONS[:headers] = ...` | `headers_map: ...`<br/>`Pagy::OPTIONS[:headers_map] = ...` |

- _Notice that the `:limit` header default is now `'page-limit'` (it was `'page-items'`)._
  - Set `Pagy::OPTIONS[:headers_map] = { limit: 'page-items', ... }` to preserve your current API, if it relies on the old default values.

==- `jsonapi`

{.compact}

| Search (old)                     | Replace with (new)     |
|----------------------------------|------------------------|
| `pagy_jsonapi_links(@pagy, ...)` | `@pagy.urls_hash(...)` |

- _Notice that the `nil` links are now removed as the `JSON:API` specification requires._
- **IMPORTANT**: Enable the feature by explicitly setting the `jsonapi: true` option _(in the initializer or `pagy` method)_.

==- `keyset`

{.compact}

| Search (old)                        | Replace with (new)            |
|-------------------------------------|-------------------------------|
| `pagy_keyset(...)`                  | `pagy(:keyset, ...)`          |
| `pagy_keyset_first_url(@pagy, ...)` | `@pagy.page_url(:first, ...)` |
| `pagy_keyset_next_url(@pagy, ...)`  | `@pagy.page_url(:next, ...)`  |
| `filter_newest: ...`                | override `compose_predicate`  |

- Replace any existing `:jsonify_keyset_attributes` with `:pre_serialize`.
  - The lambda receives the same `keyset_attributes` argument, but it must modify the specific values directly. The lambda's return value is ignored. For example: `->(attrs) { attrs[:created_at] = attrs[:created_at].strftime('%F %T.%6N') }`.

==- `limit`

{.compact}

| Search (old)                       | Replace with (new)                |
|------------------------------------|-----------------------------------|
| `limit_param: :...` (symbol value) | `limit_key: '...'` (string value) |
| `limit_extra: ...`                 | delete                            |
| `client_limit: ...`                | delete                            |

- Enable the feature by setting `client_limit: allowed_max_limit` option _(in the initializer or `pagy` method)_.

==- `metadata`

{.compact}

| Search (old)                                         | Replace with (new)                                    |
|------------------------------------------------------|-------------------------------------------------------|
| `pagy_metadata(@pagy, ...)`                          | `@pagy.data_hash(...)`                                |
| `metadata: ...`<br/>`Pagy::OPTIONS[:metadata] = ...` | `data_keys: ...`<br/>`Pagy::OPTIONS[:data_keys] = ...` |
| data_key -> `:scaffold_url`                          | `:url_template`                                       |

==- `overflow`

- The `Pagy::OverflowError` has been replaced by the `Pagy::RangeError`; however, it is no longer raised by default.
- Pagy rescues the `Pagy::RangeError` and serves an empty page by default.
  - Now, Pagy behaves the same as it did before when requiring the overflow extra and using its default settings.
- The legacy `pagy.overflow?` is now the `pagy.in_range?` method, which checks/returns the opposite state/boolean.
- The `overflow: :last_page` behavior has been discontinued because it provides nearly no benefit:
  - **Why is there little benefit in serving the last page?**
    - The navigation bar for an out-of-range request is rendered identically to that of the last page.
    - The only difference is that there are no records/results to display.
    - The "previous page" button points to the last page, so if users truly want to see the last page results (which they have likely already seen), they can simply click the link.
- **Summary for keeping the same behavior**:
  - The `:overflow` option is not used anymore.
  - If you did not use the extra (i.e., Pagy raised errors), set `raise_range_error: true`.
  - If you used `overflow: :empty_page` or just required the overflow extra, simply remove it (this is now the default behavior).
  - If you used `overflow: :last_page` and still want this behavior despite the reasons above, see this [How To Example](/guides/how-to/#pagyrangeerror).

==- `standalone`

- Replace the `:url` variable with the `:request` hash option. For example:

  ```ruby
  request: { base_url: 'http://www.example.com',
             path:     '/path',
             params:   { 'param1' => 1234 }, # The string-keyed params hash from the request
             cookie:   'xyz' }               # The 'pagy' cookie, only for keynav
  ```

==- `i18n`

- If absolutely necessary, uncomment or add this line to your initializer: `Pagy.translate_with_the_slower_i18n_gem!`.

==- `gearbox` _(discontinued)_

- Due to extensive overwriting for minimal benefit, you can safely remove this feature from your app without noticeable impact. Remove all `/gearbox/` related code.

==- `size` _(discontinued)_

- Pagination bars similar to WillPaginate and Kaminari are not good for a lot of reasons. If still required, adapt the legacy file from a previous commit.

==- `trim` _(discontinued)_

- It was mostly useless and half-baked, causing many complications in both the Ruby and JavaScript code for no significant benefit.
- Use an appropriate approach to address your requirement, such as utilizing URL rewriting at the HTTP server level.

:::
===

>>> Update the API...

==- Search and replace

{.compact}

| Search (old)                         | Replace with (new)                 |
|--------------------------------------|------------------------------------|
| `include Pagy::Backend`              | `include Pagy::Method`             |
| `include Pagy::Frontend`             | _(remove: integrated)_             |
| `pagy_nav(@pagy, ...)`               | `@pagy.series_nav(...)`            |
| `Pagy.root.`                         | `Pagy::ROOT.`                      |
| `page_param: :...` _(symbol value)_  | `page_key: '...'` _(string value)_ |
| `pagy_info(@pagy, ...)`              | `@pagy.info_tag(...)`              |
| `@pagy_locale = ...`                 | `Pagy::I18n.locale = ...`          |
| `@pagy.vars`                         | `@pagy.options`                    |
| `VariableError`                      | `OptionError`                      |
| `<error>.variable`                   | `<error>.option`                   |
| `fragment: '#...'` _(mandatory '#')_ | `fragment: '...'` _(optional '#')_ |
| `count_args: ...`                    | _(remove: integrated)_             |
| `outset: ...`                        | _(remove: discontinued)_           |
| `cycle: ...`                         | _(remove: discontinued)_           |

==- Replace the `:params` variable...

Use the `:querify` option, which is a `lambda` that can modify the string-keyed params hash at will. It is a bit more verbose, but it's more powerful and low-level. It solves an incompatibility with the old high-level `:params` hash/lambda and improves performance. It is part of the [URL Options](/resources/urls/#options) group that gives you full and efficient control over the URL composition. For example:

```ruby
# Old symbol-keyed, high-level hash variable
params: { a: 1, b: 2 }
# New string-keyed, low-level, direct modification of the params hash
querify: ->(p) { p.merge!('a' => 1, 'b' => 2) }
# It also allows things like:
querify: ->(p) { p.except!('not_useful').merge!('custom' => 'useful') }
```

==- Replace the `*prev*` abbreviated naming

Use `*previous*` in all the options, accessors, methods, etc.

===

>>> Finalize the upgrade...

==- JavaScript

If your `pagy-old.rb` contains any JavaScript setup, it should still work, so you can move it to the `pagy.rb` file; however, for apps with builders, consider using the new [Pagy.sync](/resources/javascript/#pick-a-configuration) and removing all the old entries from your JavaScript config files.

==- Stylesheets

The CSS for the default pagy helpers has new selectors and variables. See the new [Stylesheets](/resources/stylesheets) to interactively update your custom CSS.

!!!success CSS Frameworks
Supported CSS frameworks (like Bootstrap and Bulma) don't require any change.
!!!

==- Pagy::I18n and Locale Files

{{ include "snippets/mini-step" step: "1" }} If your `pagy-old.rb` contains the `Pagy::I18n` setup, and the setup includes some custom dictionary files, then uncomment and set up the relevant `Pagy::I18n` lookup section in the `pagy.rb` file. _(See the [I18n docs](/resources/i18n) for details)_

{{ include "snippets/mini-step" step: "2" }} Update your custom dictionary files (if any) to the new [dictionary structure](/resources/i18n/#sample-dictionary), or they won't work correctly.

{{ include "snippets/mini-step" step: "3" }} Remove all the I18n code from the `pagy-old.rb`. All the locales are autoloaded when your app uses them.

==- Overriding

- Overriding methods in controllers/helpers is either impossible or discouraged.
- The cleanest approach for local overriding is via Ruby refinements or the initializer for global override.
- Check the [How To Override Pagy Methods](/guides/how-to/#override-pagy-methods).
- If your `pagy-old.rb` contains overridden methods, copy the methods over to the `pagy.rb` initializer; however, consider that:
  - Internal Pagy protected methods have been extensively refactored, likely renamed, and occasionally removed.
  - You should reconcile internal overrides by reviewing the updated Pagy codebase.

You may also want to check these internal renamings:

{.compact}

| Search (old)         | Replace with (new)     |
|----------------------|------------------------|
| `pagy_url_for(@pagy` | `@pagy.page_url(`      |
| `pagy_anchor(pagy`   | `pagy.a_lambda(`       |
| `pagy_t`             | `Pagy::I18n.translate` |
| `label_for`          | `page_label`           |
| `label`              | `page_label`           |

==- Delete the `pagy-old.rb` file

At this point, there should be no more code in the `pagy-old.rb`.

===

>>>

{{ include "snippets/ask-for-support" }}

!!!warning
Please report any issues with this guide by opening a [New Docs Issue](https://github.com/ddnexus/pagy/issues/new?template=Documentation.yml).
!!!

---

### Resources ARIA (`docs/resources/ARIA.md`)

---
label: ARIA
icon: accessibility
order: 60
---

#

## :icon-accessibility:&nbsp;&nbsp;ARIA Attributes

---

Pagy provides a consistent set of ARIA-compliant attributes across all its helpers.

=== :icon-code:&nbsp; Nav helpers

Pagy provides a customizable `aria-label` for the root element of its helpers. It is usually a `nav` element. For the few helper styles that use a different root element, Pagy adds a `role="navigation"` attribute.

The default string for the `aria-label` of the root element is "Page" / "Pages" (translated and pluralized according to the total number of pages). This is arguably a better description of the navigation content than just "Pagination" (which is also difficult to translate in certain languages).

!!!danger Don't rely on ARIA default with multiple nav elements!
The `nav` elements are `landmark roles` and should be distinctly labeled.
!!!

!!!success Override the default `:aria_label`s for multiple navs with distinct values!
```erb
<%# Explicitly set the aria_label %>
<%== @pagy.series_nav(aria_label: 'Search result pages') %>
```
!!!

==- :icon-link:&nbsp; Links

Links to the previous and next pages are rendered as "&lt;" and "&gt;" and are aria-labelled as translated `"Previous"` and `"Next"`. You can customize both the link text and the `aria-label` by editing the `pagy.previous`, `pagy.next`, `pagy.aria_label.previous`, and `pagy.aria_label.next` values in the locale files.

Page links do not include an `aria-label` attribute because their text is a simple number (read by screen readers in the user's native language), making an explicit attribute redundant and inefficient.

Disabled links include the `aria-disabled="true"` attribute.

The current page is marked with the `aria-current="page"` attribute.

The `role="link"` attribute is applied to link elements in styles that lack an `a` tag or a `href` attribute.

The `role="separator"` is applied to the `:gap`.

===

---

### Resources I18n (`docs/resources/i18n.md`)

---
label: I18n
icon: globe
order: 70
---

#

## :icon-globe:&nbsp;&nbsp;Internationalization

---

Pagy handles string translation and pluralization using its own efficient i18n implementation.

!!!success
Apps using **only the English language** are supported out of the box **without** any I18n setup!
!!!

=== :icon-list-ordered:&nbsp; Setup

All Pagy strings are stored in **dictionary files** within its [locales](https://github.com/ddnexus/pagy/tree/master/gem/locales) directory. These files adopt the same structure as the standard locale files for the `i18n` gem, allowing you to use them seamlessly with both engines.

>>> Pick the I18n engine...

+++ Pagy::I18n

!!!success Recommended: ~18x faster and ~10x lighter
It works seamlessly even if the rest of your app works with the `i18n` gem.
!!!

Set the `Pagy::I18n.locale` for each request. For example:

```rb Controller
before_action { Pagy::I18n.locale = params[:locale] }
```

**Notice**: The default `Pagy::I18n.locale` is `'en'`.

+++ I18n gem

!!!warning Slower
!!!

Switch the translation to the `i18n` gem. Pagy will only provide its dictionaries...

```rb [pagy.rb initializer](/toolbox/configuration/initializer)
Pagy.translate_with_the_slower_i18n_gem!
```

+++

>>>

==- :icon-book:&nbsp; Sample Dictionary

:::code source="/gem/locales/en.yml" title="en.yml":::

==- :icon-pencil:&nbsp; Customizing dictionaries

!!!warning IMPORTANT
If you use `Pagy::I18n`, name your custom dictionary as `<locale>.yml`.
!!!

Place your custom dictionaries in a directory and add its path to the I18n pathnames.

```rb [pagy.rb initializer](/toolbox/configuration/initializer)
# Example for Rails:
Pagy::I18n.pathnames << Rails.root.join('config/locales/pagy')
```

==- :icon-mortar-board:&nbsp; Contributing a Language

{{ include "snippets/ask-for-support" }}

>>> Find the pluralization of your language...

{{ include "snippets/mini-step" step: "1" }} Locate the file of your locale in the [list of pluralizations](https://github.com/svenfuchs/rails-i18n/tree/master/rails/pluralization).

{{ include "snippets/mini-step" step: "2" }} Check the pluralization used in it.

{{ include "snippets/mini-step" step: "3" }} Confirm that Pagy has the same pluralization in the [p11n directory](https://github.com/ddnexus/pagy/tree/master/gem/lib/pagy/modules/i18n/p11n).

||| If missing, you have two choices...

{{ include "snippets/mini-step" step: "easy" }} [Create a new Feature Request](https://github.com/ddnexus/pagy/discussions/new?category=feature-requests) asking for the addition of the pluralization for your locale.

{{ include "snippets/mini-step" step: "best" }} Create a new module for the pluralization _(by adapting the same pluralization from the corresponding `rails-i18n` file)_ and include tests for it.

|||

>>> Duplicate an existing dictionary...

{{ include "snippets/mini-step" step: "1" }} Pick a _(similarly pluralized)_ dictionary in the [gem/locales](https://github.com/ddnexus/pagy/tree/master/gem/locales) directory.

{{ include "snippets/mini-step" step: "2" }} Set the `p11n` entry to the right pluralization module name in the [p11n directory](https://github.com/ddnexus/pagy/tree/master/gem/lib/pagy/modules/i18n/p11n).

{{ include "snippets/mini-step" step: "3" }} Provide the necessary plurals for `aria_label.nav` and `item_name`. <br/>
E.g., with the `EastSlavic` rule, provide the plurals for `one`, `few`, `many`, and `other`. With `Other`, provide only the `other` value, etc.

>>>

==- :icon-location:&nbsp; Localization

Localization is needed **only** by the calendar with non-`en` locales. See the [calendar localization](/toolbox/paginators/calendar/#localization).

===

---

### Resources Javascript (`docs/resources/javascript.md`)

---
label: JavaScript
icon: file-code
order: 80
---

#

## :icon-file-code:&nbsp;&nbsp;JavaScript

---

!!!tip Add the `oj` gem to your Gemfile
It is not a requirement, but if present, the pagy `JSON` generation will be faster.
!!!

### Setup

>>> Pick a file...

+++ pagy.mjs

!!!success Good for apps **with** a minifier _(Sprockets, builders, ...)_
!!!

Make `Pagy` available in your JavaScript environment with...

```js application.js
import Pagy from "pagy.mjs"
```

+++ pagy.min.js

!!!success Good for apps **without** a minifier _(Propshaft, Importmaps, ...)_
!!!

Make `Pagy` available in your JavaScript environment with...

```erb ERB template / HTML page
<%= javascript_include_tag "pagy.min.js" ...%>


<script src="/path/to/pagy.min.js"></script>
```

+++ pagy.js

!!!warning Use it only for debugging Pagy itself.
!!!

+++

{{ include "snippets/pick-a-conf" resource: ":javascript" resource_dir: "javascripts" remote_dir: "app/javascript" }}

>>> Run the `Pagy.init` on...

+++ "load" event

Pick which applies to your environment:

```js
window.addEventListener("load", Pagy.init)
window.addEventListener("turbo:load", Pagy.init)
window.addEventListener("turbolinks:load", Pagy.init)
window.addEventListener("your-event", Pagy.init)
```

+++ Stimulus connect

```js
connect() {
  Pagy.init(this.element);
}
```

+++

>>>

---

### Resources Stylesheets (`docs/resources/stylesheets.md`)

---
label: Stylesheets
icon: file
order: 90
image: ""
---

#

## :icon-file:&nbsp;&nbsp;Stylesheets

---

Pagy includes a couple of CSS files and the tools to integrate your app's themes _interactively_.

!!!warning
You don't need any stylesheets if you use the pagy `:bootstrap` or `:bulma` helpers and styles.
!!!

### Setup

>>> Pick a file...

+++ pagy.css

!!!success Good for any app
!!!

==- CSS Source

:::code source="/gem/stylesheets/pagy.css" title="pagy.css":::

===

+++ pagy-tailwind.css

!!!warning Works only with apps using Tailwind
!!!

==- CSS Source

:::code source="/gem/stylesheets/pagy-tailwind.css" title="pagy-tailwind.css":::

===

+++

{{ include "snippets/pick-a-conf" resource: ":stylesheet" resource_dir: "stylesheets" remote_dir: "app/assets/stylesheets" }}

>>> Customize the style...

Add this line to any template/layout `<head>` while developing:

```erb
<%== Pagy.dev_tools %>
```

and adjust a few sliders to see the change in real time, right in your app, with the [Pagy Wand](/sandbox/dev_tools). Then copy the `CSS Override` field and paste it into your own CSS.

:::raised
{width=300}
:::

>>>

==- :icon-key-asterisk:&nbsp; Selectors

To ensure a minimalistic valid output, complete with all the [ARIA attributes](/resources/ARIA.md), pagy outputs a single line with the minimum number of tags and attributes required to identify all the parts of the nav bars:

- The output of the `series_nav` and `series_nav_js` helpers is a series of `a` tags inside a `nav` tag wrapper.
- The disabled links are simply missing the `href` attribute.
- The `pagy series-nav` and `pagy series-nav-js` classes are assigned to the `nav` tag.

{{ include "snippets/run-app" app: "demo" }}

<br/>

!!!tip
- You can target the `gap` with `.pagy a[role="separator"]`
- You can target the previous and next links by using `.pagy a:first-child` and `.pagy a:last-child` pseudo-classes.
- Check the stylesheet comments to target other specific elements.
!!!

==- :icon-arrow-left:&nbsp; RTL

The pagy stylesheets automatically support Right-To-Left (RTL) languages. It respects the standard `dir="rtl"` attribute of the `html` tag or any parent element of the pagination.

===

---

### Resources Urls (`docs/resources/urls.md`)

---
label: URLs
icon: crosshairs
order: 50
---

#

## :icon-crosshairs:&nbsp;&nbsp;URLs

---

Pagy uses the current URL as the base to generate all the other page URLs. It retrieves it from the `self.request` when available, or from a `:request` option set to a `Rack::Request` or a simple Hash of `:base_url`, `:path`, `:params` _(and a pagy `:cookie` value in case of Keynav pagination)_.

!!!primary
Pagy generates its specialized URLs ~20x faster than generic helpers like Rails' `url_for`. When possible, it just replaces the page value, without recalculating every part of a URL.
!!!

==- :icon-sliders:&nbsp; Options

These options give you full control over the URL composition for [paginators](/toolbox/paginators.md) and [helpers](/toolbox/helpers.md):

:::

=== Consumed by Paginators

{{ include "options/paginator-url" }}

=== Consumed by Helpers

{{ include "options/helper-url" }}

:::

==- :icon-blocked:&nbsp; Params

!!!danger No framework params!
Pagy ignores the `request.params` because they are modified differently by different frameworks (Rails, Sinatra, etc.). It can only rely on the `request.GET` and `request.POST` methods, which are consistent across all Rack environments.
!!!

!!!danger No symbolic params!
The params that Pagy handles when composing its URLs are **strictly string-keyed**. That's how every gem/framework handles URLs, because it avoids the maintenance and performance overhead of converting symbols back and forth during URL composition.
!!!

==- :icon-download:&nbsp; GET

Serving paginated collections is a retrieval action that does not modify data; therefore, it should rely on GET requests. Since most applications follow this standard, Pagy parses and produces GET URLs out of the box.

==- :icon-upload:&nbsp; POST

If you must use POST to retrieve paginated collections, you should build your own POST forms/requests using the data provided by Pagy _(e.g., via the [data_hash](/toolbox/helpers/data_hash) or instance readers)_. However, Pagy parses and handles POST request parameters out of the box without additional configuration.

==- :icon-stop:&nbsp; Dynamic Segments

Routers (like the Rails router) allow defining parameters as part of the path (e.g., `/items/:page`)...

!!!danger
Pagy neither supports nor recommends dynamic path segments for the `:page` param.
!!!

#### Why?

The Cons are overwhelming.

##### :icon-thumbsup:&nbsp; Pros

{.list-icon}
- • Aesthetically cleaner URLs
- • Possibility to cache single pages at the edge _(rarely necessary)_

##### :icon-thumbsdown:&nbsp; Cons

{.list-icon}
- **RFC 3986 Compliance**
  - • The `:page` parameter represents non-hierarchical data, which fits the definition of the query string component.
  - • It does not fit the definition of the path component, which represents hierarchical resources.
- **Data Identification**
  - • A query string parameter is labeled data (`?page=2`) identifiable without external context.
  - • A path segment (`/2`) is unlabeled and relies on external routing logic to be handled, so it cannot be identified/used by agnostic code.
- **Performance**
  - • Dynamic segments are framework-specific routing concepts, not query params concepts.
  - • Using framework code is not only non-agnostic, but significantly slower than pagy's generic query param handling.
  - • Using it _(or even just checking for it)_ would be an unnecessary burden for all the apps.

:::
==- OK, but what if I still want it in my own app?

<br/>

!!!danger This file is provided as a courtesy configuration
It is not an officially supported or tested feature, as it bypasses the standard, high-performance Pagy URL generation logic.

**Use it at your own risk and extra maintenance!**
!!!

:::code source="/gem/apps/enable_rails_page_segment.rb" title="enable_rails_page_segment.rb":::

:::
===

---

### Sandbox Console (`docs/sandbox/console.md`)

---
label: Console
icon: terminal
order: 90
---

#

## :icon-terminal:&nbsp;&nbsp;Console

---

Allows you to interact with Pagy in an [irb](https://github.com/ruby/irb) or [pry](https://github.com/pry/pry) environment without an app, providing a pre-configured stubbed environment for you.

You can use a few methods to create a simple collection paginated with the `:offset` paginator or to set the parameters as needed.

```
/* Detailed source-code truncated for AI context efficiency. */
```

---

### Sandbox Dev Tools (`docs/sandbox/dev_tools.md`)

---
label: DevTools
icon: ai-model
order: 80
---

#

## :icon-ai-model:&nbsp;&nbsp;DevTools

---

Pagy offers a couple of tools to ease your development process.

You can activate them by adding this line in the `<head>` of your layout/page:

```erb
<%== Pagy.dev_tools %>
```

:::content-center
{{ include "snippets/run-app" app: "demo" }}
:::

#### :icon-north-star:&nbsp; Pagy Wand

Adjust a few sliders to see the style changing in real time, right in your app...

- Control most of the visual aspects of pagy with a few presets and sliders.
- Copy/paste the generated "CSS Override" block into your stylesheet to persist it in your app.
- Click on the :icon-question: icon on the Wand for help.

:::raised
{width=450}
:::

<br/>

Watch the Pagy Wand [demo video](https://youtu.be/GhhNRrVG9jg):

:::raised
[{width=450}](https://youtu.be/GhhNRrVG9jg)
:::

<br/>

#### :icon-sparkle:&nbsp; Pagy AI

Get answers to your specific questions, right in your app. Just type questions like: _"What are the available paginators for DBs and search platforms?"_

---

### Sandbox Playground (`docs/sandbox/playground.md`)

---
label: Playground
order: 100
icon: play
---

#

## :icon-play:&nbsp;&nbsp;Playground

---

You can showcase, clone, and develop a few Pagy apps without needing to set anything up on your side!

```sh
$ pagy --help
```

### :icon-apps:&nbsp; Apps

We have a few single-file apps ready to run in your browser for various purposes. Most of them are used to run the [E2E Test](https://github.com/ddnexus/pagy/blob/master/.github/workflows/e2e-test.yml) workflow.

==- :icon-pulse:&nbsp; Repro _(interaction example)_ {#repro}

You can use this app as a starting point to try Pagy or reproduce issues to get support or file bug reports.

>>> Clone the app

```sh
pagy clone repro
```

You should find the `./repro.ru` cloned app file in the current directory. Feel free to rename or move it as you wish.

>>> Develop it

This command runs your `rackup` app with a `puma` server.

```sh
pagy path/to/your-repro.ru
```

>>> Open a browser and navigate to <http://127.0.0.1:8000>

>>>

==- :icon-ruby:&nbsp; Rails {#rails}

Use it to reproduce **Rails-related** Pagy issues.

```sh
pagy clone rails
pagy ./rails.ru
```

==- :icon-search:&nbsp; Active Search {#active-search}

The interactive showcase for the `:rails_active_search` paginator, in active and passive modes.

```sh
pagy active_search
```

==- :icon-eye:&nbsp; Demo {#demo}

The interactive showcase for all the pagy helpers and CSS styles.

```sh
pagy demo
```

==- :icon-calendar:&nbsp; Calendar {#calendar}

The interactive showcase and reproduction tool for the `:calendar` paginator.

```sh
pagy calendar
```

==- :icon-key:&nbsp; Keyset / Keynav {#keysets}

The interactive showcases/repros for the `:keyset` and `:keynav_js` paginators with `ActiveRecord` or `Sequel` sets, and for multiple independent `:keynav_js` instances (`keynav+root_key`):

```sh
pagy --help | grep key
  keynav+root_key    Showcase the Keynav pagination with independent instances
  keynav             Showcase the Keynav pagination (ActiveRecord example)
  keyset             Showcase the Keyset pagination (ActiveRecord example)
  keyset_sequel      Showcase the Keyset pagination (Sequel example)

pagy keynav
pagy keynav+root_key
pagy keyset
pagy keyset_sequel
```

===

==- :icon-stop:&nbsp; Troubleshooting

All the pagy apps use [bundler/inline](https://bundler.io/guides/bundler_in_a_single_file_ruby_script.html), which should be able to install all the gems automatically at app startup.

Depending on your environment, you might get this message for some gem:

```txt
You have already activated GEMNAME v1, but your Gemfile requires GEMNAME v2.
Prepending `bundle exec` to your command may solve this.
```

If `bundle exec` doesn't solve it, then try `bundle update` and `gem cleanup`. If you encounter another error after that:

```txt
... `find_spec_for_exe': can't find gem GEMNAME (>= 0.x) with executable EXEC (Gem::GemNotFoundException)
```

then `gem pristine GEMNAME` should solve the problem.

===

---

### Toolbox Helpers (`docs/toolbox/helpers.md`)

---
label: "&nbsp;✳&nbsp;&nbsp;&nbsp;Helpers"
icon: mention
order: 80
---

#

## ✳&nbsp;&nbsp;Helpers

---

The `@pagy` instance provides all the helpers to use in your code.

Its class is determined by the paginator used, but you can safely ignore it.

!!!success The `@pagy` helpers are autoloaded only if used!
Unused code consumes no memory.
!!!

---

### Toolbox Index.Yml (`docs/toolbox/index.yml`)

label: Toolbox
icon: package
order: 80
expanded: true

---

### Toolbox Paginators (`docs/toolbox/paginators.md`)

---
label: "&nbsp;✳&nbsp;&nbsp;&nbsp;Paginators"
icon: database
order: 90
---

#

## ✳&nbsp;&nbsp;Paginators

---

||| The `pagy` Method

The `pagy` method provides a common interface to all paginators. Include it where you are going to paginate a collection _(usually in ApplicationController)_:

```ruby
include Pagy::Method
```

You can use it to paginate ANY collection, with ANY technique. For example:

```ruby
@pagy, @records = pagy(:offset, collection, **options)
@pagy, @records = pagy(:keyset, set, **options)
@pagy, @records = pagy(...)
```

- `:offset`, `:keyset`, etc. are symbols identifying the [paginator](#paginators). They implement the specific pagination.
- `@pagy` is the pagination instance. It provides all the instance helper methods to use in your code.
- `@records` are the records belonging to the requested page.

!!!info
The `pagy` method expects to find the rack request at `self.request`; however, you can also use pagy [outside controllers or views](/guides/how-to/#use-pagy-outside-controllers-or-views), or pass your own `:request` option.
!!!

|||

### Paginators

!!!tip Also read the [Choose Right](/guides/choose-right.md) guide to ensure good performance and a smooth workflow.
!!!

The `paginators` are symbolic names of different pagination types/contexts (e.g., `:offset`, `:keyset`, `:countless`, etc.). You pass the name to the `pagy` method and pagy will internally instantiate and handle the appropriate paginator class.

!!!warning Avoid instantiating Pagy classes directly
Instantiate paginator classes only if the documentation explicitly suggests it.
!!!

!!!success Paginators and classes are autoloaded only if used!
Unused code consumes no memory.
!!!

==- :icon-stop:&nbsp; Troubleshooting

:::
==- Records may repeat in different pages or be missing

||| :icon-question: Unordered PostgreSQL collection

PostgreSQL collections must be ordered.

||| :icon-check-circle: Solutions

Chain something like `.order(:id)` to your collection. _See the [PostgreSQL Documentation](https://www.postgresql.org/docs/16/queries-limit.html#:~:text=When%20using%20LIMIT,ORDER%20BY)_

|||
:::
===

---

###  Includes Options Helper Url (`docs/_includes/options/helper-url.md`)

`absolute: true`
: Makes the URL absolute.

`path: '/my_path'`
: Overrides the request path in pagination URLs. Use the path only (not the absolute URL). _(see [Override the request path](/guides/how-to#override-the-request-path))_

`fragment: '...'`
: URL fragment string.

`querify: tweak`
: Set it to a `lambda` to directly edit the passed string-keyed params hash itself. Its result is ignored.
  ```ruby
  tweak = ->(q) { q.except!('not_useful').merge!('custom' => 'useful') }
  ```

---

###  Includes Options Helper (`docs/_includes/options/helper.md`)

`anchor_string: 'data-turbo-frame="paginate"'`
: Concatenate a verbatim raw string to the internal HTML of the anchor tags. It must contain properly formatted HTML attributes. It's not suitable for `*_hash` helpers.

{{ include "helper-url" }}

---

###  Includes Options Keyset (`docs/_includes/options/keyset.md`)

`keyset: {...}`
: Set it only to force the `keyset` hash of column/order pairs. _(It is set automatically from the set order)_

`tuple_comparison: true`
: Enable the tuple comparison e.g. `(brand, id) > (:brand, :id)`. It works only with the same direction order, hence, it's ignored for mixed order. Check how your DB supports it (your `keyset` should include only `NOT NULL` columns).

`pre_serialize: serialize`
: Set it to a `lambda` that receives the `keyset_attributes` hash. Modify this hash directly to customize the serialization of specific values (e.g., to preserve `Time` object precision). The lambda's return value is ignored.
    ```ruby
    serialize = lambda do |attributes|
      # Convert it to a string matching the stored value/format in SQLite DB
      attributes[:created_at] = attributes[:created_at].strftime('%F %T.%6N')
    end
    ```

{{ include "paginator" }}

---

###  Includes Options Navs (`docs/_includes/options/navs.md`)

`id: 'my-nav'`
: Set the `id` HTML attribute of the `nav` tag.

`aria_label: 'My Label'`
: Override the default `pagy.aria_label.nav` string of the `aria-label` attribute.<br/>See [ARIA](/resources/ARIA.md).

  !!!danger
  The `nav` elements are `landmark roles` and should be distinctly labeled!
  !!!

  !!!success
  Override the default `:aria_label`s for multiple navs with distinct values!
  ```erb
  <%# Explicitly set the aria_label %>
  <%== @pagy.series_nav(aria_label: 'Search result pages') %>
  ```
  !!!

---

###  Includes Options Offset (`docs/_includes/options/offset.md`)

`count_over: true`
: Use this option with `GROUP BY` collections to calculate the total number of results using `COUNT(*) OVER ()`.

`raise_range_error: true`
: Enable the `Pagy::RangeError` (which is otherwise rescued to an empty page by default).

{{ include "paginator" }}

---

###  Includes Options Paginator Url (`docs/_includes/options/paginator-url.md`)

`jsonapi: true`
: Enables JSON:API-compliant URLs with nested query string (e.g., `?page[number]=2&page[size]=100`).

`root_key: 'my_root'`
: Set it to enable nested URLs with nested query string `?my_root[page]=2&my_root[limit]=100`. Use it to handle multiple pagination objects in the same request.

`page_key: 'my_page'`
: Set it to change the key string used for the `:page` in URLs (default `'page'`).

`limit_key: 'my_limit'`
: Set it to change the key string used for the `:limit` in URLs (default `'limit'`).

---

###  Includes Options Paginator (`docs/_includes/options/paginator.md`)

`limit: 10`
: Specifies the number of items per page (default: `20`).

`client_limit: 200`
: Allow the client to request a `:limit` up to `:client_limit`. A higher requested `:limit` is silently capped.

  **IMPORTANT** If falsey or zero, the client cannot request any `:limit`.

`page: force_page`
: Set it only to force the current `:page`. _(It is set automatically from the request param)_

`request: request || hash`
: Pagy tries to find the `Rack::Request` at `self.request`. Set it only when it's not directly available in your code (e.g., Hanami, standalone app, test,...). For example:
    ```ruby
    hash_request = { base_url: 'http://www.example.com',
                     path:     '/path',
                     params:   { 'param1' => 1234 }, # The string-keyed params hash from the request
                     cookie:   'xyz' }               # The 'pagy' cookie, only for keynav
    ```

{{ include "paginator-url" }}

---

###  Includes Options Series (`docs/_includes/options/series.md`)

`slots: 9`
: Override the default number of page `:slots` used for the navigation bar.
  - `slots < 7` fills the slots with contiguous pages around the current one.
  - `slots >= 7` reserves the first and last slots for the first and last pages, separated from the rest by a `...` (`:gap`) slot when needed.
  - Prefer odd numbers of slots, which place the current page in the central slot.

`compact: true`
: Fill all the slots with contiguous pages, regardless of the number of slots.

---

###  Includes Snippets All But Keyset (`docs/_includes/snippets/all-but-keyset.md`)

!!!warning
{{ $.what | object.default "It" }} works with all paginators but `:keyset`.
!!!

---

###  Includes Snippets Ask For Support (`docs/_includes/snippets/ask-for-support.md`)

!!!question

Feel free to ask for further help via [Pagy Support](https://github.com/ddnexus/pagy/discussions/categories/q-a).

!!!

---

###  Includes Snippets Js Required (`docs/_includes/snippets/js-required.md`)

&nbsp;&nbsp;[!button variant="info" icon="alert" size="s" corners="pill" text="JavaScript Setup Required!"](/resources/javascript)

---

###  Includes Snippets Mini Step (`docs/_includes/snippets/mini-step.md`)

[!badge variant="info" size="s" corners="pill" text="{{ $.step }}"]&nbsp;

---

###  Includes Snippets Nav Styles (`docs/_includes/snippets/nav-styles.md`)

==- :icon-eye:&nbsp; Styles

`:pagy/nil`
: Pagy default style

`:bootstrap`
: Set `classes: 'pagination pagination-sm any-class'` style option to override the default `'pagination'` class.

`:bulma`
: Set `classes: 'pagination is-small any-class'` style option to override the default `'pagination'` classes.

---

###  Includes Snippets Offset Readers (`docs/_includes/snippets/offset-readers.md`)

`offset`
: The OFFSET used in the SQL query.

`count`
: The collection count.

`from`
: The position in the collection of the first item on the page. _(Different Pagy classes may use different value types for it)._

`to`
: The position in the collection of the last item on the page. _(Different Pagy classes may use different value types for it)._

`last`
: The last page.

`pages`
: The number of pages.

`previous`
: The previous page.

{{ include "paginator-readers" }}

---

###  Includes Snippets Paginator Readers (`docs/_includes/snippets/paginator-readers.md`)

`next`
: The next page.

`page`
: The current page.

`limit`
: The items per page.

`in`
: The actual items in the page.

`records`
: The fetched records for the current page.

`options`
: The hash of options of the object.

---

###  Includes Snippets Pick A Conf (`docs/_includes/snippets/pick-a-conf.md`)

>>> Pick a configuration...

+++ Sync

!!!success Works with any app
!!!

The following statement will <u>copy and keep synced</u> your picked `pagy*` file in your own asset dir, so it will be processed like an asset of your app.

```rb [pagy.rb initializer](/toolbox/configuration/initializer)
# Replace 'pagy*' with the file you picked
Pagy.sync({{ $.resource }}, Rails.root.join('{{ $.remote_dir }}'), 'pagy*') if Rails.env.development?
```
==- Sync Task

If you prefer to sync manually or during an automation step, you can define your own task with a single line in the `Rakefile`, or any `*.rake` file:

```rb
# Pagy::SyncTask.new(resource, destination, *targets)
# Replace 'pagy*' with the file you picked
Pagy::SyncTask.new({{ $.resource }}, Rails.root.join('{{ $.remote_dir }}'), 'pagy*')
```

and exec it with...

```sh
bundle exec rake pagy:sync{{ $.resource }}
```
===

+++ Pipeline

!!!warning Works only with apps with an assets pipeline
!!!

```rb
Rails.application.config.assets.paths << Pagy::ROOT.join('{{ $.resource_dir }}')
```

+++

---

###  Includes Snippets Run App (`docs/_includes/snippets/run-app.md`)

[!button corners="pill" variant="info" icon="play-24" text="Check it out with `bundle exec pagy {{ $.app }}`"](/sandbox/playground/#{{ $.anchor | object.default $.app }})

---

### Toolbox Configuration Initializer (`docs/toolbox/configuration/initializer.md`)

---
label: Initializer
icon: gear
order: 80
---

#

## :icon-gear:&nbsp;&nbsp;Initializer

---

Set your global options/defaults, or configure a few special features by editing the sample file below.

Ensure it loads by saving it into the Rails `config/initializers` directory, or by requiring it.

[!file pagy.rb](/gem/config/pagy.rb)

:::code source="/gem/config/pagy.rb" title="pagy.rb" language="ruby":::

---

### Toolbox Configuration Options (`docs/toolbox/configuration/options.md`)

---
label: "Options"
icon: sliders
order: 100
---

#

## :icon-sliders:&nbsp;&nbsp;Options Hierarchy

---

!!! This page documents the options hierarchy
The actual options are documented alongside the [paginators](../paginators) and [helpers](../helpers) that consume them.
!!!

Pagy implements a hierarchical options system working at three different levels, regardless of where the option gets consumed.

### Levels

>>> Global

- For example, `Pagy::OPTIONS[:limit] = 10` set in the [pagy.rb initializer](initializer).
- The `Pagy::OPTIONS` are inherited by all paginators and helpers.
- **Good practice**: Call `Pagy::OPTIONS.freeze` after you set your `Pagy::OPTIONS`.

>>> Paginator

- For example, `pagy(paginator, collection, **options)`.
- The options passed to a paginator override the `Pagy::OPTIONS` for that instance.
- They are also inherited by all the helpers used by the instance.

>>> Helper

- For example, `@pagy.series_nav(**options)`.
- The options passed to a helper override the options affecting its output.
- The options consumed upstream are not affected.

>>>

!!!success
- **Keep options local**
  - For clarity, set options as close to where they're consumed as possible.
- **Widen the scope when needed**
  - Use higher-level options when they conveniently affect broader scopes.
!!!

---

### Toolbox Helpers Anchor Tags (`docs/toolbox/helpers/anchor_tags.md`)

---
label: anchor_tags
icon: code-square
order: 140
image: ""
---

#

## :icon-code-square:&nbsp;&nbsp;anchor_tags

---

:::raised
{width=73}
:::
<br/>

:::content-center
{{ include "snippets/run-app" app: "demo" }}
:::

The `previous_tag` and `next_tag` return the enabled/disabled previous/next page anchor tag.

Useful to build minimalistic UIs that don't use nav bar links (e.g., `:countless`, `:keyset` paginators).

!!!success
`next_tag` works with all paginators
!!!

{{ include "snippets/all-but-keyset" what: "`previous_tag`" }}

=== :icon-tools:&nbsp; Usage

```erb
<%== @pagy.next_tag %>
<%== @pagy.previous_tag %>
```

==- :icon-pin:&nbsp; Examples

```ruby Console
require 'pagy/console'
=> true

>> puts @pagy.previous_tag
<a href="/path?example=123&amp;page=2" rel="prev" aria-label="Previous">&lt;</a>
=> nil

>> puts @pagy.next_tag
<a href="/path?example=123&amp;page=4" rel="next" aria-label="Next">&gt;</a>
=> nil

>> puts @pagy.next_tag(text: 'Show Next', aria_label: 'my-next')
<a href="/path?example=123&amp;page=4" rel="next" aria-label="my-next">Show Next</a>
=> nil
```

==- :icon-sliders:&nbsp; Options

`text: 'My Page'`
: Override the default text _(instead of looking up the `pagy.previous`/`pagy.next` entry in the dictionary)_.

`aria_label: 'My Link'`
: Override the default aria label _(instead of looking up the `pagy.aria_label.previous`/`pagy.aria_label.next` entry in the dictionary)_.

{{ include "options/helper-url" }}

===

---

### Toolbox Helpers Data Hash (`docs/toolbox/helpers/data_hash.md`)

---
label: data_hash
icon: list-unordered
order: 200
---

#

## :icon-list-unordered:&nbsp;&nbsp;data_hash

---

`data_hash` plucks a hash of key-value pairs from the pagy object. It is useful for exporting pagination data to JavaScript frameworks like Vue.js, React.js, etc.

!!!success It works with all paginators
!!!

=== :icon-tools:&nbsp; Usage

```ruby Controller
@pagy, @records = pagy(:offset, collection, **options)
pagy_hash       = @pagy.data_hash(data_keys: %i[page previous next previous_url next_url ...])
#=> { page: 3, previous: 2, next: 4, previous_url: ... }
render json: { data: @records, pagy: pagy_hash }
```

==- :icon-pin:&nbsp; Examples

```ruby Console
require 'pagy/console'
=> true

>> @pagy, @records = pagy(:offset, collection.new, page: 3)
=> [#<Pagy::Offset:0x00007f0802c9bd80 @request=#<Pagy::Request:0x00007f0802c858c8 @options={page: 3, request: #<Pagy::Request:0x00007f0802c858c8 ...>, client_limit: nil, limit: 20, count: 1000}, @base_url="http://www.example.com", @path="/path", @params={example: "123"}, @cookie=nil>, @options={limit: 20, limit_key: "limit", page_key: "page", page: 3, client_limit: nil, count: 1000}, @limit=20, @count=1000, @page=3, @last=50, @offset=40, @in_range=true, @from=41, @to=60, @in=20, @previous=2, @next=4>, [41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60]]

>> @pagy.data_hash(data_keys: %i[page previous next previous_url next_url])
=> {page: 3, previous: 2, next: 4, previous_url: "/path?example=123&page=2", next_url: "/path?example=123&page=4"}
```

==- :icon-sliders:&nbsp; Options

`data_keys: %i[...]`
: For efficiency, always set the `:data_keys` option to restrict the output to ONLY the keys you need among the default list:
  - `:count`
  - `:current_url`
  - `:first_url`
  - `:from`
  - `:in`
  - `:last`
  - `:last_url`
  - `:limit`
  - `:next`
  - `:next_url`
  - `:options`
  - `:page`
  - `:page_url` _(alias of `:current_url`)_
  - `:pages`
  - `:previous`
  - `:previous_url`
  - `:series`
  - `:to`
  - `:url_template`

  Notice that you can also add other `@pagy` instance method names not included in the default list.

{{ include "options/helper-url" }}

==- :icon-tools:&nbsp; Usage of `:url_template`

This is a URL string containing the `"P "` page token as a placeholder for the page value.

For example: `'/foo?page=P &bar=baz'`.

Replace it with JavaScript to generate the actual page URLs:

```javascript
pageUrl = url_template.replace('P ', '123')
// Result: '/foo?page=123&bar=baz'
```

!!!warning You may not need it for simple cases!
Consider using the available `:*_url` data keys directly instead of relying on the `:url_template`.
!!!

===

---

### Toolbox Helpers Headers Hash (`docs/toolbox/helpers/headers_hash.md`)

---
label: headers_hash
icon: list-unordered
order: 190
---

#

## :icon-list-unordered:&nbsp;&nbsp;headers_hash

---

`headers_hash` generates the standard `link` header defined in [RFC-8288](https://tools.ietf.org/html/rfc8288), and adds 4 customizable headers useful for pagination, which you can merge into the `response.headers`.

It also adheres to the header casing introduced by `rack` version `3+` _(see the [rack-issue](https://github.com/rack/rack/issues/1592))_.

!!!success It works with all paginators
!!!

=== :icon-tools:&nbsp; Usage

```rb Controller
# Any paginator will work
@pagy, @records = pagy(:offset, collection, **options)

# Merge the headers to the response
response.headers.merge!(@pagy.headers_hash)
render json: @records
```

==- :icon-pin:&nbsp; Examples

```ruby Console
require 'pagy/console'
=> true

>> @pagy, @records = pagy(:offset, collection.new, page: 3)
=> [#<Pagy::Offset:0x00007f0802c9a020 @request=#<Pagy::Request:0x00007f0802c84e28 @options={page: 3, request: #<Pagy::Request:0x00007f0802c84e28 ...>, client_limit: nil, limit: 20, count: 1000}, @base_url="http://www.example.com", @path="/path", @params={example: "123"}, @cookie=nil>, @options={limit: 20, limit_key: "limit", page_key: "page", page: 3, client_limit: nil, count: 1000}, @limit=20, @count=1000, @page=3, @last=50, @offset=40, @in_range=true, @from=41, @to=60, @in=20, @previous=2, @next=4>, [41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60]]

>> @pagy.headers_hash(absolute: true)
=> {"link" => "<http://www.example.com/path?example=123>; rel=\"first\", <http://www.example.com/path?example=123&page=2>; rel=\"prev\", <http://www.example.com/path?example=123&page=4>; rel=\"next\", <http://www.example.com/path?example=123&page=50>; rel=\"last\"", "current-page" => "3", "page-limit" => "20", "total-count" => "1000", "total-pages" => "50"}
```

```text Example of default output
link <https://example.com:8080/foo?page=1>; rel="first", <https://example.com:8080/foo?page=2>; rel="prev",
<https://example.com:8080/foo?page=4>; rel="next", <https://example.com:8080/foo?page=50>; rel="last"
current-page 3
page-limit 20
total-pages 50
total-count 1000
```

==- :icon-sliders:&nbsp; Options

`headers_map: {...}`
: Customize the headers:

    ```rb
    default_map = { page:  'current-page',
                    limit: 'page-limit',
                    count: 'total-count',
                    pages: 'total-pages' }

    # Optional customization
    headers_map = { page:  'current-page',
                    limit: 'per-page',
                    pages: false,  # disable the output
                    count: 'total' }
    headers = @pagy.headers_hash(headers_map:)
    # Note: You can also pass the `:headers_map` option to the paginator
    ```

{{ include "options/helper-url" }}

==- :icon-light-bulb:&nbsp; Suggestions

>>> Instead of explicitly merging the headers before each rendering...

If you use Rails, you can add an `after_action` to your application controller:

```ruby Controller (after_action)
# It merges the headers if `@pagy` is initialized
after_action { response.headers.merge!(@pagy.headers_hash) if @pagy }

# Paginate normally in your action
@pagy, records = pagy(:offset, collection, **options)
render json: records
```

>>> If your code is consistent across different actions...

You can encapsulate the statements in a custom `pagy_render` method in your application controller. For example:

```ruby Controller (method)
def pagy_render(collection, **)
  pagy, records = pagy(:offset, collection, **) # Any other paginator works as well
  response.headers.merge!(pagy.headers_hash)    # Adds pagination headers to the response
  render json: records
end

# And use it in your standard actions:
pagy_render(collection, **options)
```

>>>

===

---

### Toolbox Helpers Info Tag (`docs/toolbox/helpers/info_tag.md`)

---
label: info_tag
icon: code-square
order: 120
image: ""
---

#

## :icon-code-square:&nbsp;&nbsp;info_tag

---

:::raised
<p id="text-only">Displaying items 201-220 of 1000 in total</p>
:::
<br/>

:::content-center
{{ include "snippets/run-app" app: "demo" }}
:::

`info_tag` provides information about the content of the current pagination.

{{ include "snippets/all-but-keyset" }}

=== :icon-tools:&nbsp; Usage

```erb
<%== @pagy.info_tag(**options) %>
```

==- :icon-pin:&nbsp; Examples

```ruby Console
require 'pagy/console'
=> true

>> puts @pagy.info_tag # @pagy generated by countless/keynav doesn't have count
<span class="pagy info">Page 3 of 4</span>
=> nil

>> puts @pagy.info_tag
<span class="pagy info">Displaying items 41-60 of 1000 in total</span>
=> nil

>> puts @pagy.info_tag(id: 'the-info-tag', item_name: 'Products')
<span id="the-info-tag" class="pagy info">Displaying Products 41-60 of 1000 in total</span>
=> nil
```

==- :icon-sliders:&nbsp; Options

`id: 'my-info'`
: The `id` HTML attribute of the `span` tag wrapping the info.

`item_name: 'Products'`
: The already pluralized string that will be used in place of the default `item/items`.

===

---

### Toolbox Helpers Input Nav Js (`docs/toolbox/helpers/input_nav_js.md`)

---
label: input_nav_js
icon: code
order: 150
image: ""
---

#

## :icon-code:&nbsp;&nbsp;input_nav_js{{ include "snippets/js-required" }}

---

:::raised
{width=204}

---

{width=178}

---

{width=201}
:::
<br/>

:::content-center
{{ include "snippets/run-app" app: "demo" }}
:::

`input_nav_js` combines navigation and pagination info in a single compact element.

It is the fastest and lightest navigator, recommended when you care about efficiency and server load while still needing a UI (see [Maximize performance](/guides/how-to#maximize-performance)).

{{ include "snippets/all-but-keyset" }}

=== :icon-tools:&nbsp; Usage

```erb
<%== @pagy.input_nav_js(**options) %>  <%# default pagy style %>
<%== @pagy.input_nav_js(:bootstrap, **options) %>
<%== @pagy.input_nav_js(:bulma, **options) %>
```

==- :icon-pin:&nbsp; Examples

```
/* Detailed source-code truncated for AI context efficiency. */
```

{{ include "snippets/nav-styles" }}

==- :icon-sliders:&nbsp; Options

{{ include "options/navs" }}

{{ include "options/helper" }}

==- :icon-alert:&nbsp; Caveats

!!!danger Overriding `*_js` helpers is not recommended
The `*_js` helpers are tightly coupled with the JavaScript code, so any partial overriding on one side would be quite fragile and might break in future releases.
!!!

===

---

### Toolbox Helpers Limit Tag Js (`docs/toolbox/helpers/limit_tag_js.md`)

---
label: limit_tag_js
icon: code-square
order: 130
image: ""
---

#

## :icon-code-square:&nbsp;&nbsp;limit_tag_js{{ include "snippets/js-required" }}

---

:::raised
{width=202}
:::
<br/>

:::content-center
{{ include "snippets/run-app" app: "demo" }}
:::

The `limit_tag_js` allows the user to select any arbitrary limit per page, up to the `:client_limit` option. It raises a `Pagy::OptionError` exception if the `:client_limit` is not truthy.

{{ include "snippets/all-but-keyset" }}

=== :icon-tools:&nbsp; Usage

```erb
<%== @pagy.limit_tag_js %>
```

!!!success Pagy requests the _right_ page number
After selecting a new limit, pagy reloads the page that roughly contains the same items shown before the reload.
!!!

==- :icon-pin:&nbsp; Examples

```ruby Console
require 'pagy/console'
=> true

>> puts @pagy.limit_tag_js(client_limit: 100)
<span class="pagy limit-tag-js" data-pagy="WyJsdGoiLDQxLCIvcGF0aD9leGFtcGxlPTEyMyZwYWdlPVAgIiwiUCAiLCJMICJd"><label>Show <input name="limit" type="number" min="1" max="100" value="20" style="padding: 0; text-align: center; width: 3rem;"><a style="display: none;">#</a> items per page</label></span>
=> nil

>> puts @pagy.limit_tag_js(client_limit: 100, id: 'my-elector', item_name: 'Products')
<span id="my-elector" class="pagy limit-tag-js" data-pagy="WyJsdGoiLDQxLCIvcGF0aD9leGFtcGxlPTEyMyZwYWdlPVAgIiwiUCAiLCJMICJd"><label>Show <input name="limit" type="number" min="1" max="100" value="20" style="padding: 0; text-align: center; width: 3rem;"><a style="display: none;">#</a> Products per page</label></span>
=> nil
```

==- :icon-sliders:&nbsp; Options

{{ include "options/helper-url" }}

==- :icon-alert:&nbsp; Caveats

!!!danger Overriding `*_js` helpers is not recommended
The `*_js` helpers are tightly coupled with the JavaScript code, so any partial overriding on one side would be quite fragile and might break in future releases.
!!!

===

---

### Toolbox Helpers Page Url (`docs/toolbox/helpers/page_url.md`)

---
label: page_url
icon: diff-renamed
order: 175
---

#

## :icon-diff-renamed:&nbsp;&nbsp;page_url

---

`page_url` returns the URL of any page of any instance. If the page is not available, it returns `nil`. It is useful to build minimalistic UIs that don't use nav bar links (e.g., [:keyset](../paginators/keyset.md) paginator).

!!!success It works with all paginators
!!!

=== :icon-tools:&nbsp; Usage

```ruby
@pagy.page_url(:next)
@pagy.page_url(23, page_key: 'custom_page')
...
```

==- :icon-pin:&nbsp; Examples

```ruby Console
require 'pagy/console'
=> true

>> @pagy, @records = pagy(:offset, collection.new, page: 3)
=> [#<Pagy::Offset:0x00007f0802af62c8 @request=#<Pagy::Request:0x00007f0802faf238 @options={page: 3, request: #<Pagy::Request:0x00007f0802faf238 ...>, client_limit: nil, limit: 20, count: 1000}, @base_url="http://www.example.com", @path="/path", @params={example: "123"}, @cookie=nil>, @options={limit: 20, limit_key: "limit", page_key: "page", page: 3, client_limit: nil, count: 1000}, @limit=20, @count=1000, @page=3, @last=50, @offset=40, @in_range=true, @from=41, @to=60, @in=20, @previous=2, @next=4>, [41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60]]

>> @pagy.page_url(:first)
=> "/path?example=123"

>> @pagy.page_url(:current)
=> "/path?example=123&page=3"

>> @pagy.page_url(:page) # alias of :current
=> "/path?example=123&page=3"

>> @pagy.page_url(:previous)
=> "/path?example=123&page=2"

>> @pagy.page_url(:next)
=> "/path?example=123&page=4"

>> @pagy.page_url(:last)
=> "/path?example=123&page=50"

>> @pagy.page_url(23, page_key: 'custom_page')
=> "/path?example=123&custom_page=23"

>> @pagy.page_url(23, absolute: true)
=> "http://www.example.com/path?example=123&page=23"

>> @pagy.page_url('long-page-id', absolute: true)
=> "http://www.example.com/path?example=123&page=long-page-id"
```

==- :icon-sliders:&nbsp; Options

{{ include "options/helper-url" }}

===

---

### Toolbox Helpers Series Nav (`docs/toolbox/helpers/series_nav.md`)

---
label: series_nav
icon: code
order: 170
image: ""
---

#

## :icon-code:&nbsp;&nbsp;series_nav

---

:::raised
{width=288}

---

{width=248}

---

{width=304}
:::
<br/>

:::content-center
{{ include "snippets/run-app" app: "demo" }}
:::

`series_nav` returns an HTML string containing pagination links, wrapped in a `nav` tag, ready to be used in your view.

{{ include "snippets/all-but-keyset" }}

=== :icon-tools:&nbsp; Usage

```erb
<%== @pagy.series_nav(**options) %>  <%# default pagy style %>
<%== @pagy.series_nav(:bootstrap, **options) %>
<%== @pagy.series_nav(:bulma, **options) %>
```

==- :icon-pin:&nbsp; Examples

```
/* Detailed source-code truncated for AI context efficiency. */
```

{{ include "snippets/nav-styles" }}

==- :icon-sliders:&nbsp; Options

{{ include "options/series" }}

{{ include "options/navs" }}

{{ include "options/helper" }}

===

---

### Toolbox Helpers Series Nav Js (`docs/toolbox/helpers/series_nav_js.md`)

---
label: series_nav_js
icon: code
order: 160
image: ""
---

#

## :icon-code:&nbsp;&nbsp;series_nav_js{{ include "snippets/js-required" }}

<br>

+++Pagy

:::raised
{width=428}
:::
:::raised
{width=358}
:::
:::raised
{width=288}
:::

+++Bootstrap

:::raised
{width=428}
:::
:::raised
{width=358}
:::
:::raised
{width=288}
:::

+++Bulma

:::raised
{width=428}
:::
:::raised
{width=358}
:::
:::raised
{width=288}
:::

+++

:::content-center
{{ include "snippets/run-app" app: "demo" }}
:::

`series_nav_js` functions similarly to a [series_nav](series_nav.md), with the following added features:

1. Optional responsiveness: dynamically fills the container width.
2. Improves performance and optimizes resource usage (see [Maximize performance](/guides/how-to#maximize-performance)).

{{ include "snippets/all-but-keyset" }}

=== :icon-tools:&nbsp; Usage

```erb
<%== @pagy.series_nav_js(**options) %>  <%# default pagy style %>
<%== @pagy.series_nav_js(:bootstrap, **options) %>
<%== @pagy.series_nav_js(:bulma, **options) %>
```

==- :icon-pin:&nbsp; Examples

```
/* Detailed source-code truncated for AI context efficiency. */
```

{{ include "snippets/nav-styles" }}

==- :icon-sliders:&nbsp; Options

{{ include "options/series" }}

`steps: { 0 => 5, 540 => 7, ... }`
: Enable responsiveness. Assign a different number of `:slots` to different tag widths. It overrides the `:slots` option.

{{ include "options/navs" }}

{{ include "options/helper" }}

==- :icon-log:&nbsp; In Depth: `:steps` Option

Notice: when `:steps` is not set, the `series_nav_js` behaves almost like a `series_nav`: just faster.

Set it as a hash, where the keys are integers representing the widths in pixels, and the values are the `:slots` options to be applied for those widths.

For example:

`{ 0 => 5, 540 => 7, 720 => 9 }` means that from `0` to `540` pixels width, Pagy will use `5` slots, from `540` to `720` it will use `7` slots, and over `720` it will use `9` slots. (Read more about the `:slots` option in the [Control the pagination bar](/guides/how-to#control-the-pagination-bar) section.)

!!!warning :steps must contain a `0` width
You can set any number of steps with any arbitrary width/slots. The only requirement is that the `:steps` hash must always contain the `0` width, or a `Pagy::OptionError` exception will be raised.
!!!

!!! Notice
The `:steps` option overrides the `:slots` option, while the `:compact` option applies to all the steps.
!!!

#### Setting the right steps

<br/>

Setting the `:steps` can enhance responsiveness and ensure seamless transitions.

Consider these guidelines to achieve optimal results:

1. Define discrete `:steps` using width/slots pairs to control the pagination behavior.
2. Ensure the container's width accommodates all slots for a smooth transition as it resizes.
3. Synchronize the pagy `:steps` with your container's discrete width changes, for consistent alignment.
4. Test responsiveness to confirm that assigned slots fit within the corresponding width for each step.

==- :icon-alert:&nbsp; Caveats

!!!warning HTML Fallback
If JavaScript is disabled in the client browser, this helper will not render anything. You should implement your own HTML fallback:
```erb
<noscript><%== @pagy.series_nav %></noscript>
```
!!!

!!!warning Window Resizing
The `series_nav_js` elements are automatically re-rendered on window resize. If another function changes the size without causing a window resize, you need to explicitly re-render:
```js
document.getElementById('my-pagy-nav-js').render();
```
!!!

!!!danger Overriding `*_js` helpers is not recommended
The `*_js` helpers are tightly coupled with the JavaScript code, so any partial overriding on one side would be quite fragile and might break in future releases.
!!!

===

---

### Toolbox Helpers Urls Hash (`docs/toolbox/helpers/urls_hash.md`)

---
label: urls_hash
icon: list-unordered
order: 180
---

#

## :icon-list-unordered:&nbsp;&nbsp;urls_hash

---

`urls_hash` returns the `:first`, `:previous`, `:next`, `:last` non-`nil` URLs hash.

!!! JSON:API
With the `jsonapi: true` option, the `:previous` key becomes `:prev`, as the [JSON:API](https://jsonapi.org/format/#fetching-pagination) specification requires.
!!!

!!!success It works with all paginators
!!!

=== :icon-tools:&nbsp; Usage

```ruby Controller
urls_hash = @pagy.urls_hash(**options)
```

==- :icon-pin:&nbsp; Examples

```
/* Detailed source-code truncated for AI context efficiency. */
```

==- :icon-sliders:&nbsp; Options

{{ include "options/helper-url" }}

===

---

### Toolbox Paginators Calendar (`docs/toolbox/paginators/calendar.md`)

---
label: :calendar
icon: calendar-24
order: 60
image: ""
---

#

## :icon-calendar:&nbsp;&nbsp;:calendar

:::raised
{width=600}
:::

:::content-center
{{ include "snippets/run-app" app: "calendar" }}
:::

!!!warning ActiveSupport Required!
!!!

The `:calendar` is a [TIME-RANGE](/guides/choose-right/#time-range) paginator.

It enables cascade-filtering of the collection by time units _(year, quarter, month, week, and day)_, followed by paginating the filtered collection using the `:offset` paginator.

!!!success Use this paginator when most pages contain results.
!!!

!!!warning Avoid using it for sparse datasets with numerous empty pages.
!!!

==- :icon-list-ordered:&nbsp; Setup

You must define a few simple methods in your app to configure and coordinate the objects created by the `pagy` method. See the following examples and comments _(adapted from the [Calendar app code](/sandbox/playground/#calendar))_.

```ruby Controller
# Note: All time values must be instances of ActiveSupport::TimeWithZone.
# REQUIRED: return the start and end limits of the collection as a 2-item array
def pagy_calendar_period(collection)
  starting = collection.minimum(:created_at)
  ending   = collection.maximum(:created_at)
  [starting.in_time_zone, ending.in_time_zone]
end

# REQUIRED: return the collection filtered by a time period
def pagy_calendar_filter(collection, from, to)
  collection.where(created_at: from...to)
end

# OPTIONAL: return the array of counts per time unit
# If this method is defined, pagy will add an extra 'empty-page' CSS class
# to the links leading to empty pages, along with a title attribute containing information about each page link.
def pagy_calendar_counts(collection, unit, from, to)
  # If collection is in order: :desc, add the reverse: true option to the next line
  collection.group_by_period(unit, :created_at, range: from...to).count.values
end

# Example usage with default options.
def index
  @calendar, @pagy, @records = pagy(:calendar, collection,
                                    year:  {},
                                    month: {},
                                    offset: {})
end
```

```erb view (template)

<%== @calendar[:year].series_nav %>
<%== @calendar[:month].series_nav %>


<%== @pagy.info_tag %> for <%== @calendar[:month].page_label(@pagy.page, format: '%B %Y') %>.

... Render @records here ...


<%== @pagy.series_nav %>

<p>Showtime: <%= @calendar.showtime %></p>
<a href="<%= @calendar.url_at(Time.zone.now) %>">Go to now</a>
```

=== :icon-tools:&nbsp; Usage

```ruby
@calendar, @pagy, @records = pagy(:calendar, collection, year: {}, month: {}, offset: {})
```

- `@calendar` is a specialized hash that contains the pagy time unit objects (e.g., `:year` and `:month` in this example).
- `@pagy` is the object representing the current page of records.
- `@records` is the paginated subset of the collection.
- The `:year` and `:month` parameters create time unit objects (default options are used in this example).
- `:offset` is the offset instance that paginates the time-filtered collection.

==- :icon-gear:&nbsp; Configuration

The calendar configuration defines the calendar objects to be generated. These objects filter the collection by the selected time units.

You can include one or more unit levels using keys like `:year`, `:quarter`, `:month`, `:week`, or `:day`. Assign each key a hash of unit options. Use an empty hash for default values, e.g., `year: {}, month: {}, ...`.

#### Unit Options

<br/>

`order: :desc`
: Sets the order of the unit, either `:asc` or `:desc`. Make sure to order your collection accordingly.

`format: '<strftime>'`
: Change the label format for the unit links.

!!!warning Option Restrictions
Do not try to set `:page`, `:page_key`, `:querify`, or `:period` options manually. These options are handled automatically, so setting them explicitly has no effect.

The `Pagy::OPTIONS` are not applied to the unit objects.
!!!

#### The `disabled` flag

<br/>

The calendar is enabled by default. However, you can include an optional `:disabled` boolean flag in the `configuration` hash to disable the calendar bars. This is useful to display the regular pages of the collection without any filtering or calendar UI.

#### Offset Object

<br/>

- The [:offset](offset) paginator object is always used, regardless of the `:disabled` flag value. If omitted, a default one will be created.
- It is not subject to the Option Restrictions mentioned above.

==- :icon-hash:&nbsp; Methods

`@calendar.url_at(time, **options)`
: Returns a URL complete with all parameters for the pages in each filter bar that includes the given time. For example:
  `@calendar.url_at(Time.zone.now)` generates the filter bar URLs pointing to today.

  If `time` is outside the pagination range, it raises a `Pagy::RangeError`; however, you can pass the `fit_time: true` option to avoid the error and get the URL to the page closest to the passed time argument (first or last page).

`@calendar.showtime`
: Displays the time of the smallest time unit currently visible on the calendar.

==- :icon-location:&nbsp; Localization

Pagy provides the calendar localization only for `en` locales. For other locales, pagy requires the `rails-i18n` gem.

Pass the locales that your app uses to the method `localize_with_rails_i18n_gem` in the [pagy.rb initializer](/toolbox/configuration/initializer):

```ruby
Pagy::Calendar.localize_with_rails_i18n_gem(*your_locales)
```

==- :icon-alert:&nbsp; Caveats

!!!warning Calendar pages with no records are visible and accessible.
You may want to display a message when `@records.empty?`.
!!!

===

---

### Toolbox Paginators Countish (`docs/toolbox/paginators/countish.md`)

---
label: :countish
icon: move-to-end-24
order: 95
---

#

## :icon-move-to-end:&nbsp;&nbsp;:countish

---

`:countish` is an [OFFSET](/guides/choose-right/#offset) paginator that memoizes the result of the `COUNT` query in the page links, running it only once per collection (instead of once per page), optionally recounting it when it's stale.

It **fully** supports all the helpers and navigators.

=== :icon-tools:&nbsp; Usage

```ruby Controller
# count only once and memoize for all pages
@pagy, @records = pagy(:countish, collection, **options)
# count once, memoize, and recount when the memoized count is older than ttl
@pagy, @records = pagy(:countish, collection, ttl: 300, **options)
```

- `@pagy` is the pagination instance. It provides the [readers](#readers) and the [helpers](../helpers) to use in your code.
- `@records` represents the paginated collection of records for the page (lazy-loaded records).
- `:ttl` is the Time To Live of the memoized count. Counts only once if `nil`.

==- :icon-sliders:&nbsp; Options

`ttl: 180`
: A positive number of seconds enables recounting. Setting it to `nil` (falsey) queries the DB for the COUNT only once, and reuses it for all the other pages served.

  !!!warning Recounting/TTL
  - Recounting gets the user more precise info and minimizes the page differences with lengthy page-browsing and abundant DB insertions/deletions.
  - It does not fix the OFFSET-intrinsic data-shift of records on active DB insertions/deletions.
  !!!

{{ include "options/offset" }}

==- :icon-mention:&nbsp; Readers

{{ include "snippets/offset-readers" }}

===

---

### Toolbox Paginators Countless (`docs/toolbox/paginators/countless.md`)

---
label: :countless
icon: move-to-end-24
order: 90
---

#

## :icon-move-to-end:&nbsp;&nbsp;:countless

---

`:countless` is an [OFFSET](/guides/choose-right/#offset) paginator that avoids the `COUNT` query, cutting the number of queries per page in half. UI helpers work with some limitations (see [Caveat](#caveat)).

!!!warning Consider using the `:keynav_js` paginator when possible!
The [:keynav_js](keynav_js.md) offers identical UI features but uses the faster [KEYSET](/guides/choose-right/#keyset) pagination.
!!!

=== :icon-tools:&nbsp; Usage

```ruby Controller
@pagy, @records = pagy(:countless, collection, **options)
```

- `@pagy` is the pagination instance. It provides the [readers](#readers) and the [helpers](../helpers) to use in your code.
- `@records` represents the eager-loaded `Array` of records for the page.

==- :icon-sliders:&nbsp; Options

`headless: true`
: Use this option when UI is unnecessary (e.g., for infinite scrolling) and/or to skip eager loading. In this scenario:
  - `@pagy` is incompatible with any helpers.
  - `@records` behaves like a standard collection.
  - The collection ends when `@records.size < @pagy.limit`.

{{ include "options/offset" }}

==- :icon-mention:&nbsp; Readers

{{ include "snippets/offset-readers" }}

==- :icon-alert:&nbsp; Caveat

!!!warning Nav bar links beyond the highest visited page are not known/displayed.
!!!

===

---

### Toolbox Paginators Elasticsearch Rails (`docs/toolbox/paginators/elasticsearch_rails.md`)

---
label: :elasticsearch_rails
icon: search
order: 50
---

#

## :icon-search:&nbsp;&nbsp;:elasticsearch_rails

---

`:elasticsearch_rails` is a [SEARCH](/guides/choose-right/#search) paginator for `ElasticsearchRails` search results.

=== :icon-tools:&nbsp; Usage

+++ Active mode

!!!success Pagy searches and paginates
You use the `pagy_search` method in place of the `search` method.
!!!

```ruby Model
extend Pagy::Search
```

```ruby Controller
# Get the collection in one of the following ways
search = Article.pagy_search(params[:q])
search = Article.pagy_search(params[:q]).records
search = Article.pagy_search(params[:q]).results
# Paginate it
@pagy, @response = pagy(:elasticsearch_rails, search, **options)

# IMPORTANT: If the Elasticsearch max_result_window is != 10_000, sync it with pagy
@pagy, @response = pagy(:elasticsearch_rails, search, max_result_window: 1_000, ...)
```

+++ Passive mode

!!!success You search and paginate
Pagy creates its object out of your result.
!!!

```ruby Controller
# Standard response (already paginated)
@response = Article.search(params[:q], from: 0, size: 10, ...)
# Get the pagy object out of it
@pagy = pagy(:elasticsearch_rails, @response, **options)

# IMPORTANT: If the Elasticsearch max_result_window is != 10_000, sync it with pagy
@pagy = pagy(:elasticsearch_rails, @response, max_result_window: 1_000, ...)
```

+++

!!!
Search paginators use the same positional technique as [:offset](offset.md) paginators, with shared options and readers.
!!!

==- :icon-sliders:&nbsp; Options

`max_result_window: 1_000`
: Set it to the actual `max_result_window` applied by Elasticsearch, to get an accurate pagination count (default `10_000`).

`search_method: :my_search`
: Customize the name of the `ElasticsearchRails` search method to use in active mode (default `:search`).

{{ include "options/paginator" }}

==- :icon-mention:&nbsp; Readers

{{ include "snippets/offset-readers" }}

===

---

### Toolbox Paginators Keynav Js (`docs/toolbox/paginators/keynav_js.md`)

---
label: :keynav_js
icon: key
order: 70
---

#

## :icon-key:&nbsp;&nbsp;:keynav_js{{ include "snippets/js-required" }}

---

`:keynav_js` is a fast [KEYSET](/guides/choose-right/#keyset) paginator that supports the UI. It's a Pagy-exclusive technique.

The Keynav pagination adds the numeric variables (`@page`, `@last`, `@previous`, `@next`, `@in`) to its instances, supporting their usage with the UI. It does so by transparently exchanging data with the client, which stores the state of the pagination.

If something goes wrong on the client side, it falls back to the [:countless](countless.md) paginator seamlessly.

{{ include "snippets/run-app" app: "keynav" anchor: "keysets" }}

!!!warning This documentation extends the [:keyset](keyset.md) documentation.
It's easier to understand if you are familiar with the [:keyset](keyset.md) docs.
!!!

==- :icon-list-ordered:&nbsp; Setup

>>> [Keyset Setup](keyset#setup)

>>> [JavaScript Support](/resources/javascript.md)

>>>

=== :icon-tools:&nbsp; Usage

```ruby Controller
@pagy, @records = pagy(:keynav_js, collection, **options)
```

- `@pagy` is the pagination instance. It provides the [readers](#readers) and the [helpers](../helpers) to use in your code.
- `@records` is the eager-loaded `Array` of the page records.

==- :icon-sliders:&nbsp; Options

{{ include "options/keyset" }}

==- :icon-mention:&nbsp; Readers

`last`
: The last page.

`pages`
: The number of pages.

`previous`
: The previous page

{{ include "snippets/keyset-readers" }}

==- :icon-log:&nbsp; In Depth: Cutoffs Filtering

Let's take a new look at the diagram of the keyset pagination explained in the [Keyset documentation](keyset#in-depth-cutoffs):

```
                  │ first page (10)  >│ second page (10) >│ last page (9)  >│
beginning of set >[· · · · · · · · · X]· · · · · · · · · Y]· · · · · · · · ·]< end of set
                                      ▲                   ▲
                                   cutoff-X            cutoff-Y
```

Let's suppose that we navigate to page #3 (i.e., the last page), and we click on the link for page #2. We have stored the `cutoff-X`, so we can pull the 10 records after `cutoff-X` again as we did the first time... but are we sure that we would get the same results?

Let's suppose that the database just changed: 1 record was inserted before `cutoff-X`, and 2 records were deleted after `cutoff-X`...

```
                  │ page #1 (11)       >│ page #2 (8)  >│ page #3 (9)    >│
beginning of set >[· · · · · · · · · · X]· · · · · · · Y]· · · · · · · · ·]< end of set
                                        ▲               ▲
                                   cutoff-X        cutoff-Y
```

At this point, pulling 10 records from the `cutoff-X` would also get the first 2 records of page #3, and if you then navigate to page #3, you would pull the same 2 records again.

Indeed, not only have the results changed, but the cutoffs appear to have also shifted their absolute position in the set. In reality, the cutoffs have the same value as before, so they maintained their relative position in the set. However, now there is a different number of records falling into the same pages, which is totally consistent with the changes, but possibly unexpected. That is... if you have the mindset of OFFSET pagination, where the pages are split by number of records (absolute position) and not by their position relative to the records in the set.

!!!
The main goal of pagination is to split the results into manageable chunks and ensure it is as fast and accurate as possible, so the variation in the page size doesn't seem relevant to that. However, should it be relevant to you, you can always use the classic OFFSET pagination and accept its slowness and inaccuracy.
!!!

Pagy keynav doesn't use the LIMIT to pull the records of already visited pages. Instead, it replaces the LIMIT with the same filter used for the `beginning` of the page, and compounds it with the negated filter of the `ending` of the page.

For example, the filtering of the page could be logically described like:

```
- Page #1 `WHERE NOT AFTER CUTOFF-X`                    <- only ending filter
- Page #2 `WHERE AFTER CUTOFF-X AND NOT AFTER CUTOFF-Y` <- combined beginning + ending filter
- Page #3 `WHERE AFTER CUTOFF-Y LIMIT 10`               <- regular beginning filter (no cutoff for last page)
```

!!! Implementing page-rebalancing
When the number of records on a visited page has drastically changed, it would be helpful to mitigate the surprise effect on the user by:

- Automatically compacting the empty (or almost empty) visited pages.
- Automatically splitting the excessively grown visited pages.
!!!

==- :icon-alert:&nbsp; Caveat

!!!warning Nav bar links beyond the highest visited page are not known/displayed.
!!!

===

---

### Toolbox Paginators Keyset (`docs/toolbox/paginators/keyset.md`)

---
label: :keyset
icon: key
order: 80
---

#

## :icon-key:&nbsp;&nbsp;:keyset

---

`:keyset` is the **fastest** [KEYSET](/guides/choose-right/#keyset) paginator for `ActiveRecord::Relation` or `Sequel::Dataset` SQL ordered collections.

It uses a much faster pagination technique than [OFFSET](/guides/choose-right/#offset).

{{ include "snippets/run-app" app: "keyset" anchor: "keysets" }}

==- :icon-blocked:&nbsp; Constraints

!!!tip When a UI is needed, and using [KEYSET Pagination](/guides/choose-right/#keyset) is possible, use the [:keynav_js](keynav_js) paginator to overcome almost all these constraints.
!!!

!!!warning With any KEYSET pagination technique...
- You can only paginate from one page to the next: no jumping to arbitrary pages.
- The `set` must be `uniquely ordered`. Add the primary key (usually `:id`) as the last order column to be sure.
- You should add the most suitable DB index for your ordering strategy, or there will be no performance gain.
!!!

!!!warning With Pagy `:keyset`...
You don't know the `previous` and the `last` page; you only know the `first` and `next` pages.

:icon-light-bulb: If you want to paginate backward, just call `reverse_order` on your set, and proceed forward.
!!!

==- :icon-list-ordered:&nbsp; Setup

Depending on your **order requirements**, here is how you set it up for maximum efficiency:

+++ No Order Requirement

Just order by `:id`. It's fast out of the box without any setup...

```rb
set = collection.order(:id)
```

+++ Specific Order Requirement

>>> Ensure that your set is `uniquely ordered`

{{ include "snippets/mini-step" step: "Option 1" }} If the combination of the values of the ordered columns **is certainly unique**...

```rb
set = collection.order(:brand).order(:model)
```

{{ include "snippets/mini-step" step: "Option 2" }} If **it's possibly not unique**, just append a unique column to the order (e.g., your primary keys).

```rb
set = collection.order(:last_name).order(:first_name).order(:id)
```

>>> Ensure that your table has the appropriate index AND index type

{{ include "snippets/mini-step" step: "1" }} The index must include the exact same columns and ordering direction of your set.<br>
{{ include "snippets/mini-step" step: "2" }} The index type must minimize the data scan (e.g., B-tree, B+ tree, ...).

>>>

+++

=== :icon-tools:&nbsp; Usage

```ruby Controller
@pagy, @records = pagy(:keyset, set, **options)
```

- `@pagy` is the pagination instance. It provides the [readers](#readers) and the [helpers](../helpers) to use in your code.
- `@records` is the eager-loaded `Array` of the page records.

```ERB

<%== @pagy.next_tag(text: 'Next page &gt;') %>
```

==- :icon-sliders:&nbsp; Options

{{ include "options/keyset" }}

==- :icon-mention:&nbsp; Readers

{{ include "snippets/keyset-readers" }}

==- :icon-book:&nbsp; Glossary

There are a few peculiar aspects of the keyset pagination technique that you might not be familiar with. Here is a concise list:

`offset pagination`
: The technique to fetch each page by incrementing the `offset` from the collection start.<br/>It requires two queries per page (or only one if you use [countless](countless.md)): it's slow toward the end of big tables.<br/>It can be used for a rich frontend: it's the classic pagy pagination.

`keyset pagination`
: The technique to fetch the next page starting after the latest fetched record in a `uniquely ordered` collection.<br/>It requires only one query per page (without OFFSET). If properly indexed, it's the fastest technique, regardless of the table size and position. Supports only infinite pagination, with no other frontend helpers.

`keynav pagination`
: The Pagy-exclusive technique to use `keyset` pagination, providing **nearly complete** UI support. The fastest technique with UI capabilities.

`uniquely ordered`
: The property of a `set`, when the concatenation of the values of the ordered columns is unique for each record. It is similar to a composite primary `key` for the ordered table, but dynamically based on the `keyset` columns.

`set`
: The `uniquely ordered` `ActiveRecord::Relation` or `Sequel::Dataset` collection to paginate.

`keyset`
: The hash of column/order pairs. Pagy extracts it from the order of the `set`.

`keyset attributes`
: The hash of attributes of the `keyset` columns of a record.

`cutoff`
: The value that identifies where the `page` ends, and the `next` one begins. It is the `Base64` URL-safe encoded string of the serialized array of the `keyset attribute` values.

`cutoffs`
: The array of `cutoff`s of the known pagination state, used only by [keynav](keynav_js) to keep track of the visited pages during the navigation. They are cached in the `sessionStorage` of the client.

`page`
: The current `page`, i.e. the page of records beginning after the `cutoff` of the previous page. Also the `:page` option, which is set to the `cutoff` of the previous page.

`next`
: The next `page`, i.e. the page of records beginning after the `cutoff`. Also the `cutoff` value returned by the `next` method.

==- :icon-log:&nbsp; In Depth: Cutoffs

The `cutoff` of a `page` is the **value** that identifies where the `page` _has ended_, and the `next` one begins.

Let's consider an example of a simple `set` of 29 records, with an `id` column populated by character keys, and its order is: `order(:id)`.

Assuming a LIMIT of 10, the _"first page"_ will just include the first 10 records in the `set`: no `cutoff` value is known so far...

```
                  │ first page (10)  >│ rest (19)                          >│
beginning of set >[· · · · · · · · · ·]· · · · · · · · · · · · · · · · · · ·]< end of set
```

At this point, it's the exact same first page pulled with OFFSET pagination, however, we don't want to use OFFSET to get the records after the first 10: that would be slow in a big table, so we need a way to identify the beginning of the next page without COUNTing the records (i.e., the whole point we want to avoid for performance).

So we read the `id` of the last one, which is the value `X` in our example... and that is the `cutoff` value of the first page. It can be described as: _"the point up to the value `X` in the `id` column"_.

Notice that this is not like saying _"up to the record `X`"_. It's important to understand that a `cutoff` refers just to a cutoff value of a column (or the values of multiple columns, in case of multi-column keysets).

Indeed, that very record could be deleted right after we read it, and our `cutoff-X` will still be the valid truth that we paginated the `set` up to the "X" value, cutting any further record off the `page`...

```
                  │ first page (10)  >│ second page (10) >│ rest (9)       >│
beginning of set >[· · · · · · · · · X]· · · · · · · · · ·]· · · · · · · · ·]< end of set
                                      ▲
                                   cutoff-X
```

For getting the `next` page of records (i.e. the _"second page"_) we pull the `next` 10 records AFTER the `cutoff-X`. Again, we read the `id` of the last one, which is `Y`: so we have our new `cutoff-Y`, which is the end of the current `page`, and the `next` will go AFTER it...

```
                  │ first page (10)  >│ second page (10) >│ last page (9)  >│
beginning of set >[· · · · · · · · · X]· · · · · · · · · Y]· · · · · · · · ·]< end of set
                                      ▲                   ▲
                                   cutoff-X            cutoff-Y
```

When we pull the `next` page from the `cutoff-Y`, we find only the remaining 9 records, which means that it's the _"last page"_, which naturally ends with the end of the `set`, so it doesn't have any `cutoff` value to separate it from further records.

!!! Keynotes
- A `cutoff` identifies a "cutoff value" for a `page` in the `set`. It is not a record, nor a reference to it.
- Its value is extracted from the `keyset attributes values` array of the last record of the `page`, converted to JSON, and encoded as a Base64 URL-safe string, for easy use in URLs.
  - The `:keyset` paginator embeds it in the request URL; the `:keynav_js` paginator caches it on the client `sessionStorage`.
- All the `page`s but the last end with the `cutoff`.
- All the `page`s but the first begin AFTER the `cutoff` of the previous `page`.
!!!

==- :icon-stop:&nbsp; Troubleshooting

:::
==- {{ include "snippets/mini-step" step:"1" }} Records may repeat or be missing from successive pages

||| :icon-question: Order issue

_Not unique combination..._

```rb
Product.order(:name, :production_date)
```

||| :icon-check-circle: Append the primary key to the order

_The `:id` is usually the primary key..._

```rb
Product.order(:name, :production_date, :id)
```

|||

||| :icon-question: Encoding issue

The generic `to_json` method used to encode the `page` may lose some information when decoded.

||| :icon-check-circle: Solution

- Check the actual executed DB query and the actual stored value.
- Identify the column that has a format that doesn't match the keyset.
- Override the encoding with the [:pre_serialize](#options) option.

|||

==- {{ include "snippets/mini-step" step:"2" }} The order is OK, but the DB is still slow

||| :icon-question: Index issue

The index has the wrong order, or it's the wrong type.

||| :icon-check-circle: Solutions

- Ensure that the index reflects exactly the column sequence and order of your keyset.
- Use a B-tree or B+ Tree index. Use SQL `EXPLAIN ANALYZE` or a similar tool to confirm no table scan is performed.
- With same-direction keysets, enabling the `:tuple_comparison` option may help if your DB supports it.

|||

===

---

### Toolbox Paginators Meilisearch (`docs/toolbox/paginators/meilisearch.md`)

---
label: :meilisearch
icon: search
order: 40
---

#

## :icon-search:&nbsp;&nbsp;:meilisearch

---

`:meilisearch` is a [SEARCH](/guides/choose-right/#search) paginator for `Meilisearch` search results.

=== :icon-tools:&nbsp; Usage

+++ Active mode

!!!success Pagy searches and paginates
You use the `pagy_search` method in place of the `ms_search` method.
!!!

```ruby Model
extend Pagy::Search
ActiveRecord_Relation.include Pagy::Search # Statically enable pagy_search on the model relations
```

```ruby Controller
# Get the collection in one of the following ways
search = Article.pagy_search(params[:q])
search = Article.pagy_search(params[:q]).results
# Paginate it
@pagy, @response = pagy(:meilisearch, search, **options)
```

+++ Passive mode

!!!success You search and paginate
Pagy creates its object out of your result.
!!!

```ruby Controller
# Standard results (already paginated)
@results = Article.ms_search(params[:q], hits_per_page: 10, page: 10, ...)
# Get the pagy object out of it
@pagy    = pagy(:meilisearch, @results, **options)
```

+++

!!!
Search paginators use the same positional technique as [:offset](offset.md) paginators, with shared options and readers.
!!!

==- :icon-sliders:&nbsp; Options

`search_method: :my_search`
: Customize the name of the `Meilisearch` search method to use in active mode (default `:ms_search`).

{{ include "options/paginator" }}

==- :icon-mention:&nbsp; Readers

{{ include "snippets/offset-readers" }}

===

---

### Toolbox Paginators Offset (`docs/toolbox/paginators/offset.md`)

---
label: :offset
icon: move-to-end
order: 100
---

#

## :icon-move-to-end:&nbsp;&nbsp;:offset

---

`:offset` is a generic [OFFSET](/guides/choose-right/#offset) paginator usable with ORM collections or regular `Array` objects.

It uses the complete [OFFSET](/guides/choose-right/#offset) pagination technique, which triggers two SQL queries per request:

- a `COUNT` query to get the count;
- an `OFFSET` + `LIMIT` query to get the records.

It **fully** supports all the helpers and navigators.

!!!warning Consider using the `:countish` paginator when possible!
The [:countish](countish) paginator offers identical UI features, but it's up to 2x faster.
!!!

=== :icon-tools:&nbsp; Usage

```ruby Controller
@pagy, @records = pagy(:offset, collection, **options)
```

- `@pagy` is the pagination instance. It provides the [readers](#readers) and the [helpers](../helpers) to use in your code.
- `@records` represents the paginated collection of records for the page (lazy-loaded records).

==- :icon-sliders:&nbsp; Options

{{ include "options/offset" }}

==- :icon-mention:&nbsp; Readers

{{ include "snippets/offset-readers" }}

===

---

### Toolbox Paginators Rails Active Search (`docs/toolbox/paginators/rails_active_search.md`)

---
label: :rails_active_search
icon: search
order: 35
nav:
  badge:
    text: "NEW"
    variant: info
---

#

## :icon-search:&nbsp;&nbsp;:rails_active_search

---

`:rails_active_search` is a [SEARCH](/guides/choose-right/#search) paginator for `ActiveSearch` (`rails-active_search` gem) search results.

{{ include "snippets/run-app" app: "active_search" anchor: "active-search" }}

=== :icon-tools:&nbsp; Usage

+++ Active mode

!!!success Pagy searches and paginates
You use the `pagy_search` method in place of the `search` method.
!!!

```ruby Model
extend Pagy::Search
```

```ruby Controller
# Get the collection (ActiveSearch keyword arguments, e.g., index: or scope:, are passed through)
search = Article.pagy_search(params[:q])
# Chain any ActiveSearch::Query method that returns a query (e.g., filter, sort, highlight)
search = Article.pagy_search(params[:q]).filter(status: 'published').sort(published_at: :desc)
# Paginate it: @results is the ActiveSearch::Results of the page
@pagy, @results = pagy(:rails_active_search, search, **options)
```

!!!warning
Do not chain the pagination methods (`limit`, `offset`, `page`) nor the methods that end the chain (e.g., `results`, `to_native_query`).
!!!

+++ Passive mode

!!!success You search and paginate
Pagy creates its object out of your result.
!!!

```ruby Controller
# Standard results (already paginated)
@results = Article.search(params[:q]).limit(10).offset(20).results
# or
@results = Article.search(params[:q]).page(3, per_page: 10).results
# Get the pagy object out of it
@pagy    = pagy(:rails_active_search, @results, **options)
```

!!!warning
The `:limit` and `:page` are extracted from the results, so use a single Integer `per_page` with `page`: an Array of page sizes is not supported.
!!!

+++

!!!
Search paginators use the same positional technique as [:offset](offset.md) paginators, with shared options and readers.
!!!

==- :icon-sliders:&nbsp; Options

`search_method: :my_search`
: Customize the name of the `ActiveSearch` search method to use in active mode (default `:search`).

{{ include "options/paginator" }}

==- :icon-mention:&nbsp; Readers

{{ include "snippets/offset-readers" }}

===

---

### Toolbox Paginators Searchkick (`docs/toolbox/paginators/searchkick.md`)

---
label: :searchkick
icon: search
order: 30
---

#

## :icon-search:&nbsp;&nbsp;:searchkick

---

`:searchkick` is a [SEARCH](/guides/choose-right/#search) paginator for `Searchkick` search results.

=== :icon-list-ordered:&nbsp; Setup

```ruby pagy.rb (initializer)
Searchkick.extend Pagy::Search
```

=== :icon-tools:&nbsp; Usage

+++ Active mode

!!!success Pagy searches and paginates
You use the `pagy_search` method in place of the `search` method.
!!!

```ruby Model
extend Pagy::Search
```

```ruby Controller
# Single model
search = Article.pagy_search(params[:q])
# Multiple models
search = Searchkick.pagy_search(params[:q], models: [Article, Category])
# Paginate it
@pagy, @response = pagy(:searchkick, search, **options)

# IMPORTANT: If the Elasticsearch max_result_window is != 10_000, sync it with pagy
@pagy, @response = pagy(:searchkick, search, max_result_window: 1_000, ...)
```

+++ Passive mode

!!!success You search and paginate
Pagy creates its object out of your result.
!!!

```ruby Controller
# Standard results (already paginated)
@results = Article.search(params[:q], page: 1, per_page: 10, ...)
# Get the pagy object out of it
@pagy = pagy(:searchkick, @results, **options)

# IMPORTANT: If the Elasticsearch max_result_window is != 10_000, sync it with pagy
@pagy = pagy(:searchkick, @results, max_result_window: 1_000, ...)
```

+++

!!!
Search paginators use the same positional technique as [:offset](offset.md) paginators, with shared options and readers.
!!!

==- :icon-sliders:&nbsp; Options

`max_result_window: 1_000`
: Set it to the actual `max_result_window` applied by Elasticsearch, to get an accurate pagination count (default `10_000`).

`search_method: :my_search`
: Customize the name of the `Searchkick` search method to use in active mode (default `:search`).

{{ include "options/paginator" }}

==- :icon-mention:&nbsp; Readers

{{ include "snippets/offset-readers" }}

===

---

### Toolbox Paginators Typesense Rails (`docs/toolbox/paginators/typesense_rails.md`)

---
label: :typesense_rails
icon: search
order: 20
---

#

## :icon-search:&nbsp;&nbsp;:typesense_rails

---

`:typesense_rails` is a [SEARCH](/guides/choose-right/#search) paginator for `Typesense::Rails` search results.

=== :icon-list-ordered:&nbsp; Setup

Ensure `Typesense.configuration[:pagination_backend] == nil`.

=== :icon-tools:&nbsp; Usage

+++ Active mode

!!!success Pagy searches and paginates
You use the `pagy_search` method in place of the `search` method.
!!!

```ruby Model
extend Pagy::Search
```

```ruby Controller
# Get the collection
search = Article.pagy_search(params[:q], to_query)
# Paginate it
@pagy, @response = pagy(:typesense_rails, search, **options)
```

+++ Passive mode

!!!success You search and paginate
Pagy creates its object out of your result.
!!!

```ruby Controller
# Standard results (already paginated)
@results = Article.search(params[:q], to_query, { per_page: 10, page: 10, ... })
# Get the pagy object out of it
@pagy    = pagy(:typesense_rails, @results, **options)
```

+++

!!!
Search paginators use the same positional technique as [:offset](offset.md) paginators, with shared options and readers.
!!!

==- :icon-sliders:&nbsp; Options

`search_method: :my_search`
: Customize the name of the `Typesense::Rails` search method to use in active mode (default `:search`).

{{ include "options/paginator" }}

==- :icon-mention:&nbsp; Readers

{{ include "snippets/offset-readers" }}

===

---

--- METRICS ---
- Files Extracted: 65
- Estimated Token Budget: ~79744 tokens
- Canonical Reference: https://codewiki.google/github.com/ddnexus/pagy
