12

I want to catch unknown action error in Rails 3, that shows "Unknown Action" error on development and the 404.html on production. I tried putting this rescue_from handler on my ApplicationController (and also on an actual controller, just in case) but I still see the ugly error.

I have custom stuff on the 404, and it can't be plain .html file.

My route:

match '/user/:id/:action', controller: 'users'

The URL I'm accessing: /user/elado/xxx

The rescue_from code:

rescue_from AbstractController::ActionNotFound, :with => :action_not_found

def action_not_found
  render text: "action_not_found"
end

The error in the browser:

Unknown action

The action 'xxx' could not be found for UsersController

And in the console:

Started GET "/user/elado/xxx" for 127.0.0.1 at 2011-09-07 19:16:27 -0700

AbstractController::ActionNotFound (The action 'xxx' could not be found for UsersController):

Tried also rescue_from ActionController::UnknownAction.

Any suggestions? Thanks!

elado
  • 8,510
  • 9
  • 51
  • 60

4 Answers4

14

rescue_from was slightly broken when Rails 3 came out (still broken in 3.1 too). Basically you can't:

rescue_from ActionController::RoutingError

anymore. See here.

The solution, for now, is what hamiltop recommends. Use a catch all route that goes to your "routing error" route. Make sure you put it at the end of your config\routes.rb file so it is processed last.

# Any routes that aren't defined above here go to the 404
match "*a", :to => "application#routing_error"

def routing_error
    render "404", :status => 404
end

Note: This method has one major drawback. If you use an engine such as Jammit or Devise the catch all will route will make Rails ignore the engine's routes.

If you aren't using an engine that has it's own routes then you should be fine. However, if you do use an engine that defines its own routes see @arikfr's answer.

Seth Jackson
  • 539
  • 4
  • 16
  • The following link provides some good insight, also it gives an example of how to better handle exception (3.2 or higher). http://geekmonkey.org/articles/29-exception-applications-in-rails-3-2 – Agustin Jun 09 '13 at 19:11
9

Using a catch all route to handle 404 erros (as @Seth Jackson suggested) has one major drawback: if you use any Rails engines that define their own routes (such as Jammit) their routes will be ignored.

Better and more compliant solution would be to use a Rack middleware that will catch 404 errors. In one of my projects, I've implemented such Rack middleware that reports these errors to Hoptoad. I've based my implementation on this one: https://github.com/vidibus/vidibus-routing_error, but instead of invoking my Rails app again to handle the 404 error, I do it in the Rack middleware and let nginx to show the 404 page.

arikfr
  • 3,311
  • 1
  • 25
  • 28
  • Nice catch. I had forgotten about this. I've notice this with other engines (Devise) also. I'll add a note about this to my answer. – Seth Jackson Sep 08 '11 at 14:02
2

If you really want to rescue AbstractController::ActionNotFound in a controller, you can try something like this:

class UsersController < ApplicationController

  private

  def process(action, *args)
    super
  rescue AbstractController::ActionNotFound
    respond_to do |format|
      format.html { render :404, status: :not_found }
      format.all { render nothing: true, status: :not_found }
    end
  end


  public

  # actions must not be private

end

This overrides the process method of AbstractController::Base that raises AbstractController::ActionNotFound (see source).

user1003545
  • 2,078
  • 1
  • 15
  • 15
0

Have you tried a catch all route?

http://railscasts.com/episodes/46-catch-all-route

The wildcard route that you are current using is a Bad Idea(tm).

I would recommend defining the routes you care about, and then doing a catchall as the last line of routes.rb (routes defined first in routes.rb trump later definitions). Then you can render whatever page you want (and specify the 404 status code).

Edit: If you really want to use your current approach... (although this seems like it could be deprecated)

def rescue_action(exception) case exception when ActionNotFound, UnknownAction then # Handle these exceptions here else super end end

hamiltop
  • 453
  • 6
  • 9
  • I am actually using catch all route for something else, but I could actually add only the actual available action instead of :action. Thanks. But am I doing something wrong with the `rescue_from`? It looks like it should work... – elado Sep 08 '11 at 03:49
  • Thanks, though `def rescue_action(exception); case exception; when ActionController::UnknownAction, AbstractController::ActionNotFound then render text: "404"; else render text: "other exception"; end; end` in ApplicationController or UsersController didn't work. – elado Sep 08 '11 at 04:27