Hoopla!

now with extra whiz-bang!

Hoopla!

Making Will Paginate work with Scope Out

August 10, 2007 · 7 comments

Clearly I'm loving scope out. It's dramatically reduced the amount and complexity of code for my current app. Another plugin I've come to love is will_paginate because of the very Ruby-like way it allows paginating without any configuration.

Unfortunately these two plugins don't quite work together well. Like Rails itself will_paginate does a fine job of finding the records but is less reliable for counting them.

Example:


class Person < ActiveRecord::Base
  scope_out :women, :conditions => ["people.sex = ?", 'F']
end
35.times do
  Person.create(:sex => 'F')
end
14.times do
  Person.create(:sex => 'M')
end
@people = Person.paginate_women(:all, :page => 2)
@people.size # => 4
@people.total_entries # => 49

Whoops. That makes for some confusing pagination.

There's a few possibilities for fixing this. My favorite is to hope that Mislav can apply this patch. But in the meantime here's a quick way around it:


module WillPaginate
  module Finder
    module ClassMethods
      def wp_count_with_scope!(options, args, finder)
        if respond_to?(scoper = finder.sub(/^find/, 'with'))
            send(scoper) { wp_count_without_scope!(options, args, finder) }
        else
          wp_count_without_scope!(options, args, finder)
        end
      end
      alias_method_chain :wp_count!, :scope
    end
  end
end

Tags:·

7 responses so far ↓

  • 1 Ted // Aug 10, 2007 at 01:13 PM

    OK I've read it three times and I don't quite get it :-\ When you call "Person.paginate_women" what is scope_out (unpatched) doing behind the scenes? Looking for the method "find_women"? If so why isn't it finding it and why is it returning all the records? I'm lost :-(
  • 2 Jack Danger // Aug 11, 2007 at 12:41 AM

    You're really close Ted. Will Paginate translates paginate_women(:all) into find_women(:all) just fine. Then find_women(:all) triggers the scope_out method_missing that runs find(:all) inside the :women scope. And that process works fine. What's broken is that Will Paginate then calls plain old count() with the same conditions as passed to find - but it isn't executed inside a scope because it's not one of those magical methods that would get intercepted by scope_out's method_missing. So it can properly find all the right records but when it does it's counting query to come up with a total it's way, way off.
  • 3 Jack Danger // Aug 11, 2007 at 12:48 AM

    Wow. I even confused myself there. First of all – I just added Textile formatting to comments because these run on sentences of mine are bad enough.

    Second of all:

    paginate_women(:all) is turned into the following by will_paginate’s method_missing: find_women(:all) # which is turned into the following by scope_out’s method_missing: with_scope :find => {:conditions => ['people.sex = ?', 'F']} do find(:all) end But then to get a total number of entries to do the pagination links will_paginate calls: count(:all) which kills the Ruby magic.
  • 4 Ted // Aug 11, 2007 at 06:38 AM

    Thanks for the clarification :-)

  • 5 Chris // Aug 17, 2007 at 08:05 PM

    will_paginate and scope_out should now play nicely as of r326. Thanks for the post.

  • 6 Jack Danger // Aug 22, 2007 at 09:11 AM

    Thanks Chris!

  • 7 Zubin // Feb 03, 2008 at 01:35 AM

    They appear to be playing nicely until you add :per_page to the mix.

    eg this works: @pages = Page.paginate_all_approved :page => params[:page]

    but this doesn’t: @pages = Page.paginate_all_approved :page => params[:page], :per_page => 5

    The navigation links to non-existent pages (probably counting find(:all)?).

Leave a Comment