Before I post about how to add fragment caching I’d like to share this, in case people haven’t seen it. I remember seeing this technique in Rick Olson’s code a while ago, so full credit to him.
ActiveRecord raises an RecordNotFound exception if it can’t find the database record with the ID you requested.
def show
@person = Person.find(params[:id])
end
If params[:id] doesn’t match a database record an ActiveRecord::NotFound exception will be raised. Using ActionController’s rescue_action_in_public method we can capture these exceptions and throw 404’s accordingly, and for me at least, this covers about 99% of use cases.
To do this application wide, add the following protected method to your ApplicationController.rb
def rescue_action_in_public(e)
if e.is_a? ActiveRecord::RecordNotFound
render :file => "#{RAILS_ROOT}/public/404.html",
:status => '404 Not Found'
else
super
end
end
In reality I usually move that render call out into a render_404 method, so you can handle HTML, XML and any other types of requests. It also allows you to call it from within the controller subclasses if needed.
def render_404
respond_to do |format|
format.html { render :file => "#{RAILS_ROOT}/public/404.html", :status => '404 Not Found' }
format.xml { render :nothing => true, :status => '404 Not Found' }
end
true
end
def rescue_action_in_public(e)
case e when ActiveRecord::RecordNotFound
render_404
else
super
end
end
To be a good little 404er with the above code all you need to do is ensure that your important database calls throw this exception, which brings me to an important point: as of Rails 1.1, the only type of find that raises a RecordNotFound exception is a find by id (read the first paragraph of documentation on the the find method). If you’re doing a different kind of find you have to handle it yourself:
def show
@tag = Tag.find_by_name(params[:name]) or raise ActiveRecord::RecordNotFound
end
The same thing applies (i.e. raising your own exception) with find(:all) and find(:first) as well.
If you want to throw a 404 directly you can just do:
def show
@tag = Tag.find_by_name(params[:name]) or (render_404 and return)
end
There’s discussion on the rails-core list about adding a find! method, which would make this approach even cleaner.
Related posts:
- How To Develop a jQuery Plugin Creating a jQuery plugin is easier than you might think....
- What Makes a Good 404 Page? What makes a good "page not found" page? Is the...







It’s brilliant! I must have been looking at the same code as you were because I found the article after I started ‘googling’ for more information on this technique.
Anyway, just a little tip/pointer for anyone who is thinking of using this:
– rescue_action_in_public is for requests answering false to local_request? (From the documentation). Meaning, if you’re trying to test this on your local machine, it will still render the default exception template with RecordNotFound info on it. Seems obvious once you know it, but it took me solid 10 minutes to figure out why I ‘didn’t seem to work on my machine’.
Replacing rescue_action_in_public with rescue_action will redirect/render on your local machine/dev environment also.
Cheers,
Ilya
September 2nd, 2006 at 1:29 pm
I love it when that happens!
Thanks for pointing out that rescue_action_in_public only happens in production. If you want to emulate it in dev you can define
rescue_action_locallyand call the other rescue, as follows:September 5th, 2006 at 12:12 am
And i was even using find(:first, :condition=>{… to make bogus find-operation _not_ raise an error but return false which i handled afterwards. Your method is so much more ellegant. Thank you, Tim.
November 20th, 2006 at 2:20 am
Hm… little problem: although i used “rescue_action” and everything works in dev mode my tests fail:
get :show, {:id=>42, :path=>”somepage.html”}
assert_response 404
gives me:
Couldn’t find Page with ID=42
Any idea?
November 20th, 2006 at 3:22 am
OK. Got it. At the beginning of the controller_test stubs there is this line:
# Re-raise errors caught by the controller.
class PageController; def rescue_action(e) raise e end; end
Of course i had to uncomment the redefinition of rescue_action to make the tests pass. Is there any bad side-effects in doing this?
More cheers, Niko. :)
November 21st, 2006 at 1:27 am
Rick Olson also has made use of the “save!” method through similar means. He’s the only guy I’ve seen actually use save! (which raises an error if the save fails) and have his rescue_action catch it. Very slick.
January 12th, 2007 at 4:26 pm
… i’m not so sure…
..that 404 is really adequate, after all “Page not found” is not the same as “Record not found”.
It may be ok in the case of a “show” action, but it can also occur in “update” and “destroy” actions where “Page not found” doesn’t make any sense whatsoever.
Think of a raise-condition where 2 people handling the same record and one person deletes the record and the other one trys to delete/update after and boom…
This maybe a rare situation but still mixing “Page not found” and “Record not found” feels wrong to me.
So where as the concept is a good one (putting the Rescue into the ApplicationController) the actual action taken seems to be the wrong one.
August 25th, 2007 at 8:00 pm
[...] from actions when you want to trigger a 404 error (as described here). [...]
July 25th, 2008 at 5:58 am