Way to intercept method calls? - Reopened

B

Brian Takita

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
 
J

Jens Wille

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 ;-) 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
 
T

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 ;-) 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 <=3D 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?

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

Jens Wille

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 ;-)

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? ;-)
You can look at Facets' tracepoint.rb library, to see another
variation on a set_trace_func wrapper.
looks nice!

cheers
jens
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,540
Members
45,024
Latest member
ARDU_PROgrammER

Latest Threads

Top