Tangkap semua pengecualian di pengontrol rel

90

Apakah ada cara untuk menangkap semua pengecualian yang tidak cocok di pengontrol rel, seperti ini:

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

Terima kasih

Neigaard
sumber

Jawaban:

93
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
Chris Johnsen
sumber
39
Bukankah aturan untuk TIDAK PERNAH menangkap Pengecualian?
RonLugge
2
tapi bagaimana saya bisa menangkap semua tipe dalam rescue => eblok saja?
Matrix
7
@RonLugge itu sepenuhnya bergantung pada situasi yang dihadapi. menerapkan "tidak pernah" sebagai aturan praktis adalah ide yang buruk.
Justin Skiles
11
@JustinSkiles Catching Exception akan menangkap kesalahan sintaks (dan juga mengganggu sinyal). Beri saya satu skenario bagus untuk melakukan itu dalam kode produksi. Saya bisa menangkap sinyal secara langsung, tetapi Anda perlu melakukannya secara eksplisit untuk memperjelas bahwa Anda sedang membuat penangan sinyal. Hanya menangkap Exception ... ide buruk, buruk. Menangkap bahkan hal-hal yang tidak boleh Anda coba tangkap.
RonLugge
6
Salah satu dari beberapa kasus umum di mana masuk akal untuk menyelamatkan dari Pengecualian adalah untuk tujuan pencatatan / pelaporan, dalam hal ini Anda harus segera mengajukan kembali pengecualian: stackoverflow.com/a/10048406/252346
aelesbao
199

Anda juga dapat menentukan metode 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

Bergantung pada apa tujuan Anda, Anda mungkin juga ingin mempertimbangkan untuk TIDAK menangani pengecualian pada basis per-pengontrol. Sebaliknya, gunakan sesuatu seperti permata exception_handler untuk mengelola respons terhadap pengecualian secara konsisten. Sebagai bonus, pendekatan ini juga akan menangani pengecualian yang terjadi pada lapisan middleware, seperti parsing permintaan atau kesalahan koneksi database yang tidak dilihat aplikasi Anda. The exception_notifier permata mungkin juga menarik.

BOFH
sumber
4
Ini bahkan lebih berguna karena memungkinkan untuk menangkap pengecualian dengan cara KERING.
m33lky
Dan jika saya menggunakan rescue_from tanpa parameter? akankah itu berperilaku sama dengan penyelamatan? menangkap semua kesalahan?
minohimself
2
Bukankah itu praktik yang buruk rescue_from Exception? Pemahaman saya adalah bahwa lebih baik menyelamatkan dari StandardError, jadi hal-hal seperti SyntaxErrordan LoadErrortidak tertangkap.
lobati
Ya, itu bentuk yang buruk untuk menyelamatkan 'Exception'. Lihat "Batu Ruby Luar Biasa" Avdi Grimm untuk alasan mengapa hal itu bisa menjadi masalah.
Midwire
35

Anda dapat menangkap pengecualian berdasarkan jenis:

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
mohamed-ibrahim
sumber
2
Berhati-hatilah untuk tidak menyelamatkan Exceptionsecara langsung; lihat stackoverflow.com/questions/10048173/…
Thibaut Barrère
10

rescue tanpa argumen akan menyelamatkan kesalahan apapun.

Jadi, Anda pasti menginginkan:

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
PreciousBodilyFluids
sumber
8
Pertanyaan basi, tapi jawaban ini salah. penyelamatan tanpa argumen hanya menangani robot
Keith Gaddis
0

Sebenarnya, jika Anda benar-benar ingin menangkap semuanya , Anda cukup membuat aplikasi pengecualian Anda sendiri, yang memungkinkan Anda menyesuaikan perilaku yang biasanya ditangani oleh middleware PublicExceptions: https://github.com/rails/rails/blob/4-2 -stabil / actionpack / lib / action_dispatch / middleware / public_exceptions.rb

Sekelompok jawaban lain berbagi permata yang melakukan ini untuk Anda, tetapi sebenarnya tidak ada alasan Anda tidak bisa hanya melihatnya dan melakukannya sendiri.

Peringatan: pastikan Anda tidak pernah memunculkan pengecualian di penangan pengecualian Anda. Jika tidak, Anda akan mendapatkan FAILSAFE_RESPONSE yang jelek https://github.com/rails/rails/blob/4-2-stable/actionpack/lib/action_dispatch/middleware/show_exceptions.rb#L4-L22

BTW, perilaku di pengontrol berasal dari rescuable: https://github.com/rails/rails/blob/4-2-stable/activesupport/lib/active_support/rescuable.rb#L32-L51

BF4
sumber
0

Penanganan kesalahan untuk pengalaman pengguna yang lebih baik adalah hal yang sangat sulit untuk dilakukan dengan benar.

Disini saya telah menyediakan template yang lengkap untuk mempermudah hidup Anda. Ini lebih baik daripada permata karena sepenuhnya dapat disesuaikan dengan aplikasi Anda.

Catatan: Anda dapat melihat versi terbaru template ini kapan saja di situs web saya: https://westonganger.com/posts/how-to-properly-implement-error-exception-handling-for-your-rails-controllers

Kontroler

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

Menguji

Untuk mengujinya dalam spesifikasi Anda, Anda dapat menggunakan template berikut:

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
Weston Ganger
sumber