BUILDING A SOCIAL NETWORKING
WEBSITE WITH RUBY ON RAILS

RailsSpace will_paginate

Posted over 2 years ago by Raul Parolari

The old pagination used in Rails 1.2.3 has been removed from the code in 2.0, but it is still supported via the plugin classic_pagination. However, there are good reasons to kiss her goodbye:

  • first, a practical one: the classic_pagination plugin exists just for temporary compatibility; there is no guarantee that it will be maintained.
  • second, the plugin forces the programmer to jump through some hoops: see listing 11.10 (or in the final version the method ‘paginate’ in application.rb), which, depending on the argument class, calls the plugin (‘super’) or does ‘manual pagination’; the writer’s comment (which aptly captures the absurdity of the situation) is: “Paginate by hand”.
  • third, it has issues of performances when the number of pages is large.
  • finally, a design opinion: this code is a bit uncharacteristic for Rails; the class chosen for Pagination is the ActionController, but the logic seems instead more related to ActiveRecord and ActionView (as pagination is a ‘presentation’ of ‘Model’ instances). The only advantage that I see in having this in the Controller is that the params method is available (while in a Model, it is not); so, a parameter such as ‘params[:page]’ does not need to be passed (in a scale from 0 to 10, weight=1 !).

There are several plugins that replace pagination; I examine here one of the most praised, will_paginate.

Let us start from the good news; give a look at the listing we just mentioned, 11.10 (method ‘paginate’ in application.rb): well, it just disappears! there is simply no need for it. It is remarkable that at the introduction of that method (start of chapter 11.1.4), the book comments:

community_controller.rb (listing 11.9)
# what we would really like to do is this:
def search
  if params[:q]
    ..
    @pages, @users = paginate(@users)
  end
end

Well, with will_paginate that wish becomes reality! if the collection on which we want to paginate has already been computed, eg in @users, we can just write:

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

And to paginate on a model we could write (example):

@specs = Spec.paginate(:page => curr_page, :per_page => 10)

But wait a moment: the old pagination returned two objects (see above, @pages, @users), one to manage the pagination, the other with the results for the current page; now, we have only one object, @specs in the example!

The answer is: indeed we have just one object, and it is a bit smarter; it knows both the result of the search and the one for the pagination! (removing the confusion around those 2 objects was such a gift!).

