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:

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

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:

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”

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

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

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:

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

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

On Sep 29, 11:22 pm, Jens W. [email protected] wrote:

invocation from Kernel#set_trace_func

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?

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.

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

  1. 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

  1. [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

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