Pagination with acts_as_taggable_on_steroids, acts_as_ferret, and will_paginate

20 Aug 2007
UPDATE (6/6/08): This is out of date for the latest Rails and will_paginate. So I needed to paginate large collection of data in a new app I am working on. will_paginate is a good drop in replacement for the the default rails paginator. But I am also using acts_as_taggable_on_steroids (for tagging) and acts_as_ferret (for searching), so i needed special pagination for those scenarios.

acts_as_ferret

A quick Google search led me to this for paginating acts_as_ferret search results. I modified the offset calculation and ended up with this:
module ActsAsFerret
  module ClassMethods
    def paginate_search(query, options = {})
      options, page, per_page = wp_parse_options!(options)
      offset = (page.to_i - 1) * per_page
      options.merge!(:offset => offset, :limit => per_page)
      result = result = find_by_contents(query, options)
      returning WillPaginate::Collection.new(page, per_page, result.total_hits) do |pager|
        pager.replace result
      end
    end
  end
end
Drop that in a file lib/ferret_pagination.rb, require it in you environment.rb, and you can now do this in your controller:
@entries = Entry.paginate_search params[:query], 
                                       :page => params[:page],
                                       :per_page => 20

acts_as_taggable (on steroids)

So with that out of the way, I was now ready to tackle paginating entries tagged with a certain tag. Another quick google search turned up some ideas in the will_paginate comments. I used this one as a starting point and this is what I ended up with:
module ActiveRecord
  module Acts #:nodoc:
    module Taggable #:nodoc:
      module SingletonMethods
        # Return the number of time this class has been tagged with this tag
        def tagging_counts(tag)
          count_by_sql("select count(*) FROM tags, taggings WHERE " + sanitize_sql(['tags.name = ? AND tags.id = taggings.tag_id AND taggings.taggable_type = ?', tag, name]))
        end
        
        # paginate a call to find_tagged_with
        # tag is the tag to find
        # options is the option to use for pagination (:page, :per_page) and for find_tagged_with
        def paginate_by_tag(tag, options = {})
          options, page, per_page = wp_parse_options!(options)
          offset = (page.to_i - 1) * per_page
          options.merge!(:offset => offset, :limit => per_page.to_i)
          items = find_tagged_with(tag, options)
          count = tagging_counts(tag)
          returning WillPaginate::Collection.new(page, per_page, count) do |p|
            p.replace items
          end
        end
      end
    end
  end
end
Again, drop that in a file lib/taggable_pagination.rb, require it in you environment.rb, and you can now do this in your controller:
@entries = Entry.paginate_by_tag @tag.name, 
                                     :order => 'entries.created_at DESC',
                                     :page => params[:page], 
                                     :per_page => 20

Thanks

Thanks to Brandon for posting the ferret pagination code, Jim for the acts as taggable pagination code, and PJ for the will_paginate code. UPDATED: Corrected problem noted in comments

blog comments powered by Disqus