In this post I describe how I add pagination to a blog using the Pagy gem.

Adding records to test the pagination functionality

Before adding the pagination functionality, I want to add a number of random blog posts so I can test out the implementation.

To add blog posts, I could use a library like Faker, a gem that populates models with fake data for development and testing.

But in my case, I don't need anything sophisticated, so I will just create random posts in the database directly using the seeds.rb file.

One thing to remember when seeding data is that running db:seeds should be idempotent, that is, it should always create the same data in the database, no matter how many times the command is run.

With this in mind, instead of creating records with Article.create, which would add additional records every time I run db:seeds again, I make sure that records are created with Article.find_or_create_by instead, which will create records only if they don't exist.

# db/seeds.rb

100.times do |n|
  Article.find_or_create_by(
    title: "Article #{n}",
    body: "This is my #{n}th article")
end

Adding the Pagy gem

There are several Ruby gems that handle pagination. Pagy is commonly used because it's faster, lighter, and simpler to use than other alternatives.

To add the library to my application I use the bundle add command like so:

$ bundle add pagy

This will add the gem to the Gemfile and run bundle install.

With the gem installed, I can then open ApplicationController in my text editor, and include Pagy::Backend, as explained in the Pagy instructions:

# app/controller/application_controller.rb

class ApplicationController < ActionController::Base
  include Pagy::Backend
end

This will make Pagy available to all the controllers in the application, because they all inherit from ApplicationController.

In a similar way, I include Pagy::Frontend to the application helper file so Pagy helpers are made available:

# app/helpers/application_helper.rb

module ApplicationHelper
  include Pagy::Frontend
end

The next step is to wrap article collections inside controller actions with Pagy.

Using Pagy inside actions

If I look at the articles controller, I can see that the index action gets all the articles from the database and assigns them to the @articles collection.

# app/controllers/articles_controller.rb

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end
end

To enable the pagination, all I have to do is take this collection and pass it to the pagy() method. The method will return a Pagy object loaded with several methods used for the pagination itself, as well as the original article collection.

# app/controllers/articles_controller.rb

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
    @pagy, @articles = pagy(@articles)
  end
end

When this is done, I go to the articles' index page view template and add the pagination links like so:

# app/views/articles/index.html.erb

<%== pagy_nav @pagy %>

Note the double equal syntax <%== ... %> in the erb tag. This version of the tag will render the html links generated by pagy as raw html, without sanitizing them first. If we had used the single equal version of the tag, the output of pagy_nav would be sanitized, and displayed on the page with escaped characters.

Since this html is generated by Pagy, it's safe to display the unsanitized links.

With these changes, our pagination should work correctly, which we can verify if we look at the page.

Styling the pagination links

Our pagination links have default classes applied by Pagy and look OK for initial setup and testing, but they would need some more robust styling for production.

An advantage of Pagy is that it integrates with various CSS framework. Since I mosty use Tailwind CSS I will use this integration to style my links.

All I have to do is go to the Tailwind CSS integration page for Pagy and copy the example styles in that page.

I will paste these styles at the bottom of the app/assets/stylesheets/application.tailwind.css file and restart the server to see the changes.

/* app/assets/stylesheets/application.tailwind.css */

 @tailwind base;
 @tailwind components;
 @tailwind utilities;

 @layer components {
   /* existing code */
 }

 /* CSS classes provided by Pagy */
 .pagy-nav,
 .pagy-nav-js {
   @apply flex space-x-2;
 }

 .pagy-nav .page a,
 .pagy-nav .page.active,
 .pagy-nav .page.prev.disabled,
 .pagy-nav .page.next.disabled,

 /* ... */

By adding the Tailwind styles I end up with the pagination navigation links looking much better, and of course I can always edit these styles to match the look of the application.

Displaying the pagination links conditionally

Right now, the pagination links will always show up at the bottom of the page, even if we only have one single blog article. It would be nice to remove the pagination links if the number of articles in the collection is less than the number of articles per page set up by Pagy.

It's easy enough to add this functionality by adding an if condition and display the links only when needed:

# app/views/articles/index.html.erb

<% if @pagy.counts > @pagy.items %>
  <%== pagy_nav @pagy %>
<% end %>

With this code we take advantage of the methods provided by Pagy on the @pagy object. and will essentially see the pagination links only if there are more than one page of results.

Rescuing errors

To keep track of our position in the pagination, Pagy adds parameters to the URL, similar to this:

http://10.0.0.10:3000/?page=2

If we try to access a pagination number that's too high our collection, like /?page=2000 for example, Pagy will throw a Pagy::OverflowError error, like so:

Pagy::OverflowError in ArticlesController#index
expected :page in 1..6; got 2000

We can rescue this error and redirect the user to the first page of the blog in this way:

# app/controllers/articles_controller.rb

def index
  @articles = Article.all
  @pagy, @articles = pagy(@articles)

rescue Pagy::OverflowError
  redirect_to articles_path(page: 1)
end

In the redirect we set the page parameter to 1, so the URL will reflect that and will show the pages from the beginning.

Another way of achieving the same thing is to use retry to re-run the index action with parameters modified.

# app/controllers/articles_controller.rb

def index
  @articles = Article.all
  @pagy, @articles = pagy(@articles)

rescue Pagy::OverflowError
  params[:page] = 1
  retry
end

The code above will rescue the error by first modifying params[:page] and then re-running the index method from the beginning.

Conclusion

In this article I have explained how I add pagination to a collection of articles on a blog by using the popular Pagy Ruby library.

Photo by Pixabay