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.
Related
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.
I am trying to catch a custom error, and redirect to root path after it occurs. My code in controller looks like this:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
rescue_from Errors::MyError, :with => :my_error
private
def my_error
redirect_to root_path, alert: "my Error"
end
end
Redirect is not working, after error occurs I am still at the same page. What is funny, my server logs shows that I was redirected.
Redirected to http.....
Completed 302 Found in 1908ms (ActiveRecord: 0.5ms)
What happened? And why redirect havent occur?
When you are doing an AJAX call to your server, the browser expect the response to be in javascript ( you talk to me in the JS language, I answer using JS language too, quite simple ). In your case, you need to test if the request is HTML or Javascript, and use the corresponding redirect:
def my_error
path_to_redirect = root_path
if request.xhr? # tests if the request comes from an AJAX call
render :js => "window.location = '#{path_to_redirect}'"
else
redirect_to path_to_redirect, alert: "my Error"
end
end
For more answers about AJAX redirects in Rails: Rails 3: How to "redirect_to" in Ajax call?
for ex: I type the url: localhost:3000/hoasung01 then it throws a message:
Unknown action
The action 'hoasung01' could not be found for StaticController
And in console:
AbstractController::ActionNotFound (The action 'hoasung01' could not be found for StaticController):
I spent many hours to research and try some ways:
rescue_from AbstractController::ActionNotFound, with: :page_not_found
#(ActionController::RoutingError, ActionController::UnknownController, ::AbstractController::ActionNotFound, ActiveRecord::RecordNotFound)
protected
def page_not_found
render 'layouts/404.html', :status => 403, :layout => false
end
but it does not render 404.html page. ( I use rails 4.1.5 )
In development you are shown a special error page to help you figure out errors. I you want to test that 404 and 500s work look at this blog post: http://thepugautomatic.com/2014/08/404-with-rails-4/
The section Verify in development says t config/environments/development.rb and add the following line:
config.consider_all_requests_local = false
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!
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