subject.public_methods(false).each do |meth|
(class << self; self; end).class_eval do
define_method meth do |*args|
subject.send meth, *args
end
end
end
end
end
The context of the example can be summarized as, you want to delegate from the instance all the public methods defined on the constructor argument.
Ali Aghareza pointed out to me that defining methods on the metaclass of an instance isn't the nicest thing to do. The problem with it is that you've made it much harder for anyone else to change the behavior of the instance.
Here's a more simplified example. The following code creates a new Object and defines the hello_world method on the Object instance.
; self; end
end
end
obj = Object.new
obj.metaclass.class_eval do
"hello"
end
end
obj.hello_world # => "hello"
This works fine; however, if someone wanted to change the way hello_world behaved, by defining the method on the metaclass you force them to make their change by redefining the method on the metaclass. The current solution does not allow you to extend modules and alter the behavior of the instance.
The following example demonstrates that extending a module does not change the behavior of an instance if the behavior has been defined on the metaclass.
; self; end
end
end
obj = Object.new
obj.metaclass.class_eval do
"hello"
end
end
obj.hello_world # => "hello"
"hola"
end
end
obj.extend Spanish
obj.hello_world # => "hello"
A better solution is to change the behavior of the instance by extending modules instead of defining behavior on the metaclass.
obj = Object.new
"hello"
end
end
obj.extend(English).hello_world # => "hello"
Now that the behavior is defined on an ancestor instead of the metaclass you can change the behavior by extending another module.
obj = Object.new
"hello"
end
end
obj.extend(English).hello_world # => "hello"
"hola"
end
end
obj.extend(Spanish).hello_world # => "hola"
This solution works fine for our simple example, but it can also be applied to our first (much more complicated) example, even without knowing how to define the module. In the case of the Decorator, you can simply define an anonymous module and immediately extend it.
mod = Module.new do
subject.public_methods(false).each do |meth|
define_method meth do |*args|
subject.send meth, *args
end
end
end
extend mod
end
end
I really like the idea, thanks for sharing!
ReplyDeleteThis is great, much more intuitive and no need to introduce the concept of singleton class to do this kind of metaprogramming. One more win against cognitive overload.
ReplyDelete