File: active_support/rescuable.rb

Overview
Module Structure
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: ActiveSupport#1
  module: Rescuable#3
has properties
module method: included / 1 #4
method: rescue_with_handler / 1 #71
method: handler_for_rescue / 1 #78
  module: ClassMethods#11
has properties
method: rescue_from / 2 #44

Code

   1  module ActiveSupport
   2    # Rescuable module adds support for easier exception handling.
   3    module Rescuable
   4      def self.included(base) # :nodoc:
   5        base.class_inheritable_accessor :rescue_handlers
   6        base.rescue_handlers = []
   7 
   8        base.extend(ClassMethods)
   9      end
  10 
  11      module ClassMethods
  12        # Rescue exceptions raised in controller actions.
  13        #
  14        # <tt>rescue_from</tt> receives a series of exception classes or class
  15        # names, and a trailing <tt>:with</tt> option with the name of a method
  16        # or a Proc object to be called to handle them. Alternatively a block can
  17        # be given.
  18        #
  19        # Handlers that take one argument will be called with the exception, so
  20        # that the exception can be inspected when dealing with it.
  21        #
  22        # Handlers are inherited. They are searched from right to left, from
  23        # bottom to top, and up the hierarchy. The handler of the first class for
  24        # which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if
  25        # any.
  26        #
  27        #   class ApplicationController < ActionController::Base
  28        #     rescue_from User::NotAuthorized, :with => :deny_access # self defined exception
  29        #     rescue_from ActiveRecord::RecordInvalid, :with => :show_errors
  30        #
  31        #     rescue_from 'MyAppError::Base' do |exception|
  32        #       render :xml => exception, :status => 500
  33        #     end
  34        #
  35        #     protected
  36        #       def deny_access
  37        #         ...
  38        #       end
  39        #
  40        #       def show_errors(exception)
  41        #         exception.record.new_record? ? ...
  42        #       end
  43        #   end
  44        def rescue_from(*klasses, &block)
  45          options = klasses.extract_options!
  46 
  47          unless options.has_key?(:with)
  48            if block_given?
  49              options[:with] = block
  50            else
  51              raise ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument."
  52            end
  53          end
  54 
  55          klasses.each do |klass|
  56            key = if klass.is_a?(Class) && klass <= Exception
  57              klass.name
  58            elsif klass.is_a?(String)
  59              klass
  60            else
  61              raise ArgumentError, "#{klass} is neither an Exception nor a String"
  62            end
  63 
  64            # put the new handler at the end because the list is read in reverse
  65            rescue_handlers << [key, options[:with]]
  66          end
  67        end
  68      end
  69 
  70      # Tries to rescue the exception by looking up and calling a registered handler.
  71      def rescue_with_handler(exception)
  72        if handler = handler_for_rescue(exception)
  73          handler.arity != 0 ? handler.call(exception) : handler.call
  74          true # don't rely on the return value of the handler
  75        end
  76      end
  77 
  78      def handler_for_rescue(exception)
  79        # We go from right to left because pairs are pushed onto rescue_handlers
  80        # as rescue_from declarations are found.
  81        _, rescuer = Array(rescue_handlers).reverse.detect do |klass_name, handler|
  82          # The purpose of allowing strings in rescue_from is to support the
  83          # declaration of handler associations for exception classes whose
  84          # definition is yet unknown.
  85          #
  86          # Since this loop needs the constants it would be inconsistent to
  87          # assume they should exist at this point. An early raised exception
  88          # could trigger some other handler and the array could include
  89          # precisely a string whose corresponding constant has not yet been
  90          # seen. This is why we are tolerant to unknown constants.
  91          #
  92          # Note that this tolerance only matters if the exception was given as
  93          # a string, otherwise a NameError will be raised by the interpreter
  94          # itself when rescue_from CONSTANT is executed.
  95          klass = self.class.const_get(klass_name) rescue nil
  96          klass ||= klass_name.constantize rescue nil
  97          exception.is_a?(klass) if klass
  98        end
  99 
 100        case rescuer
 101        when Symbol
 102          method(rescuer)
 103        when Proc
 104          rescuer.bind(self)
 105        end
 106      end
 107    end
 108  end