Reputation: 347
I'm trying to wrap all instance methods of TestClass
to perform code before and after an instance method is called. So far, this code is working:
module Wrapper
def wrap(*methods)
prependable_module = Module.new do
methods.each do |m|
define_method(m) do |*args, &block|
p 1
super(*args, &block)
p 3
end
end
end
prepend prependable_module
end
end
class TestClass
extend Wrapper
wrap :instance_method1
def instance_method1
p 2
end
end
TestClass.new.instance_method1 # => 1, 2, 3
I can call wrap
with all method names as arguments. If I try to wrap all methods without listing them individually, I need to call it using instance_methods(false)
at the bottom of the class definition.
class TestClass
extend Wrapper
def instance_method1
p 2
end
wrap(*instance_methods(false))
end
In Rails, all callback methods like before_action
or after_create
are usually called on top of the class definition. My goal is to call wrap
on top of the class definition as well (without listing all methods individually). In this case, I can't call instance_methods(false)
on top of the class definition, because at this point no method has been defined.
Thanks for your help!
Thanks to Kimmo Lehto's approach I can wrap every instance method using the method_added
hook. I don't want to prepend a new module for every defined method, so I add all overridden methods to the same module.
module Wrapper
def method_added(method_name)
tmp_module = find_or_initialize_module
return if tmp_module.instance_methods(false).include?(method_name)
tmp_module.define_method(method_name) do |*args, &block|
p 1
super(*args, &block)
p 3
end
end
def find_or_initialize_module
module_name = "#{name}Wrapper"
module_idx = ancestors.map(&:to_s).index(module_name)
unless module_idx
prepend Object.const_set(module_name, Module.new)
return find_or_initialize_module
end
ancestors[module_idx]
end
end
class TestClass
extend Wrapper
def instance_method1
p 2
end
end
tc = TestClass.new
tc.instance_method1 # => 1, 2, 3
Upvotes: 2
Views: 168
Reputation: 6041
You could use the Module#method_added hook to automatically wrap any methods that are added.
You will need some magic to not get a stack overflow from an infinite loop.
Another option is to use TracePoint to trigger the wrapping once the class has been defined. You can use the Module#extended
to set up the tracepoint. Something like:
module Finalizer
def self.extended(obj)
TracePoint.trace(:end) do |t|
if obj == t.self
obj.finalize
t.disable
end
end
end
def finalize
wrap(*instance_methods(false))
end
end
Classes are usually not exactly "closed" unless you explicitly .freeze
them so it's a bit of a hacky solution and will not trigger if methods are added afterwards. method_added
is probably your best bet.
Upvotes: 1