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

Changeset 7597

Show
Ignore:
Timestamp:
09/23/07 21:56:22 (10 months ago)
Author:
bitsweat
Message:

Introduce ActionController::Base.rescue_from to declare exception-handling methods. Cleaner style than the case-heavy rescue_action_in_public. Closes #9449.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/actionpack/CHANGELOG

    r7596 r7597  
    11*SVN* 
     2 
     3* Introduce ActionController::Base.rescue_from to declare exception-handling methods. Cleaner style than the case-heavy rescue_action_in_public.  #9449 [norbert] 
    24 
    35* Rename some RequestForgeryProtection methods.  The class method is now #protect_from_forgery, and the default parameter is now 'authenticity_token'.  [Rick] 
  • trunk/actionpack/lib/action_controller/rescue.rb

    r7592 r7597  
    4242      base.rescue_templates.update DEFAULT_RESCUE_TEMPLATES 
    4343 
     44      base.class_inheritable_hash :rescue_handlers 
     45      base.rescue_handlers = {} 
     46 
    4447      base.extend(ClassMethods) 
    4548      base.class_eval do 
     
    5154      def process_with_exception(request, response, exception) 
    5255        new.process(request, response, :rescue_action, exception) 
     56      end 
     57 
     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      # 
     61      # class ApplicationController < ActionController::Base 
     62      #   rescue_from User::NotAuthorized, :with => :deny_access # self defined exception 
     63      #   rescue_from ActiveRecord::RecordInvalid, :with => :show_errors 
     64      # 
     65      #   protected 
     66      #     def deny_access 
     67      #       ... 
     68      #     end 
     69      # 
     70      #     def show_errors(exception) 
     71      #       exception.record.new_record? ? ... 
     72      #     end 
     73      # end 
     74      def rescue_from(*klasses) 
     75        options = klasses.extract_options! 
     76        unless options.has_key?(:with) # allow nil 
     77          raise ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument." 
     78        end 
     79 
     80        klasses.each do |klass| 
     81          rescue_handlers[klass.name] = options[:with] 
     82        end 
    5383      end 
    5484    end 
     
    5989        log_error(exception) if logger 
    6090        erase_results if performed? 
     91 
     92        return if rescue_action_with_handler(exception) 
    6193 
    6294        # Let the exception alter the response if it wants. 
     
    88120      end 
    89121 
    90  
    91122      # Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>).  By 
    92123      # default will call render_optional_error_file.  Override this method to provide more user friendly error messages.s 
     
    98129      # or just return headers if no such file exists. For example, if a 500 error is  
    99130      # being handled Rails will first attempt to render the file at <tt>public/500.html</tt>.  
    100       # If the file doesn't exist, the body of the response will be left empty 
     131      # If the file doesn't exist, the body of the response will be left empty. 
    101132      def render_optional_error_file(status_code) 
    102133        status = interpret_status(status_code) 
     
    130161      end 
    131162 
     163      # Tries to rescue the exception by looking up and calling a registered handler. 
     164      def rescue_action_with_handler(exception) 
     165        if handler = handler_for_rescue(exception) 
     166          if handler.arity != 0 
     167            handler.call(exception) 
     168          else 
     169            handler.call 
     170          end 
     171          true # don't rely on the return value of the handler 
     172        end 
     173      end 
     174 
    132175    private 
    133176      def perform_action_with_rescue #:nodoc: 
     
    147190      def response_code_for_rescue(exception) 
    148191        rescue_responses[exception.class.name] 
     192      end 
     193 
     194      def handler_for_rescue(exception) 
     195        if handler = rescue_handlers[exception.class.name] 
     196          method(handler) 
     197        end 
    149198      end 
    150199 
  • trunk/actionpack/test/controller/rescue_test.rb

    r7181 r7597  
    44 
    55class RescueController < ActionController::Base 
     6  class NotAuthorized < StandardError 
     7  end 
     8 
     9  class RecordInvalid < StandardError 
     10  end 
     11 
     12  rescue_from NotAuthorized, :with => :deny_access 
     13  rescue_from RecordInvalid, :with => :show_errors 
     14 
    615  def raises 
    716    render :text => 'already rendered' 
     
    1625    raise ActionController::NotImplemented.new(:get, :put) 
    1726  end 
     27 
     28  def not_authorized 
     29    raise NotAuthorized 
     30  end 
     31 
     32  def record_invalid 
     33    raise RecordInvalid 
     34  end 
    1835   
    19   def missing_template; end 
     36  def missing_template 
     37  end 
     38 
     39  protected 
     40    def deny_access 
     41      head :forbidden 
     42    end 
     43 
     44    def show_errors(exception) 
     45      head :unprocessable_entity 
     46    end 
    2047end 
    21  
    2248 
    2349class RescueTest < Test::Unit::TestCase 
     
    3965  end 
    4066 
    41  
    4267  def test_rescue_action_locally_if_all_requests_local 
    4368    @controller.expects(:local_request?).never 
     
    7095  end 
    7196 
    72  
    7397  def test_rescue_action_in_public_with_error_file 
    7498    with_rails_root FIXTURE_PUBLIC do 
     
    93117    assert_equal ' ', @response.body 
    94118  end 
    95  
    96119 
    97120  def test_rescue_unknown_action_in_public_with_error_file 
     
    117140    assert_equal ' ', @response.body 
    118141  end 
    119  
    120142 
    121143  def test_rescue_missing_template_in_public 
     
    130152  end 
    131153 
    132  
    133154  def test_rescue_action_locally 
    134155    get :raises 
     
    138159    assert @response.body.include?("don't panic"), "Response should include exception message." 
    139160  end 
    140  
    141161 
    142162  def test_local_request_when_remote_addr_is_localhost 
     
    153173    end 
    154174  end 
    155  
    156175 
    157176  def test_rescue_responses 
     
    183202  end 
    184203 
    185  
    186204  def test_clean_backtrace 
    187205    with_rails_root nil do 
     
    216234    assert_response :method_not_allowed 
    217235    assert_equal "GET, HEAD, PUT", @response.headers['Allow'] 
     236  end 
     237 
     238  def test_rescue_handler 
     239    get :not_authorized 
     240    assert_response :forbidden 
     241  end 
     242 
     243  def test_rescue_handler_with_argument 
     244    @controller.expects(:show_errors).once.with { |e| e.is_a?(Exception) } 
     245    get :record_invalid 
    218246  end 
    219247