Will Paginate
For the past several years the `will_paginate` gem has become one of the most popular gems to integrate into a rails application, and for good reason. I remember back to when I started developing PHP applications, integrating pagination was nasty business. Will paginate makes pagination so easy that you can integrate it within a matter of minutes and it works very well.
Guide Tasks
  • Read Tutorial

What will_paginate Provides

For the past several years the will_paginate gem has become one of the most popular gems to integrate into a rails application, and for good reason. I remember back to when I started developing PHP applications, integrating pagination was nasty business. Will paginate makes pagination so easy that you can integrate it within a matter of minutes and it works very well. In addition to basic pagination it also offers:

  • The ability to apply custom CSS styles

  • An AJAX implementation

  • Multiple customization options, many of which can be performed on the fly

Drawbacks

The key drawback I've run into has been that will_paginate doesn't play nicely with other pagination gems, namely Kaminari. Why is this an issue? Surely you wouldn't use two different pagination gems in the same application, right? Not quite. Other gems, such as Rails Admin and Smart Listing have Kaminari as a dependency, which causes conflicts. That's why I created these initializers for anytime where I need to resolve the conflicts: Kaminari / Will Paginate.

Implementation / Will Paginate Example

To get started let's create a scaffold so we can quickly get our CRUD functionality. As I've mentioned in multiple other posts this is not a best practice, I simply do it for tutorials like this where the development focus is on another feature, for a production app I typically like using the Resource/Model/Controller generators. Let's run:

rails g scaffold Post title:string content:text

After running rake db:migrate we will have everything we need to implement pagination. Start off by going to the RubyGems.org page and grabbing the latest stable version of the gem and place it in the Gemfile:

gem 'will_paginate', '~> 3.1'

After running bundle install open up the posts controller and update the index action query:

# app/controllers/posts_controller.rb

def index
  @posts = Post.paginate(page: params[:page], per_page: 10)
end

As you can see we're passing the paginate method two named arguments:

  • page - This will tell the method what page the user is on so it knows which results to render.

  • per_page - This is an optional argument where we say how may results we want per page.

After that we simply need add in a call to the will_paginate view helper method in our post's index view template, the template should look like this:

<!-- app/view/posts/index.html.erb -->

<p id="notice"><%= notice %></p>

<h1>Listing Posts</h1>

<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Content</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.title %></td>
        <td><%= post.content %></td>
        <td><%= link_to 'Show', post %></td>
        <td><%= link_to 'Edit', edit_post_path(post) %></td>
        <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<%= will_paginate @posts %>

<br>

<%= link_to 'New Post', new_post_path %>

So all we did was add in the line <%= will_paginate @posts %>. If you startup the Rails server and navigate to localhost:3000/posts you will see the index page, but since it doesn't have any data it won't show the pagination links. Side debugging note: if pagination links aren't showing up, make sure there are enough records :)

Open up the rails console and run the following command:

100.times { |p| Post.create!(title: "My #{p} Post", content: "My great post") }

This will create 100 posts for us to see in the view.

Now startup the rails server and go back to localhost:3000/posts and you'll see that our pagination works perfectly! I love the simplicity of the implementation, pagination, which used to take hours of development time and could be pretty bug prone can be properly implemented in a few lines of code.

As you click on the links notice how the page param changes each time? That's how the paginate method in the controller knows what records to query.

Styling

Out of the box the pagination is pretty ugly, so let's style the pagination component. Mislav, the creator and maintainer of Will paginate gave some baseline styles that are pretty easy to implement. Let's add these styles into the application. Create a new file called pagination.css, place it in the app/assets/stylesheets/ directory, and add the following code:

/* app/assets/stylesheets/pagination.css */

.digg_pagination {
  background: white;
  cursor: default;
  /* self-clearing method: */ }
  .digg_pagination a, .digg_pagination span, .digg_pagination em {
    padding: 0.2em 0.5em;
    display: block;
    float: left;
    margin-right: 1px; }
  .digg_pagination .disabled {
    color: #999999;
    border: 1px solid #dddddd; }
  .digg_pagination .current {
    font-style: normal;
    font-weight: bold;
    background: #2e6ab1;
    color: white;
    border: 1px solid #2e6ab1; }
  .digg_pagination a {
    text-decoration: none;
    color: #105cb6;
    border: 1px solid #9aafe5; }
    .digg_pagination a:hover, .digg_pagination a:focus {
      color: #000033;
      border-color: #000033; }
  .digg_pagination .page_info {
    background: #2e6ab1;
    color: white;
    padding: 0.4em 0.6em;
    width: 22em;
    margin-bottom: 0.3em;
    text-align: center; }
    .digg_pagination .page_info b {
      color: #000033;
      background: #6aa6ed;
      padding: 0.1em 0.25em; }
  .digg_pagination:after {
    content: ".";
    display: block;
    height: 0;
    clear: both;
    visibility: hidden; }
  * html .digg_pagination {
    height: 1%; }
  *:first-child + html .digg_pagination {
    overflow: hidden; }

.apple_pagination {
  background: #f1f1f1;
  border: 1px solid #e5e5e5;
  text-align: center;
  padding: 1em;
  cursor: default; }
  .apple_pagination a, .apple_pagination span {
    padding: 0.2em 0.3em; }
  .apple_pagination .disabled {
    color: #aaaaaa; }
  .apple_pagination .current {
    font-style: normal;
    font-weight: bold;
    background-color: #bebebe;
    display: inline-block;
    width: 1.4em;
    height: 1.4em;
    line-height: 1.5;
    -moz-border-radius: 1em;
    -webkit-border-radius: 1em;
    border-radius: 1em;
    text-shadow: rgba(255, 255, 255, 0.8) 1px 1px 1px; }
  .apple_pagination a {
    text-decoration: none;
    color: black; }
    .apple_pagination a:hover, .apple_pagination a:focus {
      text-decoration: underline; }

.flickr_pagination {
  text-align: center;
  padding: 0.3em;
  cursor: default; }
  .flickr_pagination a, .flickr_pagination span, .flickr_pagination em {
    padding: 0.2em 0.5em; }
  .flickr_pagination .disabled {
    color: #aaaaaa; }
  .flickr_pagination .current {
    font-style: normal;
    font-weight: bold;
    color: #ff0084; }
  .flickr_pagination a {
    border: 1px solid #dddddd;
    color: #0063dc;
    text-decoration: none; }
    .flickr_pagination a:hover, .flickr_pagination a:focus {
      border-color: #003366;
      background: #0063dc;
      color: white; }
  .flickr_pagination .page_info {
    color: #aaaaaa;
    padding-top: 0.8em; }
  .flickr_pagination .previous_page, .flickr_pagination .next_page {
    border-width: 2px; }
  .flickr_pagination .previous_page {
    margin-right: 1em; }
  .flickr_pagination .next_page {
    margin-left: 1em; }

Now you can update the index view template with one of the css classes:

<!-- app/view/posts/index.html.erb -->

<div class="apple_pagination">
  <%= paginate @posts, :container => false %>
</div>

Refresh the page and you'll see the new styles have been applied.

If your application uses bootstrap, you can also use the will_paginate-bootsrap gem to leverage Bootstrap styles.

Code

Resources