Considering an interface like this:
foo = manager.queue_task :foo
bar = manager.queue_task :bar
manager.execute
foo = foo.unwrap
bar = bar.unwrap
I wanted to see if I could avoid requiring the unwrap step for “ergonomics”. So, I came up with this very dangerous-seeming beauty:
class ForwardingSubstitute
def self.new
backchannel = []
obj = super
true_exec = BasicObject.instance_method :instance_exec
true_exec.bind(obj).call do
@backchannel = backchannel
end
[obj, backchannel]
end
instance_methods.each do |name|
undef_method name
end
def method_missing(name, *args, &block)
@backchannel.last.public_send name, *args, &block
end
end
Now you can hold on to the backchannel Array
and slip the value in whenever it’s actually ready. Using the object before the value is present just results in a NoMethodError on nil.
Now you don’t have to unwrap. The execute function can just put the results into the appropriate backchannels as saved by the queue_task calls.
Ruby warns me undefing object_id
and __send__
are particularly fraught, but if they’re all being forwarded to another object (which do work as expected), it seems like it might be mostly ok?
I’ve found two significant issues myself.
-
Class ===
still truly recognizes that the substitute is not the same as the delegated object, while#is_a?
refers to the delegated object. - Methods like
Array#pop
returnself
and that transparently strips the delegator. I don’t think the stripping is actually a problem since you get back the delegated object itself anyway.
I scrapped it for being too subtle in any case, but I’d love to hear of any other issues it might have.