Rails:在 Rails 控制器中捕获所有异常
Posted
技术标签:
【中文标题】Rails:在 Rails 控制器中捕获所有异常【英文标题】:Rails: Catch all exceptions in a rails controller 【发布时间】:2011-04-11 06:49:48 【问题描述】:有没有办法在 Rails 控制器中捕获所有未捕获的异常,如下所示:
def delete
schedule_id = params[:scheduleId]
begin
Schedules.delete(schedule_id)
rescue ActiveRecord::RecordNotFound
render :json => "record not found"
rescue ActiveRecord::CatchAll
#Only comes in here if nothing else catches the error
end
render :json => "ok"
end
谢谢
【问题讨论】:
【参考方案1】:你也可以定义一个rescue_from方法。
class ApplicationController < ActionController::Base
rescue_from ActionController::RoutingError, :with => :error_render_method
def error_render_method
respond_to do |type|
type.xml render :template => "errors/error_404", :status => 404
type.all render :nothing => true, :status => 404
end
true
end
end
根据您的目标,您可能还需要考虑不在每个控制器的基础上处理异常。相反,使用类似exception_handler gem 的东西来一致地管理对异常的响应。作为奖励,这种方法还将处理中间件层发生的异常,例如您的应用程序看不到的请求解析或数据库连接错误。 exception_notifier gem 可能也很有趣。
【讨论】:
这更加方便,因为它允许以 DRY 方式捕获异常。 如果我使用没有参数的rescue_from?这会和救援一样吗?捕获所有错误?rescue_from Exception
不是坏习惯吗?我的理解是最好从StandardError
中解救出来,这样SyntaxError
和LoadError
这样的东西就不会被抓到了。
是的,拯救“异常”是不好的形式。请参阅 Avdi Grimm 的“Exceptional Ruby”,了解为什么会出现问题。【参考方案2】:
begin
# do something dodgy
rescue ActiveRecord::RecordNotFound
# handle not found error
rescue ActiveRecord::ActiveRecordError
# handle other ActiveRecord errors
rescue # StandardError
# handle most other errors
rescue Exception
# handle everything else
raise
end
【讨论】:
规则不是从不捕获异常吗? 但我如何才能捕获rescue => e
块中的所有类型?
@RonLugge 这完全取决于手头的情况。将“从不”作为经验法则是个坏主意。
@JustinSkiles 捕获异常将捕获语法错误(以及中断信号)。给我一个在生产代码中执行此操作的好方案。我可以直接捕获信号,但是您需要明确地这样做以明确您正在创建信号处理程序。只是捕捉异常......坏,坏主意。甚至可以捕捉到你不应该尝试捕捉的东西。
从异常中拯救出来的少数常见情况之一是用于记录/报告目的,在这种情况下,您应该立即重新引发异常:***.com/a/10048406/252346【参考方案3】:
您可以按类型捕获异常:
rescue_from ::ActiveRecord::RecordNotFound, with: :record_not_found
rescue_from ::NameError, with: :error_occurred
rescue_from ::ActionController::RoutingError, with: :error_occurred
# Don't resuce from Exception as it will resuce from everything as mentioned here "http://***.com/questions/10048173/why-is-it-bad-style-to-rescue-exception-e-in-ruby" Thanks for @Thibaut Barrère for mention that
# rescue_from ::Exception, with: :error_occurred
protected
def record_not_found(exception)
render json: error: exception.message.to_json, status: 404
return
end
def error_occurred(exception)
render json: error: exception.message.to_json, status: 500
return
end
【讨论】:
注意不要直接从Exception
解救;见***.com/questions/10048173/…【参考方案4】:
rescue
不带参数将挽救任何错误。
所以,你会想要:
def delete
schedule_id = params[:scheduleId]
begin
Schedules.delete(schedule_id)
rescue ActiveRecord::RecordNotFound
render :json => "record not found"
rescue
#Only comes in here if nothing else catches the error
end
render :json => "ok"
end
【讨论】:
过时的问题,但这个答案是不正确的。没有参数的救援只处理标准错误robots.thoughtbot.com/rescue-standarderror-not-exception【参考方案5】:为了更好的用户体验而进行错误处理是一件非常困难的事情。
在这里,我提供了一个完整的模板,让您的生活更轻松。这比 gem 更好,因为它完全可以为您的应用程序定制。
注意:您可以随时在我的网站上查看此模板的最新版本:https://westonganger.com/posts/how-to-properly-implement-error-exception-handling-for-your-rails-controllers
控制器
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
测试
要在您的规范中对此进行测试,您可以使用以下模板:
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
【讨论】:
【参考方案6】:实际上,如果你真的想捕捉所有东西,你只需创建自己的异常应用程序,它可以让你自定义通常由 PublicExceptions 中间件处理的行为:https://github.com/rails/rails/blob/4-2-stable/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
堆栈中的位置https://github.com/rails/rails/blob/4-2-stable/railties/lib/rails/application/default_middleware_stack.rb#L98-L99 配置https://github.com/rails/rails/blame/4-2-stable/guides/source/configuring.md#L99 这可以像使用路由 http://blog.plataformatec.com.br/2012/01/my-five-favorite-hidden-features-in-rails-3-2/ 或自定义控制器一样简单(但请参阅 https://github.com/rails/rails/pull/17815 了解不使用路由的原因)许多其他答案分享了可以为您做到这一点的宝石,但您真的没有理由不能只看它们并自己做。
一个警告:确保您永远不会在您的异常处理程序中引发异常。否则你会得到一个丑陋的 FAILSAFE_RESPONSE https://github.com/rails/rails/blob/4-2-stable/actionpack/lib/action_dispatch/middleware/show_exceptions.rb#L4-L22
顺便说一句,控制器中的行为来自可恢复的:https://github.com/rails/rails/blob/4-2-stable/activesupport/lib/active_support/rescuable.rb#L32-L51
【讨论】:
以上是关于Rails:在 Rails 控制器中捕获所有异常的主要内容,如果未能解决你的问题,请参考以下文章
Ruby on Rails - 未捕获的ReferenceError:$未定义