Le traitement des erreurs pour une expérience utilisateur plus agréable est une chose très difficile à réaliser correctement.
J'ai fourni ici un modèle complet pour vous faciliter la vie. C'est mieux qu'un joyau car il est entièrement personnalisable à votre application.
Remarque : vous pouvez consulter la dernière version de ce modèle à tout moment sur mon site web : https://westonganger.com/posts/how-to-properly-implement-error-exception-handling-for-your-rails-controllers
Contrôleur
class ApplicationController < ActiveRecord::Base
def is_admin_path?
request.path.split("/").reject{|x| x.blank?}.first == 'admin'
end
private
def send_error_report(exception, sanitized_status_number)
val = true
# if sanitized_status_number == 404
# val = false
# end
# if exception.class == ActionController::InvalidAuthenticityToken
# val = false
# end
return val
end
def get_exception_status_number(exception)
status_number = 500
error_classes_404 = [
ActiveRecord::RecordNotFound,
ActionController::RoutingError,
]
if error_classes_404.include?(exception.class)
if current_user
status_number = 500
else
status_number = 404
end
end
return status_number.to_i
end
def perform_error_redirect(exception, error_message:)
status_number = get_exception_status_number(exception)
if send_error_report(exception, status_number)
ExceptionNotifier.notify_exception(exception, data: {status: status_number})
end
### Log Error
logger.error exception
exception.backtrace.each do |line|
logger.error line
end
if Rails.env.development?
### To allow for the our development debugging tools
raise exception
end
### Handle XHR Requests
if (request.format.html? && request.xhr?)
render template: "/errors/#{status_number}.html.erb", status: status_number
return
end
if status_number == 404
if request.format.html?
if request.get?
render template: "/errors/#{status_number}.html.erb", status: status_number
return
else
redirect_to "/#{status_number}"
end
else
head status_number
end
return
end
### Determine URL
if request.referrer.present?
url = request.referrer
else
if current_user && is_admin_path? && request.path.gsub("/","") != admin_root_path.gsub("/","")
url = admin_root_path
elsif request.path != "/"
url = "/"
else
if request.format.html?
if request.get?
render template: "/errors/500.html.erb", status: 500
else
redirect_to "/500"
end
else
head 500
end
return
end
end
flash_message = error_message
### Handle Redirect Based on Request Format
if request.format.html?
redirect_to url, alert: flash_message
elsif request.format.js?
flash[:alert] = flash_message
flash.keep(:alert)
render js: "window.location = '#{url}';"
else
head status_number
end
end
rescue_from Exception do |exception|
perform_error_redirect(exception, error_message: I18n.t('errors.system.general'))
end
end
Essais
Pour tester cela dans vos spécifications, vous pouvez utiliser le modèle suivant :
feature 'Error Handling', type: :controller do
### Create anonymous controller, the anonymous controller will inherit from stated controller
controller(ApplicationController) do
def raise_500
raise Errors::InvalidBehaviour.new("foobar")
end
def raise_possible_404
raise ActiveRecord::RecordNotFound
end
end
before(:all) do
@user = User.first
@error_500 = I18n.t('errors.system.general')
@error_404 = I18n.t('errors.system.not_found')
end
after(:all) do
Rails.application.reload_routes!
end
before :each do
### draw routes required for non-CRUD actions
routes.draw do
get '/anonymous/raise_500'
get '/anonymous/raise_possible_404'
end
end
describe "General Errors" do
context "Request Format: 'html'" do
scenario 'xhr request' do
get :raise_500, format: :html, xhr: true
expect(response).to render_template('errors/500.html.erb')
end
scenario 'with referrer' do
path = "/foobar"
request.env["HTTP_REFERER"] = path
get :raise_500
expect(response).to redirect_to(path)
post :raise_500
expect(response).to redirect_to(path)
end
scenario 'admin sub page' do
sign_in @user
request.path_info = "/admin/foobar"
get :raise_500
expect(response).to redirect_to(admin_root_path)
post :raise_500
expect(response).to redirect_to(admin_root_path)
end
scenario "admin root" do
sign_in @user
request.path_info = "/admin"
get :raise_500
expect(response).to redirect_to("/")
post :raise_500
expect(response).to redirect_to("/")
end
scenario 'public sub-page' do
get :raise_500
expect(response).to redirect_to("/")
post :raise_500
expect(response).to redirect_to("/")
end
scenario 'public root' do
request.path_info = "/"
get :raise_500
expect(response).to render_template('errors/500.html.erb')
expect(response).to have_http_status(500)
post :raise_500
expect(response).to redirect_to("/500")
end
scenario '404 error' do
get :raise_possible_404
expect(response).to render_template('errors/404.html.erb')
expect(response).to have_http_status(404)
post :raise_possible_404
expect(response).to redirect_to('/404')
sign_in @user
get :raise_possible_404
expect(response).to redirect_to('/')
post :raise_possible_404
expect(response).to redirect_to('/')
end
end
context "Request Format: 'js'" do
render_views ### Enable this to actually render views if you need to validate contents
scenario 'xhr request' do
get :raise_500, format: :js, xhr: true
expect(response.body).to include("window.location = '/';")
post :raise_500, format: :js, xhr: true
expect(response.body).to include("window.location = '/';")
end
scenario 'with referrer' do
path = "/foobar"
request.env["HTTP_REFERER"] = path
get :raise_500, format: :js
expect(response.body).to include("window.location = '#{path}';")
post :raise_500, format: :js
expect(response.body).to include("window.location = '#{path}';")
end
scenario 'admin sub page' do
sign_in @user
request.path_info = "/admin/foobar"
get :raise_500, format: :js
expect(response.body).to include("window.location = '#{admin_root_path}';")
post :raise_500, format: :js
expect(response.body).to include("window.location = '#{admin_root_path}';")
end
scenario "admin root" do
sign_in @user
request.path_info = "/admin"
get :raise_500, format: :js
expect(response.body).to include("window.location = '/';")
post :raise_500, format: :js
expect(response.body).to include("window.location = '/';")
end
scenario 'public page' do
get :raise_500, format: :js
expect(response.body).to include("window.location = '/';")
post :raise_500, format: :js
expect(response.body).to include("window.location = '/';")
end
scenario 'public root' do
request.path_info = "/"
get :raise_500, format: :js
expect(response).to have_http_status(500)
post :raise_500, format: :js
expect(response).to have_http_status(500)
end
scenario '404 error' do
get :raise_possible_404, format: :js
expect(response).to have_http_status(404)
post :raise_possible_404, format: :js
expect(response).to have_http_status(404)
sign_in @user
get :raise_possible_404, format: :js
expect(response).to have_http_status(200)
expect(response.body).to include("window.location = '/';")
post :raise_possible_404, format: :js
expect(response).to have_http_status(200)
expect(response.body).to include("window.location = '/';")
end
end
context "Other Request Format" do
scenario '500 error' do
get :raise_500, format: :json
expect(response).to have_http_status(500)
post :raise_500, format: :json
expect(response).to have_http_status(500)
end
scenario '404 error' do
get :raise_possible_404, format: :json
expect(response).to have_http_status(404)
post :raise_possible_404, format: :json
expect(response).to have_http_status(404)
sign_in @user
get :raise_possible_404, format: :json
expect(response).to have_http_status(500)
post :raise_possible_404, format: :json
expect(response).to have_http_status(500)
end
end
end
end