Way to intercept method calls? - Reopened

Hello, I read the thread.
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/676

Matz mentioned that he does not think its "worthy" which I interpret
to mean there is either no use case or the use case is not beneficial
to satisfy.

If there was a way to intercept all incoming calls, it would make the
"blank slate" pattern obsolete. FTWDK, The "blank slate" pattern
implementation means, one would remove all methods from an object so
method_missing can be used.
It is used to implement a lightweight and transparent Proxy to another
object. ActiveRecord Association proxies make heavy use of the blank
slate pattern.

Here is an example implementation:
<code>
class Foo
  instance_methods.each {|m| undef_method(m) unless m =~ /__/}

  def initialize(proxy_target)
    @proxy_target = proxy_target
  end

  def method_missing(name, *args, &block)
    @proxy_target.__send__(name, *args, &block)
  end
end
</code>

The problem with this pattern is that it is imperative. If another
library defines a method on Object after Foo is loaded, then the blank
slate for Foo breaks.

For example:
<code>
require "foo"
class Object
  def bar
    "I don't want to be called"
  end
end

class ProxyTarget
  def bar
    "I want to be called"
  end
end

foo = Foo.new("the proxy target")
foo.bar # "I don't want to be called"
</code>

So to solve this particular problem, you can apply the blank slate on
object instantiation:

<code>
class Foo
  def initialize(proxy_target)
    @proxy_target = proxy_target
    class << self
      instance_methods.each {|m| undef_method(m) unless(m =~ /^_/ ||
m.to_s == 'object_id')}
    end

    def method_missing(name, *args, &block)
      @proxy_target.__send__(name, *args, &block)
    end
  end
end
</code>

For some reason, #method_missing also needs to be redefined on #initialize.

Ideally, Ruby would support a way to intercept method invocation
before its sent to the real implementation. I believe Python allows
this by redefining one of the __ methods (I don't know which one, but
I think I saw somebody do this) on the proxy class.

Something like the following would be nice:
<code>
class Foo
  def initialize(proxy_target)
    @proxy_target = proxy_target
  end

  def __call__(name, *args, &block)
    if m =~ /^_/ || m.to_s == 'object_id'
      super
    else
      @proxy_target.__send__(name, *args, &block)
    end
  end
end
</code>

Anyways, removing the need for the "blank slate" pattern is a concrete
use case for this feature. I do believe that method invocation
interception would open the door for some powerful abstraction and
really cool libraries.

Also, are feature requests for Ruby officially on rubyforge.org tracker?

Thanks,
Brian

hi brian!

this is what i have come up with so far:

<http://github.com/blackwinter/scratch/tree/master/interceptable.rb>

unfortunately, it doesn't work as intended yet :wink: the open issues
being:

1) there doesn't seem to be a way to cancel the original method
invocation from Kernel#set_trace_func

<http://github.com/blackwinter/scratch/tree/6c8159a/interceptable.rb#L80>

2) local variables in the method body are indistinguishable from
method arguments when using Kernel#local_variables

<http://github.com/blackwinter/scratch/tree/6c8159a/interceptable.rb#L73>

3) [MINOR] maybe it should also apply down the inheritance chain
(replace 'klass.equal?(base)' with 'klass <= base')?

<http://github.com/blackwinter/scratch/tree/6c8159a/interceptable.rb#L69>

does anybody have an idea as to how to address these issues (1 + 2)?
or is this approach (Kernel#set_trace_func) rather silly and hopeless?

cheers
jens

It's only worth it for things like debugging. It's way to slow to be
generally useful.

You can look at Facets' tracepoint.rb library, to see another
variation on a set_trace_func wrapper.

                                                      trans.

···

On Sep 29, 11:22 pm, Jens Wille <jens.wi...@uni-koeln.de> wrote:

hi brian!

this is what i have come up with so far:

<http://github.com/blackwinter/scratch/tree/master/interceptable.rb&gt;

unfortunately, it doesn't work as intended yet :wink: the open issues
being:

1) there doesn't seem to be a way to cancel the original method
invocation from Kernel#set_trace_func

<scratch/interceptable.rb at 6c8159a1620c721c2487c410b237118e0e279c87 · blackwinter/scratch · GitHub;

2) local variables in the method body are indistinguishable from
method arguments when using Kernel#local_variables

<scratch/interceptable.rb at 6c8159a1620c721c2487c410b237118e0e279c87 · blackwinter/scratch · GitHub;

3) [MINOR] maybe it should also apply down the inheritance chain
(replace 'klass.equal?(base)' with 'klass <= base')?

<scratch/interceptable.rb at 6c8159a1620c721c2487c410b237118e0e279c87 · blackwinter/scratch · GitHub;

does anybody have an idea as to how to address these issues (1 + 2)?
or is this approach (Kernel#set_trace_func) rather silly and hopeless?

hi trans!

Trans [2008-09-30 05:28]:

It's only worth it for things like debugging.

sure, but i thought it might work, so i tried. that's all :wink:

maybe using the method_added hook in conjunction with define_method
and the notorious alias_method chain works out better...

It's way to slow to be generally useful.

i'm not so much concerned with speed at the moment, i'd rather want
it to work somehow. besides, we don't love ruby for her speed, do
we? :wink:

You can look at Facets' tracepoint.rb library, to see another
variation on a set_trace_func wrapper.

looks nice!

cheers
jens