Wolfmans Howlings

A programmers Blog about Programming solutions and a few other issues

Paginating acts_as_taggable with will_paginate

Posted by Jim Morris on Mon Jul 30 15:04:50 -0700 2007

A question I see asked a lot is how do I paginate acts_as_taggable (on steroids)?

I haven't seen any answers I liked, so I created my own, which I'm sure a few people won't like either ;) But it works for me (tm).

I use will_paginate, but this does not work with custom finds that plugins define themselves, as is the case with acts_as_taggable.

If you do use a custom find_by_sql you have to hit the database twice, once to find the total number of items and then the paginated find.

I have a situation where I generate a tag cloud with every page, and part of that tag cloud has already calculated the number of tags for each classification I use.

I combined this with the paginator gem to get myself pages without too many hits to the database.

The first thing I do is pass the total count I get from the tag cloud to the action that renders the index for all those items matching the cloud... If you look at the article cited above I make this modification, this is dumbed down a bit for the sake of simplicity...

This passes the count I have already calculated to the action that will list the paginated results.

In my controller I do this to get the paginated results using will_paginate and the Pagination gem...

This fits in nicely with the tag cloud I need to calculate, and it uses will_paginate just like the regular index action does.

UPDATE

I refactored this to be more generally useful, I added the following as a protected method in application.rb...

I call it from one of my other actions like this...

@faqs= tag_paginator(Post, 'FAQ', nil, per_page, params[:page], 'updated_at DESC')

Passing in nil as the third parameter causes the tag_paginator method to call Post.count_tags which is not part of the acts_as_taggable methods, I added it to the SingletonMethods module myself...

If you don't want to hack acts_as_taggable then simply leave that part out and call the count_by_sql yourself.

My refactored tagged action from above now looks like this...

UPDATE Johns suggestion works as of today (2/12/2009) so none of the above is needed...

  options = Car.find_options_for_tagged_with(params[:tag_name]).merge :page => params[:page] @cars = Car.paginate(options)

or my example from above...

 opts= Faq.find_options_for_tagged_with('FAQ')
 @faqs= Faq.paginator(opts.merge(:page => params[:page]))

Posted in Rails  |  Tags acts_as_taggable,will_paginate  |  20 comments

