Topic: Paginating Collections

I hate pagination, it's always a big pain in the rump. Luckily Rails has helpers that make it almost effortless, for most situations. The paginate helper method will write SQL queries that offset and limit the result set. There's also the Paginator class that helps with the previous and next stuff.

The problem for me though is the extremely close ties to SQL that it imposes. I'm not complaining, it's a difficult problem to solve elegantly. But in order to filter the result set, you must pass in SQL through the :conditions parameter. I was fine with this until I had a query that joined four tables that needed pagination. I've gotten in the habit of encapsulating these types of queries in the Model, so I can re-use them.

Even if I wanted to repeat myself, I don't anticipate that this query will be very quick. The paginate helper does two queries, one to retrieve the result, and one to count the total number of records. The total is used to calculate the total number of pages for this result.

The alternative is to bring back the entire result set every time and chop it up in Ruby. Now I'm sure this makes some people cringe, but it really depends on the case. I'm searching through a maximum of 500 records. So the typical result set shouldn't be more that 100 records. The downside is obvious, but the upside is that I only have to do the query once, and now I can use any Collection as the result set.

So whether you like it or not I created a simple helper method that works similarly to paginate . In fact I was able to still use the Paginator class.

Here's the helper method. It can go in your application controller.

def paginate_collection(coll, options = {})
  # Grab params and fix em up
  per = options[:per_page].to_i
  page = (params[:page] || 1).to_i
  offset = (page - 1) * per

  # Create a Paginator based on the collection data
  pages = Paginator.new self, coll.size, per, page

  # Grab the sub-set of the collection
  paged = coll[offset..(offset + per - 1)]

  return [pages, paged]
end


As you can see the method returns the Paginator and the collection just like the paginate helper method.

@per_page = 20
@images = Image.search(q, @site, session[:current_user])
@image_pages, @images = paginate_collection @images, :per_page => @per_page

So the rest is just like before.

<%= link_to "Previous page", { :page => @image_pages.current.previous } if @image_pages.current.previous %>

<%= link_to "Next page", { :page => @image_pages.current.next } if @image_pages.current.next %>


I'm not super satisfied with this solution, but in my case I prefer it to the alternative. There are also cases where you may want to paginate through a collection that didn't even come from a database.

(orginally posted: http://garbageburrito.com/blog/entry/26)