Making Will Paginate work with Scope Out

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
  • Ted said: 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 :-(
  • Jack Danger said: 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.
  • Jack Danger said: 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.
  • Ted said: Thanks for the clarification :-)
  • Chris said: will_paginate and scope_out should now play nicely as of r326. Thanks for the post.
  • Jack Danger said: Thanks Chris!
  • Zubin said: 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)?).
blog comments powered by Disqus