.

Coffee Powered

code and content

WillPaginate and custom paging.

will_paginate is the de facto Rails paging plugin, and with good reason – it’s solid, fast, and reliable. Everyone I know uses it, but a lot of people don’t use it to its full power.

I recently discovered some very cool functionality it includes – the WillPaginate::Collection class can be used as a custom paginator for effectively any enumerable collection. It’s very simple, too. I recently used it to build pages of the most popular tags on posts in my database. My data store is MongoDB, and I’m fetching an array consisting of two-element arrays, [tag, tag_count]. To use will_paginate’s functionality with this, I just use the following:

tags = Post.tag_counts(nil, {:sort => ["value", "descending"]}) # Return an array of tag/count pairs. Custom function, so it can't leverage the finder on Post.
@topics = WillPaginate::Collection.create(current_page, 20, tags.length) do |pager|
	pager.replace(tags.slice(pager.offset, pager.offset + pager.per_page))
end

current_page is a helper that derives the current page from the request parameters. The rest of it is self-explanitory. I can now use @topics in my page just as I’d use a paginated result set from the database.

- @topics.each do |topic|
    # ...
=will_paginate @topics

Bam. Doesn’t get much easier than that. You can get exceptionally creative with it, too. Effectively, all you need to know is:

  • WillPaginate::Collection#new takes 3 parameters: the current page, the per-page count, and optionally, the total number of entries.
  • The pager block variable exposes offset and per_page properties, prime for passing into a DB query or slicing an enumerable with
  • Call pager.replace(sub-array) with the current page’s set of elements.

That’s literally all there is to it. Now you can have easy pagination on just about any collection you can conceive of. Let WillPaginate handle all the heavy lifting and such. If you’ve done enough pagination by hand, you’ll probably appreciate the easy beauty of this particular method.

  • laura

    Hi Chris,

    Thanks for the great post! I’m trying to figure out how to paginate a custom find method with will_paginate and yours is the only entry I’ve found that seemed to touch on the topic.

    Unfortunately I’m a beginner to both rails and programming in general and I can’t seem to apply your sample to my code properly. I realize this isn’t a forum so it’s probably not the proper place to ask for help, but if possible, do you think you could give me some advice?

    Sorry for the strange request, but if you could point me in the right direction, I’d really appreciate it!

    Thanks,

    Laura

    recipes_controller:

    @title = “Browse”

    return if params[:commit].nil?

    @results = Recipe.find_by_browse(params)

    recipe.rb(model):

    def self.find_by_browse(params)

    @user =User.find_by_login(params[:login]) unless params[:login].blank?

    if @user.nil?

    params[:user_id] = nil

    else

    params[:user_id] = @user.id.to_s

    end

    where = []

    where << "character_id = :character_id" unless params[:character_id].blank?

    where << "category_id = :category_id" unless params[:category_id].blank?

    where << "user_id = :user_id" unless params[:login].blank?

    # where << "ingredient_name = :ingredient_name" unless params[:ingredient].blank?

    where < [where.join(" AND "), params],

    :order => “created_at DESC”)

    end

    end