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

Changeset 8664

Show
Ignore:
Timestamp:
01/19/08 02:44:45 (4 months ago)
Author:
bitsweat
Message:

Extract ActiveSupport::Callbacks from Active Record, test case setup and teardown, and ActionController::Dispatcher. Closes #10727.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/actionpack/lib/action_controller/dispatcher.rb

    r8488 r8664  
    1212      end 
    1313 
    14       # Declare a block to be called before each dispatch. 
    15       # Run in the order declared. 
    16       def before_dispatch(*method_names, &block) 
    17         callbacks[:before].concat method_names 
    18         callbacks[:before] << block if block_given? 
    19       end 
    20  
    21       # Declare a block to be called after each dispatch. 
    22       # Run in reverse of the order declared. 
    23       def after_dispatch(*method_names, &block) 
    24         callbacks[:after].concat method_names 
    25         callbacks[:after] << block if block_given? 
    26       end 
    27  
    2814      # Add a preparation callback. Preparation callbacks are run before every 
    2915      # request in development mode, and before the first request in production 
     
    3521      # code adding a preparation block may be reloaded. 
    3622      def to_prepare(identifier = nil, &block) 
     23        @prepare_dispatch_callbacks ||= [] 
     24        callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier) 
     25 
    3726        # Already registered: update the existing callback 
    38         if identifier 
    39           if callback = callbacks[:prepare].assoc(identifier) 
    40             callback[1] = block 
    41           else 
    42             callbacks[:prepare] << [identifier, block] 
    43           end 
     27        # TODO: Ruby one liner for Array#find returning index 
     28        if identifier && callback_for_identifier = @prepare_dispatch_callbacks.find { |c| c.identifier == identifier } 
     29          index = @prepare_dispatch_callbacks.index(callback_for_identifier) 
     30          @prepare_dispatch_callbacks[index] = callback 
    4431        else 
    45           callbacks[:prepare] << block 
     32          @prepare_dispatch_callbacks.concat([callback]) 
    4633        end 
    4734      end 
     
    9178    self.error_file_path = "#{::RAILS_ROOT}/public" if defined? ::RAILS_ROOT 
    9279 
    93     cattr_accessor :callbacks 
    94     self.callbacks = Hash.new { |h, k| h[k] = [] } 
    95  
    9680    cattr_accessor :unprepared 
    9781    self.unprepared = true 
    9882 
     83    include ActiveSupport::Callbacks 
     84    define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch 
    9985 
    10086    before_dispatch :reload_application 
     
    116102      @@guard.synchronize do 
    117103        begin 
    118           run_callbacks :before 
     104          run_callbacks :before_dispatch 
    119105          handle_request 
    120106        rescue Exception => exception 
    121107          failsafe_rescue exception 
    122108        ensure 
    123           run_callbacks :after, :reverse_each 
     109          run_callbacks :after_dispatch, :enumerator => :reverse_each 
    124110        end 
    125111      end 
     
    153139 
    154140      if unprepared || force 
    155         run_callbacks :prepare 
     141        run_callbacks :prepare_dispatch 
    156142        self.unprepared = false 
    157143      end 
     
    178164      end 
    179165 
    180       def run_callbacks(kind, enumerator = :each) 
    181         callbacks[kind].send!(enumerator) do |callback| 
    182           case callback 
    183           when Proc; callback.call(self) 
    184           when String, Symbol; send!(callback) 
    185           when Array; callback[1].call(self) 
    186           else raise ArgumentError, "Unrecognized callback #{callback.inspect}" 
    187           end 
    188         end 
    189       end 
    190  
    191166      def failsafe_rescue(exception) 
    192167        self.class.failsafe_response(@output, '500 Internal Server Error', exception) do 
  • trunk/actionpack/test/controller/dispatcher_test.rb

    r8564 r8664  
    1212    ENV['REQUEST_METHOD'] = 'GET' 
    1313 
    14     Dispatcher.callbacks[:prepare].clear 
     14    Dispatcher.instance_variable_set("@prepare_dispatch_callbacks", []) 
    1515    @dispatcher = Dispatcher.new(@output) 
    1616  end 
  • trunk/activerecord/lib/active_record/callbacks.rb

    r8301 r8664  
    184184      end 
    185185 
    186       CALLBACKS.each do |method| 
    187         base.class_eval <<-"end_eval" 
    188           def self.#{method}(*callbacks, &block) 
    189             callbacks << block if block_given? 
    190             write_inheritable_array(#{method.to_sym.inspect}, callbacks) 
    191           end 
    192         end_eval 
    193       end 
     186      base.send :include, ActiveSupport::Callbacks 
     187      base.define_callbacks *CALLBACKS 
    194188    end 
    195189 
     
    302296        notify(method) 
    303297 
    304         callbacks_for(method).each do |callback| 
    305           result = case callback 
    306             when Symbol 
    307               self.send(callback) 
    308             when String 
    309               eval(callback, binding) 
    310             when Proc, Method 
    311               callback.call(self) 
    312             else 
    313               if callback.respond_to?(method) 
    314                 callback.send(method, self) 
    315               else 
    316                 raise ActiveRecordError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method." 
    317               end 
    318           end 
    319           return false if result == false 
     298        result = run_callbacks(method) { |result, object| result == false } 
     299 
     300        if result != false && respond_to_without_attributes?(method) 
     301          result = send(method) 
    320302        end 
    321303 
    322         result = send(method) if respond_to_without_attributes?(method) 
    323  
    324304        return result 
    325       end 
    326  
    327       def callbacks_for(method) 
    328         self.class.read_inheritable_attribute(method.to_sym) or [] 
    329       end 
    330  
    331       def invoke_and_notify(method) 
    332         notify(method) 
    333         send(method) if respond_to_without_attributes?(method) 
    334305      end 
    335306 
  • trunk/activerecord/lib/active_record/validations.rb

    r8575 r8664  
    280280        alias_method_chain :update_attribute, :validation_skipping 
    281281      end 
     282 
     283      base.send :include, ActiveSupport::Callbacks 
     284 
     285      # TODO: Use helper ActiveSupport::Callbacks#define_callbacks instead 
     286      %w( validate validate_on_create validate_on_update ).each do |validation_method| 
     287        base.class_eval <<-"end_eval" 
     288          def self.#{validation_method}(*methods, &block) 
     289            options = methods.extract_options! 
     290            methods << block if block_given? 
     291            methods.map! { |method| Callback.new(:#{validation_method}, method, options) } 
     292            existing_methods = read_inheritable_attribute(:#{validation_method}) || [] 
     293            write_inheritable_attribute(:#{validation_method}, existing_methods | methods) 
     294          end 
     295 
     296          def self.#{validation_method}_callback_chain 
     297            read_inheritable_attribute(:#{validation_method}) || [] 
     298          end 
     299        end_eval 
     300      end 
    282301    end 
    283302 
     
    325344      # 
    326345      # This usage applies to #validate_on_create and #validate_on_update as well. 
    327       def validate(*methods, &block) 
    328         methods << block if block_given? 
    329         write_inheritable_set(:validate, methods) 
    330       end 
    331  
    332       def validate_on_create(*methods, &block) 
    333         methods << block if block_given? 
    334         write_inheritable_set(:validate_on_create, methods) 
    335       end 
    336  
    337       def validate_on_update(*methods, &block) 
    338         methods << block if block_given? 
    339         write_inheritable_set(:validate_on_update, methods) 
    340       end 
    341  
    342       def condition_block?(condition) 
    343         condition.respond_to?("call") && (condition.arity == 1 || condition.arity == -1) 
    344       end 
    345  
    346       # Determine from the given condition (whether a block, procedure, method or string) 
    347       # whether or not to validate the record.  See #validates_each. 
    348       def evaluate_condition(condition, record) 
    349         case condition 
    350           when Symbol; record.send(condition) 
    351           when String; eval(condition, record.instance_eval { binding }) 
    352           else 
    353             if condition_block?(condition) 
    354               condition.call(record) 
    355             else 
    356               raise( 
    357                 ActiveRecordError, 
    358                 "Validations need to be either a symbol, string (to be eval'ed), proc/method, or " + 
    359                 "class implementing a static validation method" 
    360               ) 
    361             end 
    362           end 
    363       end 
    364346 
    365347      # Validates each attribute against a block. 
     
    380362      # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should 
    381363      #   not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }).  The 
    382       #   method, proc or string should return or evaluate to a true or false value.       
     364      #   method, proc or string should return or evaluate to a true or false value. 
    383365      def validates_each(*attrs) 
    384366        options = attrs.extract_options!.symbolize_keys 
     
    386368 
    387369        # Declare the validation. 
    388         send(validation_method(options[:on] || :save)) do |record| 
    389           # Don't validate when there is an :if condition and that condition is false or there is an :unless condition and that condition is true 
    390           unless (options[:if] && !evaluate_condition(options[:if], record)) || (options[:unless] && evaluate_condition(options[:unless], record)) 
    391             attrs.each do |attr| 
    392               value = record.send(attr) 
    393               next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank]) 
    394               yield record, attr, value 
    395             end 
     370        send(validation_method(options[:on] || :save), options) do |record| 
     371          attrs.each do |attr| 
     372            value = record.send(attr) 
     373            next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank]) 
     374            yield record, attr, value 
    396375          end 
    397376        end 
     
    516495        # can't use validates_each here, because it cannot cope with nonexistent attributes, 
    517496        # while errors.add_on_empty can 
    518         send(validation_method(configuration[:on])) do |record| 
    519           unless (configuration[:if] && !evaluate_condition(configuration[:if], record)) || (configuration[:unless] && evaluate_condition(configuration[:unless], record)) 
    520             record.errors.add_on_blank(attr_names, configuration[:message]) 
    521           end 
    522         end 
     497        send(validation_method(configuration[:on]), configuration) do |record| 
     498          record.errors.add_on_blank(attr_names, configuration[:message]) 
     499        end 
    523500      end 
    524501 
     
    912889      end 
    913890 
    914  
    915891      private 
    916         def write_inheritable_set(key, methods) 
    917           existing_methods = read_inheritable_attribute(key) || [] 
    918           write_inheritable_attribute(key, existing_methods | methods) 
    919         end 
    920  
    921892        def validation_method(on) 
    922893          case on 
     
    960931      errors.clear 
    961932 
    962       run_validations(:validate) 
     933      run_callbacks(:validate) 
    963934      validate 
    964935 
    965936      if new_record? 
    966         run_validations(:validate_on_create) 
     937        run_callbacks(:validate_on_create) 
    967938        validate_on_create 
    968939      else 
    969         run_validations(:validate_on_update) 
     940        run_callbacks(:validate_on_update) 
    970941        validate_on_update 
    971942      end 
     
    991962      def validate_on_update # :doc: 
    992963      end 
    993  
    994     private 
    995       def run_validations(validation_method) 
    996         validations = self.class.read_inheritable_attribute(validation_method.to_sym) 
    997         if validations.nil? then return end 
    998         validations.each do |validation| 
    999           if validation.is_a?(Symbol) 
    1000             self.send(validation) 
    1001           elsif validation.is_a?(String) 
    1002             eval(validation, binding) 
    1003           elsif validation_block?(validation) 
    1004             validation.call(self) 
    1005           elsif validation_class?(validation, validation_method) 
    1006             validation.send(validation_method, self) 
    1007           else 
    1008             raise( 
    1009               ActiveRecordError, 
    1010               "Validations need to be either a symbol, string (to be eval'ed), proc/method, or " + 
    1011               "class implementing a static validation method" 
    1012             ) 
    1013           end 
    1014         end 
    1015       end 
    1016  
    1017       def validation_block?(validation) 
    1018         validation.respond_to?("call") && (validation.arity == 1 || validation.arity == -1) 
    1019       end 
    1020  
    1021       def validation_class?(validation, validation_method) 
    1022         validation.respond_to?(validation_method) 
    1023       end 
    1024964  end 
    1025965end 
  • trunk/activerecord/test/cases/validations_test.rb

    r8661 r8664  
    10301030  def test_invalid_validator 
    10311031    Topic.validate 3 
    1032     assert_raise(ActiveRecord::ActiveRecordError) { t = Topic.create } 
     1032    assert_raise(ArgumentError) { t = Topic.create } 
    10331033  end 
    10341034 
  • trunk/activesupport/CHANGELOG

    r8649 r8664  
    11*SVN* 
     2 
     3* Extract ActiveSupport::Callbacks from Active Record, test case setup and teardown, and ActionController::Dispatcher.  #10727 [Josh Peek] 
    24 
    35* Introducing DateTime #utc, #utc? and #utc_offset, for duck-typing compatibility with Time. Closes #10002 [Geoff Buesing] 
  • trunk/activesupport/lib/active_support.rb

    r8570 r8664  
    2727require 'active_support/basic_object' 
    2828require 'active_support/inflector' 
     29require 'active_support/callbacks' 
    2930 
    3031require 'active_support/core_ext' 
  • trunk/activesupport/lib/active_support/testing/setup_and_teardown.rb

    r8570 r8664  
    33    module SetupAndTeardown 
    44      def self.included(base) 
    5         base.extend ClassMethods 
     5        base.send :include, ActiveSupport::Callbacks 
     6        base.define_callbacks :setup, :teardown 
    67 
    78        begin 
     
    1011        rescue LoadError 
    1112          base.alias_method_chain :run, :callbacks 
    12         end 
    13       end 
    14  
    15       module ClassMethods 
    16         def setup(*method_names, &block) 
    17           method_names << block if block_given? 
    18           (@setup_callbacks ||= []).concat method_names 
    19         end 
    20  
    21         def teardown(*method_names, &block) 
    22           method_names << block if block_given? 
    23           (@teardown_callbacks ||= []).concat method_names 
    24         end 
    25  
    26         def setup_callback_chain 
    27           @setup_callbacks ||= [] 
    28  
    29           if superclass.respond_to?(:setup_callback_chain) 
    30             superclass.setup_callback_chain + @setup_callbacks 
    31           else 
    32             @setup_callbacks 
    33           end 
    34         end 
    35  
    36         def teardown_callback_chain 
    37           @teardown_callbacks ||= [] 
    38  
    39           if superclass.respond_to?(:teardown_callback_chain) 
    40             superclass.teardown_callback_chain + @teardown_callbacks 
    41           else 
    42             @teardown_callbacks 
    43           end 
    4413        end 
    4514      end 
     
    6433          begin 
    6534            teardown 
    66             run_callbacks :teardown, :reverse_each 
     35            run_callbacks :teardown, :enumerator => :reverse_each 
    6736          rescue Test::Unit::AssertionFailedError => e 
    6837            add_failure(e.message, e.backtrace) 
     
    9968            begin 
    10069              teardown 
    101               run_callbacks :teardown, :reverse_each 
     70              run_callbacks :teardown, :enumerator => :reverse_each 
    10271            rescue Test::Unit::AssertionFailedError => e 
    10372              add_failure(e.message, e.backtrace) 
     
    11281        yield(Test::Unit::TestCase::FINISHED, name) 
    11382      end 
    114  
    115       protected 
    116         def run_callbacks(kind, enumerator = :each) 
    117           self.class.send("#{kind}_callback_chain").send(enumerator) do |callback| 
    118             case callback 
    119             when Proc; callback.call(self) 
    120             when String, Symbol; send!(callback) 
    121             else raise ArgumentError, "Unrecognized callback #{callback.inspect}" 
    122             end 
    123           end 
    124         end 
    12583    end 
    12684  end 
  • trunk/activesupport/test/test_test.rb

    r8570 r8664  
    8080 
    8181  def test_inherited_setup_callbacks 
    82     assert_equal [:reset_callback_record, :foo], self.class.setup_callback_chain 
     82    assert_equal [:reset_callback_record, :foo], self.class.setup_callback_chain.map(&:method) 
    8383    assert_equal [:foo], @called_back 
    84     assert_equal [:foo, :sentinel, :foo], self.class.teardown_callback_chain 
     84    assert_equal [:foo, :sentinel, :foo], self.class.teardown_callback_chain.map(&:method) 
    8585  end 
    8686 
     
    105105 
    106106  def test_inherited_setup_callbacks 
    107     assert_equal [:reset_callback_record, :foo, :bar], self.class.setup_callback_chain 
     107    assert_equal [:reset_callback_record, :foo, :bar], self.class.setup_callback_chain.map(&:method) 
    108108    assert_equal [:foo, :bar], @called_back 
    109     assert_equal [:foo, :sentinel, :foo, :bar], self.class.teardown_callback_chain 
     109    assert_equal [:foo, :sentinel, :foo, :bar], self.class.teardown_callback_chain.map(&:method) 
    110110  end 
    111111