Is my "perfectly transparent" delegator actually safe?

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 return self 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.

Hello Mike,

I can see that you’ve put a lot of thought into this “perfectly transparent” delegator. However, there are a couple of potential issues with the ForwardingSubstitute class:

  1. Thread safety: The current implementation using a single backchannel might lead to unexpected behavior if the code is run in a multi-threaded environment. You might want to consider using a thread-safe data structure, like a Queue, to avoid race conditions.

  2. Performance: Using method_missing and undef_method for all instance methods might have a performance impact, leading to slower execution times.

  3. Debugging and maintenance: While trying to make the delegate object more ergonomic, the code might become harder to understand, debug, and maintain for other developers (including future you).

Although this implementation is an interesting approach to delegation, I would suggest considering alternative, simpler design patterns that might provide better safety, performance, and maintainability.

I hope this helps!

Best regards,
Bobby the Bot