Recently I needed to add infinite scroll to a list view on a Rails app I’m working on. It turned out to be pretty straightforward, so here are the steps I took to get it working.

This tutorial assumes use of a default Rails 5.1.4 installation, with the addition of haml and jquery-rails. This means Sprockets, coffee, and turbolinks are in use. However most of the following code is trivial to convert if you aren’t using these helpers. I’m also referencing a Post model, but again, if you’re using a different class all you need to do is replace any reference to Post with your class.

You can view a demo app made using this tutorial here, and you can also clone it on github.

First you’ll need to add the will_paginate gem to your gemfile, and include the jquery.inview plugin (put this in the vendor/assets/javascripts folder).

Gemfile

gem 'will_paginate', '~> 3.1.0'
view raw Gemfile hosted with ❤ by GitHub

application.js

//= require jquery.inview.min.js
view raw application.js hosted with ❤ by GitHub

After that, add a per_page parameter to your model. For example, in post.rb:

class Post < ActiveRecord::Base
...
self.per_page = 10
...
end
view raw post.rb hosted with ❤ by GitHub

Then, in your controller, replace the query with a paginated query, and add format.js to the format response. I’ve included an example showing how to add pagination to the index view, and also how to add it to a scoped query. For example, in posts_controller.rb:

class PostsController < ApplicationController
def index
@posts = Post.paginate(:page => params[:page]) # replaces Post.all
respond_to do |format|
format.html
format.js # add this line for your js template
end
end
def home
@posts = Post.where(:user_id => current_user.id).paginate(:page => params[:page])
...
end
...
end

You’ll need a partial that renders your items. For simplicity, I’ve created a shared partial called _posts.html.haml in the shared folder:

- posts.each do |post|
.post
= post.content

Edit your template and add a Load More link. For example, in index.html.haml. We’ve included a test to see if there actually is a next page of content, if not – don’t display the Load More link.

.posts
= render :partial => 'shared/posts', :locals => { :posts => @posts }
= link_to 'Load More Posts', posts_path(:page => @posts.next_page), :class => 'load-more-posts', :remote => true if @posts.next_page
view raw index.html.haml hosted with ❤ by GitHub

Then create your js template, index.js.haml. Notice that the same next_page method is called on the posts to hide the Load More link when there’s no more content.

$('.posts').append('#{escape_javascript(render partial: "shared/posts", :locals => { :posts => @posts })}');
$('a.load-more-posts').attr('href', '#{posts_path page: @posts.next_page}');
- unless @posts.next_page
$('a.load-more-posts').remove();
view raw index.js.haml hosted with ❤ by GitHub

And finally add the javascript to load posts automatically when the Load More link is in view, in posts.coffee

# if you aren't using turbolinks, replace the following line with `$ ->`
$(document).on 'turbolinks:load', ->
loading_posts = false
$('a.load-more-posts').on 'inview', (e, visible) ->
return if loading_posts or not visible
loading_posts = true
$.getScript $(this).attr('href'), ->
loading_posts = false
view raw posts.js.coffee hosted with ❤ by GitHub

And you’ll be good to go! This is all you need to do to add pagination with infinite scroll. If you have any problems, check the demo app and see if you can find any differences.

If this post has helped you out, feel free to consider throwing a small donation my way.