Catch all exceptions in a rails controller

后端 未结 6 1057
春和景丽
春和景丽 2020-11-28 02:20

Is there a way to catch all uncatched exceptions in a rails controller, like this:

def delete
  schedule_id = params[:scheduleId]
  begin
    Schedules.delet         


        
6条回答
  •  -上瘾入骨i
    2020-11-28 03:07

    Error handling for a nicer user experience is a very tough thing to pull off correctly.

    Here I have provided a fully-complete template to make your life easier. This is better than a gem because its fully customizable to your application.

    Note: You can view the latest version of this template at any time on my website: https://westonganger.com/posts/how-to-properly-implement-error-exception-handling-for-your-rails-controllers

    Controller

    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
    

    Testing

    To test this in your specs you can use the following template:

    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
    

提交回复
热议问题