Bir ray denetleyicisindeki tüm istisnaları yakalayın


90

Bir ray denetleyicisindeki tüm yakalanmamış istisnaları yakalamanın şöyle bir yolu var mı:

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

teşekkür ederim

Yanıtlar:


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

39
Kural İstisnayı ASLA yakalamıyor mu?
RonLugge

2
ancak tüm türleri rescue => eyalnızca blokta nasıl yakalayabilirim ?
Matrix

7
@RonLugge tamamen eldeki duruma bağlıdır. Genel bir kural olarak "asla" kelimesini uygulamak kötü bir fikirdir.
Justin Skiles

11
@JustinSkiles Catching Exception sözdizimi hatalarını yakalar (ve sinyalleri de keser). Bunu üretim kodunda yapmak için bana iyi bir senaryo verin. Sinyalleri doğrudan yakalayabilirim, ancak bir sinyal işleyici oluşturduğunuzu açıkça belirtmek için bunu açıkça yapmanız gerekir. İstisnayı yakalıyorum ... kötü, kötü fikir. Yakalamaya çalışmamanız gereken şeyleri bile yakalar.
RonLugge

6
Exception'dan kurtarmanın mantıklı olduğu birkaç yaygın durumdan biri, günlük kaydı / raporlama amaçlıdır; bu durumda istisnayı hemen yeniden yükseltmelisiniz: stackoverflow.com/a/10048406/252346
aelesbao

199

Ayrıca bir rescue_from yöntemi de tanımlayabilirsiniz.

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

Hedefinizin ne olduğuna bağlı olarak, istisnaları denetleyici bazında ele almamayı da düşünebilirsiniz. Bunun yerine, istisnalara verilen yanıtları tutarlı bir şekilde yönetmek için exception_handler gem gibi bir şey kullanın . Bonus olarak, bu yaklaşım aynı zamanda ara katman katmanında meydana gelen, istek ayrıştırma veya uygulamanızın görmediği veritabanı bağlantı hataları gibi istisnaları da ele alacaktır. Exception_notifier mücevher da ilgi olabilir.


4
Bu, istisnaları KURU bir şekilde yakalamaya izin verdiği için daha da kullanışlıdır.
m33lky

Ve rescue_from parametresiz kullanırsam? bu kurtarma ile aynı şekilde davranacak mı? tüm hataları yakalamak?
minohimself

2
Kötü bir uygulama değil rescue_from Exceptionmi? Benim anlayış dan kurtarmaya iyi olduğudur StandardErrorgibi şeyler böylece, SyntaxErrorve LoadErroryakalanmış değildir.
lobati

Evet, 'İstisna'yı kurtarmak kötü bir biçim. Bunun neden sorunlu olabileceğine dair nedenler için Avdi Grimm'in "Olağanüstü Yakut" a bakın.
Midwire

35

Türe göre istisnaları yakalayabilirsiniz:

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
ExceptionDoğrudan kurtarmamaya dikkat edin ; bakınız stackoverflow.com/questions/10048173/…
Thibaut Barrère

10

rescue hiçbir argüman olmaması herhangi bir hatayı kurtarmaz.

Yani isteyeceksiniz:

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
Bayat soru, ancak bu cevap yanlış. bağımsız değişken olmadan kurtarma yalnızca StandardError
Keith Gaddis

0

Aslında, gerçekten her şeyi yakalamak istiyorsanız , sadece kendi istisnalar uygulamanızı oluşturursunuz, bu da genellikle PublicExceptions ara yazılım tarafından işlenen davranışı özelleştirmenize izin verir: https://github.com/rails/rails/blob/4-2 -stable / actionpack / lib / action_dispatch / middleware / public_exceptions.rb

Diğer cevapların bir kısmı bunu sizin için yapan cevherleri paylaşıyor, ancak bunlara bakıp kendi başınıza yapmamanız için gerçekten hiçbir neden yok.

Bir uyarı: istisna işleyicinizde asla istisna oluşturmadığınızdan emin olun. Aksi takdirde çirkin bir FAILSAFE_RESPONSE https://github.com/rails/rails/blob/4-2-stable/actionpack/lib/action_dispatch/middleware/show_exceptions.rb#L4-L22 alırsınız

BTW, denetleyicideki davranış kurtarılabilirden geliyor: https://github.com/rails/rails/blob/4-2-stable/activesupport/lib/active_support/rescuable.rb#L32-L51


0

Daha iyi bir kullanıcı deneyimi için hata yönetimi, doğru şekilde çekilmesi çok zor bir şeydir.

Burada, hayatınızı kolaylaştırmak için eksiksiz bir şablon sağladım. Bu bir mücevherden daha iyidir çünkü uygulamanıza göre tamamen özelleştirilebilir.

Not: Bu şablonun en son sürümünü web sitemde istediğiniz zaman görüntüleyebilirsiniz: https://westonganger.com/posts/how-to-properly-implement-error-exception-handling-for-your-rails-controllers

Kontrolör

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

Test yapmak

Bunu spesifikasyonlarınızda test etmek için aşağıdaki şablonu kullanabilirsiniz:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.