Proxies, procs and yield

January 5th, 2007 by kowsik

First the definition: Proxies are objects that masquerade as some other object that’s contained within them, effectively intercepting all messages to the contained object. Proxies are used in multiple places like debugging, tracing, intercepted delegation, benchmarking, etc. But those have already been solved. This post is not about that.

class Proxy
    instance_methods.each do |m|
        undef_method m unless m.to_str =~ /^__(send|id)__$/
    end

    def initialize obj
        @obj = obj
    end

    def method_missing id, *opts
        @obj.__send__ id, *opts
    end
end

That’s the simplest you are going to get. First the Proxy class undef’s all the instance methods that it inherits from Object. Then we override method_missing to intercept the method that’s being invoked on us, along with the arguments and dispatch them to the contained object. How does it work?

proxy1 = Proxy.new(42)
p proxy1/42                  # => 1

proxy2 = Proxy.new("hello")
p proxy2[1,3]                # => "ell"

So far so good and it works as expected. We can make the Proxy object behave like an Fixnum or a String What if we try the following?

proxy = Proxy.new("hello")
proxy.each { |ch| p ch }

proxy.rb:11:in `each': no block given (LocalJumpError)
        from proxy.rb:11:in `__send__'
        from proxy.rb:11:in `method_missing'

Oops, what happened there? Ruby passes in the block of code to method_missing and we are not sending this into the proxied object’s each method. When it invokes yield, this causes an exception to be thrown since it doesn’t have the block. Hmm…Turns out there’s a pretty clean way to solve this. We modify method_missing as follows:

    def method_missing id, *opts, &block
        return @obj.__send__ id, *opts if block.nil?
        @obj.__send__(id, *opts) { |*args| block.call *args }
    end

I didn’t realize this before, but there are two things going on here. The first one is the * argument is not greedy and preserves the code block that you can trap as a separate argument. The second one is to have * arguments in proc’s. Together they give us what we need. We save the proc, if any, sent to the method_missing and then pass that along with the __send__. Closures take care of the rest and with the wildcarded arguments, we don’t have to worry about the number of arguments that yield is going to be invoked with.

Posted in Ruby, Tools | Permalink | Trackback

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.