company-logo

How to use will_paginate with non-ActiveRecord collection/array

will_paginate is very well designed plugin. Besides ActiveRecord object integration, it can integrate with array and any collection that you may have. The README.rdoc (in version 2.2.2) and wiki clearly and concisely document how to use it with ActiveRecord objects. I recently needed to use it for a collection outside of activerecord and here is how I did it.

My input params supplied the current page and the per_page. So, I had

current_page = params[:page]
per_page = params[:per_page] # could be configurable or fixed in your app

1) Fetch your collection:
In case of ActiveRecord object, we would do:

@posts = Post.paginate :page => params[:page], :per_page => 50

In case of non-ActiveRecord, the onus is still on us to fetch just the records that we need. If we can’t and we need to work with full collection, will_paginate doesn’t mind. So, here we go…

# Non-ActiveRecord Post
@posts = Post.perform_search_and_obtain_collection(params[:criteria])

2) IMP*: Create an instance of WillPaginate::Collection class.

@page_results = WillPaginate::Collection.create(current_page, per_page, @results.total_results) do |pager|
   pager.replace(@posts.to_array)
end

WillPaginate::Collection extends Array class and added properties like @current_page, @per_page, @total_entries etc. We pass the current_page, per_page properties in constructor and we replace the array contents by converting our @posts to an array (@posts.to_array). This is assuming that @posts contains only the collection for current-page. If @post contains _all_ results, then we can do following:

@page_results = WillPaginate::Collection.create(current_page, per_page, @results.total_results) do |pager|
  start = (current_page-1)*per_page # assuming current_page is 1 based.
  pager.replace(@posts.to_array[start, per_page])
end

If our search results were already an array, then it is even easier. All we do is:

@page_results = @posts.paginate(params[:current_page], params[:per_page])

Yes, the plugin adds a method “paginate” to Array class.

Once we have an instance of WillPaginate::Collection object, it behaves exactly same as the one we normally obtain from ActiveRecord.paginate() function. So, we can continue to do this in viewer:

<ol>
<% for post in @page_results -%>
<li>Render `results` in some nice way.</li>
<% end -%>
</ol>

<p>Now let's render us some pagination!</p>
<%= will_paginate @page_results %>

Also, I encourage you to peek into the source code. I found paginated_section that renders pagination stuff at the top/bottom of page.

<% paginated_section @posts do %>
<ol id="posts">
<% for post in @posts %>
<li> ... </li>
<% end %>
</ol>
<% end %>

Even the rendering is done using a pluggable class, incase if you want to render page links differently.

Related Services: Ruby on Rails Development, Custom Software Development

  1. Daniel Lucraft Reply

    Thanks this is exactly what I need.

  2. boblu Reply

    Thank you so much. I googled for a whole day, and eventually I found your post. And your method solved all the problems I need to tack! Cheer!

  3. Iain Reply

    Yeah I looked for hours before finding this. And it was just what I needed. Thanks alot

  4. shawn Reply

    Am I the only one getting the exception: Wrong number of arguments (2 for 1) trying this:

    @page_results = @posts.paginate(params[:current_page], params[:per_page])

    Thanks! :)

  5. shawn Reply

    Found it:

    http://mislav.uniqpath.com/static/will_paginate/doc/

    2.2.0, released 2008-04-07 API changes

    Remove old, deprecated style of calling Array#paginate as “paginate(page, per_page)”.

    If you want to specify :page, :per_page or :total_entries, use a parameter hash.

  6. s!ck Reply

    hey guys, nice post. i using rails to build a client which only consumes restful webservice with activeresource, so i deactivated activerecord. i’m getting an array of activeresource object limited by :page and :per_page an so on. if i try to call will_paginate @results in the view it gives me an NoMethodError. In the controller i’m using that WillPaginate::Collection.create(…) and it doesn’t complain. but if i activate activerecord everything works. how can i say will_paginate not to be dependent on activerecord :D do i really have to patch it myself?

  7. amit Reply

    Thanks works like a charm. I used the way you describe here

  8. Javix Reply

    I can’t figure out how to use the pagination with an existing array of objects got from has_many associations. So, I have Enterprise model and Service model. Enterprise has_many :services. I’d like to show services for every enterprise; In my ServicesController in index action I do:
    before_filter : find_client
    def index
    @services = @client.services
    end

    private
    def find_client
    @client = Client.find(:client_id)
    end
    How is it possible? If try to use like that:
    @services = @client.services.paginate :page => params[:page], :order => ‘created_at’, :per_page => 20

    I get:

    NoMethodError in Enterprises#show

    Showing app/views/enterprises/show.html.erb where line #21 raised:

    undefined method `total_pages’ for #

    Extracted source (around line #21):

    18: “/services/service”,
    19: :collection => @enterprise.services, :locals=> {:client => @enterprise} %>
    20:
    21:
    22:
    23:
    24: |

  9. Dida Reply

    Nice post! I would like to run the search query to multiple models. What I understand from this post, for each model I should create a method that do the research and return it as an array. And then collect the two arrays from the models and then paginate. This means the sql query is executed for every pagination. What if the query result is huge? What is the best way to do this?

    Thanks!

  10. Qza Reply

    u r The man. Thank you so much!

  11. Phyo Wai Win Reply

    Thanks. Very helpful to me.

  12. Matt C. Reply

    Thanks for this! Honestly didn’t know that will_paginate added a ‘paginate’ method to Array. Cheers.

  13. Greg Reply

    Great post. I should have looked at the comments sooner, as I spent a bit of time before realizing that the paginate method, when called from an array, needs the params specified in a hash.

Leave a Reply

*

captcha *