Infinite Scrolling in Rails: The Basics

Ilya Bodrov-Krukowski


Pagination is a very common and widely-used navigation technique, and with good reason. First of all, consider performance. Loading all the available records in a single query can be very costly. Moreover, a user may be interested only in a couple of the most recent records (i.e., the latest posts in a blog) and does not want to wait for all records to load and render. Also, pagination makes reading the page easier by not flooding it with content.

Nowadays, many websites use a slightly different technique, called infinite scrolling (or endless page). Basically, as the user scrolls down the page, more records are loaded asynchronously using AJAX. In this manner, scrolling seems more natural and can be easier for a user than constantly clicking on a ‘Next page’ link.

In this article, I am going to explain how to implement infinite scrolling in place of classic pagination.

First, we will prepare our demo project, implementing basic pagination using the will_paginate gem. This pagination will become infinite scrolling as we work through the tutorial. This will require writing some JavaScript (and CoffeeScript) code, along side our Ruby.

The provided solution will fallback to the default pagination if a user has javascript disabled in the browser. Finally, our view will require virtually no modifications, so you can easily implement this on any website.

Other items that will be covered:

  • How to implement a “Load more” button instead of infinite scrolling, much like the one
    used on SitePoint.
  • Some gotchas and potential problems, specifically, how the History API and scroll spying can help us.

The working demo can be found at

The source code can be found on GitHub.

Sound good? Let’s get rolling!

Preparing the Project

For this article I will be using Rails 3.2.16 but you can implement the same solution with Rails 4.

Lets create a very simple blog. For now, it will only display demo posts.

$ rails new infinite_scrolling -T

-T here means that we want to skip generating a test suite (I prefer RSpec but of course you can omit this flag).

We are going to hook up some gems that will come in handy:


gem 'will_paginate', '~> 3.0.5'
gem 'betterlorem', '~> 0.1.2'
gem 'bootstrap-sass', '~>'
gem 'bootstrap-will_paginate', '~> 0.0.10'

will_paginate will, well, paginate our records. I will go into more detail about this gem in the next section. betterlorem generates demo text in our records. There are other similar gems that produce “Lorem Ipsum” text, but I’ve found this one to be the most convenient for our case (we are going to use it in seeds.rb, not in the view).

There is no point to create an award-winning design, so as a fast and easy (though not the smallest, considering the weight) solution we will use Twitter Bootstrap 3. The bootstrap-sass gem adds it into our Rails project. bootstrap-will_paginate contains some Bootstrap styling for the pagination itself.

Do not forget to run

$ bundle install

Now add

//= require bootstrap

to the application.js and

@import "bootstrap";

to the application.css.scss to include all the Bootstrap styles and scripts. Of course, in the real application you would choose only the required components.

The Model

There will be only one table: Post. It will be dead simple and contain the following

  • id (integer, primary key)
  • title (string)
  • body (text)
  • created_at (datetime)
  • updated_at (datetime)


$ rails g model Post title:string body:text
$ rake db:migrate

will create an appropriate migration and then apply it to the database.

The next step is to produce some test data. The easiest way to do this is to use seeds.rb.


50.times { |i| Post.create(title: "Post #{i}", body: BetterLorem.p(5, false, false)) }

This creates 50 posts with the body generated by BetterLorem. Each set of generated content consists of 5 paragraphs. The last two arguments tell BetterLorem to wrap the text in a p tag and include a trailing period.


$ rake db:seed

will populate our database with some test posts. Awesome!

The last thing is to create a PostsController with index and show methods, along with the corresponding views (index.html.erb and show.html.erb). Also, do not forget to set up the routes:


resources :posts, only: [:index, :show]
root to: 'posts#index'

Finally, if you’re using Rails 3, be sure to delete the public/index.html file.

The Controller

We are ready to move to the fun part. At first, let’s display some paginated posts with a truncated body. For this, we are going to use will_paginate – a simple yet convenient gem by Mislav Marohnić that works with Ruby on Rails, Sinatra, Merb, DataMapper and Sequel.

There is an alternative to this solution – kaminari by Akira Matsuda that is more powerful and more sophisticated. You can also give it a try. Basically, it doesn’t matter which gem you use.

In our controller:


@posts = Post.paginate(page: params[:page], per_page: 15).order('created_at DESC')

