ราง: จับข้อยกเว้นทั้งหมดในตัวควบคุมราง


92

มีวิธีจับข้อยกเว้นที่ไม่ได้จับทั้งหมดในตัวควบคุมรางเช่นนี้หรือไม่:

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

ขอขอบคุณ

คำตอบ:


94
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

39
ไม่ใช่กฎที่จะไม่จับข้อยกเว้น?
RonLugge

2
แต่ฉันจะจับทุกประเภทในrescue => eบล็อกเท่านั้นได้อย่างไร
Matrix

7
@RonLugge มันขึ้นอยู่กับสถานการณ์ในมือทั้งหมด การใช้คำว่า "never" ตามหลักทั่วไปถือเป็นความคิดที่ไม่ดี
Justin Skiles

11
@JustinSkiles Catching Exception จะตรวจจับข้อผิดพลาดทางไวยากรณ์ (และสัญญาณขัดจังหวะด้วย) ขอตัวอย่างสถานการณ์ที่ดีสำหรับการทำเช่นนั้นในรหัสการผลิต ฉันสามารถจับสัญญาณได้โดยตรง แต่คุณต้องทำอย่างชัดเจนเพื่อให้ชัดเจนว่าคุณกำลังสร้างตัวจัดการสัญญาณ เพียงแค่จับข้อยกเว้น ... ไม่ดีความคิดที่ไม่ดี จับได้แม้กระทั่งสิ่งที่คุณไม่ควรพยายามจับ
RonLugge

6
หนึ่งในไม่กี่กรณีทั่วไปที่การช่วยเหลือจาก Exception เป็นไปเพื่อวัตถุประสงค์ในการบันทึก / การรายงานซึ่งในกรณีนี้คุณควรเพิ่มข้อยกเว้นอีกครั้งทันที: stackoverflow.com/a/10048406/252346
aelesbao

200

คุณยังสามารถกำหนดวิธีการ 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อัญมณีนอกจากนี้ยังอาจจะเป็นที่สนใจ


5
สิ่งนี้มีประโยชน์มากยิ่งขึ้นเนื่องจากช่วยให้สามารถจับข้อยกเว้นในลักษณะแห้ง
m33lky

และถ้าฉันใช้ rescue_from โดยไม่มีพารามิเตอร์? จะมีพฤติกรรมเหมือนกับการช่วยเหลือหรือไม่? จับข้อผิดพลาดทั้งหมด?
minohimself

2
การปฏิบัติที่ไม่ดีเพื่อrescue_from Exception? ความเข้าใจของฉันคือการช่วยชีวิตจะดีกว่าStandardErrorดังนั้นสิ่งที่ต้องการSyntaxErrorและLoadErrorไม่ถูกจับ
lobati

ใช่มันเป็นรูปแบบที่ไม่ดีในการช่วยเหลือ 'Exception' ดู "ทับทิมพิเศษ" ของ Avdi Grimm สำหรับสาเหตุที่อาจเป็นปัญหาได้
Midwire

37

คุณสามารถจับข้อยกเว้นตามประเภท:

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://stackoverflow.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

2
ระวังอย่าช่วยเหลือExceptionโดยตรง ดูstackoverflow.com/questions/10048173/…
Thibaut Barrère

10

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

8
คำถามเก่า แต่คำตอบนี้ไม่ถูกต้อง การช่วยเหลือโดยไม่มีข้อโต้แย้งจะจัดการเฉพาะ
หุ่นยนต์

0

จริงๆแล้วถ้าคุณต้องการจับทุกอย่างจริงๆคุณก็แค่สร้างแอปยกเว้นของคุณเองซึ่งให้คุณปรับแต่งพฤติกรรมที่มักจะจัดการโดยมิดเดิลแวร์ PublicExceptions: https://github.com/rails/rails/blob/4-2 -stable / actionpack / lib / action_dispatch / middleware / public_exceptions.rb

คำตอบอื่น ๆ อีกมากมายแบ่งปันอัญมณีที่ทำสิ่งนี้ให้คุณ แต่ไม่มีเหตุผลที่คุณจะมองและลงมือทำด้วยตัวเองไม่ได้จริงๆ

ข้อแม้: ตรวจสอบให้แน่ใจว่าคุณไม่มีข้อยกเว้นในตัวจัดการข้อยกเว้นของคุณ มิฉะนั้นคุณจะได้รับ FAILSAFE_RESPONSE ที่น่าเกลียดhttps://github.com/rails/rails/blob/4-2-stable/actionpack/lib/action_dispatch/middleware/show_exceptions.rb#L4-L22

BTW พฤติกรรมในคอนโทรลเลอร์มาจากการช่วยชีวิต: https://github.com/rails/rails/blob/4-2-stable/activesupport/lib/active_support/rescuable.rb#L32-L51


0

ข้อผิดพลาดในการจัดการเพื่อประสบการณ์ผู้ใช้ที่ดีกว่าเป็นสิ่งที่ยากมากที่จะดึงออกมาอย่างถูกต้อง

ที่นี่ฉันได้จัดเตรียมเทมเพลตที่ครบถ้วนเพื่อให้ชีวิตของคุณง่ายขึ้น สิ่งนี้ดีกว่าอัญมณีเพราะสามารถปรับแต่งให้เข้ากับแอปพลิเคชันของคุณได้อย่างเต็มที่

หมายเหตุ: คุณสามารถดูเทมเพลตนี้เวอร์ชันล่าสุดได้ตลอดเวลาบนเว็บไซต์ของฉัน: 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.