Rails 4: Paginating an Array from Subset of Active Record Results With will_paginate
I’m working on a rollout for a new CMS and bumped into this issue last minute that took a bit longer to track down an answer for than I thought it would so I’m adding it here in the hopes someone else stumbles into an answer quicker than I did.
The Scenario
You’ve loaded your result set and potentially cached it with Redis or some other key value store so you have access to it as an array rather than an ActiveRecord collection. You have the number of total entires, but can’t successfully paginate the array results because will_paginate seems to require you have the complete result set handy rather than a subset of it so it either doesn’t render your list items or if you leave off the current page number it renders but always assumes its page 1. Its assumed you’ve already figured out that you’ll need to add the following either in your controller or an initializer to make will_paginate work with array collections.
require 'will_paginate/array'
will_paginate expects the collection being paginated to contain the complete result set, and will populate the list of page numbers based on a combination of the current_page, per_page, and total_entries values. The issue with this is that if your array only contains the subset you want to display, it would represent the first X (X = # per_page) records in the array, and for all pages other than page 1 it would try to look into the chunk of the array corresponding to that page and find nothing (since you only have the subset you want to display) and your list would render as if it were an empty set.
If you leave out the current page in this situation, it defaults to 1 which means it will work since its looking for the first X records which would be the results you want to display, but in this context the viewhelper doesn’t know you’re not really on page 1 and will make every page render with page 1 as the current focus which makes the ‘Next’ and ‘Previous’ links essentially useless.
At first the only solution I could think of was to populate an array with enough elements to fool it into thinking the complete result set was present and then slicing the results into the right spot but with 100k articles potentially making up the results that’s a horrible solution.
After enough searching the real fix in the end is actually pretty simple and just required a paginate function to be added to my articles_controller.
def paginate(articles,page,limit,total_entries)
@articles = WillPaginate::Collection.create(page, limit, total_entries) do |pager|
pager.replace(articles)
end
end
This function accepts the subset of articles you want to display as an array, the current page number, the limit, and the total number of entries. Then rather than relying on the default constructor, it passes in the page, limit, and total_entries to give the viewhelper what it needs to work and then replaces the values of the current page with the array of articles you pass it, skipping the need for it to chunk through a massive array for the 20 or so articles you actually wanted.
Hope this helps someone :). And with that, back to the deployment.