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 forbefore_each_method :class, &block
before_instance_method &block
- alias forbefore_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
Reading the Last N Lines of a File in Ruby
Set Global Default Encoding For Ruby 1.9
Why Does Ruby'S 'Gets' Includes the Closing Newline
Checking If a Variable Is Defined in Sass
How to Convert Datetime.Now to Utc in Ruby
Running a Command from Ruby Displaying and Capturing the Output
Rails Model, View, Controller, and Helper: What Goes Where
Really Cheap Command-Line Option Parsing in Ruby
Undefined Method 'Visit' When Using Rspec and Capybara in Rails
Difference Between $Stdout and Stdout in Ruby
How to Implement a Short Url Like the Urls in Twitter
Passing a Method as a Parameter in Ruby
One-Liner to Recursively List Directories in Ruby