Creating infinite scroll using will_paginate gem.

Working example

Lets start by making a new test application:

$ rails new infinite_scroll --database=postgresql -T
... Installing a bunch of stuff ...
Bundle complete! 15 Gemfile dependencies, 62 gems now installed.  
Use `bundle show [gemname]` to see where a bundled gem is installed.  
         run  bundle exec spring binstub --all
* bin/rake: spring inserted
* bin/rails: spring inserted

and cding into our app and open the project in your text editor or IDE of choice:

$ cd infinite_scroll

Let's add our pagination gem for the application to our Gemfile:

# Gemfile
# Use will_paginate for infinite scroll
gem 'will_paginate', '~> 3.1.0'  

And while we are at it, let's add a gem to help us generate test data under group :development:

# Gemfile
# Use faker to generate dummy data
gem 'faker'  

Now, let's bundle install:

$ bundle install
... Installing a bunch of stuff ...
Bundle complete! 17 Gemfile dependencies, 64 gems now installed.  
Use `bundle show [gemname]` to see where a bundled gem is installed.  

Before we forget (because I always do) let's create our database:

$ rails db:create
Created database 'infinite_scroll_development'  
Created database 'infinite_scroll_test'  

Now let's create a model with a title, author, genre, publication date, and summary:

$ rails g model book title:string author:string genre:string pub_date:datetime summary:string
Running via Spring preloader in process 43880  
invoke  active_record  
create    db/migrate/20170509035312_create_books.rb  
create    app/models/book.rb  

Now, migrate our new model:

$ rails db:migrate
== 20170509035312 CreateBooks: migrating ======================================
-- create_table(:books)
   -> 0.0509s
== 20170509035312 CreateBooks: migrated (0.0510s) =============================

Now, unless we are forgetting anything. We should have the minimum to create our application. Let's jump out of the console for a bit and generate some data. In our seeds file we can begin to do just that. We need to create a sizable number of Books so that we can make use of our infinite scroll. Let's start with a modest 100:

# db/seeds.rb
100.times do  
  title             = Faker::Book.title
  author            =
  genre             = Faker::Book.genre
  publication_date  = Faker::Date.between(100.years.ago, 10.years.ago)
  summary           = Faker::Hipster.paragraph(2)

  Book.create!(title: title, author: author, genre: genre, pub_date: publication_date, summary: summary)

We can then run our seed file by going back to our console and running the following:

$ rails db:seed

If everything was successful then you shouldn't see any output, but you can verify the data was generated by opening up a rails console (I suggest opening a new window) with this command:

$ rails c

You should now see a similar prompt in your terminal:

2.3.0 :010 >  

(note: the 2.3.0 denotes the ruby version)

Now if you type Book.count (capitalization counts) it should return a count of 100 records.

2.3.0 :010 > Book.count  
   (4.1ms)  SELECT COUNT(*) FROM "books"
 => 100
2.3.0 :011 >  

And we can inspect a Book with the following command:

2.3.0 :011 > Book.first  
  Book Load (0.4ms)  SELECT  "books".* FROM "books" ORDER BY "books"."id" ASC LIMIT $1  [["LIMIT", 1]]
 => #<Book id: 2, title: "The Moon by Night", author: "Lacey Will", genre: "Short story", pub_date: "1921-02-28 00:00:00", summary: "Messenger bag slow-carb photo booth helvetica cray...", created_at: "2017-05-09 04:21:33", updated_at: "2017-05-09 04:21:33">
2.3.0 :012 >  

Great! Everything works so far. We can close out of our console and continue on with the project.

Let's finish this section by creating a controller for app:

$ rails g controller books

And then we will make our books controller look like this:

# app/controllers/books_controller.rb
class BooksController < ApplicationController  
  def index

Next, we need to add a root route to our app and we will point it to the books#index action. So, inside our routes.rb file we modify the file so it looks like so:

# config/routes.rb
Rails.application.routes.draw do  
  root to: 'books#index'

Now let's create a page to put everything in. We will create two files. First an index page to link up to our index action

$ touch app/views/books/index.html.erb

And a partial that will contain our book partial

$ touch app/views/books/_book.html.erb

Let's add a bit of html to see if we have done everything right so far.

<!-- app/views/books/index.html.erb -->  
<h1>Books List:</h1>  

Now, finally we can start up our server and see if any of this worked:

$ rails s

navigate to "localhost:3000" in your browser of choice:


Not very exciting, but the base is there. Let's begin with the pagination. If you've used will_paginate before, turning it into an infinite scroll is pretty simple.

Let's begin by going back to our books controller and paginate our Books by adding the following within our index action:

@books = Book.paginate(:page => params[:page], :per_page => 10)

Now let's display @books as well as our pagination controls on our index page. Under <h1>Books list:</h1> in our root page add the following:

<!-- app/views/books/index.html.erb -->  
      <th>Publication Date</th>
  <tbody id="book-list">
    <%= render @books %>

<div id="infinite-scrolling">  
  <%= will_paginate %>

And inside our <%= render @books %> we will add the following code

<!-- app/views/books/_book.html.erb -->  
<tr class="book-row">  
    <td><%= %></td>
    <td><%= book.title %></td>
    <td><%= %></td>
    <td><%= book.pub_date.to_time.strftime('%B %e, %Y') %></td>

Now we should see a batch of 10 books as well as our pagination controls.


Back in our console, we can create our javascript files to contain our jQuery:

$ touch app/views/books/index.js.erb
$ touch app/assets/javascripts/pagination.js

Inside our index.js.erb we can add our JS that will handle appending new sets of books to the table.

// index.js.erb
$('#book-list').append('<%= j render @books %>');
<% if @books.next_page %>  
  $('.pagination').replaceWith('<%= j will_paginate %>');
<% else %>  
<% end %>  

And our pagination.js script will handle our triggers:

// pagination.js
$(function() {
  if ($('#infinite-scrolling').size() > 0) {
    return $(window).on('scroll', function() {
      var more_posts_url;
      more_posts_url = $('.pagination .next_page').attr('href');
      if (more_posts_url && $(window).scrollTop() > $(document).height() - $(window).height() - 250) {

Note: You will have to play with this line specifically:

(more_posts_url && $(window).scrollTop() > $(document).height() - $(window).height() - 250)

And play with the 250 number to the point where it will continue to load more books. It's all dependent on what is one the screen and where it is. 250 works for me and this specific application.


Everything should work! In fact, the loading is so fast for me that the "Loading..." won't even display for more than half a second in this example because the loading is lightning quick. The only reason it is there currently because you will get some interesting results if you remove that. Namely, you will get repeated batches of the next "page" for as many times more_posts_url can be captured. If it's able to capture it eight times in your scroll then it will load the next set of ten eight times. replacing the html with "Loading..." prevents that from happening.

At this point you could style your table, or add in a spinner icon instead of a plain "Loading..." text element. Perhaps even add some bootstrap too.

I've added some bootstrap styling and a working demo here:

Demo here