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!
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.
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.
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).
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
Related
This question already has answers here:
rescue_from ActionController::RoutingError doesn't work
(2 answers)
Closed last month.
In my Rails 7 API only mode, I'm trying to catch case when someone is trying to make a request into api/v1/mandate instead of api/v1/mandates (classic ActionController::RoutingError case). In such case it will render the 404 error with some build in stuff as JSON response. I want to customize that JSON error answer inside my BaseController. I was trying:
#base_controller.rb
module Api
module V1
class BaseController < ActionController::API
rescue_from ActionController::RoutingError, with: :render_not_found_error
private
def render_not_found_error(exception)
render json: { error: true, message: exception.message }, status: :not_found
end
end
end
end
I've also have:
#config/environments/development.rb
config.consider_all_requests_local = true
But nothing changed, still doesn't get my JSON error.
You can't rescue routing errors in a controller because the exceptions occur in the routing layer, not the controller.
For missing routes, you need a "catch-all" route that can capture them and then forward that to a controller which performs your "exception" handling.
In practice this often shakes out as a NotFoundController with different actions depending on the type of route being handled.
Can you help me in this. I want to add 404 error page to my website.
I tried many ways but nothing works for me.
Here is one of them.
ApplicationController
unless ActionController::Base.consider_all_requests_local
rescue_from Exception, :with => :render_404
end
private
def render_404
render :template => 'error_pages/404', :layout => false, :status => :not_found
end
Then set up error_pages/404.html
Can anyone fix this?
Rescuing 404 (usually originating from ActiveRecord::RecordNotFound) from your controller will not always help, because there're also routing errors (ActionController::RoutingError)
To present exceptions to user rails calls config.exceptions_app, which defaults to ActionDispatch::PublicExceptions.new(Rails.public_path) and just renders public/404.html etc.
For most cases it's enough to place your error pages there, but if you absolutely must render something dynamic - you can override the exceptions app.
One common hack is to route exceptions back into your main routes via config.exceptions_app = self.routes and adding regular routes to handle them:
get '/404', to: 'errors#not_found'
get '/422', to: 'errors#unacceptable'
get '/500', to: 'errors#server_error'
But beware, that if there's already an exception - you may get an exception loop, so it's better to have separate error handling app/engine
If you are simply wanting custom static error pages, you can change the HTML inside of public/404.html, public/422.html and public/500.html.
So I have a Rails 4.2 app & I'm inside a controller action of a Rails engine such as AlchemyCMS. I want to redirect from inside the engine controller to a Rails error handling controller.
I tried:
redirect_to controller: 'ErrorsController', action: 'show', status: 404
unfortunately the application looks for a controller in the engine namespace and returns something like:
No route matches {:action=>"show", :controller=>"alchemy/ErrorsController", :locale=>"en", :urlname=>"more"}
You can raise error in your engine controller and rescue it inside application_controller.rb file of your app:
rescue_from YourRaisedException do |exception|
redirect_to controller: 'ErrorsController', action: 'show', status: 404
end
The most Rails-y way to do this would be to create a route entry in your config/routes.rb file, then use the _url helper in your controller (for a redirect it has to be _url, not _path. See here). Something like
# in config/routes.rb
match '/404', to: 'errors#show', via: :all, as: :file_not_found
And then in your EngineController:
redirect_to file_not_found_url
Note that you can't actually specify a status of 404 for your redirect; the docs say it must be one of the 3XX codes, which makes sense since those are the ones that correspond to a redirect.
I have a Rails app which is part of a larger web site. I'd like to have a single 404 error page that's used consistently across the site. Currently I have a static page that's part of the landing site, which for historical reasons is served from the public section of the Rails app:
public/landing/404.html
Now I'd like my Rails app to serve that page in case of a 404 error. The approach I've tried is adapted from this blog post:
config/application.rb:
config.exceptions_app = self.routes
config/routes.rb:
match '/404', to: redirect('/landing/404'), via: :all
This appears to work, in that a 404 error will deliver the landing/404.html page to the user agent that made the request. However, it delivers the page with status 200, because the server successfully redirected to the static page. So, not web standards compliant (and not very RESTful!).
My question is: can I serve the static page but with a 404 response code? Or is there a better way to DRY up my error page config?
You can do the following in application_controller.rb:
unless Rails.application.config.consider_all_requests_local
rescue_from ActiveRecord::RecordNotFound, with: :render_404
end
Here, as you can guess, you can catch any type of exceptions you are interested in (ActionController::RoutingError, ActionController::UnknownController, or generally Exception), and it will only redirect in production, whereas in development it will show real exception.
def render_404
respond_to do |format|
format.html { render( layout: false, file: Rails.root.join( 'public', 'landing', status.to_s ), status: status ) }
format.all { render nothing: true, status: 404 }
end
end
and leave routes.rb as it is.
There are many solutions for creating customized error handling pages, but almost none for Rails 4:
Basic Rails 404 Error Page
Dynamic error pages in Rails
The standard answer of encouraging people to modify 404.html in /public doesn't work for me because I want to use the CSS theme that resides in the asset pipeline. Is there a way that html files can access those styles defined in the asset pipeline? If not, is there a way to create a custom error handler that has access to the pipeline?
For Rails 4.1 I like this answer, add an asset type better; however I have not tried it. On Rails 4.0.8, these three references helped me:
Dynamic error pages is the second reference in the question. This worked just fine for me.
Custom error pages may have cribbed from the first reference, or the other way around, but goes the extra mile by adding some information about testing with Capybara.
I did not do the Capybara testing because I didn't want to change the test configuration; however, RSpec-Rails Request Specs clued me in to test these requests independently and see that they complete and return the correct content.
What follows is a nutshell description of what is taught by the three references:
Add the following setting to config/environments/production.rb
# Route exceptions to the application router vs. default
config.exceptions_app = self.routes
Edit the routing configuration, config/routes.rb to direct the error pages to an errors controller
# error pages
%w( 404 422 500 503 ).each do |code|
get code, :to => "errors#show", :code => code
end
will route the 404, 422, 500, and 503 page requests to the show action of the errors controller with a parameter code that has the value of the status code.
Create the controller, app/controllers/errors_controller.rb. Here is the entire content:
class ErrorsController < ApplicationController
def show
status_code = params[:code] || 500
flash.alert = "Status #{status_code}"
render status_code.to_s, status: status_code
end
end
My preference was to set a status message on flash.alert
Create the pages themselves. I use .erb Here is app/views/errors/500.html.erb
<p>Our apology. Your request caused an error.</p>
<%= render 'product_description' %>
So you see that you can render a partial. The page renders with all of the layout boilerplate from app/views/layouts/application.html.erb or any other layout boilerplate that you have configured. That includes the <div id='alert'><%= alert %></div> that displays the status message from the flash.
Tested with RSpec by adding a test file, spec/requests/errors_request_spec.rb. Here is abbreviated content of that file that shows a test of the 500 status page:
require 'rails_helper'
RSpec.describe "errors", :type => :request do
it "displays the 500 page" do
get "/500"
assert_select 'div#alert', 'Status 500'
assert_select 'div[itemtype]'
end
end
The first assertion checks for the flash alert. The second assertion checks for the partial.
We've made a gem which does this for you: exception_handler.
There is also a great tutorial here.
I also wrote an extensive answer on the subject here.
Middleware
# config/application.rb
config.exceptions_app = ->(env) { ExceptionController.action(:show).call(env) }
Controller
# app/controllers/exception_controller.rb
class ExceptionController < ApplicationController
respond_to :json, :js, :html
before_action :set_status
def show
respond_with #status
end
private
def set_status
def status
#exception = env['action_dispatch.exception']
#status = ActionDispatch::ExceptionWrapper.new(env, #exception).status_code
#response = ActionDispatch::ExceptionWrapper.rescue_responses[#exception.class.name]
end
end
end
View
# app/views/exception/show.html.erb
<h1>404 error</h1>
This is very simple version - I can explain more if you wish.
Basically, you need to hook into the config.exceptions_app middleware, it will capture any exception in the middleware stack (as opposed to rendering the entire environment), allowing you to send the request to your own controller#action.
If you comment, I'll help you out some more if you want!