Rails 4: How do I create a custom 404 page that us

2020-02-08 03:06发布

问题:

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?

回答1:

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:

  1. Dynamic error pages is the second reference in the question. This worked just fine for me.

  2. 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.

  3. 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:

  1. Add the following setting to config/environments/production.rb

    # Route exceptions to the application router vs. default
    config.exceptions_app = self.routes
    
  2. 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.

  3. 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

  4. 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.

  5. 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.



回答2:

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!