Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

AbstractController::ActionNotFound should be rescued

#6275

Suppose I have the following routes.rb:

match ':controller/:action', :controller => /api\/(room|user)/
match 'api/*others', :to => 'api/base#render_404_as_json'

and controllers:

class Api::BaseController
  def render_404_as_json
    ...
    render :text => '{"status":"error","code":"invalid_api_method"}'
  end
end

class Api::RoomController < Api::BaseController
end

class Api::UserController < Api::BaseController
end

Now, when "/api/bogus" gets hit, the request is routed to render_404_as_json as it should.

However, when "/api/room/bogus" gets hit, it raises AbstractController::ActionNotFound, even if I had the following lines in api/base_controller.rb or application_controller.rb.

rescue_from Exception, :with => :render_500_as_json
rescue_from AbstractController::ActionNotFound, :with => :render_404_as_json

My goal here is to never return an unstructured HTML response of 404 for any URL under /api.

I think we have two ways to get around this:

  • AbstractController::ActionNotFound should be raised inside RoomController so that you can catch it and do your own stuff, or
  • When AbstractController::ActionNotFound is raised, continue down the routing matches (in the example above, 'api/*others' in the next line matches)

Reported by Kenn Ejima · January 10th, 2011 @ 10:33 PM

State: duplicate
Milestone: none
Assigned to: nobody
Importance: Low

Activity

  1. Andrew White
    Andrew White
    • State changed from new to duplicate
    • Importance changed from to Low

    This is essentially a duplicate of #4444 Can no longer rescue_from ActionController::RoutingError. You've almost got the workaround from that ticket but your problem is that the first route is partially matching on the controller. If you add an :action regexp constraint listing the valid actions it should fall through to the second route.

    February 9th, 2011 @ 04:24 AM

  2. Kenn Ejima
    Kenn Ejima

    Thanks Andrew, patching render_exception worked as you said. In my case:

    require 'action_dispatch/middleware/show_exceptions'
    module ActionDispatch
      class ShowExceptions
        private
        def render_exception_with_template(env, exception)
          if exception.kind_of? AbstractController::ActionNotFound
            request = Request.new(env)
            if request.env['action_dispatch.request.path_parameters'][:controller] =~ /^api\/.+/
              return Api::BaseController.action(:render_404_as_json).call(env)
            end
          end
          render_exception_without_template(env, exception)
        end
        alias_method_chain :render_exception, :template
      end
    end
    

    I have 20 controllers and 100+ actions under the /api context so listing them all in the :action regexp constraint seemed like a bad idea.

    February 23rd, 2011 @ 10:16 PM

  3. Kevin Watt
    Kevin Watt

    I'm trying your solution, but don't seem to have access to MyApp::HomeController in this context. Any tips? Is it in any strange namespaces besides the one I define in application.rb?

    March 19th, 2011 @ 01:35 AM

  4. Andrew White
    Andrew White

    Shouldn't it be in the global namespace? i.e. HomeController and not MyApp::HomeController.

    March 19th, 2011 @ 06:36 AM

  5. Kevin Watt
    Kevin Watt

    That fixed it, thanks. When rendering your action you can't re-raise and expect that to get caught like normal, since it's not being wrapped in the begin block that does the rescue_from handling in the first place.

    I wish there was a way to have my web server (litespeed, but shouldn't matter) to see that it got a status 404 from the backend handler, then render the standard error page.

    Though routing errors still cause a 500 rather than 404 which sucks.

    March 19th, 2011 @ 02:43 PM