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 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: 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
How to Quickly Reorder a Ruby Array Given an Order
How to Remove Blank Values Params from Query String
Why Does Ruby Only Permit Certain Operator Overloading
Ruby Tcpsocket Write Doesn't Work, But Puts Does
Your Ruby Version Is 2.2.4, But Your Gemfile Specified 2.3.0
How to Show Error Message on Rails Views
What Is the Most Ruby-Ish Way of Accessing Nested Hash Values at Arbitrary Depths
Devise Raises Error with Rails 4.2 Upgrade
Thor & Yaml Outputting as Binary
Rails: Violates Foreign Key Constraint
Rails/Activerecord: Save Changes to a Model's Associated Collections
Listing Directories at a Given Level in Amazon S3
Getting Error "Exceeded Available Parameter Key Space"
What's the Best Way to Implement Acls to a Rails Application
Ransack, Postgres - Sort on Column from Associated Table with Distinct: True
What's the Difference Between /\P{Alpha}/I and /\P{L}/I in Ruby