Ticket #10727: activesupport_callbacks.diff
| File activesupport_callbacks.diff, 19.7 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 … … 115 115 end 116 116 117 117 def sentinel 118 assert_equal [:foo, :bar, : bar, :foo], @called_back118 assert_equal [:foo, :bar, :foo], @called_back 119 119 end 120 120 end -
activesupport/lib/active_support/callbacks.rb
old new 1 module ActiveSupport 2 module Callbacks 3 class Callback 4 def self.run(callbacks, object, enumerator = :each) 5 callbacks.send(enumerator) do |callback| 6 result = callback.call(object) 7 return false if result == false 8 end 9 end 10 11 attr_reader :name, :method, :options 12 13 def initialize(name, method, options = {}) 14 @name = name 15 @method = method 16 @options = options 17 end 18 19 def call(object) 20 evaluate_method(method, object) if should_run_callback?(object) 21 end 22 23 private 24 def evaluate_method(method, object) 25 case method 26 when Symbol 27 object.send(method) 28 when String 29 eval(method, object.instance_eval { binding }) 30 when Proc, Method 31 method.call(object) 32 else 33 if method.respond_to?(name) 34 method.send(name, object) 35 else 36 raise ArgumentError, 37 "Callbacks must be a symbol denoting the method to call, a string to be evaluated, " + 38 "a block to be invoked, or an object responding to the callback method." 39 end 40 end 41 end 42 43 def should_run_callback?(object) 44 if options[:if] 45 evaluate_method(options[:if], object) 46 elsif options[:unless] 47 !evaluate_method(options[:unless], object) 48 else 49 true 50 end 51 end 52 end 53 54 def self.included(base) 55 base.extend ClassMethods 56 end 57 58 module ClassMethods 59 def define_callbacks(*callbacks) 60 callbacks.each do |callback| 61 class_eval <<-"end_eval" 62 def self.#{callback}(*methods, &block) 63 options = methods.extract_options! 64 methods << block if block_given? 65 callbacks = methods.map { |method| Callback.new(:#{callback}, method, options) } 66 (@#{callback}_callbacks ||= []).concat callbacks 67 end 68 69 def self.#{callback}_callback_chain 70 @#{callback}_callbacks ||= [] 71 72 if superclass.respond_to?(:#{callback}_callback_chain) 73 superclass.#{callback}_callback_chain + @#{callback}_callbacks 74 else 75 @#{callback}_callbacks 76 end 77 end 78 end_eval 79 end 80 end 81 end 82 83 def run_callbacks(kind, enumerator = :each) 84 Callback.run(self.class.send("#{kind}_callback_chain"), self) 85 end 86 end 87 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" … … 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) 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)