Integrating Pagy with Hanami
by Paweł Świątkowski
Pagy – “the ultimate pagination gem that outperforms the others in each and every benchmark and comparison” – made all Ruby-related news lately. It is no surprise, after all it promises a lot: being simple, being fast, being ORM-agnostic and working with every Rack-based framework.
All of these are really good features. Afer all, it’s true what the gem’s website says: pagination is not a rocket science and it should be kept simple, stupid. Current solutions, namely will_paginate and kaminary, grew very complex and tightly coupled to Ruby on Rails. We really needed (and deserved) something better. And I’m glad it’s here.
However, I’m going to check its other promises: working with every ORM and every framework. To do so, I present you a tutorial about integrating Pagy with Hanami.
Let’s start by taking a demo Hanami app - “Bookshelf” from here. I clone it and then make one little adjustment in
apps/web/templates/books/index.html.erb, changing from ugly
div-based displaying of books into unordered list:
This is an extra step, of course you don’t need to do this. Another optional thing is to create a Rake task to fill our database with sample data. Here’s my code, using Faker gem (in
Now let’s turn to real task. I added
gem 'pagy' to my
Gemfile and then followed the tutorial.
Web::Controllers::Books I added required inclusion line:
include Pagy::Backend and then edided its
call method to include using Pagy:
There are couple of things to note here:
- First, we need to add
pagy_datato our exposures, since, unlike other frameworks, Hanami does not let you use all the instance variables from controller in views and templates.
- Second, we cannot use name
pagyfor the instance variable (like the tutorial suggests), because it would introduce a naming conflict between exposed method and internal
pagymathod. The result is a nasty error about wrong number of arguments, which was quite hard to debug.
- Instead of a model, we pass a repository to
pagymethod. Hanami does not use models (unlike ActiveRecord) at all. We have entities and repositories here and repository is a place to put the logic connected to pagination (setting offsets and limits).
Since we are passing a repository, it’s easy to guess we need to add some code there. Two methods, to be precise. One is
count - to calculate number of all records in the database. I don’t quite understand why the method is not htere by default, after all it’s equally useful/useless as
all method, but it’s not.
Second thing is actual method for fetchng data from the database. I called it
page, which is probably not the best name, but it works in this simple example. Here is the code for those two:
Back to the controller
This step is the first one where we have to dive a bit into Pagy’s internals. Fortunately, this is described in the docs:
Sub-method called only by the
pagymethod, it returns the page items (i.e. the records belonging to the current page).
The original implementation works for ActiveRecord (and other ORMs formed after it, so using active record pattern, such as Mongoid), but does not work for data mapper pattern (like Hanami::Model or ROM). This is how it looks:
We need to overwrite it to use our repository (passed as first argument). I did it in the controller, but of course, if you are going to have more paginations on more models, it would be a good idea to put it in some separate module and include everywhere:
With that, the so-called backend part is done. Now let’s go to views and templates.
We are going to the template first. The docs tell us to put pagy view methods in it and call it a day. All we need to do here is to replace instance variables
@pagy with our
With little surprise, it does not work. But the rest of the job is to be done in the view.
First thing to do is to
include Pagy::Frontend. Then we need some custom hacking too. First thing is
pagy_url_for. Now, this is more or less where I stopped liking Pagy a bit. Let’s take a look at the original implementation:
What it does is assuming there is a
request method/object available here in the template. This is a huge violation of MVC, because why on Earth would a template or view need to know about a low-level object from Rack? It shouldn’t. Hanami goes very far in enforcing it and simply disallows you to expose it from the controller.
There is probably a more universal solution out there, but I decided to keep it simple. Here’s how my method looks like:
It mixes a little bit of Pagy’s magic (uses its
vars object with all the configuration for pagination) with simple Hanami routes. I real-life usage you might want to add a check if page number is one and if it is - not add a page to params.
One last thing to take care of is HTML escaping.
Pagy::Frontend generates strings and you can’t just throw them in the template, but first “unescape” (i.e. mark as safe) in the view:
And that’s it! Now you can enjoy working pagination in your Bookshelf application in Hanami.
The complete code can be found in my fork on separate branch.
I really like what Pagy attempts to do: bring back Ruby into Rails-world.
However, I had to write quite a lot of custom code to make it work with Hanami. The backend part is actually close to writing this all by hand (as I said, it’s no rocket science). A bit more work is done by Pagy’s frontend part. In the end, I think it should be advertised as ORM-agnostic (if your ORM has API like ActiveRecord) and working on every Rack-based framework (if it’s similar to Rails).
Should you use Pagy or write your own pagination? It’s rather up to you. Now you see how many customizations are required to work with simplest setup. I did not play with it enough (yet) to tell how it would look in more complex situation. For example, if you don’t want to name your repository method
count, you need to override one more Pagy’s intenal method (it just assumes that it’s called
count). But it’s totally doable.
One thing that looks promising are Pagy extras - for example a module to make pagination template part look nice in Boostrap. Hopefully, this part will grow and there will be more low-hanging fruits for those who decided to use Pagy in their Hanami application.
Please also note that there is alternative
hanami-pagination gem available. It’s written by Anton Davydov, one of the core developers of Hanami.