Skip to content
This repository

Can no longer rescue_from ActionController::RoutingError #671

Closed
lighthouse-import opened this Issue May 15, 2011 · 57 comments
Lighthouse Import

Imported from Lighthouse. Original ticket at: http://rails.lighthouseapp.com/projects/8994/tickets/4444
Created by Luigi Montanez - 2011-02-14 05:42:01 UTC

In Rails 2.3.x, one is able to stick this in ApplicationController to present the user with a custom 404 screen:

rescue_from(ActionController::RoutingError) { render :text => 'This is a custom 404.' }

Now in Rails 3, because routing is done as middleware (ActionDispatch), it seems that the ActionController::RoutingError that gets thrown by ActionDispatch no longer can be caught from ApplicationController -- the error is already thrown and ActionDispatch renders /templates/rescues/routing_error.erb before the controller can rescue_from the error.

Lighthouse Import

Imported from Lighthouse.
Comment by Ben Marini - 2010-04-27 02:38:02 UTC

I verified this with a freshly generated rails app from the master branch. I haven't investigated too far, but at this point the best way I can see to do this in rails 3 is to subclass and swap out the ActionDispatch::ShowExceptions middleware. Or add a route that matches anything...is there a better way?

Lighthouse Import

Imported from Lighthouse.
Comment by Jean - 2010-05-27 20:09:31 UTC

I am also hitting this problem, but my understanding of the internals of rails3 is very limited atm ...

Couldn't we define the rescue handling as an option to the route ? (thus in route.rb)

Lighthouse Import

Imported from Lighthouse.
Comment by Neeraj Singh - 2010-05-28 03:35:17 UTC

Put following code in an initializer in a rails3 app.

module ActionDispatch
  class ShowExceptions
    def render_exception(env, exception)
      if exception.kind_of? ActionController::RoutingError
        render(500, 'it was routing error')
      else
        render(500, "some other error")
      end
    end
  end
end      

There might be a better way but it works in my quick test.

Lighthouse Import

Imported from Lighthouse.
Comment by José Valim - 2010-07-13 19:58:09 UTC

The best way to handle a missing route in Rails 3 is by adding the following line at the end of your router:

match "*", :to => "home#routing_error"

And add the routing_error action. We will be working on a better solution, but it is targeted for 3.1.

Lighthouse Import

Imported from Lighthouse.
Comment by Hubert Łępicki - 2010-07-25 16:47:51 UTC

José, are you sure that your solution actually works? I think it's not working as you can't simply say match "*" to match everything in Rails 3 so far?

Lighthouse Import

Imported from Lighthouse.
Comment by Hubert Łępicki - 2010-07-25 16:52:04 UTC

I mean, you have to do it like this:

match '/:anything', :to => "home#routing_error", :constraints => { :anything => /.*/ }

Lighthouse Import

Imported from Lighthouse.
Comment by Hubert Łępicki - 2010-07-26 07:14:54 UTC

I was giving it a bit more thought since yesterday. The solution with match "/:anything" is also not perfect for some people, as it'll override all routes from plugins/engines. I now think that best solution is to use custom rack middleware for handling 404 pages in Rails.

Lighthouse Import

Imported from Lighthouse.
Comment by Matthew Gibbons - 2010-08-13 14:14:51 UTC

The approach that I have adopted to solve this (for now) is to make a call back into the application (from ActionDispatch::ShowExcpetions#render_exception), specifically to a controller that is there for the purpose of rendering an error page. This has all the benefits of using the layout and styling of the host application. However, if a second exception occurs whilst handling the exception in this way, the original method is called, and the default is rendered.

It is better explained here: http://accuser.cc/posts/1-rails-3-0-exception-handling

For those who would rather copy and paste than read the above... ;)

  1. http://gist.github.com/522944
  2. http://gist.github.com/522924
Lighthouse Import

Imported from Lighthouse.
Comment by Andre Pankratz - 2010-08-17 17:11:44 UTC

Here is a gem that may solve this issue: http://github.com/vidibus/vidibus-routing_error

Lighthouse Import

Imported from Lighthouse.
Comment by Ryan Bigg - 2010-10-09 21:00:28 UTC

Automatic cleanup of spam.

Lighthouse Import

Imported from Lighthouse.
Comment by gucki - 2010-09-22 13:51:13 UTC

Please ignore my previous comment, it does not work that way. Seems rails had some routes cached, so it seemed to work initially.

Lighthouse Import

Imported from Lighthouse.
Comment by gucki - 2010-09-22 13:34:45 UTC

The easiest way for me was to add this very last route:

match ''=> lambda { |env| raise ActionController::RoutingError, env["PATH_INFO"] }

Lighthouse Import

Imported from Lighthouse.
Comment by Ryan Bigg - 2010-10-19 07:22:55 UTC

Automatic cleanup of spam.

Lighthouse Import

Imported from Lighthouse.
Comment by Tian Davis - 2010-11-12 00:25:17 UTC

