Paginator with MongoMapper

A quick one here, but it took a few minutes to work out the best approach so I figured I could perhaps save someone else some time. I’ve been playing with MongoMapper and MongoDB (more on that later I’m sure) and after scaffolding a model I wanted to add pagination to the index action, since the list was loading a few hundred items. After a little searching in the MongoMapper group archives, it was clear that will_paginate wasn’t going to work, since it has some reliance on the ORM being used (though it does support both ActiveRecord and DataMapper). After spending a bit of time fruitlessly trying to get ActiveScaffold to work with MongoMapper, I didn’t want to try to do the same thing with will_paginate. So, someone in the MongoMapper group posted some helpful advice about using Paginator and I figured I would try that.

It turned out to be very easy indeed. After the usual gem install paginator I just had to a add a few lines to my controller:

  def index
    @pager = Paginator.new(MyModel.count, 20) do |offset, per_page|
      MyModel.find(:all, :offset => offset, :limit => per_page, :order => 'name asc, file_date asc')
    end
    @mythings = @pager.page(params[:page])

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @mythings }
    end
  end

This could pretty much be ActiveRecord, of course, since MongoMapper makes the finds look easy. A few things to note here… There’s the initial MyModel.count which determines the number of items so that Paginator knows how many pages it will take to show them all, based on our 20 items per page. That of course works because my find is for all; otherwise the count will have to be based on the actual query, of course. Caching the count would be smart, though with Mongo it’s pretty fast and my data set’s not going to be millions so it doesn’t worry me in this particular case. Something to think about, but in general providing a paginating interface for a massive data set isn’t very smart anyway.

The find is pretty straightforward, and then @mythings is filled with the items for the page. In the view, we do this:

<% @mythings.each do |thing| %>
  <tr>
    <td><%= thing.name %></td>
    <td>Etc...</td>
  </tr>
<% end %>
</table>

<br />

Page <%= @mythings.number %> -
<%= link_to("Prev", mythings_path(:page => @mythings.prev.number)) if @mythings.prev? %>
<%= link_to("Next", mythings_path(:page => @mythings.next.number)) if @mythings.next? %>
<br />

This should be clear enough — Paginator adds a couple of attributes to @mythings that can be used to display the current page number and determine whether there’s a previous or a next page for which a link should be shown.

And that’s it — simple pagination using MongoMapper or any other persistence layer you might want to use, since Paginator makes no assumptions about it. This means there’s a little more work to do yourself, but as you can see from the code above, not much.

Advertisements
5 comments
  1. Hi,

    thanks for the write-up. I just used that and it works great!

    I think I’ll experiment with will_paginate later on, as it provides a bit more functionality.

    thanks!

    — Thibaut

  2. Glad it was helpful! If you do get will_paginate to work, please share the info, as you’re right that it does provide more functionality. It seems much more tied in to ActiveRecord, though, so I don’t know how possible it will be. I spent some time looking at getting ActiveScaffold to work outside the ActiveRecord world, and it seems very difficult indeed, unfortunately.

  3. I should also mention that I’m using this same approach now with Redis, and it works great. The Redis “SORT” command provides the same offset/limit/order options, so it just fits right in.

    • That’s a great question, and I have to admit that I’m not sure! I don’t know if that’s new or if I simply missed it when I was looking into this, but from that info I’m not certain how it would fit in. Sorry I can’t answer the question directly!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: