Imagine, you frequently need to wrap a set of methods from a super class inside some static code or apects but with few lines results/different for each method, for example, wrap update notification around array methods to get an observable array.
My first thought, I want a class level method to be used like:
class MyArray < Array
extend ObserverHelper # defines :wrap_method
wrap_notify(:[]=) do |index_or_range, *args|
[self[index_or_range], super(index_or_range, *args)]
# much nicer it would be to define
# removed = self[index_or_range]
# added = super(index_or_range, *args)
# but this makes it even more complicated (suggestions welcome!)
end
...
end
So far so good. How to implement :wrap_method now? My most promising approach reached this far. Does not work because of super:
require 'observer'
module ObserverHelper
include Observable
def wrap_notify(meth, &block)
define_method meth,->(*args) {
removed, added = instance_exec(*args, &block)
changed
notify_observers(removed, added)
added
}
end
end
Errors:
If defined directly on array subclass: super called outside of method
If defined in an module and included to array: "super from singleton method that is defined to multiple classes is not supported; this will be fixed in 1.9.3 or later" or "Segmentation fault" if the method has the proper return value)
I searched some time but didn't find an advisable solution to change the binding of a closure, however, maybe I miss something or you can solve it without?
Rails does those things by evaluating strings, not sure yet If I would like to got this way. Performance is not yet important but will be. Thus, I need feedback about the different possibilities and performance issues.
Finally, I got two more verbose solutions running. First, pass the instance, no super and no self:
wrap_observer(:[]=) do |instance, index_or_range, *args|
[instance[index_or_range], instance.send(meth, index_or_range, *args)]
end
The other one uses wrap_observer as instance_method:
def []=(index_or_range, *args)
wrap_observer do
[self[index_or_range], super(index_or_range, *args)]
end
end
which I regard as preferable.
A solution for the DSL may be to define the methods without the wrapper and than iterate through the defined methods adding it.
Are there more ways to solve this performant and maintainable with my intended DSL?
The only way to rebind a block is to define a method with it, which also allows to use super. So, you would want to define two methods -- something that can be done using modules:
module ObserverHelper
def wrap_notify(meth, options={}, &block)
override = Module.new do
define_method(meth, &block)
end
notifier = Module.new do
define_method meth do |*args|
removed, added = super(*args)
options ||= {}
changed
notify_observers(removed, added)
after
end
end
include override
include notifier
end
end
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