Personally, I'm waiting for an Official solution from the Rails Core Team. Until then I need a simple, no side effect solution for catching rogue routes. For now, my solution is an elaboration on José Valim's recommendation:
http://techoctave.com/c7/posts/36-rails-3-0-rescue-from-routing-error-solution

With this approach, you'll be in unison with the Rails Core Team. Moreover, I incorporate Rails 3 Route Globbing, so you get the added benefit of knowing exactly which rogue route was entered. As a result, you're free to handle routing errors as creatively as you'd like.

Lighthouse Import

Imported from Lighthouse.
Comment by avocade (at gmail) - 2010-12-19 02:50:57 UTC

Issue for more than just rescuing from routing errors, CanCan::AccessDenied too:

http://www.tonyamoyal.com/2010/07/28/rails-authentication-with-devise-and-cancan-customizing-devise-controllers/

Lighthouse Import

Imported from Lighthouse.
Comment by Kevin Watt - 2011-03-18 15:40:52 UTC

This is a huge bug and I feel it's priority should be raised.

It's not acceptable for a broken link in a production app to result in a blank screen, this is why we make 404 handlers. But once the request is dispatched to rails it seems to be rails job to render the 404 page, which it can't do because of this bug.

Lighthouse Import

Imported from Lighthouse.
Comment by Kevin Watt - 2011-03-18 16:56:03 UTC

Just to add,
match '/:anything', :to => "home#routing_error", :constraints => { :anything => /./ } or
match "
", :to => "home#routing_error"

Don't seem to work when the controller exists but the action doesn't. Exception log still just shows "AbstractController::ActionNotFound (The action 'rejsasdf' could not be found for HomeController):" and a blank page is rendered.

Lighthouse Import

Imported from Lighthouse.
Comment by Andrew White - 2011-03-19 06:48:59 UTC

Are you using :action as the dynamic segment name? If so Rails will try to find the action in the controller. The following path works for me:

match '*path', :to => 'errors#not_found'

This won't catch a missing root path - but I'm guessing you're website is there.

Lighthouse Import

Imported from Lighthouse.
Comment by Andre Pankratz - 2011-03-19 08:36:52 UTC

A catch-all route at the end of your routes my seem like a simple fix, but it has a major drawback:
If your application relies on engines that extend your app with their own routes, things will break because those routes will never get fired!

I've published a gem that solves the routing error. It basically catches the exception on Rack-level and re-raises it on application-level: https://github.com/vidibus/vidibus-routing_error

Lighthouse Import

Imported from Lighthouse.
Comment by Andre Pankratz - 2011-03-19 08:37:41 UTC

Oops, I removed José. Sorry.

Lighthouse Import

Imported from Lighthouse.
Comment by Kevin Watt - 2011-03-19 14:36:35 UTC

I am using non-restful routes, which is perhaps why the catch-all doesn't work for me

match ':controller(/:action(/:id))', :constraints => {:id => /.*/}
José Valim
Owner

Yeah, you can no longer do that. You need to define a catch all route.

José Valim josevalim closed this May 15, 2011
Mathieu Martin
webmat commented June 01, 2011

Why has this issue been closed? As far as I can tell, Rails 3.1 RC1 still hasn't added back support rescue_from, right?

Or is it going to be in the next RC / official release?

José Valim
Owner

No Exceptron did not get in Rails 3.1. :( We are going to merge it into master soon.

Rob Sanheim

Should this issue be re-opened until the actual fix is merged?

Gavin Todes
GAV1N commented July 10, 2011

Any word on the status of this issue?

Alberto Perdomo

Can someone please provide some more info on the status of this issue? I'm still seeing it on edge.
Steps to reproduce: https://gist.github.com/1114757

Samuel Kadolph

With how rails 3 was restructured to be a real rack app, it's not longer easy and clean to raise that error inside the context of a controller instance.
The best approach is a catch all route at the end of routes.rb: `get "*" => "home#not_found". You could also route it to an action that would raise the error and then you could handle it with a rescue_from.

Alberto Perdomo

Ok, I understand it's going to stay this way.
Thanks for clarifying.

Santiago Pastorino
Owner

Keep an eye on exceptron

Corin Langosch

@spastorino There's no gem yet...? :-(

Santiago Pastorino
Owner

@gucki released now

Corin Langosch

@spastorino great, thanks. :)

But too bad I cant seem to get it working: I added the gem but I still can't catch ActionController::RoutingError with a rescue_from Exception from withing my ApplicationController.

Santiago Pastorino
Owner

@gucki exceptron has nothing to do with rescue_from, I will write a blog post soon explaining how to use it and merge it to Rails 3.2. Meanwhile you can take a look at the tests to see the way you should use it :)

Corin Langosch

@spastorino Ok, I think I got it g. I just send a pull request for an updated readme :)

Reinier de Lange

For those that are using gems that have their own routes: you can also add the catch-all route after initialize to account for that. Just put something like this in application.rb:

    # 404 catch all route
    config.after_initialize do |app|
      app.routes.append{ match '*a', :to => 'application#render_404' } unless config.consider_all_requests_local
    end
Paul McMahon

In case anyone else was like me, saw this bug, and decided to use exceptron: don't - it is abandoned.

Alex Shearn

Just to let fellow googlers know: this issue still appears to exist in rails 3.1.1, although I have yet to update to rails 3.2 in case of possible breakage.

Nunzio Fiore

I m in a big trouble and i think that this issue could be useful for me in some way, can somebody of you help me?

I used:

match '*a', :to => 'errors#e404'

(i tried also
config.after_initialize do |app|
app.routes.append{ match '*a', :to => 'application#render_404' } unless config.consider_all_requests_local
end
in application.rb but with no success)

and it works great but... I use omniauth and I have url like that

/auth/facebook for example

that are no listed in my gemfile...

I thought that @moiristo answer could help me but I have again an error because my

/auth/:service redirect to 404...

sorry for my english...-
-can you help me in some way?

Reinier de Lange

had the exact same problem a while back. It's because the omniauth middleware is only executed after your rails app tells rack it couldn't process the request. So, I had to add a route to do just that:

match '/auth/:provider' => 'sessions#omniauth' 
  # Render a 404 to trigger the omniauth middleware  
  def omniauth
    render :text => '404', :status => 404
  end

you could probably also create a custom responder to handle this..

Nunzio Fiore

@moiristo you are the best, thank you very much
I added the match in application#omniauth
and the method in applicationcontroller
and it run perfectly

sincerely I didnt' understand very well why we had to add render :text => '404', :status => 404, but this is mine problem and I have to study it.
thank you again
Nunzio

Reinier de Lange

It's because your rails app is a rack app as well. Rack calls every registered app until one handles it. This is what happens when you redirect. When you don't redirect and respond with 404, the next app in the chain is called. One of these apps is the omniauth middleware.

Nunzio Fiore
Rubén Díaz Platero

Hello,

I want to handle not only routing errors. I have used the code from #671 (comment) and is working fine.

What I would like to do is to render a page directly instead of displaying a message. Therefore I've replaced --> render(500, 'it was routing error') for this --> render :file => "#{RAILS_ROOT}/public/500.html", :status => 500

But I get an error: wrong number of arguments (1 for 2)

This is what im trying:

module ActionDispatch
class ShowExceptions
def render_exception(env, exception)
if exception.kind_of? ActionController::RoutingError
#render(500, 'it was routing error')
render :file => "#{RAILS_ROOT}/public/404.html", :status => 404
else
#render(500, "some other error")
render :file => "#{RAILS_ROOT}/public/500.html", :status => 500
end
end
end
end

Any help will be very welcome! Thanks in advance!

Reinier de Lange

why don't you just use rescue_from for anything other than routing errors?

Rubén Díaz Platero

Hi, I thought rescue_from didnt work in Rails 3.0.X versions. Reading now better the post I assume that the only one that doesnt work is for the Routing errors. Sorry, my bad!

bkimble

@canma5 Correct - rescue_from doesn't work with ActionController::RoutingError in 3.2.0, but does work for everything else.

Jonathan Rochkind

What is the current status?

11 months ago, josé:

And add the routing_error action. We will be working on a better solution, but it is targeted for 3.1.

10 months ago, josé:

No Exceptron did not get in Rails 3.1. :( We are going to merge it into master soon.

Now we're in several patch releases of rails 3.2. Is there any new official way to deal with this? Or is the only way still a catch-all route? The problem with the catch-all route, as others have mentioned, is it messes up engine-supplied routes. To deal with that, you can do it in an after_initialize block or some other initializer you hook in after engine routes are loaded... but this starts getting kind of cumbersome and confusing for the beginner. At one point José suggested Rails core team planned to implement some other fix -- is there still such a plan/desire?

Matt Green

+1 looking forward to a status update.

Spencer Fry

Any update on how to handle this?

Damien Mathieu
Collaborator

This issue shouldn't be closed.

Guillermo Iguaran guilleiguaran reopened this April 24, 2012
Guillermo Iguaran guilleiguaran closed this April 29, 2012
Guillermo Iguaran
Owner

Closing, see @spastorino comment

Jonathan Rochkind

I do not understand how to apply @spastorino's comment. Is it possible to rescue from Routing exceptions in current Rails, and if so, how?

José Valim
Owner

For everyone waiting on a status update, I've written briefly about it here (point 3):

http://blog.plataformatec.com.br/2012/01/my-five-favorite-hidden-features-in-rails-3-2/

Jonathan Rochkind

Thanks. Is rescue_from discouraged/deprecated behavior, and the new middleware-based config.exceptions_app should be used instead? Or just for routing errors?

Robert Grimm

Regarding the blog post that josevalim posted, is that the real way to handle this? I like that method and it works well for me except for integration tests that tests for a 404 server response since the ActiveRecord::RecordNotFound exception bubbles up to the tests. I've been able to fix that by doing the 'rescue_from ActiveRecord::RecordNotFound' thing but that seems like duplication since I also have a 'match 404' in my routes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.