public
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
Run callbacks from object's metaclass
josh (author)
Tue Jul 15 19:58:52 -0700 2008
commit  e0846c8417093853f4f7f62732983e990c28d669
tree    6dfa45ce6048591d54389bc0f63977850cbdeff7
parent  98dd7226fa2fb306008fb5d89e38ac2391453664
...
1
2
 
 
3
4
5
...
1
2
3
4
5
6
7
0
@@ -1,5 +1,7 @@
0
 *2.1.1 (next release)*
0
 
0
+* Run callbacks from object's metaclass [Josh Peek]
0
+
0
 * TimeWithZone: when crossing DST boundary, treat Durations of days, months or years as variable-length, and all other values as absolute length. A time + 24.hours will advance exactly 24 hours, but a time + 1.day will advance 23-25 hours, depending on the day. Ensure consistent behavior across all advancing methods [Geoff Buesing]
0
 
0
 * Fix TimeWithZone unmarshaling: coerce unmarshaled Time instances to utc, because Ruby's marshaling of Time instances doesn't respect the zone [Geoff Buesing]
...
269
270
271
272
 
 
 
 
 
 
 
 
 
273
274
275
...
269
270
271
 
272
273
274
275
276
277
278
279
280
281
282
283
0
@@ -269,7 +269,15 @@ module ActiveSupport
0
     #   pass
0
     #   stop
0
     def run_callbacks(kind, options = {}, &block)
0
-      self.class.send("#{kind}_callback_chain").run(self, options, &block)
0
+      callback_chain_method = "#{kind}_callback_chain"
0
+
0
+      # Meta class inherits Class so we don't have to merge it in 1.9
0
+      if RUBY_VERSION >= '1.9'
0
+        metaclass.send(callback_chain_method).run(self, options, &block)
0
+      else
0
+        callbacks = self.class.send(callback_chain_method) | metaclass.send(callback_chain_method)
0
+        callbacks.run(self, options, &block)
0
+      end
0
     end
0
   end
0
 end
...
84
85
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
88
89
...
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
0
@@ -84,6 +84,30 @@ class CallbacksTest < Test::Unit::TestCase
0
   end
0
 end
0
 
0
+class MetaclassCallbacksTest < Test::Unit::TestCase
0
+  module ModuleWithCallbacks
0
+    def self.extended(object)
0
+      object.metaclass.before_save :raise_metaclass_callback_called
0
+    end
0
+
0
+    def module_callback_called?
0
+      @module_callback_called ||= false
0
+    end
0
+
0
+    def raise_metaclass_callback_called
0
+      @module_callback_called = true
0
+    end
0
+  end
0
+
0
+  def test_metaclass_callbacks
0
+    person = Person.new
0
+    person.extend(ModuleWithCallbacks)
0
+    assert !person.module_callback_called?
0
+    person.save
0
+    assert person.module_callback_called?
0
+  end
0
+end
0
+
0
 class ConditionalCallbackTest < Test::Unit::TestCase
0
   def test_save_conditional_person
0
     person = ConditionalPerson.new

Comments

technoweenie Wed Jul 16 14:24:45 -0700 2008

Wow, this is breaking one of my apps. I’d suggest leaving this out of 2-1-stable.


SystemStackError in 'Whatevs blah blah client code'
stack level too deep
(eval):9:in `before_validation_callback_chain'
(eval):10:in `before_validation_callback_chain'
spec/models/whatevs_spec.rb:75:
spec/models/whatevs_spec.rb:3:
josh Wed Jul 16 15:58:55 -0700 2008

Really!?! I’ll revert it from stable in the meantime. Can you whip up a test case for that and ping me.

methodmissing Wed Jul 16 16:02:55 -0700 2008

Doesn’t play well with Marshal.

cache_fu enabled apps break with :

TypeError: singleton can’t be dumped

/test/cases/associations/extension_test.rb:36:in `test_marshalling_extensions’

Roman2K Wed Jul 16 17:52:02 -0700 2008

I think you should drop this change because from what I understand, each time a callback is run, #metaclass is called which creates a singleton class for the object if it doesn’t already exist, and from my benchmarks, this is very inefficient both CPU- and memory-wise. Please correct me if I’m wrong.

josh Wed Jul 16 18:17:32 -0700 2008

Causing too many problems, will revert.

josh Wed Jul 16 18:28:51 -0700 2008

@methodmissing Can whip up a marshal test for activerecord. Alarms should of sounded if that broke.