All right; let us now proceed methodically and show how the code in chapters 10&11 can be changed. First, delete the old plugin and install will_paginate (see http://rock.errtheblog.com/will_paginate):

Let’s start recoding a few methods; the new code is:

controllers/community_controller.rb, method index:
def index  
  @title = "Community"
  @letters = ("A".."Z").to_a
  if params[:id] 
    @initial  = params[:id] 
    curr_page = params[:page]
    specs  = Spec.search_on_last_name(@initial)
    users  = specs.collect { |spec| spec.user }
    @users = users.paginate(:page => curr_page, :per_page => 10)
    end
end

Here the pagination is done in the last step, on users (not any longer on specs). So, the search method invoked is just a model search method:

models/spec.rb:
# search specs based on last_name initial
def self.search_on_last_name(name_initial)
  find(:all, :conditions => ["last_name like ?", name_initial + '%'], 
             :order => "last_name, first_name")
end

Now, the partials which present the paginated users: first, the _user_table.rhtml (or _user_table.html.erb if you use the rails 2.0 naming convention; both are supported in 2.0); I show what changes from the old to the new version:

views/_user_table.rhtml; old code:
  <% if paginated? %>
    <tr>
      <td colspan="4" align="right">
      Pages: <%= pagination_links(@pages, :params => params) %>
    </td>
    </tr>
  <% end %>
views/_user_table.rhtml; new code:
  <tr>
    <td colspan="4" align="right">
      <%= will_paginate @users %>
    </td>
  </tr>

Finally, we have to recode the _result_summary.rhtml file, that displays the total number of results and the ones displayed in the page. Here I did not find really friendly methods (I mean methods that avoid the business of ‘offset +- 1’); so we will pretend that the friendly methods exist, and then we will add them to the plugin:

views/_result_table.rhtml:
<% if @users %>
  <p>
  Found <%= pluralize(@users.total_entries, "match") %>.
  <% if @users.paginated? %>
    Displaying users <%= @users.first_item %>–<%= @users.last_item %>.
  <% end %>
  </p>
<% end %>

Now we add to the plugin the new methods added, by reopening.. ehm, which is the will_paginate class? let’s ask Ruby! bring up the rails console:

ruby script/console
>> Spec.paginate(:all, :page => 1).class
>> WillPaginate::Collection

All right, so we need to reopen the WillPaginate::Collection class; we have learnt how to reopen a class in a recent post (on Session); thus, under the lib directory we write a file (that must be required) that will do:

lib/will_paginate_ext.rb:
class WillPaginate::Collection 

  def first_item; offset + 1; end

  def last_item ; first_item + length - 1; end

  def paginated?; total_entries > size; end 
end

[Note: in case anyone finds similar methods in will_paginate, let me know and I will edit this post. The point here is just to centralize the treacherous +-1 and >,>= operations].

All this work pays handsomely when we add the search for users across spec/faq/user (chapter 11). As mentioned at the beginning, the long and twisted method ‘paginate’ (listing 11.10) in application.rb disappears! we just need to change one call from the community controller in the action ‘search’:

views/controllers/community_controller.rb; old code:
  @pages, @users = paginate(@users)
controllers/community_controller.rb; new code:
  @users = @users.paginate(:page => params[:page], :per_page => 10)

This single call builds the paginated @users, that can be fed to the View search.html.erb (which calls the same partials that we have seen above). With one shot (ie, one line), we have solved the problem; nice!

And, to finish chapter 11, we port the pagination for the A/S/L search: the old code is:

views/controllers/community_controller.rb browse method; old code:
  specs = Spec.find_by_asl(params)
  @pages, @users = paginate(specs.collect { |spec| spec.user })
And the new code is:
views/controllers/community_controller.rb browse method
  specs = Spec.find_by_asl(params)
  users  = specs.collect { |spec| spec.user }
  @users = users.paginate(:page => params[:page], :per_page => 10)
Notice that we could concatenate statements, for example:
  specs  = Spec.find_by_asl(params)
  @users = specs.collect { |spec| spec.user }.paginate(:page => params[:page], :per_page => 10)

This is very much in vogue in Ruby/Rails as it is a compact notation, which eliminates intermediate local variables (and if you really want to impress your friends, you can even concatenate the 3 statements!).

Personally, I still like lines of code of about 80 characters (instead of 100, or 140 with the 3 lines concatenated; which is not unusual in Rails internal code!). In any case, the important is to understand all these types of notations.

Last observations:

  • All the code presented above was tested ok (using the web browser, and running the test suite).
  • RailsSpace uses pagination again later, in chapter 15, for posts in blogs; the modification should be easy, if you have read this post (so, we leave it as an exercise to the motivated reader :-).
  • Finally: for more on will_paginate, see the brilliant screencast from Ryan Bates at http://media.railscasts.com/videos/051_will_paginate.mov

Comments

There are 12 comments on this post.

John Miller
posted over 2 years ago

Thanks for detailing these changes. I have two questions:

  1. You say

“thus, under the lib directory we write a file (that must be required) that will do: lib/willpaginateext.rb:”

but I wonder where this file gets required? Searching around, I find app/helpers/application_helper.rb where ‘string’ and ‘object’ are required, so I added

require ‘willpaginateext’

Right? Then I see at the bottom of that page the ‘def paginate’ method; I assume that can be deleted?

  1. You say

“RailsSpace uses pagination again later, in chapter 15, for posts in blogs; the modification should be easy, if you have read this post (so, we leave it as an exercise to the motivated reader :-).”

However, I’m not sure we have the same idea about ‘easy’! My naive attempt at this is to look for ” throughout the site, and I find it in only one place:

/app/views/profile/_blog.html

Following your example, I removed the and corresponding statements, and changed

to:

Therefore, the entire file looks like:

  Post  of

  Posts &ndash; of

“posts/post”, :collection => @posts %>

Is this correct? And are there any other changes to make so that blog posts will be properly paginated?

Thanks!


John Miller
posted over 2 years ago

Trying again to get formatting:

Thanks for detailing these changes. I have two questions:

  1. You say

“thus, under the lib directory we write a file (that must be required) that will do: lib/willpaginateext.rb:”

but I wonder where this file gets required? Searching around, I find app/helpers/application_helper.rb where ‘string’ and ‘object’ are required, so I added

require ‘willpaginateext’

Right? Then I see at the bottom of that page the ‘def paginate’ method; I assume that can be deleted?

  1. You say

“RailsSpace uses pagination again later, in chapter 15, for posts in blogs; the modification should be easy, if you have read this post (so, we leave it as an exercise to the motivated reader :-).”

However, I’m not sure we have the same idea about ‘easy’! My naive attempt at this is to look for ” throughout the site, and I find it in only one place:

/app/views/profile/_blog.html

Following your example, I removed the and corresponding statements, and changed

to:

Therefore, the entire file looks like:

  Post  of

  Posts &ndash; of

“posts/post”, :collection => @posts %>

Is this correct? And are there any other changes to make so that blog posts will be properly paginated?

Thanks!


John Miller
posted over 2 years ago

Sorry, there are no instructions for getting any kind of formatting into these comments.


ike
posted over 2 years ago

Hey John,

i did add require ‘will_paginate_ext’ into helpers/application_helper.rb and deleted the paginated? method in there as well.

here is what needs to be done to make chapter 15 (posts in blogs) working with will_paginate:

  1. app/controllers/application.rb method makeprofilevars: change @pages, @posts = paginate(@blog.posts, :per_page => 3) into @posts = @blog.posts.paginate(:page => params[:page], :per_page => 3)

  2. app/controllers/post_controller.rb method index: change @pages, @posts = paginate(@blog.posts) into @posts = @blog.posts.paginate(:page => params[:page], :per_page => 4)

  3. app/views/posts/index.html.erb change

into

  1. app/views/profile/_blog.html.erb change

into

  1. app/views/profile/_blog.html.erb change

into

that’s basically all. tests and browsing works for me now.

Regards, Ike


ike
posted over 2 years ago

formatting really seems to be hard here ;-)

  1. app/views/posts/index.html.erb change \ into \

