How to Intercept Method Call in Ruby

How can I intercept method call in ruby?

This is one way to do it:

class Superclass
def before_each_method name
p [:before_method, name]
end

def self.method_added name
return if @__last_methods_added && @__last_methods_added.include?(name)
with = :"#{name}_with_before_each_method"
without = :"#{name}_without_before_each_method"
@__last_methods_added = [name, with, without]
define_method with do |*args, &block|
before_each_method name
send without, *args, &block
end
alias_method without, name
alias_method name, with
@__last_methods_added = nil
end
end

class SubclassA < Superclass
def my_new_method
p :my_new_method
end

def my_new_other_method
p :my_new_other_method
end
end

SubclassA.new.my_new_method
SubclassA.new.my_new_other_method

This will create a wrapper method using the alias_method_chaining method as soon as the method you'd like to wrap is defined in the subclass.

How to intercept class method calls, not just instance method calls

Here is my solution modified from the answer in the link you provided. I moved the hook logic from super class to a separate module so that when ever a class needs the hook, it just include or extend that module and call the hook method.

  • before_each_method type, &block - type can be :class or :instance, and the block is the code to be executed before each method. The block will be evaluated under certain environments, that is, for instance methods, self in the block is the instance; for class methods, self in the block is the class.
  • before_class_method &block - alias for before_each_method :class, &block
  • before_instance_method &block - alias for before_each_method :instance, &block
module MethodHooker
def self.included(base)
base.extend(ClassMethods)
end
def self.extended(base)
base.extend(ClassMethods)
end
module ClassMethods
def before_each_method type, &block
singleton = class << self; self; end
case type
when :instance
this = self
singleton.instance_eval do
define_method :method_added do |name|
last = instance_variable_get(:@__last_methods_added)
return if last and last.include?(name)
with = :"#{name}_with_before_each_method"
without = :"#{name}_without_before_each_method"
instance_variable_set(:@__last_methods_added, [name, with, without])
this.class_eval do
define_method with do |*args, &blk|
instance_exec(name, args, blk, &block)
send without, *args, &blk
end
alias_method without, name
alias_method name, with
end
instance_variable_set(:@__last_methods_added, nil)
end
end
when :class
this = self
singleton.instance_eval do
define_method :singleton_method_added do |name|
return if name == :singleton_method_added
last = instance_variable_get(:@__last_singleton_methods_added)
return if last and last.include?(name)
with = :"#{name}_with_before_each_method"
without = :"#{name}_without_before_each_method"
instance_variable_set(:@__last_singleton_methods_added, [name, with, without])
singleton.class_eval do
define_method with do |*args, &blk|
instance_exec(name, args, blk, &block)
send without, *args, &blk
end
alias_method without, name
alias_method name, with
end
instance_variable_set(:@__last_singleton_methods_added, nil)
end
end
end
end
def before_class_method &block
before_each_method :class, &block
end
def before_instance_method &block
before_each_method :instance, &block
end
end
end

class Test
extend MethodHooker
before_each_method :instance do |method, args, block|
p [method, args, block]
puts "before instance method(#{method}) #{@var}"
end
before_class_method do |method, args, block|
puts "before class method(#{method}) #{@class_instance_var}"
end
@class_instance_var = 'stackoverflow'
def initialize
@var = 1
end
def test(a, b, c)
puts "instance method test"
end
def self.test1
puts "class method test"
end
end

Test.new.test(1, "arg2", [3]) {|t| t}
Test.test1

The output will be something like:

[:initialize, [], nil]
before instance method(initialize)
[:test, [1, "arg2", [3]], #<Proc:0x00000001017d5eb8@/Users/test/before_method.rb:88>]
before instance method(test) 1
instance method test
before class method(test1) stackoverflow
class method test

Ruby method interception

Less code was changed from original. I modified only 2 line.

class MethodInterception

def self.before_filter(method)
puts "before filter called"
method = method.to_s
eval_string = "
alias_method :old_#{method}, :#{method}

def #{method}(*args)
puts 'going to call former method'
old_#{method}(*args)
puts 'former method called'
end
"
puts "going to call #{eval_string}"
class_eval(eval_string) # <= modified
puts "return"
end
end

class HomeWork < MethodInterception

def say_hello
puts "say hello"
end

before_filter(:say_hello) # <= change the called order
end

This works well.

HomeWork.new.say_hello
#=> going to call former method
#=> say hello
#=> former method called

call before methods in model on ruby

You can do this with prepend. prepend is like include in that it adds a module to the ancestors of the class, however instead of adding it after the class it adds it before.

This means that if a method exists both in the prepended module and the class then the module implementation is called first (and it can optionally call super if it wants to call the base class).

This allows you to write a hooks module like so:

module Hooks
def before(*method_names)
to_prepend = Module.new do
method_names.each do |name|
define_method(name) do |*args, &block|
puts "before #{name}"
super(*args,&block)
end
end
end
prepend to_prepend
end
end


class Example
extend Hooks
before :foo, :bar

def foo
puts "in foo"
end
def bar
puts "in bar"
end
end

In real use you would probably want to stash that module somewhere so that each call to before doesn't create a new module but that is just an inplementation detail

Executing code for every method call in a Ruby module

Like this:

module M
def self.before(*names)
names.each do |name|
m = instance_method(name)
define_method(name) do |*args, &block|
yield
m.bind(self).(*args, &block)
end
end
end
end

module M
def hello
puts "yo"
end

def bye
puts "bum"
end

before(*instance_methods) { puts "start" }
end

class C
include M
end

C.new.bye #=> "start" "bum"
C.new.hello #=> "start" "yo"

Ruby: how to intercept method, fiddle with the args and then call the method

alias_method_chain exists for this exact situation.

It's not always the right choice, but works well when you have a fitting use case, which this one looks to be.

Ruby: Catching All Methods Sent to an Object

As noted in the comments and the answer from @DanCheail, if you're using ruby 1.9+ you should use a BasicObject rather than undefining methods of Object. At the time of this posting usage of 1.8.x was still quite prevalent, even though 1.9 had been released for some time.


Rather than attempting to "redefine" anything, you could wrap your object in a blanked (for the most part) proxy object, something like:

class MyProxy
instance_methods.each do |meth|
# skipping undef of methods that "may cause serious problems"
undef_method(meth) if meth !~ /^(__|object_id)/
end

def initialize(object)
@object = object
end

def method_missing(*args)
# do stuff
p args
# ...
@object.send(*args)
end
end

MyProxy.new(MyClass.new).whatever


Related Topics



Leave a reply



Submit