Update: Click here for will_paginate 2.0
We all remember the story: guy says don’t use pagination, guy gets 700+ diggs, people yell at guy.
What they had missed was that he didn’t say there was something inherently wrong with pagination (we use it, they use it), but that the Rails pagination code is really bad.
So, Err’s gonna end your troubles by stealing some PHP code. The horror, but as the wise DHH once said:
“When something is too hard it means that you’re not cheating enough.”
Couldn’t agree more. Chowhound was in desperate need of a better pagination solution to help the search bots crawl its 400k topics with ease. I have cool features to build and this is a solved problem, so I go to Google.
30 seconds later, I find Digg-style Pagination. As I mentioned, it’s written in PHP, but there’s something truly statisfying when you take 120 lines of PHP and slim it down to around 40 in Ruby.
The gent even provided some starter CSS so it doesn’t look half-bad from the get-go.
Nice, here’s how it works:
app/models/post.rbclass Post < ActiveRecord::Base cattr_reader :per_page @@per_page = 50 end
app/controller/posts_controller.rb
def index @board = Board.find(params[:id]) @posts, @page = Post.paginate_all_by_board_id(@board.id, :page => params[:page]) endapp/views/posts/index.rhtml
<%= will_paginate(@board.post_count, Post.per_page) %>
Your views will paginate, the code says so.
Grab the plugin here: ./script/plugin install \ svn://errtheblog.com/svn/plugins/will_paginate
Inspect the code here: http://require.errtheblog.com/plugins/browser/will_paginate/
Update: K. Adam Christensen had the right idea moving cruft out of the controller and submitted a sweet addition to the plugin. Chris gave it a little bit of Err love, and I’ve updated the example above to show off how to properly exploit the wonders of this plugin. Ain’t open source grand?
Update #2: With the cleaner code, I skipped the obvious issue of calculating the page number twice. I’ve updated the plugin and the example above with the proper way. Thanks again to Adam.
Update #3: Who else wants on this gravy train? We just added a :per_page option thanks to Dr. Nic. Use it as such:
@posts, @page = Post.paginate_all_by_board_id(@board.id, :page => params[:page], :per_page => 100)
Update #4: This plugin got a whole lot more badass, check out how we’re paginatin’ again for the infos.
There is also the fantastic plugin, paginating find, available at http://cardboardrocket.com/ (not my site), which i used on my website for paginating.
A bit of association extension funnery leads to some very nice looking pagination code:
Very nice!
I spend a lot of time thinking about when to page, and what to do when it’s time to not page. I wrote about my findings in this article:
http://unspace.ca/discover/pageless
People are such hot-heads, sometimes.
PJ, your link at the end of the article doesn’t work.
Awesome…once again – I think this is the most useful rails blog.
Is that supposed to be ‘cattr_reader’ or ‘attr_reader’?
You guys slashdot’d our trac. It’s back up now. Sheesh.
Very nice, I came up with an almost exactly the same implementation a few months back, though I did integrate my adaption with paginating_find instead:
http://www.igvita.com/blog/2006/09/10/faster-pagination-in-rails/
Nathan, cattr_reader is correct. We just prefer using those over your standard constant.
Post.per_page is more pleasing to me than Post::PER_PAGE
I like this, but there is one problem: the controller is a little too fat for me. I wrote up something that would keep the controller a little leaner: http://pastie.caboo.se/38120
So now, you should only have to do: @ def index @board = Board.find(params[:id]) @posts = Post.paginate_all_by_board_id(@board.id, :page => params[:page]) end @
There is also room in it to allow for a :per_page key if required.
As a follow up, someone has built upon what I started and cleaned it up some: http://pastie.caboo.se/38132
In the README (and the example above), change:
to…
I realize now that you guys are the ones to clean up my code. Thanks.
One other thing is bothering me, I don’t like where @page has to be calculated. It seems that it’s being calculated twice, once in the controller and once in the model(finder.rb). So I was thinking, what if you did something like this?
Now the model’s method will return the collection and the page number that was calculated.
If that’s not the answer, then perhaps some other centralized area to calculate the page, a to_page_number method or something and then pass that calculated page to the @paginate method, like in Dr Nic’s README change.
Thanks again. This has come at the perfect time for me when I needed to add in some pagination to my current project.
Ok, probably not important as you’ve got this code in the finder:
Perhaps remove the params cleaner line from README etc then if not important.
While I’m on a roll (sarcasm)...
If we’re forcing the will_paginate helper to know what page its up to as a clean integer (via @page by default), then…
wait… I agree with what K-Adam said above.
Another nifty feature would be for the finder/paginate method to auto-calculate the total count using the find options.
If I’ve got a fancy:
Then I find myself creating @maps_count like:
And passing @maps_count to the helper.
Perhaps this “#{self.table_name.pluralize}_count”@ attribute could be autocreated by the paginator.
That looks pretty sweet.
As a follow up, someone has built upon what I started and cleaned it up some: http://pastie.caboo.se/38132
Looks great. Soo little code, such a big impact.
I myself am using the Paginating Find plugin with a view helper based on the one on Ilya Grigorik blog. I was wondering if this plugin is better still that Paginating Find in terms of performance.
You guys are keeping me honest. I updated the plugin so it’ll return the items and the page number.
Dr. Nic, I like the idea, let me noodle over it a bit.
Oh oh oh.
Can we pass :per_page option to paginate so per_page can be overridden from the controller?
Pastie: http://pastie.caboo.se/38371
Can’t… stop…
Allow more defaults for macro…
Because…
Looks cool :)
Could someone describe the Post.paginate_all_by_board_id method?
Cheers ;-)
“Could someone describe the Post.paginate_all_by_board_id method?”
Seconded! Where does this dynamic(?) method come from? I couldn’t find info on it.
(I like the look of this plugin very much!)
Oups, finally found it using small codes from various comments.
Controller:
The paginate method is defined in will_paginate/lib/finder.rb and was added after I made the initial post. In hindsight, I probably should have left the original example alone and just have added a snippet in the update.
It allows you to do anything the find method does:
Alex’s example above is how I originally had it, where I manually did the limit and offset in the controller, but the paginate method is too sweet to pass up.
Thanks PJ, and you learned me a new ruby trick with the “include WillPaginate” in Model, it made my day ;-)
Thanks for the clarification.
So (if i understand correctly) through the method missing handling in will_paginate/lib/finder.rb a call to
will result (essentially) in a call to and the extra options (:page, :per_page) are examined to set the offset and limit of the find query.smart stuff!
Can you talk a little about how this is better than the built-in Rails pagination? Some benchmarks perhaps?
Its appear that this plugin requires edge rails, im on 1.1.6 and ran into trouble. Would help if the blog entry is updated to reflect the same.
I did a similar approach a while ago:
http://anotherdan.com/2006/2/2/implement-pagination-in-your-models
I’m so glad you didn’t call it act_as_pages or similar! :)
The acts_as naming convention for plugins is dead, you heard it here first.
I liked the simplicity of the plugin and yet I couldn’t stand looking at the code for will_paginate helper. I made a rewrite that is a lot more Ruby-ish and makes the helper more configurable. No more ton of repetition and unreadable code. The above patch also changes the helper to show the link for “previous” at the beginning, not near the end.
Jake: See our newest post here for more on alias_method_chain. Or, check this pastie.
Hey, I am using both will_paginate and paginating_find plugins in two different applications… I like both, they are all awesome… but..
which one is better in terms of performance ? anyone has experience with both of them?
Is there any way I can send a collection of a previous query to the pagination??
Chris: thanks for the pastie…I get “undefined method `method_missing_with_will_paginate’ for class `ActiveRecord::Base” when I try that though
Where does @board.post_count come from? I’m getting the standard undefined method `xyz_count’ for # error.
I’m getting the same errors that Jake mentioned a few comments up. I’m not on rails 1.2 yet either, and I updated the finder. rb file to look like the pastie listed, but I get the same error as Jake still.
You have an outstanding good and well structured site. I enjoyed browsing through it.s
Thanks for the plugin, it’s pretty sweet and short.
Like Sam, I had problems with passing along params to the other pages.
I had to modify the link_to calls to something like:Unless I’m painfully (and shamefully!) wrong, I don’t see a way to specify or merge params into the links generated by will_paginate, so if you can add something like this into the plugin it would be awesome.
Pastie with the diff: http://pastie.caboo.se/48666
This plugin seems to be very interesting. But I don’t undertand how to use it with a complex request build with find_by_sql.
@records = Model.find_by_sql(My_very_complexe_sql_request) How to paginate these records ?
Small bug in will_paginate.rb missing the params.merge(:page => page) on the first 7 links when at the beginning of the pages.
diff for fix:why i have this error: undefined method `paginate_all_by_user_id’ for Topic:Class
i do as author said.
The will_paginate page link output seems to ignore any routes that exist on the page in which it was used.
IE: my url is /foo/blogs/ but the page links are pointing to the controller and action name instead. Any advice on how to correct? Thanks.
I too, need to pass url params (using pagination on named routes with special params) into the will_paginate method.
Advice appreciated!
Ignore me. This is working fine with named routing. Not sure what I was doing wrong before. Nice work, guys. ;)
Any tips/advice in using this plugin in combination with acts_as_ferret to paginate search results?
willpaginate remote so that you can call via Ajax. Somebody that knows more than me may be can do a better job with the code #
——- def will_paginate(total_count, per_page, to_update, action, page = @page) adjacents = 2 prev_page = page – 1 next_page = page + 1 last_page = (total_count / per_page.to_f).ceil lpm1 = last_page – 1 endI am new to rails and installed will_paginate but found an error
uninitialized constant controller_nameController::Board
Support for find_by_sql would be swell. I’ve modified the plugin to do this myself, but I’d prefer keeping plugins clean of my own filth..
I thought about find_by_sql and discovered tha it was tough because find_by_sql does not take *args as argument. All finder.rb does though is to find a method to run and return the output of running the method and the @page that you just passed as a second argument. In the case of find_by_sql you can run directly the method and assign the output to the array, you know the page so you can assign it to @page. Like this: @items=Item.find_by_sql(sql) @total_count=Item.find_by_sql(sql_count) @page=params[:page] the real contribution of this plugin I think is in will_paginate <= will_paginate(@total_count.to_i, ItemGroup.per_page’)> This way you do not have to mess with the plugin. It works with simple find and you do it differently with find_by_sql. Any way much nicer than the original Paginate plugin supplied with Rails.
Not sure what’s going on here but I installed this today (5/10/2007), via a piston import, and I’m getting the following error..
undefined method `slice’ for {}:Hash
the application trace looks like this…
{RAILS_ROOT}/vendor/plugins/will_paginate/lib/finder.rb:26:in `method_missing’
{RAILS_ROOT}/app/controllers/configurations_controller.rb:16:in `list’
the line in my controller is this…
@configs, @page = Configuration.paginate(:page => params[:page])
according to the README, this should work. But it’s trying to slice a hash. that’s not allowed. I’m using Rails 1.2.3 and Ruby 1.8.4. I checked if a more recent Ruby install has added the “slice” method but I haven’t seen if it has.
Any help is appreciated.
(e)
oops. sorry folks. should check my textile reference before using those RAILS_ROOT references.
Matte, i’ve had the same problem and i found, that in Rails 1.2.3 Hash#slice method is not available (i don’t know why). You have to add that method to yours rails app, here is a source: http://dev.rubyonrails.org/browser/trunk/activesupport/lib/active_support/core_ext/hash/slice.rb?rev=5726
Anyone knows what’s going on? Why that :order parameter is ommited and the other works?
Thanks in advance for the response!
Sometimes you want the the results in each page reversed. i.e: If you :order => ‘created_on desc’ and want the newest record to show first.
With this patch to the view helper, you can do:
Looks like a great implementation, I got the latest code from the site at http://require.errtheblog.com/plugins/browser/will_paginate
However, I am getting an undefined method ‘paginate’ error when I made the call to paginate, I am sure it is something simple. Any help will be much appreciated.
Getting this to work with ferret wasn’t bad. The only thing I really needed to do was alias the ferret finder to be compatible with the plugin. So I added this somewhere that gets loaded:
module FerretMixin module Acts module ARFerret module ClassMethods alias :find_all_by_contents :find_by_contents end end end end
This way you can now do something like this:
YourModel.paginate_by_contents(your_ferret_query, :total_entries => YourModel.total_hits(your_ferret_query), :page => options[:page])
If you have this error:
undefined method `paginate_XXXXX’ for XXXX:Class
All you have to do is restart the server to initialize the plugin.
Nate, I tried your mixin to Ferret and am unable to get it working. I receive the error:
undefined method ‘find_by_contents’ for module ‘FerretMixin::Acts::ARFerret::ClassMethods’
I’m not sure what the problem is because I do know AAF is being required and works as expected. I tried putting this mixin in both environment.rb and application_controller.rb to no avail. :(
Any advice is great appreciated getting will_paginate and acts_as_ferret to play nicely together!
Jason,
Hmm, no idea. It sounds like the mixin is loaded before acts_as_ferret is loaded so that find_by_contents doesn’t yet exist for that alias call.
I’m not all that handy with how rails loads plugins and such. All I know is that I have this mixin defined inside a file “my_app.rb” in the RAILS_ROOT/lib directory. Then at the bottom of my environment file I require that libary “require my_app”.
Does that change anything for you?
Jason,
you should change code mentioned by Nate to:
I’m VERY close to successfully making will_paginate work with acts_as_ferret. (Yes, I am using the latest will_paginate posted on a different thread)
This works as expected (YES!), however I have extra find_options to pass onto acts_as_ferret’s find_by_contents method.
AAF’s find_by_contents has this signature:And I’m wanting to pass :joins and :conditions as the find_options argument.
So I begin:
This behavior works fine with AAF, so I’m positive it has to do with the way will_paginate is intercepting the finder methods.
Is there a recommended way to make will_paginate pass this extra option to acts_as_ferret? I’m new to writing mixins so I thought I would ask here first.
Thanks!
I meant to mention, that the above call causes will_paginate to fail with ”:page parameter required”, so it’s obviously not liking this extra argument.
Nate,
it worked like a charm, except that you have to use instead of .Thanks, Robertas Aganauskas
Just a note to warn others that the method_missing magic here will get grumpy in an RSpec context – too much method missing, I suppose… I’m gonna change my copy of the plugin to have a hardcoded :paginate method – it can fall back on method missing still for paginate_all_by_foo_id but for the basic case, it’ll be basic. If my code doesn’t suck I’ll share it.
Thanks!
I am using this to paginate results from a search form, so the data paginated can be different every time. My intitial search works fine, but the controller no longer has access to the search parameters once the view submits back to change the page, and the query bombs out. Anyone know a way around this?
Excellent web site I will be visiting often
def index @board = Board.find(params[:id]) @posts, @page = Post.paginate_all_by_board_id(@board.id, :page => params[:page]) end
...i’m using only one table, can anyone explain me this code with one table? thanks!
Here’s a problem, if this returns only ONE record..
...this returns a CampaignMember object, rather than an array with one object inside of it. Not ideal.
(just a few conditions to decide whether to delete the object or not). This works well, but makes the pagination inaccurate as it removes items after the pagniator has done its magic on the find_by_contents method. A page with 5 results normally, might only show 4. If the last page had 1 result on it but this method deleted it then the page would be strangely empty etc…
Is there a way to filter the results before they’re paginated? I tried to do it inside the module but once the deleteif had run, the paginator complained that it couldn’t find total_hits in the array called result.
Your help would very much be appreciated :)
Just to clarify, the above filters aren’t able to be done via SQL or anything like that. They rely on being able to have the object in the applications context.
Whoops. I may have just posted the above to the wrong blog :) see: http://opensoul.org/2007/8/17/acts_as_ferret-will_paginate
in my opinion the way will_paginate handles its params is utterly annoying. All kinds of hoops to do if you want to build a layer on top. For example if you don’t know ahead of time if your find options will be empty, you have to conditionalize the call to paginate. And you can’t just pass the whole params array, because will_paginate will pass on all the params it doesn’t want to the find method which will complain about things it doesn’t recognize. paginating_find is less obtrusive and easier to build a layer on top of.
Impressed with the clean approach of this plugin, have not explored this plugin completely…but just had few questions popping up.
1) Can we provide a custom style to the pagination or we are forced to use the default css, I know we can edit the default css but then is there a way to pass a custom class.
2) For my pagination I dont want to use the text “next” and “previous” but images instead, is there a way to override these like we have in writing custom pagiantion
Hello, I have a question about will_paginate
Is it possible to paginate the hash’s values saved on session , for instance session[:my_list] ???
THANK YOU and Have a good 1!
does anyone have this working with acts_as_taggable_on_steroids ? I’m new and digging into the code is a bit overwhelming at this point.
I needed only the next and previous link tags for a recent project. I thought setting :inner_window and :outer_window to 0 or nil would do it but it didn’t. Here is what I ended up using if anyone else needs it.
Johnny, thanks for the code, verry usefull!
Hi,
I’m hitting a problem when using the :include option with paginate. It looks the include option is being ignored. Has anyone else tried this?
having trouble getting the code: svn://errtheblog.com/svn/plugins/will_paginate http://require.errtheblog.com/plugins/browser/will_paginate/
neither of these seem to work anymore =(
Hi,
jus a small problem with the helper api. when we do a will_paginate(@sales, :params => params) just dont’ work, because if u have a params[:page], it just masque the page from navigator.
one way to go : will_paginate(@sales, :params => params.clone.delete_if {|key,value| key == “page”} )
it should be better if will_paginate just delete himself the “page” in options params !
I got this error message: undefined method `wp_parse_options!’ for #
Is this method deprecated?
I need to perform fulltext search with acts_as_ferret and paginate it. I take the example from: http://www.mckinneystation.com/2007/08/20/pagination-with-acts_as_taggable_on_steroids-acts_as_ferret-and-will_paginate
Forget my previous comment, I fix it parsing the options manually.
Will paginate is so useful! Thanks! I know even use the merb port, merb_paginate!
Chime in.