Forum: Ruby How to intercept all function calls of a object?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Uncutstone W. (Guest)
on 2006-05-21 11:25
What I want is something like this:

class HelloBye
  def hello
    puts "hello"
  end
  def bye
    puts "byebye"
  end
end

a = HelloBye.new
a.extend #add an intercptor to a
a.hello

Then I can get this kind of result:

call method hello of class HelloBye
hello
return from method hello of class HelloBye

And similar thing for 'bye' and other methods if existed in object a.
Joey (Guest)
on 2006-05-21 11:36
(Received via mailing list)
Simon Kröger (Guest)
on 2006-05-21 14:59
(Received via mailing list)
uncutstone wu wrote:
> And similar thing for 'bye' and other methods if existed in object a.
Some food for thoughts:
------------------------------------------------------------
def intercept obj
  meth = obj.methods - %w{__send__ __id__}
  klass, name = class << obj; self; end, obj.class.name
  meth.each do |m|
    klass.module_eval %[
    def #{m} *a,&b
      puts 'in #{name}##{m}'
      super
      puts 'out #{name}##{m}'
    end]
  end
  obj
end

class HelloBye
  def hello
    puts "hello"
  end
  def bye
    puts "byebye"
  end
end

a = HelloBye.new
intercept a #add an intercptor to a
a.hello
a.bye
------------------------------------------------------------

in HelloBye#hello
hello
out HelloBye#hello
in HelloBye#bye
byebye
out HelloBye#bye

method missing would also be an option of course, but wrapping an
object into another one has several drawbacks (e.g. dup and friends
don't work as expected anymore)

cheers

Simon
Robert K. (Guest)
on 2006-05-21 16:36
(Received via mailing list)
2006/5/21, uncutstone wu <removed_email_address@domain.invalid>:
> Then I can get this kind of result:
>
> call method hello of class HelloBye
> hello
> return from method hello of class HelloBye

I once hacked something quite complex together that supports multiple
hooks and threads. The code is on the Wiki
http://wiki.rubygarden.org/Ruby/page/show/MethodHooks

Kind regards

robert
Simen (Guest)
on 2006-05-21 19:06
Robert K. wrote:
> 2006/5/21, uncutstone wu <removed_email_address@domain.invalid>:
>> Then I can get this kind of result:
>>
>> call method hello of class HelloBye
>> hello
>> return from method hello of class HelloBye
>
> I once hacked something quite complex together that supports multiple
> hooks and threads. The code is on the Wiki
> http://wiki.rubygarden.org/Ruby/page/show/MethodHooks
>
> Kind regards
>
> robert

I did something similar:

class Object

  @@methods = {}
  @@pre     = {}
  @@post    = {}

  def self.new_method(m)
    define_method(m) do |*args|
      call_pres m, args
      result = @@methods[m].bind(self).call *args
      call_posts m, args
      result
    end
  end

  def self.wrap( method, wrapping_method )
    @@methods[method] ||= instance_method(method).clone
    define_method(method) do |*args|
      send wrapping_method, @@methods[method].bind(self), *args
    end
  end

  def self.pre( method, pre_method )
    @@methods[method] ||= instance_method(method).clone
    (@@pre[method] ||= []) << pre_method
    new_method method
  end

  def self.post( method, post_method )
    @@methods[method] ||= instance_method(method).clone
    (@@post[method] ||= []) << post_method
    new_method method
  end

  def call_pres( method, arguments )
    return unless @@pre[method]
    @@pre[method].each do |pre|
      send pre, *arguments
    end
  end

  def call_posts( method, arguments )
    return unless @@post[method]
    @@post[method].each do |post|
      send post, *arguments
    end
  end

end

You could hookup before and after methods:

class Test

  def add(one, two)
    result = one + two
    puts "#{one} + #{two} = #{result}"
    result
  end

  pre :add, :pre_add

  def pre_add one, two
    puts "adding #{one} and #{two}"
  end

  post :add, :post_add

  def post_add( one, two )
    puts "done adding #{one} and #{two}"
  end

end

Test.new.add 1, 2
# results in:
# adding 1 and 2
# 1 + 2 = 3
# done adding 1 and 2
# => 3

You can also wrap methods:

class Test

  def subtract( one, two )
    result = one - two
    puts "#{one} - #{two} = #{result}"
    result
  end

  wrap :subtract, :wrap_subtract

  def wrap_subtract( subtracter, one, two )
    puts "about to subtract #{one} and #{two}..."
    subtracter.call one, two
    puts "done..."
  end

end

Test.new.subtract 3, 2
# results in
# about to subtract 3 and 2...
# 3 - 2 = 1
# done...
# => nil

And off course hooking up on hooks places them inside the method chain.
Simen (Guest)
on 2006-05-21 19:09
Simen wrote:
> Robert K. wrote:
>> 2006/5/21, uncutstone wu <removed_email_address@domain.invalid>:
>>> Then I can get this kind of result:
>>>
>>> call method hello of class HelloBye
>>> hello
>>> return from method hello of class HelloBye
>>
>> I once hacked something quite complex together that supports multiple
>> hooks and threads. The code is on the Wiki
>> http://wiki.rubygarden.org/Ruby/page/show/MethodHooks
>>
>> Kind regards
>>
>> robert
>
> I did something similar:
> ...

I should add, though, that it acts funny when you redefine hookups for
other classes. It obviously needs to be extended to work in practice.
Consider it an idea.
Robert K. (Guest)
on 2006-05-21 19:46
(Received via mailing list)
2006/5/21, Simen <removed_email_address@domain.invalid>:
> I should add, though, that it acts funny when you redefine hookups for
> other classes. It obviously needs to be extended to work in practice.
> Consider it an idea.

Same with my code - I never used it in production. But then again it's
probably not worth the effort to enhance it as it's planned to have
method hooking in one of the next major Ruby releases.

Kind regards

robert
This topic is locked and can not be replied to.