How to add 404 error page in ruby on rails app? - ruby-on-rails

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.

Related

DRYing up Rails error page

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.

How do I redirect missing *.htm pages to a known page in Rails?

We've recently replaced an old HTML site with a shiny new Ruby on Rails one. Unfortunately a lot of the search engines are still looking for pages like 'index.htm' which no longer exist.
We already have a default route which catches bad URLs and gives a 404, but for URLs which end in .htm, I'd prefer to redirect to different page instead with a 302.
This is what we currently use a catch-all:
match '*a', :to => 'errors#routing'
What can I add above that to catch 'index.htm' and anything else *.htm?
Use match '*a.htm' => redirect("/", :status => 302)
Assuming you have Rails routes that are identical to the legacy routes (without .html at the end), you should be able to add an optional :format in your routes. Check out the defalt catch all route definition:
match ':controller(/:action(/:id))(.:format)'
The key is (.:format) at the end. Try adding that to other routes and it should work with legacy and non-legacy URLs.
I tried the first solution without much success. This worked fine though.
match '*a.htm', to: 'errors#redirect_to_home'
where the errors controller is:
# -*- encoding : utf-8 -*-
class ErrorsController < ApplicationController
def routing
render_404
end
def redirect_to_home
redirect_to root_url
end
end

Rails 3.2 error routing issue. Error ID is conflicting with other object ID

We just upgraded our app to Rails 3.2.2 and are now having a routing issue for handling errors.
Per José Valim's blog post, we added the following:
config.exceptions_app = self.routes to config/application.rb
match "/404", :to => "errors#not_found" to config/routes.rb
(and the appropriate controller/views).
The problem is we need ourdomain.com/id to display an index page for a product category of id.
So, now ourdomain.com/404 shows our 404 page, when it should show our category listing page for the category with an id of 404.
How can we work around this?
Is there a way to make the app prepend each error with error_ before it's evaluated by routes?
Or, maybe somehow set config.exceptions_app to reference a namespace in the routes file?
Or, can I create a second route set and set config.exceptions_app = self.second_set_of_routes?
Thanks!
We had the same problem -- error codes colliding with ids for resources at the root level (e.g., collisions between ourdomain.com/:resource_id and ourdomain.com/404).
We modified José Valim's solution by adding a route constraint that only applies when handling an exception:
# Are we handling an exception?
class ExceptionAppConstraint
def self.matches?(request)
request.env["action_dispatch.exception"].present?
end
end
MyApp::Application.routes.draw do
# These routes are only considered when there is an exception
constraints(ExceptionAppConstraint) do
match "/404", :to => "errors#not_found"
match "/500", :to => "errors#internal_server_error"
# Any other status code
match '*a', :to => "errors#unknown"
end
...
# other routes, including 'match "/:resource_id"'
end
(We only stumbled on this solution last night, so it hasn't had much burn-in time. We are using Rails 3.2.8)
There's one solution which I've found so far:
# application_controller.rb
def rescue_404
rescue_action_in_public CustomNotFoundError.new
end
def rescue_action_in_public(exception)
case exception
when CustomNotFoundError, ::ActionController::UnknownAction then
#render_with_layout "shared/error404", 404, "standard"
render template: "shared/error404", layout: "standard", status: "404"
else
#message = exception
render template: "shared/error", layout: "standard", status: "500"
end
end
def local_request?
return false
end
rescue_action_in_public is the method that Rails calls to handle most errors.
local_request? the method tells Rails to stop sucking if it's local request
# config/routes.rb
match '*path', controller: 'application', action: 'rescue_404' \
unless ::ActionController::Base.consider_all_requests_local
It simply says that it can’t find any other route to handle the request (i.e. the *path) it should call the rescue_404 action on the application controller (the first method above).
EDIT
This version worked for me well!
Try to add to application.rb
# 404 catch all route
config.after_initialize do |app|
app.routes.append{ match '*a', to: 'application#render_not_found' } \
unless config.consider_all_requests_local
end
See: https://github.com/rails/rails/issues/671#issuecomment-1780159
It seems that this route is hard coded at the show_exceptions method (see source)
Sorry, but I don't think of a way of doing it besides changing the line 45 on the source above to:
env["PATH_INFO"] = "/error_#{status}"
(what is, needless to say, no solution at all).
It doesn't hurt to ask:If you thought it was nice to have your own error controller implemented so simply and desperately want to have it, than wouldn't it even be more "RESTful" if your route were yourdomain.com/product/:id?

How to properly render custom 404 and 500 pages?

Is there are way to tell Rails to render your custom error pages (for example, the ones you write in your ErrorsController)? I've searched many topics, and the one that seems to kinda work was to add to your ApplicationController something like
if Rails.env.production?
rescue_from Exception, :with => :render_error
rescue_from ActiveRecord::RecordNotFound, :with => :render_not_found
rescue_from ActionController::UnknownController, :with => :render_not_found
rescue_from ActionController::UnknownAction, :with => :render_not_found
end
and then you write your methods render_error and render_not_found the way you want. This seems to me like a really unelegant solution. Also, it's bad, because you have to know exactly what are all the errors that could happen. It's a temporary solution.
Also, there is really no easy way to rescue an ActionController::RoutingError that way. I saw that one way was to add something like
get "*not_found", :to => "errors#not_found"
to your routes.rb. But what if you want to raise somewhere an ActionController::RoutingError manually? For example, if a person who is not an administrator tries to go to "adminy" controllers by guessing the URL. In those situations I prefer raising a 404 more than raising an "unauthorized access" error of some kind, because that would actually tell the person that he guessed the URL. If you raise it manually, it will try to render a 500 page, and I want a 404.
So is there a way to tell Rails: "In all cases you would normally render a 404.html or a 500.html, render my custom 404 and 500 pages"? (Of course, I deleted the 404.html and 500.html pages from the public folder.)
Unfortunately there aren't any methods I know of that can be overridden to provide what you want. You could use an around filter. Your code would look something like this:
class ApplicationController < ActionController::Base
around_filter :catch_exceptions
protected
def catch_exceptions
yield
rescue => exception
if exception.is_a?(ActiveRecord::RecordNotFound)
render_page_not_found
else
render_error
end
end
end
You can handle each error as you see fit in that method. Then your #render_page_not_found and #render_error methods would have to be something like
render :template => 'errors/404'
You would then need to have a file at app/views/errors/404.html.[haml|erb]

Catch Unknown action in Rails 3 for custom 404

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

Resources