The call to the paginate method accepts a page option telling it which GET parameter to use to fetch the required page number. The per_page option specifies how many records should be displayed per page. The per_page option can be specified for the whole model or for the whole project like this:


class Post
  self.per_page = 10

will_paginate.rb (in an initializer)

WillPaginate.per_page = 10

The paginate method returns a an ActiveRecord::Relation so we can chain on a call to the order method.

The View


<div class="page-header">
  <h1>My posts</h1>

<div id="my-posts">
  <%= render @posts %>

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

A page header is specified with the help of a Bootstrap class. The next block, #my-posts, contains our paginated posts. Using render @posts displays each post from the array using the _post.html.erb partial. The last block, #infinite-scrolling, houses the pagination controls.

Note that will_paginate is clever enough to understand that we want to paginate @posts. You can specify it explicitly like this: will_paginate @posts.

Here is our post partial.


  <h2><%= link_to post.title, post_path(post) %></h2>

  <small><em><%= post.timestamp %></em></small>

  <p><%= truncate(strip_tags(post.body), length: 600) %></p>

We wrap every post with a div, then display the title that acts as a link to read the whole post. The timestamp indicates when the post was created. This timestamp method is defined inside the model like this:


def timestamp
  created_at.strftime('%d %B %Y %H:%M:%S')

Lastly we use the strip_tags method to remove all the tags from the post and truncate to strip all but the first 600 symbols. This ends our work with the views (I have omitted markup for the layout.html.erb and show.html.erb as it is not that important; you can check it in the GitHub repo).

Infinite Scrolling

We are ready to transform our pagination to infinite scrolling. jQuery will help us with this task.

Create a new file inside the javascripts directory.

jQuery ->
  if $('#infinite-scrolling').size() > 0
    $(window).on 'scroll', ->
      more_posts_url = $('.pagination .next_page a').attr('href')
        if more_posts_url && $(window).scrollTop() > $(document).height() - $(window).height() - 60
            $('.pagination').html('<img src="/assets/ajax-loader.gif" alt="Loading..." title="Loading..." />')
            $.getScript more_posts_url

Here we are binding a scroll event to the window, only if the pagination is present on the page. When the user scrolls, fetch the link to the next page – visiting it will make Rails load the records from that page (we still need to make some modifications to the controller to get this working).

Then, check that the URL is present and the user scrolled to the bottom of the page minus 60px. This is when we want to load more posts. This value of 60px is arbitrary, and you’ll probably want to change it for your case.

If these conditions are true we are replacing our pagination with a “loading” GIF image that can be freely downloaded at The last thing to do is to actually perform an asynchronous request using the URL that we’ve fetched previously. $.getScript will load a JS script from the server and then execute it.

Note the two return instructions. By default, CoffeeScript will return the last expression (the same concept applies to Ruby) but here we do not want the jQuery function or event handler to return anything so specifying return means “return nothing”.

The PostController#index method should respond to both HTML and JavaScript. We are going to use respond_to to achieve that:


@posts = Post.paginate(page: params[:page], per_page: 15).order('created_at DESC')
respond_to do |format|

The last thing to do is to create a view that will be rendered when responding with JS:


$('#my-posts').append('<%= j render @posts %>');
<% if @posts.next_page %>
  $('.pagination').replaceWith('<%= j will_paginate @posts %>');
<% else %>
<% end %>

We are rendering more posts by appending them to the #my-posts block. After that, check to see if there are more pages left. If so, replace the current pagination block (which at this point contains “loading” image) with a new pagination. Otherwise, we remove the pagination controls and unbind the scroll event from window, as there is no point in listening to it anymore.

At this point, our infinite scrolling is ready. Even if a user has JavaScript disabled in the browser, it will be presented with the default pagination that has some styling applied thanks to the bootstrap-will_paginate gem.

One thing worth mentioning is that scrolling will fire loads of scroll events. If you want to delay handling of this event you can use an OpenSource library BindWithDelay written by Brian Grinstead. To use it, simply download it and include it in the project. Then, make the following modifications to the script:

$(window).bindWithDelay 'scroll', ->
  # the code
, 100

This will delay firing the event by 100ms. $(window).off('scroll'); inside the index.js.erb will still unbind the event, so no modifications is needed there.

This ends the first part of the article. In the next part we are going to talk about the “Load more” button and some problems that arise when using infinite scrolling. Thanks for reading and see you soon!

CSS Master, 3rd Edition