I wanted to write a little "Deprecate-It" lib and used the "method_added" callback a lot. But now I noticed that this callback is not triggered, when including a module.
Are there any callbacks or workarounds, to get class "Foobar" informed when somewhing is included to itself?
Small Demo to demonstrate:
# Including Moduls won't trigger method_added callback
module InvisibleMethod
def invisible
"You won't get a callback from me"
end
end
class Foobar
def self.method_added(m)
puts "InstanceMethod: '#{m}' added to '#{self}'"
end
def visible
"You will get a callback from me"
end
include InvisibleMethod
end
[:invisible, :visible, :wont_exist].each do |meth|
puts "#{meth}: #{Foobar.public_method_defined? meth}"
end
That's the result:
InstanceMethod: 'visible' added to 'Foobar'
invisible: true
visible: true
wont_exist: false
Additional Information:
I really need to use a hook like method_added.
ActiveModel is adding public_instance_methods to Class during runtime though anonymous Modules.
The problem is that including modules doesn't add methods to the classes - it only changes the method call chain. This chain defines which classes/module will be searched for a method, that is not defined for the class in question. What happens when you include a module is an addition of an entry in that chain.
This is exactly the same as when you add a method in a superclass - this doesn't call method_added
since it is not defined in the superclass. It would be very strange if a subclass could change the behavior of a superclass.
You could fix that by manually calling method added for an included module, by redefining include
for your class:
class Foobar
def self.include(included_module)
included_module.instance_methods.each{|m| self.method_added(m)}
super
end
end
And it is much safer than redefining the included
method in Module
- the change is narrowed only to the classes, that you have defined yourself.
As suggested by one of the comments, you could use some other hook to get the behavior you want. For example, try to add this at the beginning of your code:
class Module
def included(klass)
if klass.respond_to?(:method_added)
self.instance_methods.each do |method|
klass.method_added(method)
end
end
end
end
Whenever a module is included in a class, all instance methods of that module will be notified to the class, as long as it defines the method method_added
. By running your code with the change above I get this result:
InstanceMethod: 'visible' added to 'Foobar'
InstanceMethod: 'invisible' added to 'Foobar'
invisible: true
visible: true
wont_exist: false
Which I think is the behavior you want.
I think deprecation
is not big isue to require a library. it is implemented like this in datamapper
. About method_added
hook; it is working as expected because methods already added to module
not class
. Only you can get your expected result monkey patching included
hook.
# got from https://github.com/datamapper/dm-core/blob/master/lib/dm-core/support/deprecate.rb
module Deprecate
def deprecate(old_method, new_method)
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{old_method}(*args, &block)
warn "\#{self.class}##{old_method} is deprecated, use \#{self.class}##{new_method} instead (\#{caller.first})"
send(#{new_method.inspect}, *args, &block)
end
RUBY
end
end # module Deprecate
class MyClass
extend Deprecate
def old_method
p "I am old"
end
deprecate :old_method, :new_method
def new_method
p "I am new"
end
end
m = MyClass.new
m.old_method
# MyClass#old_method is deprecated, use MyClass#new_method instead (pinger.rb:27:in `<main>')
# "I am new"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With