Ticket #10727: activesupport_callbacks.2.diff
| File activesupport_callbacks.2.diff, 24.8 kB (added by josh, 6 months ago) |
|---|
-
activesupport/test/callbacks_test.rb
old new 1 require 'abstract_unit' 2 3 class Record 4 include ActiveSupport::Callbacks 5 6 define_callbacks :before_save, :after_save 7 8 class << self 9 def callback_symbol(callback_method) 10 returning("#{callback_method}_method") do |method_name| 11 define_method(method_name) do 12 history << [callback_method, :symbol] 13 end 14 end 15 end 16 17 def callback_string(callback_method) 18 "history << [#{callback_method.to_sym.inspect}, :string]" 19 end 20 21 def callback_proc(callback_method) 22 Proc.new { |model| model.history << [callback_method, :proc] } 23 end 24 25 def callback_object(callback_method) 26 klass = Class.new 27 klass.send(:define_method, callback_method) do |model| 28 model.history << [callback_method, :object] 29 end 30 klass.new 31 end 32 end 33 34 def history 35 @history ||= [] 36 end 37 end 38 39 class Person < Record 40 [:before_save, :after_save].each do |callback_method| 41 callback_method_sym = callback_method.to_sym 42 send(callback_method, callback_symbol(callback_method_sym)) 43 send(callback_method, callback_string(callback_method_sym)) 44 send(callback_method, callback_proc(callback_method_sym)) 45 send(callback_method, callback_object(callback_method_sym)) 46 send(callback_method) { |model| model.history << [callback_method_sym, :block] } 47 end 48 49 def save 50 run_callbacks(:before_save) 51 run_callbacks(:after_save) 52 end 53 end 54 55 class ConditionalPerson < Record 56 before_save Proc.new { |r| r.history << [:before_save, :proc] }, :if => Proc.new { |r| true } 57 before_save Proc.new { |r| r.history << "b00m" }, :if => Proc.new { |r| false } 58 before_save Proc.new { |r| r.history << [:before_save, :proc] }, :unless => Proc.new { |r| false } 59 before_save Proc.new { |r| r.history << "b00m" }, :unless => Proc.new { |r| true } 60 61 def save 62 run_callbacks(:before_save) 63 run_callbacks(:after_save) 64 end 65 end 66 67 class CallbacksTest < Test::Unit::TestCase 68 def test_save_person 69 person = Person.new 70 assert_equal [], person.history 71 person.save 72 assert_equal [ 73 [:before_save, :symbol], 74 [:before_save, :string], 75 [:before_save, :proc], 76 [:before_save, :object], 77 [:before_save, :block], 78 [:after_save, :symbol], 79 [:after_save, :string], 80 [:after_save, :proc], 81 [:after_save, :object], 82 [:after_save, :block] 83 ], person.history 84 end 85 end 86 87 class ConditionalCallbackTest < Test::Unit::TestCase 88 def test_save_conditional_person 89 person = ConditionalPerson.new 90 person.save 91 assert_equal [ 92 [:before_save, :proc], 93 [:before_save, :proc] 94 ], person.history 95 end 96 end -
activesupport/test/test_test.rb
old new 79 79 teardown :foo, :sentinel, :foo 80 80 81 81 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) 83 83 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) 85 85 end 86 86 87 87 protected … … 104 104 teardown :bar 105 105 106 106 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) 108 108 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) 110 110 end 111 111 112 112 protected -
activesupport/lib/active_support/callbacks.rb
old new 1 module ActiveSupport 2 module Callbacks 3 class Callback 4 def self.run(callbacks, object, options = {}) 5 enumerator = options.delete(:enumerator) || :each 6 termination = options.delete(:terminate_on) 7 8 callbacks.send(enumerator) do |callback| 9 callback.call(object) 10 return result if !termination.nil? && result == termination 11 end 12 end 13 14 attr_reader :kind, :method, :identifier, :options 15 16 def initialize(kind, method, options = {}) 17 @kind = kind 18 @method = method 19 @identifier = options.delete(:identifier) 20 @options = options 21 end 22 23 def call(object) 24 evaluate_method(method, object) if should_run_callback?(object) 25 end 26 27 private 28 def evaluate_method(method, object) 29 case method 30 when Symbol 31 object.send(method) 32 when String 33 eval(method, object.instance_eval { binding }) 34 when Proc, Method 35 method.call(object) 36 else 37 if method.respond_to?(kind) 38 method.send(kind, object) 39 else 40 raise ArgumentError, 41 "Callbacks must be a symbol denoting the method to call, a string to be evaluated, " + 42 "a block to be invoked, or an object responding to the callback method." 43 end 44 end 45 end 46 47 def should_run_callback?(object) 48 if options[:if] 49 evaluate_method(options[:if], object) 50 elsif options[:unless] 51 !evaluate_method(options[:unless], object) 52 else 53 true 54 end 55 end 56 end 57 58 def self.included(base) 59 base.extend ClassMethods 60 end 61 62 module ClassMethods 63 def define_callbacks(*callbacks) 64 callbacks.each do |callback| 65 class_eval <<-"end_eval" 66 def self.#{callback}(*methods, &block) 67 options = methods.extract_options! 68 methods << block if block_given? 69 callbacks = methods.map { |method| Callback.new(:#{callback}, method, options) } 70 (@#{callback}_callbacks ||= []).concat callbacks 71 end 72 73 def self.#{callback}_callback_chain 74 @#{callback}_callbacks ||= [] 75 76 if superclass.respond_to?(:#{callback}_callback_chain) 77 superclass.#{callback}_callback_chain + @#{callback}_callbacks 78 else 79 @#{callback}_callbacks 80 end 81 end 82 end_eval 83 end 84 end 85 end 86 87 def run_callbacks(kind, options = {}) 88 Callback.run(self.class.send("#{kind}_callback_chain"), self, options) 89 end 90 end 91 end -
activesupport/lib/active_support/testing/setup_and_teardown.rb
old new 2 2 module Testing 3 3 module SetupAndTeardown 4 4 def self.included(base) 5 base.extend ClassMethods 5 base.send :include, ActiveSupport::Callbacks 6 base.define_callbacks :setup, :teardown 6 7 7 8 begin 8 9 require 'mocha' … … 12 13 end 13 14 end 14 15 15 module ClassMethods16 def setup(*method_names, &block)17 method_names << block if block_given?18 (@setup_callbacks ||= []).concat method_names19 end20 21 def teardown(*method_names, &block)22 method_names << block if block_given?23 (@teardown_callbacks ||= []).concat method_names24 end25 26 def setup_callback_chain27 @setup_callbacks ||= []28 29 if superclass.respond_to?(:setup_callback_chain)30 superclass.setup_callback_chain + @setup_callbacks31 else32 @setup_callbacks33 end34 end35 36 def teardown_callback_chain37 @teardown_callbacks ||= []38 39 if superclass.respond_to?(:teardown_callback_chain)40 superclass.teardown_callback_chain + @teardown_callbacks41 else42 @teardown_callbacks43 end44 end45 end46 47 16 # This redefinition is unfortunate but test/unit shows us no alternative. 48 17 def run_with_callbacks(result) #:nodoc: 49 18 return if @method_name.to_s == "default_test" … … 63 32 ensure 64 33 begin 65 34 teardown 66 run_callbacks :teardown, : reverse_each35 run_callbacks :teardown, :enumerator => :reverse_each 67 36 rescue Test::Unit::AssertionFailedError => e 68 37 add_failure(e.message, e.backtrace) 69 38 rescue *Test::Unit::TestCase::PASSTHROUGH_EXCEPTIONS … … 98 67 ensure 99 68 begin 100 69 teardown 101 run_callbacks :teardown, : reverse_each70 run_callbacks :teardown, :enumerator => :reverse_each 102 71 rescue Test::Unit::AssertionFailedError => e 103 72 add_failure(e.message, e.backtrace) 104 73 rescue StandardError, ScriptError … … 111 80 result.add_run 112 81 yield(Test::Unit::TestCase::FINISHED, name) 113 82 end 114 115 protected116 def run_callbacks(kind, enumerator = :each)117 self.class.send("#{kind}_callback_chain").send(enumerator) do |callback|118 case callback119 when Proc; callback.call(self)120 when String, Symbol; send!(callback)121 else raise ArgumentError, "Unrecognized callback #{callback.inspect}"122 end123 end124 end125 83 end 126 84 end 127 85 end -
activesupport/lib/active_support.rb
old new 26 26 require 'active_support/vendor' 27 27 require 'active_support/basic_object' 28 28 require 'active_support/inflector' 29 require 'active_support/callbacks' 29 30 30 31 require 'active_support/core_ext' 31 32 -
activerecord/test/validations_test.rb
old new 1017 1017 1018 1018 def test_invalid_validator 1019 1019 Topic.validate 3 1020 assert_raise(A ctiveRecord::ActiveRecordError) { t = Topic.create }1020 assert_raise(ArgumentError) { t = Topic.create } 1021 1021 end 1022 1022 1023 1023 def test_throw_away_typing -
activerecord/lib/active_record/validations.rb
old new 279 279 alias_method_chain :save!, :validation 280 280 alias_method_chain :update_attribute, :validation_skipping 281 281 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 282 301 end 283 302 284 303 # All of the following validations are defined in the class scope of the model that you're interested in validating. … … 324 343 # end 325 344 # 326 345 # 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 end331 346 332 def validate_on_create(*methods, &block)333 methods << block if block_given?334 write_inheritable_set(:validate_on_create, methods)335 end336 337 def validate_on_update(*methods, &block)338 methods << block if block_given?339 write_inheritable_set(:validate_on_update, methods)340 end341 342 def condition_block?(condition)343 condition.respond_to?("call") && (condition.arity == 1 || condition.arity == -1)344 end345 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 condition350 when Symbol; record.send(condition)351 when String; eval(condition, record.instance_eval { binding })352 else353 if condition_block?(condition)354 condition.call(record)355 else356 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 end362 end363 end364 365 347 # Validates each attribute against a block. 366 348 # 367 349 # class Person < ActiveRecord::Base … … 379 361 # method, proc or string should return or evaluate to a true or false value. 380 362 # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should 381 363 # 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. 383 365 def validates_each(*attrs) 384 366 options = attrs.extract_options!.symbolize_keys 385 367 attrs = attrs.flatten 386 368 387 369 # 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 396 375 end 397 376 end 398 377 end … … 515 494 516 495 # can't use validates_each here, because it cannot cope with nonexistent attributes, 517 496 # 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 523 500 end 524 501 525 502 # Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time: … … 911 888 end 912 889 end 913 890 914 915 891 private 916 def write_inheritable_set(key, methods)917 existing_methods = read_inheritable_attribute(key) || []918 write_inheritable_attribute(key, existing_methods | methods)919 end920 921 892 def validation_method(on) 922 893 case on 923 894 when :save then :validate … … 959 930 def valid? 960 931 errors.clear 961 932 962 run_ validations(:validate)933 run_callbacks(:validate) 963 934 validate 964 935 965 936 if new_record? 966 run_ validations(:validate_on_create)937 run_callbacks(:validate_on_create) 967 938 validate_on_create 968 939 else 969 run_ validations(:validate_on_update)940 run_callbacks(:validate_on_update) 970 941 validate_on_update 971 942 end 972 943 … … 990 961 # Overwrite this method for validation checks used only on updates. 991 962 def validate_on_update # :doc: 992 963 end 993 994 private995 def run_validations(validation_method)996 validations = self.class.read_inheritable_attribute(validation_method.to_sym)997 if validations.nil? then return end998 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 else1008 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 end1014 end1015 end1016 1017 def validation_block?(validation)1018 validation.respond_to?("call") && (validation.arity == 1 || validation.arity == -1)1019 end1020 1021 def validation_class?(validation, validation_method)1022 validation.respond_to?(validation_method)1023 end1024 964 end 1025 965 end -
activerecord/lib/active_record/callbacks.rb
old new 183 183 base.send :alias_method_chain, method, :callbacks 184 184 end 185 185 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 194 188 end 195 189 196 190 # Is called when the object was instantiated by one of the finders, like <tt>Base.find</tt>. … … 301 295 def callback(method) 302 296 notify(method) 303 297 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, :terminate_on => false) 299 300 if result != false && respond_to_without_attributes?(method) 301 result = send(method) 320 302 end 321 303 322 result = send(method) if respond_to_without_attributes?(method)323 324 304 return result 325 305 end 326 306 327 def callbacks_for(method)328 self.class.read_inheritable_attribute(method.to_sym) or []329 end330 331 def invoke_and_notify(method)332 notify(method)333 send(method) if respond_to_without_attributes?(method)334 end335 336 307 def notify(method) #:nodoc: 337 308 self.class.changed 338 309 self.class.notify_observers(method, self) -
actionpack/test/controller/dispatcher_test.rb
old new 11 11 @output = StringIO.new 12 12 ENV['REQUEST_METHOD'] = 'GET' 13 13 14 Dispatcher. callbacks[:prepare].clear14 Dispatcher.instance_variable_set("@prepare_dispatch_callbacks", []) 15 15 @dispatcher = Dispatcher.new(@output) 16 16 end 17 17 -
actionpack/lib/action_controller/dispatcher.rb
old new 11 11 new(output).dispatch_cgi(cgi, session_options) 12 12 end 13 13 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_names18 callbacks[:before] << block if block_given?19 end20 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_names25 callbacks[:after] << block if block_given?26 end27 28 14 # Add a preparation callback. Preparation callbacks are run before every 29 15 # request in development mode, and before the first request in production 30 16 # mode. … … 34 20 # existing callback. Passing an identifier is a suggested practice if the 35 21 # code adding a preparation block may be reloaded. 36 22 def to_prepare(identifier = nil, &block) 23 @prepare_dispatch_callbacks ||= [] 24 callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier) 25 37 26 # 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 44 31 else 45 callbacks[:prepare] << block32 @prepare_dispatch_callbacks.concat([callback]) 46 33 end 47 34 end 48 35 … … 90 77 cattr_accessor :error_file_path 91 78 self.error_file_path = "#{::RAILS_ROOT}/public" if defined? ::RAILS_ROOT 92 79 93 cattr_accessor :callbacks94 self.callbacks = Hash.new { |h, k| h[k] = [] }95 96 80 cattr_accessor :unprepared 97 81 self.unprepared = true 98 82 83 include ActiveSupport::Callbacks 84 define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch 99 85 100 86 before_dispatch :reload_application 101 87 before_dispatch :prepare_application … … 115 101 def dispatch 116 102 @@guard.synchronize do 117 103 begin 118 run_callbacks :before 104 run_callbacks :before_dispatch 119 105 handle_request 120 106 rescue Exception => exception 121 107 failsafe_rescue exception 122 108 ensure 123 run_callbacks :after ,:reverse_each109 run_callbacks :after_dispatch, :enumerator => :reverse_each 124 110 end 125 111 end 126 112 end … … 152 138 ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord) 153 139 154 140 if unprepared || force 155 run_callbacks :prepare 141 run_callbacks :prepare_dispatch 156 142 self.unprepared = false 157 143 end 158 144 end … … 177 163 @controller.process(@request, @response).out(@output) 178 164 end 179 165 180 def run_callbacks(kind, enumerator = :each)181 callbacks[kind].send!(enumerator) do |callback|182 case callback183 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 end188 end189 end190 191 166 def failsafe_rescue(exception) 192 167 self.class.failsafe_response(@output, '500 Internal Server Error', exception) do 193 168 if @controller ||= defined?(::ApplicationController) ? ::ApplicationController : Base