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.

There was a problem

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