ike
posted over 2 years ago

… anyway.

just apply the same changes to views/posts/index.html.erb and views/profile/blog.html.erb, which have been made above in the post in _resulttable.rhtml.

Have a good one, Ike


Falk
posted over 2 years ago

@Raul Parolari

[Note: in case anyone finds similar methods in will_paginate, let me know and I will edit this post. The point here is just to centralize the treacherous +-1 and >,>= operations]

“willpaginate” offers the function “pageentries_info(collection)” to display a standard result summary.

API-Link:

http://rock.errtheblog.com/will_paginate/classes/WillPaginate/ViewHelpers.html

With the help of the function “pageentriesinfo(collection) you could insert:

for example, or use the given source code in the api to create a personalized version of the result summary.

Regards, Falk


Jason
posted over 2 years ago

Can someone post what changes need to be made to use willpaginate. So far the book has been great but i’m really having trouble getting willpaginate to wrok.

I added require ‘will_paginate’ and restarted my webrick sever. Then when I try to call the paginate function i get an error saying the function can not be found.


Chirag Patel
posted over 2 years ago

I bought the RailsSpace book and am trying to use the willpaginate update for Rails 2.0. I am having a ton of problems. Basically I am not getting any of the paging items to show up per the discussion above for my community. All I see is “Found . Displaying users –. ” (this is what is returned from my _resultsummary partial.

Per the text above my resultsummary.html.erb file reads as

Found .        
    Displaying users –.

This seems to be missing alot of info. Did I forgot something? How do I call the paginize? function. Do I even need it? I deleted the original one from my applicationhelper file. Any help would be much appreciated.

So far I love this book!

Chirag


David McGeen
posted over 2 years ago

In the resulttable.rhtml instead of

Found

Displaying users -

I found it much easier to use

inbetween and


David McGeen
posted over 2 years ago

Stupid thing got rid of the code.

Instead of what was there i used pageentriesinfo

documentation can be found here http://mislav.caboo.se/static/will_paginate/doc/


ryan
posted about 1 year ago

Where do I get that “search_on” method. Or where should it be?