Currently our news feed just loads every post at once. This will become unmanageable over time. We’re first going to implement pagination with a gem called will_paginate, then with a bit of Javascript, we’ll turn that into an infinitely-scrolling feed that loads posts asynchronously as you scroll down.

Pagination

Again, I’m simply referring to the will_paginate github repo to set this up. Add the new gem to your gemfile:

### Gemfile

...
gem 'will_paginate'

and run bundle install.

Now we need to change instances of @posts = Post.all in our controller to @posts = Post.paginate(page: params[:page]). If you press cmd+F in your Cloud9 editor you can automatically replace all instances of Post.all. Do this in the posts controller.

Replace

Add <%= will_paginate @posts %> to the bottom of the newsfeed — this renders a page selector.

### app/views/shared/_feed.html.erb

...
<%= will_paginate @posts %>

We also need to change users#show:

### app/views/posts_controller.rb

...
  def show
    @user = User.find(params[:id])
    @post = Post.new
    @posts = Post.where("user_id = ? or receiver_id = ?", @user.id, @user.id).paginate(page: params[:page])
  end
...

The last step is to specify in our Post model how many posts there are per page.

### app/models/post.rb

class Post < ActiveRecord::Base
  self.per_page = 10
...

Now restart your server and create more than 10 posts. You’ll see a page navigator appear at the bottom.

Pagination

Infinite Scroll

We need to write some javascript that will grab the next page of pagination results when we scroll to the bottom of our newsfeed. Create the file app/assets/javascript/infinite_scroll.js:

### app/assets/javascript/infinite_scroll.js

$(document).on('ready page:load', function () {
  var isLoading = false;
  if ($('#infinite-scrolling').size() > 0) {
    $(window).on('scroll', function() {
      var more_posts_url = $('.pagination a.next_page').attr('href');
      if (!isLoading && more_posts_url && $(window).scrollTop() > $(document).height() - $(window).height() - 60) {
        isLoading = true;
        $.getScript(more_posts_url).done(function (data,textStatus,jqxhr) {
          isLoading = false;
        }).fail(function() {
          isLoading = false;
        });
      }
    });
  }
});

In our app/shared/_feed.html.erb replace <%= will_paginate @posts %> with the following lines:

### app/views/shared/_feed.html.erb

...
<div id="infinite-scrolling">
  <%= will_paginate @posts %>
</div>

So what’s going to happen is when we scroll down to the bottom of our page, our javascript is going to request the next page link for some javascript instead of the usual HTML, using the $.getScript more_posts_url jQuery method. We need to show our controllers how to respond to requests for javascript. There are two actions which have the feed on them, and therefore will be responding to these requests: posts#index and users#show we’ll create a shared javascript file that they both respond with when required.

First, we need a partial view that can render individual posts. Create the file app/views/posts/_post.html.erb. With the name of a model, this file has a special meaning in rails; if we call render @post , rails automatically uses this file. If we call render @posts, it’ll just render _post.html.erb once for each post.

Fill app/views/posts/_post.html.erb with the following code (which is straight from our app/views/shared/_feed.html.erb file):

### app/views/posts/_post.html.erb

<div class="panel panel-default">
  <div class="panel-body">
    <h4 style="margin-top: 0px;">
      <a href="<%=user_path(post.user)%>"><%= post.user.name %></a>
      <% if post.receiver_id %>
        > <a href="<%=user_path(post.receiver)%>"><%= post.receiver.name %></a>
      <% end %>
      <% if current_user == post.user %>
        | <%= link_to 'Delete', delete_post_path(post.id), method: :delete, data: { confirm: 'Are you sure?' } %>
      <% end %>
    </h4>
    <% if post.image.url != '/images/original/missing.png' %>
      <div align="center" style="margin-bottom: 1em;">
        <%= image_tag post.image.url, style: "max-width: 100%; max-height: 500px;" %>
      </div>
    <% end %>
    <%= post.message %>
  </div>
</div>

We can refactor app/view/shared/_feed.html.erb and make it a lot cleaner now:

### app/views/shared/_feed.html.erb

<% if current_user %>
    <div class="panel panel-default">
...
    </div>
<% end %>

<div id="feed">
    <%= render @posts %>
</div>

<div id="infinite-scrolling">
  <%= will_paginate @posts %>
</div>

Create the file app/views/shared/post_page.js.erb:

### app/views/shared/post_page.js.erb

$('#feed').append('<%= j render @posts %>');
<% if @posts.next_page %>
  $('.pagination').replaceWith('<%= j will_paginate @posts %>');
<% else %>
  $(window).off('scroll');
  $('.pagination').remove();
<% end %>

When we scroll to the bottom of our feed, the javascript we wrote previously (app/assets/javascript/infinite_scroll.js) is triggered and it simulates a click of the will_paginate next page button, requesting javascript instead of HTML. We’ll set up our controller to render this javascript view (app/views/shared/post_page.js.erb), which appends the new posts to our feed, and replaces the will_paginate page selector with a new one if there are more pages to load, otherwise it removes it.

The j render is important because it escapes things like quotes and brackets that would otherwise interfere with our javascript. In javascript, you escape characters in a string with a backslash. For example, " \" " is a valid string containing a quotation mark surrounded by two spaces.

Finally our controllers. Starting with posts#index:

### app/controllers/posts_controller.rb

...
  def index
    @post = Post.new
    @posts = Post.paginate(page: params[:page])
    respond_to do |format|
      format.html
      format.js { render 'shared/post_page' }
    end
  end
...

The respond_to controller method in rails is like a switch statement; here if the request is asking for HTML, it performs the default behaviour (rendering app/views/posts/index) because we haven’t passed it a block. Is the request is asking for javascript, we’ve told our controller to actually render a different file than it would normally try to.

The same goes for users#show:

### app/controllers/users_controller.rb

...
  def show
    @user = User.find(params[:id])
    @post = Post.new
    @posts = Post.where("user_id = ? or receiver_id = ?", @user.id, @user.id).paginate(page: params[:page])
    respond_to do |format|
      format.html
      format.js { render 'shared/post_page' }
    end
  end
...

And that’s it. We now have infinite scroll.

This is the end of my course so far. I’m planning on adding more chapters in the future, and maybe releasing a whole new module, building another app. Let me know how you found things at my twitter @alexandpaterson.