Integrating Pagy with Hanami (2025 edition)
Back in 2018 I wrote a post about connecting Hanami (then 1.x) and Pagy gem together. My verdict was not that favorable. I had to write quite a lot of glue code to make it work and perhaps the worst thing was that I had to pass the request object to the template to make it work.
However, things have changed since then:
- Hanami is now 2.3, its persistence layer is more mature, based on ROM and allows to fall back to almost-bare-Sequel via relations.
- Pagy released version 43 this week. It’s advertised as a complete rewrite of its internals and APIs.
We needed a leap version to unequivocally signaling that it’s not just a major version: it’s a complete redesign of the legacy code at all levels, usage and API included.
There’s no better time to take it for another spin then!
The code
I quickly spun up a fresh Hanami app, created a migration:
ROM::SQL.migration do
change do
create_table :people do
primary_key :id
column :name, String, null: false
column :email, String, null: false
column :birth_date, Date, null: false
column :added_at, DateTime, null: false
end
end
end
Then created a relation:
module Pagynation
module Relations
class People < Hanami::DB::Relation
schema :people, infer: true
end
end
end
Next I generated the action and added Pagy to it.
require "pagy"
module Pagynation
module Actions
module People
class List < Pagynation::Action
include Deps["relations.people"]
include Pagy::Method
def handle(request, response)
pagy, records = pagy(people.order(:name), request:, client_max_limit: 40)
response.render view, pagy: pagy, people: records
end
end
end
end
end
This looks more or less like the tutorial code from the Pagy website. The only exception is that I have to pass the request directy. If not passed, Pagy expects self.request to be defined. This does not work with Hanami, where the request is passed as an argument (in a functional flavour), not burned into the controller instance.
Let’s now examine the view:
module Pagynation
module Views
module People
class List < Pagynation::View
expose :people, decorate: false
expose :paginator, decorate: false do |pagy:|
pagy
end
end
end
end
end
This is standard Hanami stuff. We passed :pagy and :people from the action via the render method, now we need to pass it on to the template. I used decorate: false here to keep the things as simple as possible (data is a simple hash, not objects). With people and paginator exposed, all that’s left is to craft a template using it.
<h1>People list</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Date of Birth</th>
</tr>
</thead>
<tbody>
<% people.each do |person| %>
<tr>
<td><%= person[:name] %></td>
<td><%= person[:email] %></td>
<td><%= person[:birth_date] %></td>
</tr>
<% end %>
</tbody>
</table>
<div>
<%= raw paginator.info_tag %>
</div>
<div>
<%= raw paginator.series_nav %>
</div>
The paginator.info_tag and paginator.series_nav are Pagy’s HTML helper to render some status info and pagination links. They are, of course, quite barebones, but more than enough to test stuff.
This all led me to this beauty, with working limit URL params, working links etc.
New verdict
This is basically great.
Everything worked pretty much out-of-the-box. If I were to imagine how seamless a pagination library for Hanami could be, this would likely look like that. Great improvements from Pagy maintainer.
The repository to try it out yourself is available here on Codeberg.
Happy paginating!