Comments

  1. Eric said on Wed Aug 15 02:30:32 -0700 2007
    I found this working with `acts_as_taggable` and `will_paginate`.

    `@events = Event.paginate_by_id(Event.find_tagged_with(params[:id]), :page => params[:page])`

    Maybe there is a more graceful way.
  2. John Wong said on Wed Sep 12 01:51:22 -0700 2007
    Hey man.... seriously .. u dont have to put so much work just to get it to work.
    Just use `acts_as_taggable_on_sterioids` ...

    And use 2 simple lines

         options = Car.find_options_for_tagged_with(params[:tag_name]).merge :page => params[:page]
        @cars = Car.paginate(options)

  3. Dave said on Fri Oct 19 17:03:59 -0700 2007
    This works unless you have `:match_all` => true in there. Once you do that, a group by is added that causes `will_paginate` to get a MySQL syntax error. Still working on a way to fix it...
  4. Aaron H. said on Mon Nov 26 18:09:51 -0800 2007
    Dave,
    I found this article while having the same problem.

    The SQL error when using :match\_all => true has to do with the will\_paginate trying to count via the select created by acts\_as\_taggable. My solution was to add the following line to the beginning of the wp\_count! function in finder.rb of will\_paginate:

    `options[:select].sub!(/\.\*/, '\1.id') if options[:select] #count by id's, not wildcard (\*)`

    This will work unless the table you are counting does not have an id column, but using active record that should be extremely rare. If that's the case, you could always have it find the name of the class's key field.
  5. wolfmanjm said on Tue Nov 27 18:17:06 -0800 2007
    the match_all option doesn't work on postgresql anyway, I found a different workaround that uses a subselect to find the ids, then looks up using IN (ids,..) if anyone needs it let me know and I'll post it.
  6. goodwill said on Sat Jan 05 01:37:06 -0800 2008
    I tried John Wong's comment but seems not working... (Dave u are referring to this right?)

    I really think if such way would work its very clean.
  7. Henry said on Sat Jan 05 14:36:06 -0800 2008
    i had an sql error using John's comment. Aaron's solution seems to work though.
  8. P jam aka the nerb said on Thu Mar 27 04:34:31 -0700 2008
    Eric, a whole rails version later and using steroids, and still your 'eloquent' code snippet works great for me.

    since i'm trying to keep my plugins as natural as possible, i thank you. oh and you too dave.
  9. P jam aka the nerb said on Thu Mar 27 04:39:47 -0700 2008
    errr, dave = jim, wolfman, whathaveyou.
  10. COP said on Wed Apr 23 01:09:37 -0700 2008
    yes Eric's solution works for me too... one line thats it. I M LOVING RoR!!!
  11. rohit /pathakorama1@gmail.com said on Tue Sep 30 23:30:10 -0700 2008
    hi sir ,could you help me please
    i am using both the plugin will paginate and acts as taggble steroids)
    i have the model Article which is acts_as_taggable
    when i try to save the article withs tags it give me error
    stack level too deep
    vendor/plugins/will paginate/lib/will_paginate/finder.rb:163:in `method_missing_without_paginate'
    vendor/plugins/will paginate/lib/will_paginate/finder.rb:164:in `method_missing'
    vendor/plugins/acts_as_taggable/lib/acts_as_taggable.rb:172:in `save_tags'
  12. Flavio Granero said on Wed Nov 12 17:46:52 -0800 2008
    Hey Guys, to make the John code works, just change the method name `find_options_for_tagged_with` by `find_options_for_find_tagged_with`.
  13. samotage said on Thu Feb 26 16:58:12 -0800 2009
    Hey,

    I banged away at this for a while, then it occured to me there has to be a simpler way?

    So I paginated the results of the find tagged with, and bingo!

    @contacts = Contact.find_tagged_with(params[:tag]).paginate(:page => params[:page], :per_page => 15)

    Hope this helps,

    Sam.
  14. Rob said on Thu Apr 02 12:44:57 -0700 2009
    @samotage - What you propose works well but judging by the SQL in my logs, it looks like it is still pulling all the records for each page? I don't see any LIMIT in the SQL statements when using .find().paginate(). In other words, on the front end, the pagination helper is giving the correct number of pages and each page is showing the correct records. But it looks like ActiveRecord is fetching the entire unpaged recordset for each page. I'm new to Ruby and Rails so maybe I'm just missing something.
  15. James Rissler said on Thu Apr 09 10:16:43 -0700 2009
    I found Eric's method to work best. No need to change either plug-in. Worked for me with the latest version of rails + plugins.
  16. Gudata said on Thu Jun 04 09:45:21 -0700 2009
    Eric solution is working perfect for me

    @events = Event.paginate_by_id(Event.find_tagged_with(params[:id]), :page => params[:page])

    10x!
  17. 司徒正美 said on Sun Jun 28 21:53:12 -0700 2009
    Thank you,John Wong!You're a genius!

      def tag
        options = Lemma.find_options_for_find_tagged_with(params[:id]).merge(:page => params[:page] ||1,:per_page =>3 )
        @lemmas = Lemma.paginate(options)
      end
  18. 司徒正美 said on Sun Jun 28 22:01:00 -0700 2009
    my platform:<br/>
    rails2.3.2<br/>
    windowxp<br/>
    will_paginate 2.3.11<br/>
    acts_as_taggable_on_steroids1.1
  19. kayen said on Sun Sep 06 21:19:43 -0700 2009
    @ wolfmanjm

    How did you get the match_all working in postgres? I'm also stuck at the same issue and would like to see your take on it!

    Thanks.
  20. wolfmanjm said on Sun Sep 06 21:33:06 -0700 2009
    That was almost 2 years ago :) I'll have to do some research to see how I did it.. I'll get back to you.

(leave email »)