This post originated from an RSS feed registered with Ruby Buzz
by Patrick Lenz.
Original Post: Fighting invalid page exceptions
Feed Title: poocs.net
Feed URL: http://feeds.feedburner.com/poocsnet
Feed Description: Personal weblog about free and open source software, personal development projects and random geek buzz.
The will_paginate plugin by Err The Blog feat. Mislav sure is one heck of a must have.
But there’s a tiny thing that’s been bugging me for a while. In December of last year (yeah, last year, been a while..) Mislav committed a change to the plugin that changed the behavior of WillPaginate::Collection to raise an exception if/when the page argument converted to an integer is smaller than 1 (which, quite naturally, any non-integer will eventually end up as).
This change was committed as an attempt to address the invalid SQL generation that happened before (you’d end up with something like LIMIT -10, 10 in the SQL given a non-integer page argument), so this is first and foremost a good thing.
If, however, the only thing that ever makes your page argument a non-integer is during those times where your app is attacked by weird spam bots trying to exploit your PHP (cough) application by shoving random strings down your URIs, things slowly start being annoying if you’re using the exception_notification plugin at the same time and end up with hundreds of exception notifications in your inbox (or, heaven forbid, tickets in your bug tracker) for the WillPaginate::InvalidPage exception on a URL like
So what’s an annoyed Ruby hacker to do? Work around it!
The global problem
You can surely go ahead and hack a rescue clause into all of your actions that make use of will_paginate but actually you really shouldn’t. You want these things handled globally.
Initially I was trying to go down the simple route, simply handling it via Rails 2.0’s rescue_from macro:
# app/controllers/application.rb
class ApplicationController < ActionController::Base
rescue_from WillPaginate::InvalidPage, :with => :invalid_page
protected
def invalid_page
render :text => 'Invalid page requested', :status => 400
end
end
But there are two problems with this approach.
First of all, I actually wanted to serve a real page instead of an error page. Occasionally someone will mistype a URL or someone will link off of his page erroneously omitting something important and I don’t want him or her looking at an error page. I’d much rather have him look at the first page of the collection he’d like to paginate, which should be closer to the page s/he actually requested.
The second problem is that the application in question isn’t running on Rails 2.0 yet. Yes, I know. Bear with me here.
Hacking the root of the problem
Instead of approaching the problem from the controller side I decided to work on it from the plugin’s perspective. I wanted a default of page = 1 unless a valid page was specified. So I ended up patching WillPaginate::Collection#initialize like this:
# config/initializers/will_paginate_extension.rb
WillPaginate::Collection.send :include, WillPaginate::CollectionExtension
module WillPaginate
module CollectionExtension
def self.included(base)
base.send :alias_method_chain, :initialize, :page_sanitizing
end
def initialize_with_page_sanitizing(page, per_page, total = nil)
page = 1 if page.to_i.zero?
initialize_without_page_sanitizing page, per_page, total
end
end
end
This will simply and quite effectively reset the page to view to 1 if anything that evaluates to a zero integer is passed in.
Okay, enough babbling for such a basic thing. Thanks for reading (if you actually got this far).