For whatever reason (don't ask) our Rails app needed to be able to define callbacks on objects. As it stands now, you can only define callbacks on classes. Well, metaclasses are classes that pertain to a single specific object, so I figured I could add the callbacks there and everything will work.
Please excuse my extremely contrived example.
p = Post.find(...)
if p.contains_porn?
before_save :filter_content
end
end
p.save # should trigger filter_content
Well it doesn't work.
After digging through the ActiveSupport source code, I saw that it only looks for callback chains in the object's class, not metaclass. That's not too hard to fix...
callback_chain_method = "_callback_chain"
# Meta class inherits Class so we don't have to merge it in 1.9
if RUBY_VERSION >= '1.9'
metaclass.send(callback_chain_method).run(self, options, &block)
else
callbacks = self.class.send(callback_chain_method) | metaclass.send(callback_chain_method)
callbacks.run(self, options, &block)
end
end
end
end
Credit to Josh Peek for the Ruby 1.9 fix
Ok, so that got it working... with one major caveat: you cannot serialize objects that use callbacks. Ruby cannot serialize objects that have metaclasses and the run_callbacks method creates a metaclass whether you use it or not.
That's fine with me, I don't really like serializing objects anyways, but apparently it's a major problem for other people...
http://github.com/rails/rails/commit/e0846c8417093853f4f7f62732983e990c28d669
http://rails.lighthouseapp.com/projects/8994/tickets/575-callbacks-don-t-work-from-extended-modules
Josh Peek suggested a solution where we store the callback chains on the objects themselves, so object specific callbacks would be done like this...
p = Post.find(...)
p.before_save :filter_content if p.contains_porn?
p.save # should trigger filter_content
That seems like a very simple, clean and rational solution. I'll look into coding it up later.