Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #10079: enhanced_rescue_from.diff

File enhanced_rescue_from.diff, 11.4 kB (added by fxn, 7 months ago)
  • actionpack/test/controller/rescue_test.rb

    old new  
    55class RescueController < ActionController::Base 
    66  class NotAuthorized < StandardError 
    77  end 
     8  class NotAuthorizedToRescueAsString < StandardError 
     9  end 
    810 
    911  class RecordInvalid < StandardError 
    1012  end 
     13  class RecordInvalidToRescueAsString < StandardError 
     14  end 
    1115 
    1216  class NotAllowed < StandardError 
    1317  end 
    14  
     18  class NotAllowedToRescueAsString < StandardError 
     19  end 
     20   
    1521  class InvalidRequest < StandardError 
    1622  end 
     23  class InvalidRequestToRescueAsString < StandardError 
     24  end 
    1725 
    1826  class BadGateway < StandardError 
    1927  end 
     28  class BadGatewayToRescueAsString < StandardError 
     29  end 
    2030 
    2131  class ResourceUnavailable < StandardError 
    2232  end 
     33  class ResourceUnavailableToRescueAsString < StandardError 
     34  end 
    2335 
     36  # We use a fully-qualified name in some strings, and a relative constant 
     37  # name in some other to test correct handling of both cases. 
     38   
    2439  rescue_from NotAuthorized, :with => :deny_access 
     40  rescue_from 'RescueController::NotAuthorizedToRescueAsString', :with => :deny_access 
     41   
    2542  rescue_from RecordInvalid, :with => :show_errors 
     43  rescue_from 'RescueController::RecordInvalidToRescueAsString', :with => :show_errors 
    2644 
    2745  rescue_from NotAllowed, :with => proc { head :forbidden } 
     46  rescue_from 'RescueController::NotAllowedToRescueAsString', :with => proc { head :forbidden } 
     47   
    2848  rescue_from InvalidRequest, :with => proc { |exception| render :text => exception.message } 
     49  rescue_from 'InvalidRequestToRescueAsString', :with => proc { |exception| render :text => exception.message } 
    2950 
    3051  rescue_from BadGateway do 
    3152    head :status => 502 
    3253  end 
     54  rescue_from 'BadGatewayToRescueAsString' do 
     55    head :status => 502 
     56  end 
    3357 
    3458  rescue_from ResourceUnavailable do |exception| 
    3559    render :text => exception.message 
    3660  end 
     61  rescue_from 'ResourceUnavailableToRescueAsString' do |exception| 
     62    render :text => exception.message 
     63  end 
    3764 
    3865  def raises 
    3966    render :text => 'already rendered' 
     
    5178  def not_authorized 
    5279    raise NotAuthorized 
    5380  end 
    54  
     81  def not_authorized_raise_as_string 
     82    raise NotAuthorizedToRescueAsString 
     83  end 
     84   
    5585  def not_allowed 
    5686    raise NotAllowed 
    5787  end 
     88  def not_allowed_raise_as_string 
     89    raise NotAllowedToRescueAsString 
     90  end 
    5891 
    5992  def invalid_request 
    6093    raise InvalidRequest 
    6194  end 
     95  def invalid_request_raise_as_string 
     96    raise InvalidRequestToRescueAsString 
     97  end 
    6298 
    6399  def record_invalid 
    64100    raise RecordInvalid 
    65101  end 
     102  def record_invalid_raise_as_string 
     103    raise RecordInvalidToRescueAsString 
     104  end 
    66105   
    67106  def bad_gateway 
    68107    raise BadGateway 
    69108  end 
     109  def bad_gateway_raise_as_string 
     110    raise BadGatewayToRescueAsString 
     111  end 
    70112 
    71113  def resource_unavailable 
    72114    raise ResourceUnavailable 
    73115  end 
    74  
     116  def resource_unavailable_raise_as_string 
     117    raise ResourceUnavailableToRescueAsString 
     118  end 
     119   
    75120  def missing_template 
    76121  end 
    77122 
     
    278323    get :not_authorized 
    279324    assert_response :forbidden 
    280325  end 
     326  def test_rescue_handler_string 
     327    get :not_authorized_raise_as_string 
     328    assert_response :forbidden 
     329  end 
    281330 
    282331  def test_rescue_handler_with_argument 
    283332    @controller.expects(:show_errors).once.with { |e| e.is_a?(Exception) } 
    284333    get :record_invalid 
    285334  end 
     335  def test_rescue_handler_with_argument_as_string 
     336    @controller.expects(:show_errors).once.with { |e| e.is_a?(Exception) } 
     337    get :record_invalid_raise_as_string 
     338  end 
    286339 
    287340  def test_proc_rescue_handler 
    288341    get :not_allowed 
    289342    assert_response :forbidden 
    290343  end 
     344  def test_proc_rescue_handler_as_string 
     345    get :not_allowed_raise_as_string 
     346    assert_response :forbidden 
     347  end 
    291348 
    292349  def test_proc_rescue_handle_with_argument 
    293350    get :invalid_request 
    294351    assert_equal "RescueController::InvalidRequest", @response.body 
    295352  end 
     353  def test_proc_rescue_handle_with_argument_as_string 
     354    get :invalid_request_raise_as_string 
     355    assert_equal "RescueController::InvalidRequestToRescueAsString", @response.body 
     356  end 
    296357 
    297358  def test_block_rescue_handler 
    298359    get :bad_gateway 
    299360    assert_response 502 
    300361  end 
     362  def test_block_rescue_handler_as_string 
     363    get :bad_gateway_raise_as_string 
     364    assert_response 502 
     365  end 
    301366 
    302367  def test_block_rescue_handler_with_argument 
    303368    get :resource_unavailable 
    304369    assert_equal "RescueController::ResourceUnavailable", @response.body 
    305370  end 
    306371 
     372  def test_block_rescue_handler_with_argument_as_string 
     373    get :resource_unavailable_raise_as_string 
     374    assert_equal "RescueController::ResourceUnavailableToRescueAsString", @response.body 
     375  end 
     376 
     377   
    307378  protected 
    308379    def with_all_requests_local(local = true) 
    309380      old_local, ActionController::Base.consider_all_requests_local = 
     
    339410    end 
    340411end 
    341412 
     413class ExceptionInheritanceRescueController < ActionController::Base 
     414 
     415  class ParentException < StandardError 
     416  end 
     417 
     418  class ChildException < ParentException 
     419  end 
     420 
     421  class GrandchildException < ChildException 
     422  end 
     423 
     424  rescue_from ChildException,      :with => lambda { head :ok } 
     425  rescue_from ParentException,     :with => lambda { head :created } 
     426  rescue_from GrandchildException, :with => lambda { head :no_content } 
     427   
     428  def raise_parent_exception 
     429    raise ParentException 
     430  end 
     431   
     432  def raise_child_exception 
     433    raise ChildException 
     434  end 
     435   
     436  def raise_grandchild_exception 
     437    raise GrandchildException 
     438  end 
     439end 
     440   
     441class ExceptionInheritanceRescueTest < Test::Unit::TestCase 
     442  
     443  def setup 
     444    @controller = ExceptionInheritanceRescueController.new 
     445    @request    = ActionController::TestRequest.new 
     446    @response   = ActionController::TestResponse.new 
     447  end 
     448   
     449  def test_bottom_first 
     450    get :raise_grandchild_exception 
     451    assert_response :no_content 
     452  end 
     453   
     454  def test_inheritance_works 
     455    get :raise_child_exception 
     456    assert_response :created 
     457  end 
     458end 
     459 
     460class ControllerInheritanceRescueController < ExceptionInheritanceRescueController 
     461  class FirstExceptionInChildController < StandardError 
     462  end 
     463 
     464  class SecondExceptionInChildController < StandardError 
     465  end 
     466   
     467  rescue_from FirstExceptionInChildController, 'SecondExceptionInChildController', :with => lambda { head :gone } 
     468   
     469  def raise_first_exception_in_child_controller 
     470    raise FirstExceptionInChildController 
     471  end 
     472 
     473  def raise_second_exception_in_child_controller 
     474    raise SecondExceptionInChildController 
     475  end 
     476end 
     477 
     478class ControllerInheritanceRescueControllerTest < Test::Unit::TestCase 
     479  
     480  def setup 
     481    @controller = ControllerInheritanceRescueController.new 
     482    @request    = ActionController::TestRequest.new 
     483    @response   = ActionController::TestResponse.new 
     484  end 
     485   
     486  def test_first_exception_in_child_controller 
     487    get :raise_first_exception_in_child_controller 
     488    assert_response :gone 
     489  end 
     490 
     491  def test_second_exception_in_child_controller 
     492    get :raise_second_exception_in_child_controller 
     493    assert_response :gone 
     494  end 
     495   
     496  def test_exception_in_parent_controller 
     497    get :raise_parent_exception 
     498    assert_response :created 
     499  end 
     500end 
    342501end # uses_mocha 
  • actionpack/lib/action_controller/rescue.rb

    old new  
    4141      base.rescue_templates = Hash.new(DEFAULT_RESCUE_TEMPLATE) 
    4242      base.rescue_templates.update DEFAULT_RESCUE_TEMPLATES 
    4343 
    44       base.class_inheritable_hash :rescue_handlers 
    45       base.rescue_handlers = {} 
     44      base.class_inheritable_array :rescue_handlers 
     45      base.rescue_handlers = [] 
    4646 
    4747      base.extend(ClassMethods) 
    4848      base.class_eval do 
     
    5555        new.process(request, response, :rescue_action, exception) 
    5656      end 
    5757 
    58       # Rescue exceptions raised in controller actions by passing at least one exception class and a :with option that contains the name of the method to be called to respond to the exception. 
    59       # Handler methods that take one argument will be called with the exception, so that the exception can be inspected when dealing with it. 
    60       # 
     58      # Rescue exceptions raised in controller actions. 
     59      #  
     60      # <tt>rescue_from</tt> receives a series of exception classes or class 
     61      # names, and a trailing :with option with the name of a method or a Proc 
     62      # object to be called to handle them. Alternatively a block can be given. 
     63      #  
     64      # Handlers that take one argument will be called with the exception, so 
     65      # that the exception can be inspected when dealing with it. 
     66      #  
     67      # Handlers are inherited. They are searched from right to left, from 
     68      # bottom to top, and up the hierarchy. The handler of the first class for 
     69      # which exception.is_a?(klass) holds true is the one invoked, if any. 
     70      #  
    6171      # class ApplicationController < ActionController::Base 
    6272      #   rescue_from User::NotAuthorized, :with => :deny_access # self defined exception 
    6373      #   rescue_from ActiveRecord::RecordInvalid, :with => :show_errors 
    64       # 
     74      #  
     75      #   rescue_from 'MyAppError::Base' do |exception| 
     76      #     render :xml => exception, :status => 500 
     77      #   end 
     78      #  
    6579      #   protected 
    6680      #     def deny_access 
    6781      #       ... 
    6882      #     end 
    69       # 
     83      #  
    7084      #     def show_errors(exception) 
    7185      #       exception.record.new_record? ? ... 
    7286      #     end 
     
    7892        end 
    7993 
    8094        klasses.each do |klass| 
    81           rescue_handlers[klass.name] = options[:with] 
     95          key = if klass.is_a?(Class) && klass <= Exception 
     96            klass.name 
     97          elsif klass.is_a?(String) 
     98            klass 
     99          else 
     100            raise(ArgumentError, "#{klass} is nor an Exception neither a String") 
     101          end 
     102             
     103          # Order is important, we put the pair at the end. When dealing with an 
     104          # exception we will follow the documented order going from right to left. 
     105          rescue_handlers << [key, options[:with]] 
    82106        end 
    83107      end 
    84108    end 
     
    192216      end 
    193217 
    194218      def handler_for_rescue(exception) 
    195         case handler = rescue_handlers[exception.class.name] 
     219        # We go from right to left because pairs are pushed onto rescue_handlers 
     220        # as rescue_from declarations are found. 
     221        _, handler = *rescue_handlers.reverse.detect do |klass_name, handler| 
     222          # The purpose of allowing strings in rescue_from is to support the 
     223          # declaration of handler associations for exception classes whose 
     224          # definition is yet unknown. 
     225          #  
     226          # Since this loop needs the constants it would be inconsistent to 
     227          # assume they should exist at this point. An early raised exception 
     228          # could trigger some other handler and the array could include 
     229          # precisely a string whose corresponding constant has not yet been 
     230          # seen. This is why we are tolerant to unkown constants. 
     231          #  
     232          # Note that this tolerance only matters if the exception was given as 
     233          # a string, otherwise a NameError will be raised by the interpreter 
     234          # itself when rescue_from CONSTANT is executed. 
     235          klass = self.class.const_get(klass_name) rescue nil 
     236          klass ||= klass_name.constantize rescue nil 
     237          exception.is_a?(klass) if klass 
     238        end 
     239 
     240        case handler 
    196241        when Symbol 
    197242          method(handler) 
    198243        when Proc