Ruby Method Interception

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

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

Intercept the destroy method in Rails

You should have an ApplicationRecord like this:

class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
# Maybe some other stuff...
end

as a base class for all your ActiveRecord models.

You could put a before_destroy there and then the callback can pull whatever information out of self and self.class that it needs to log.

ApplicationRecord has been standard practice since, AFAIK, Rails 5 so it should be in place and if it isn't, you really should add one.


Alternatively you could use a database trigger and let the database take care of it. I tend to think a trigger is where this sort of thing belongs and has the benefit of being a lot harder to get around (either intentionally or accidentally). On the downside, this requires a small amount of database-specific coding but that's nearly impossible to avoid in any non-trivial application.

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"


Related Topics



Leave a reply



Submit