I want to "fake" 404 pages in Rails. In PHP, I only send headers with error codes as follows:
header("HTTP/1.0 404 Not Found");
How does Rails do this?
#1 building
The selected answer does not work in Rails 3.1 + because the error handler has been moved to middleware (see github issue ).
This is a solution that I am very satisfied with.
In ApplicationController:
unless Rails.application.config.consider_all_requests_local rescue_from Exception, with: :handle_exception end def not_found raise ActionController::RoutingError.new('Not Found') end def handle_exception(exception=nil) if exception logger = Logger.new(STDOUT) logger.debug "Exception Message: #{exception.message} \n" logger.debug "Exception Class: #{exception.class} \n" logger.debug "Exception Backtrace: \n" logger.debug exception.backtrace.join("\n") if [ActionController::RoutingError, ActionController::UnknownController, ActionController::UnknownAction].include?(exception.class) return render_404 else return render_500 end end end def render_404 respond_to do |format| format.html { render template: 'errors/not_found', layout: 'layouts/application', status: 404 } format.all { render nothing: true, status: 404 } end end def render_500 respond_to do |format| format.html { render template: 'errors/internal_server_error', layout: 'layouts/application', status: 500 } format.all { render nothing: true, status: 500} end end
And in application.rb:
config.after_initialize do |app| app.routes.append{ match '*a', :to => 'application#not_found' } unless config.consider_all_requests_local end
In my resources (show, edit, update, delete):
@resource = Resource.find(params[:id]) or not_found
Of course, this can be improved, but at least without covering the core functions of Rails, I have different views on not found and internal error.
#2 building
These will help you
Application controller
class ApplicationController < ActionController::Base protect_from_forgery unless Rails.application.config.consider_all_requests_local rescue_from ActionController::RoutingError, ActionController::UnknownController, ::AbstractController::ActionNotFound, ActiveRecord::RecordNotFound, with: lambda { |exception| render_error 404, exception } end private def render_error(status, exception) Rails.logger.error status.to_s + " " + exception.message.to_s Rails.logger.error exception.backtrace.join("\n") respond_to do |format| format.html { render template: "errors/error_#{status}",status: status } format.all { render nothing: true, status: status } end end end
Error controller
class ErrorsController < ApplicationController def error_404 @not_found_path = params[:not_found] end end
views / errors / error_404.html.haml
.site .services-page .error-template %h1 Oops! %h2 404 Not Found .error-details Sorry, an error has occured, Requested page not found! You tried to access '#{@not_found_path}', which is not a valid page. .error-actions %a.button_simple_orange.btn.btn-primary.btn-lg{href: root_path} %span.glyphicon.glyphicon-home Take Me Home
#3 building
To test error handling, you can do the following:
feature ErrorHandling do before do Rails.application.config.consider_all_requests_local = false Rails.application.config.action_dispatch.show_exceptions = true end scenario 'renders not_found template' do visit '/blah' expect(page).to have_content "The page you were looking for doesn't exist." end end
#4 building
HTTP 404 status
To return the 404 header, simply use the: status option on the render method.
def action # here the code render :status => 404 end
If you want to render a standard 404 page, you can use the method extraction feature.
def render_404 respond_to do |format| format.html { render :file => "#{Rails.root}/public/404", :layout => false, :status => :not_found } format.xml { head :not_found } format.any { head :not_found } end end
And call it in action
def action # here the code render_404 end
If you want the operation to render an error page and stop, just use the return statement.
def action render_404 and return if params[:something].blank? # here the code that will never be executed end
ActiveRecord and HTTP 404
Also remember that Rails saves some ActiveRecord errors, such as ActiveRecord::RecordNotFound, which displays the 404 error page.
This means you don't have to save the action yourself
def show user = User.find(params[:id]) end
When the user does not exist, User.find raises ActiveRecord::RecordNotFound. This is a very powerful feature. Look at the code below
def show user = User.find_by_email(params[:email]) or raise("not found") # ... end
You can simplify this by delegating checks to Rails. Just use the exploded version.
def show user = User.find_by_email!(params[:email]) # ... end
#5 building
You can also use render files:
render file: "#{Rails.root}/public/404.html", layout: false, status: 404
You can choose whether to use the location of the layout.
Another option is to use exception to control it:
raise ActiveRecord::RecordNotFound, "Record not found."