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
Thanks for the solution to getting will_paginate and a_a_t_o_s to work together. You saved me a headache and a ton of time.
Comment by John — September 3, 2007 @ 9:07 pm
Thanks for this… But I cannot get will_paginate and a_a_t_o_s working… It paginates the first page right (and adds the links at the bottom) BUT when I hit ‘next’ or 2… I get this error:
undefined method `-’ for “2″:String
Which is coming from this line in taggable_pagination.rb:
offset = (page - 1) * per_page
I know what the error is saying but no clue how to fix it…
ALSO I want to pass in a condition, at the moment if I pass in a condition I get some kind of hash error…
I’m sure the way to do it is by changing the tagging_counts(tag) method (to something like tagging_counts(tag, second_variable) )…
Any ideas?
Comment by Coopr — September 13, 2007 @ 6:46 am
The correction should be:
offset = (page.to_i - 1) * per_page
because the page value is coming from the parameters passed in to the controller, which are always Strings.
Comment by Geoffrey — September 13, 2007 @ 9:36 am
Hmm I’m still getting the same error…
I have changed it to look like: offset = (page.to_i - 1) * per_page
But I still receive the error: undefined method `-’ for “2″:String
This is what my controller looks like:
@businesses = Business.paginate_by_tag params[:tag],
:order => ‘businesses.name DESC’,
:page => params[:page],
:per_page => 1
Any other thoughts?
Comment by Coopr — September 15, 2007 @ 11:12 pm
I tell lies…. Hahaha - it’s all working - thanks for the help.
Still wondering how to add conditions into a search, so I can do something like:
@businesses = Business.paginate_by_tag params[:tag],
:conditions => ['suburb LIKE ?', "%#{params[:location]}%”],
:order => ‘businesses.name DESC’,
:page => params[:page],
:per_page => 10
I may just have to do a paginate_by_sql - might be easier? Thanks in advance.
Comment by Coopr — September 16, 2007 @ 4:06 am
Thanks a lot !
you are great!
Comment by kritias — September 21, 2007 @ 6:53 pm
If you want to add conditions, you have to change the method a little bit
module ActsAsFerret
module ClassMethods
def paginate_search(query, options = {}, find_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, find_options)
returning WillPaginate::Collection.new(page, per_page, result.total_hits) do |pager|
pager.replace result
end
end
end
end
then, in your controller
@entries = Entry.paginate_search params[:query],
{:page => params[:page],
:per_page => 20},
{:conditions => some_condition}
Comment by Jd — September 24, 2007 @ 3:54 pm
Thanks for this, but I can’t make it work. It say:undefined method `wp_parse_options!’ for Article:Class. It seems the parameters can’t pass to the module in ‘ActsAsFerret’.
the code in the controller is:
@articles_result = Article.paginate_search( params{session[:keyword]},:page => params[:page], :per_page => 2).
I don’t know how to fix it. Thank you very much in advance.
Comment by Qi — October 4, 2007 @ 4:42 am
Forget what I send, the will_paginate plugin broken. Now it is working. Thanks for the solution
Comment by Qi — October 4, 2007 @ 6:06 am
thanks a lot!!
this article is a great help!
but I have a question
in paginate_by_tag, why you have to implement tagging_count?
you cant just do “count = find_tagged_with(tag).size” ?
Comment by kritias — November 1, 2007 @ 10:43 am
hello all
i need some help.
i was earlier using paginating_find and moved on to will_paginate now
i my view i am showing the total count of the ferret search results.
Search for “book” Produced Results
i have @products.size ————which failed
i tried @products.total_hits ——failed again
i made the result variable in AAF module as instance var and tried
@results.total_hits———-also failed
can anybody help me with this pls.
Jags
Everything from above worked for me
thanks a ton ……..thanks
Comment by jags — November 13, 2007 @ 9:32 am
hi all
how can i get the total_hits i.e the total no of results found for the search so that it can be displayed on the rhtml
pls check on my comment above for details
thanks
Comment by jags — November 16, 2007 @ 12:35 am
I include the code file by writing the following in environment.rb
require File.dirname(__FILE__) + ‘/rails_root/vendor/plugins/acts_as_ferret/lib/ferret_pagination.rb’
but I got errors, saying can’t find the method.
Can anyone help me?
Comment by Infinit — December 1, 2007 @ 11:21 am
If you saved the ferret_pagination.rb in the lib/ directory you should be able to do this in the config/environment.rb:
Rails::Initializer.run do |config|
…
end
…
# Include your application configuration below
require ‘ferret_pagination’
I believe it has to be after the Rail::Initializer so that everything else is loaded and in place. Hope this helps
Comment by Geoffrey — December 1, 2007 @ 10:15 pm
Another version, similar to Jd’s. For if you want conditions without act_as_ferret support (sounds complicated, it is!).
def paginate_by_tag(tag, options = {}, find_options = {})
page, per_page = wp_parse_options!(options)
offset = (page.to_i - 1) * per_page
find_options.merge!(:offset => offset, :limit => per_page.to_i)
items = find_tagged_with(tag, find_options)
count = tagging_counts(tag)
returning WillPaginate::Collection.new(page, per_page, count) do |p|
p.replace items
end
Thanks for the tips!
Comment by Luca — December 30, 2007 @ 1:27 pm
The most recent version of will _paginate has changed sufficiently to make the above examples break. Specifically the wp_parse_options! method no longer returns [options, page, per_page, total]. Intead it simply returns [page, per_page, total].
Comment by Francois — January 20, 2008 @ 8:44 pm
I adapted it for the 1 Feb 08 version of will_paginate, and adjusted the count to also consider conditions. I’m new at this so keen for any feedback - but the file I have in the lib folder is:
module ActiveRecord
module Acts #:nodoc:
module Taggable #:nodoc:
module SingletonMethods
# 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 = {})
page, per_page, total = wp_parse_options!(options)
if(options[:conditions].blank?)
counts = tag_counts(:conditions => sanitize_sql(”tags.name = ‘” + tag + “‘”))
else
counts = tag_counts(:conditions => sanitize_sql(options[:conditions] + ” and tags.name = ‘” + tag + “‘”))
end
count = 0
count = counts[0].count if counts.size == 1
offset = (page.to_i - 1) * per_page
options.merge!(:offset => offset, :limit => per_page.to_i)
items = find_tagged_with(tag, options)
returning WillPaginate::Collection.new(page, per_page, count) do |p|
p.replace items
end
end
end
end
end
end
Comment by Tim Haines — February 6, 2008 @ 7:47 pm
After having deleted ‘option’ in: “options, page, per_page = wp_parse_options!(options)” — comment of Francois — January 20, 2008 — rails finished croaking.
Now I wanted the field ‘lemma’ of my database (an old german dictionary) alphabetically sorted. Following http://www.railsenvy.com/2007/2/19/acts-as-ferret-tutorial
to rebuild the index, I finally wrote in (or copied into) the Controller:
sorter = Ferret::Search::SortField.new(:lemma_for_sort, :reverse => false)
@members = Member.paginate_search params[:query],
:page => params[:page] ,
:per_page => 25,
:sort => sorter
“lemma_for_sort” (in the tutorial “title_for_sort”): a method defined in the model - see tutorial.
Oh wonder, it works! But only thanks to the great help of Geoffrey and his commentators.
Comment by karl — February 10, 2008 @ 2:33 pm
I have included the following in my controller.
@entries = Entry.paginate_by_tag @tag.name,
:order => ‘entries.created_at DESC’,
:page => params[:page],
:per_page => 20
Now, how do I get to the next page? What’s the code in the view? Can anyone help me? Thanks!
Comment by marc — February 19, 2008 @ 10:35 am
Marc, I tried:
Result: something like
« Previous 1 2 3 4 5 6 7 8 9 10 11 Next »
Comment by Karl — February 21, 2008 @ 7:27 am
excuse me, typographic error, I try it again:
<%= will_paginate @members %>
Result: something like
« Previous 1 2 3 4 5 6 7 8 9 10 11 Next »
Comment by Karl — February 21, 2008 @ 7:39 am
Thanks! I shud have looked harder for it. =)
Comment by marc — February 21, 2008 @ 12:16 pm
I had to modify the taggable_paging fix a bit to make it work with the latest version of will_paginate. This solution will also accept all the same options as find_tagged_with.
Oh, and this drove me crazy… naming the method “paginate_by_tag” won’t work, because will_paginate, being too clever for its own good, will intercept the method_missing call before it gets to the module and look for find_all_by_tag. I renamed the method to _paginate_by_tag to get around this (I know there must be a more elegant solution).
module ActiveRecord
module Acts #:nodoc:
module Taggable #:nodoc:
module SingletonMethods
def count_tagged_with(*args)
options = find_options_for_find_tagged_with(*args)
options.blank? ? 0 : count(”#{table_name}.id”, options.merge(:select => nil, :distinct => true))
end
def _paginate_tagged_with(tags, options = {})
page, per_page = wp_parse_options!(options)
offset = (page.to_i - 1) * per_page
count = count_tagged_with(tags, options)
options.merge!(:offset => offset, :limit => per_page.to_i)
items = find_tagged_with(tags, options)
returning WillPaginate::Collection.new(page, per_page, count) do |p|
p.replace items
end
end
end
end
end
end
Comment by Jeremy — February 21, 2008 @ 2:48 pm
Woops, bit of a mistake there… the method here is called _paginate_tagged_with… will_paginate will send paginate_by_tag to find_all_by_tag, and paginate_tagged_with to find_tagged_with (which actually exists, but won’t get you proper pagination!).
Comment by Jeremy — February 21, 2008 @ 2:53 pm
Jeremy please be more specific, what should the name of your method be called?
Comment by Nathan — May 28, 2008 @ 10:34 am