Ruby method interception - ruby

I want to intercept method calls on a ruby-class and being able to do something before and after the actual execution of the method. I tried the following code, but get the error:
MethodInterception.rb:16:in before_filter': (eval):2:inalias_method': undefined method
say_hello' for classHomeWork'
(NameError)
from (eval):2:in `before_filter'
Can anybody help me to do it right?
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}"
eval(eval_string)
puts "return"
end
end
class HomeWork < MethodInterception
before_filter(:say_hello)
def say_hello
puts "say hello"
end
end

I just came up with this:
module MethodInterception
def method_added(meth)
return unless (#intercepted_methods ||= []).include?(meth) && !#recursing
#recursing = true # protect against infinite recursion
old_meth = instance_method(meth)
define_method(meth) do |*args, &block|
puts 'before'
old_meth.bind(self).call(*args, &block)
puts 'after'
end
#recursing = nil
end
def before_filter(meth)
(#intercepted_methods ||= []) << meth
end
end
Use it like so:
class HomeWork
extend MethodInterception
before_filter(:say_hello)
def say_hello
puts "say hello"
end
end
Works:
HomeWork.new.say_hello
# before
# say hello
# after
The basic problem in your code was that you renamed the method in your before_filter method, but then in your client code, you called before_filter before the method was actually defined, thus resulting in an attempt to rename a method which doesn't exist.
The solution is simple: Don't Do That™!
Well, okay, maybe not so simple. You could simply force your clients to always call before_filter after they have defined their methods. However, that is bad API design.
So, you have to somehow arrange for your code to defer the wrapping of the method until it actually exists. And that's what I did: instead of redefining the method inside the before_filter method, I only record the fact that it is to be redefined later. Then, I do the actual redefining in the method_added hook.
There is a tiny problem in this, because if you add a method inside of method_added, then of course it will immediately get called again and add the method again, which will lead to it being called again, and so on. So, I need to guard against recursion.
Note that this solution actually also enforces an ordering on the client: while the OP's version only works if you call before_filter after defining the method, my version only works if you call it before. However, it is trivially easy to extend so that it doen't suffer from that problem.
Note also that I made some additional changes that are unrelated to the problem, but that I think are more Rubyish:
use a mixin instead of a class: inheritance is a very valuable resource in Ruby, because you can only inherit from one class. Mixins, however, are cheap: you can mix in as many as you want. Besides: can you really say that Homework IS-A MethodInterception?
use Module#define_method instead of eval: eval is evil. 'Nuff said. (There was absolutely no reason whatsoever to use eval in the first place, in the OP's code.)
use the method wrapping technique instead of alias_method: the alias_method chain technique pollutes the namespace with useless old_foo and old_bar methods. I like my namespaces clean.
I just fixed some of the limitations I mentioned above, and added a few more features, but am too lazy to rewrite my explanations, so I repost the modified version here:
module MethodInterception
def before_filter(*meths)
return #wrap_next_method = true if meths.empty?
meths.delete_if {|meth| wrap(meth) if method_defined?(meth) }
#intercepted_methods += meths
end
private
def wrap(meth)
old_meth = instance_method(meth)
define_method(meth) do |*args, &block|
puts 'before'
old_meth.bind(self).(*args, &block)
puts 'after'
end
end
def method_added(meth)
return super unless #intercepted_methods.include?(meth) || #wrap_next_method
return super if #recursing == meth
#recursing = meth # protect against infinite recursion
wrap(meth)
#recursing = nil
#wrap_next_method = false
super
end
def self.extended(klass)
klass.instance_variable_set(:#intercepted_methods, [])
klass.instance_variable_set(:#recursing, false)
klass.instance_variable_set(:#wrap_next_method, false)
end
end
class HomeWork
extend MethodInterception
def say_hello
puts 'say hello'
end
before_filter(:say_hello, :say_goodbye)
def say_goodbye
puts 'say goodbye'
end
before_filter
def say_ahh
puts 'ahh'
end
end
(h = HomeWork.new).say_hello
h.say_goodbye
h.say_ahh

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

Jörg W Mittag's solution is pretty nice. If you want something more robust (read well tested) the best resource would be the rails callbacks module.

Related

Is there a way to know if a ruby method has been redefined?

I want to know if a method on a class gets redefined.
use case: in version 1 of ruby-clock, defining a method on an object was part of the API. In version 2, doing so will break behavior, and a different API should be used.
what I ended up doing: https://github.com/jjb/ruby-clock/pull/28/files
The best would be if I could use some sort of metaprogramming to notice when it happens.:
# this is example code which does not work
class MyClass
def my_method
puts "hello"
end
# this is not real! it's an example of what i hope is somehow possible
def self.method_defined(m, *args, &block)
if :my_method == m
raise "you should not redefine my_method!"
end
end
end
Also acceptable would be if I could check back later and see if it has changed.
# this is example code which does not work
class MyClass
def my_method
puts "hello"
end
##my_method_object_id = get_method_object_id(:my_method)
end
# later, when app initialization is done, check if the user redefined the method
# this is not real! it's an example of what i hope is somehow possible
def self.method_defined(m, *args, &block)
if MyClass.get_method_object_id(:my_method) != MyClass.my_method_object_id
raise "you should not redefine my_method!"
end
end
n.b. that MyClass.method(:my_method) is a real thing in ruby, but it always produces a new object.
This sounds pretty much like XY problem. I don't think you should go this way - if you find yourself fighting with the Ruby object model, you most probably picked the wrong tool for the job.
But for the sake of completeness... There are a couple of callbacks in Module that can help with the ideas as crazy as this one. In particular, there is Module#method_added which is called each time some instance method is defined. So we can do smth. like this (very dirty example):
class C
#foo_counter = 0
def self.method_added(name)
if name == :foo
#foo_counter += 1
if #foo_counter > 1
raise "Don't redefine foo!"
end
end
end
def foo
"foo"
end
end
now if one tries to open C and redefine foo the following happens (pry session as an example):
pry(main)> class C
pry(main)* def foo
pry(main)* "bar"
pry(main)* end
pry(main)* end
RuntimeError: Don't redefine foo!
So you can do what you want to (to some extent - it's Ruby, so what stops one from redefining the annoying hook first? :)), but please don't. You'll get nothing but troubles...
You could use the callback BasicObject#singleton_method_added.
class C
#class_methods = []
def self.singleton_method_added(m)
puts "Method #{m} was just redefined" if #class_methods.include?(m)
#class_methods << m
end
def self.c
puts 'hi'
end
end
C.c
#=> "hi"
class C
def self.c
puts 'ho'
end
end
Method c was just redefined
C.c #=> "ho"

How could I implement something like Rails' before_initialize/before_new in plain Ruby?

In Rails we can define a class like:
class Test < ActiveRecord::Base
before_initialize :method
end
and when calling Test.new, method() will be called on the instance. I'm trying to learn more about Ruby and class methods like this, but I'm having trouble trying to implement this in plain Ruby.
Here's what I have so far:
class LameAR
def self.before_initialize(*args, &block)
# somehow store the symbols or block to be called on init
end
def new(*args)
## Call methods/blocks here
super(*args)
end
end
class Tester < LameAR
before_initialize :do_stuff
def do_stuff
puts "DOING STUFF!!"
end
end
I'm trying to figure out where to store the blocks in self.before_initialize. I originally tried an instance variable like #before_init_methods, but that instance variable wouldn't exist in memory at that point, so I couldn't store or retrieve from it. I'm not sure how/where could I store these blocks/procs/symbols during the class definition, to later be called inside of new.
How could I implement this? (Either having before_initialize take a block/proc/list of symbols, I don't mind at this point, just trying to understand the concept)
For a comprehensive description, you can always check the Rails source; it is itself implemented in 'plain Ruby', after all. (But it handles lots of edge cases, so it's not great for getting a quick overview.)
The quick version is:
module MyCallbacks
def self.included(klass)
klass.extend(ClassMethods) # we don't have ActiveSupport::Concern either
end
module ClassMethods
def initialize_callbacks
#callbacks ||= []
end
def before_initialize(&block)
initialize_callbacks << block
end
end
def initialize(*)
self.class.initialize_callbacks.each do |callback|
instance_eval(&callback)
end
super
end
end
class Tester
include MyCallbacks
before_initialize { puts "hello world" }
end
Tester.new
Left to the reader:
arguments
calling methods by name
inheritance
callbacks aborting a call and supplying the return value
"around" callbacks that wrap the original invocation
conditional callbacks (:if / :unless)
subclasses selectively overriding/skipping callbacks
inserting new callbacks elsewhere in the sequence
... but eliding all of those is what [hopefully] makes this implementation more approachable.
One way would be by overriding Class#new:
class LameAR
def self.before_initialize(*symbols_or_callables, &block)
#before_init_methods ||= []
#before_init_methods.concat(symbols_or_callables)
#before_init_methods << block if block
nil
end
def self.new(*args, &block)
obj = allocate
#before_init_methods.each do |symbol_or_callable|
if symbol_or_callable.is_a?(Symbol)
obj.public_send(symbol_or_callable)
else
symbol_or_callable.(obj)
end
end
obj.__send__(:initialize, *args, &block)
end
end
class Tester < LameAR
before_initialize :do_stuff
def do_stuff
puts "DOING STUFF!!"
end
end

block hack, how to simplify block

I have a question about Ruby blocks.
For example I have a Ruby Class:
class NewClass
def initialize
#a = 1
end
def some_method
puts #a
end
end
When I do something like that:
NewClass.new do |c|
c.some_method
end
Everything is good, but is there any possibilities to do that somehow like:
NewClass.new do
some_method
end
Any ideas?
Your current code will just ignore the block anyway since you don't yield to it. For what you are trying to do in your first example you need the yield self idiom in initialize.
For why you need a block variable in the first place, think about what the receiver for some_method would be in your second example. Without an explicit receiver it's the top level main (unless this code is part of some other class of course, where that enclosing class would be self). See Dave Thomas' blog post Changing self in Ruby (or Yehuda Katz' post as pointed out by Niklas B. in the comments) for more info on that topic (the comments clear up the "proc invocation" part).
Edit: all that said, this seems to work, but I prefer the yield self version and example 1:
class NewClass
def initialize
#a = 1
end
def some_method
puts "Hello: ##a"
end
def self.build(&block)
x = self.new
x.instance_eval(&block)
x
end
end
NewClass.build do
some_method
end
This allows you to execute the block without a block variable and will return the new instance of the class for assigning to a variable etc.
class NewClass
def initialize(&block)
#a = 1
instance_eval(&block)
end
def some_method
puts #a
end
end
NewClass.new do
some_method
end
Using instance_eval should do the job. But unless you know what you are doing (and it's not just out of laziness) I'd advice against it, and go with your original approach.
def initialize(&block)
#a = 1
self.instance_eval(&block) if block_given?
end

Executing code for every method call in a Ruby module

I'm writing a module in Ruby 1.9.2 that defines several methods. When any of these methods is called, I want each of them to execute a certain statement first.
module MyModule
def go_forth
a re-used statement
# code particular to this method follows ...
end
def and_multiply
a re-used statement
# then something completely different ...
end
end
But I want to avoid putting that a re-used statement code explicitly in every single method. Is there a way to do so?
(If it matters, a re-used statement will have each method, when called, print its own name. It will do so via some variant of puts __method__.)
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"
This is exactly what aspector is created for.
With aspector you don't need to write the boilerplate metaprogramming code. You can even go one step further to extract the common logic into a separate aspect class and test it independently.
require 'aspector'
module MyModule
aspector do
before :go_forth, :add_multiply do
...
end
end
def go_forth
# code particular to this method follows ...
end
def and_multiply
# then something completely different ...
end
end
You can implement it with method_missing through proxy Module, like this:
module MyModule
module MyRealModule
def self.go_forth
puts "it works!"
# code particular to this method follows ...
end
def self.and_multiply
puts "it works!"
# then something completely different ...
end
end
def self.method_missing(m, *args, &block)
reused_statement
if MyModule::MyRealModule.methods.include?( m.to_s )
MyModule::MyRealModule.send(m)
else
super
end
end
def self.reused_statement
puts "reused statement"
end
end
MyModule.go_forth
#=> it works!
MyModule.stop_forth
#=> NoMethodError...
You can do this by metaprogramming technique, here's an example:
module YourModule
def included(mod)
def mod.method_added(name)
return if #added
#added = true
original_method = "original #{name}"
alias_method original_method, name
define_method(name) do |*args|
reused_statement
result = send original_method, *args
puts "The method #{name} called!"
result
end
#added = false
end
end
def reused_statement
end
end
module MyModule
include YourModule
def go_forth
end
def and_multiply
end
end
works only in ruby 1.9 and higher
UPDATE: and also can't use block, i.e. no yield in instance methods
I dunno, why I was downvoted - but a proper AOP framework is better than meta-programming hackery. And thats what OP was trying to achieve.
http://debasishg.blogspot.com/2006/06/does-ruby-need-aop.html
Another Solution could be:
module Aop
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def before_filter(method_name, options = {})
aop_methods = Array(options[:only]).compact
return if aop_methods.empty?
aop_methods.each do |m|
alias_method "#{m}_old", m
class_eval <<-RUBY,__FILE__,__LINE__ + 1
def #{m}
#{method_name}
#{m}_old
end
RUBY
end
end
end
end
module Bar
def hello
puts "Running hello world"
end
end
class Foo
include Bar
def find_hello
puts "Running find hello"
end
include Aop
before_filter :find_hello, :only => :hello
end
a = Foo.new()
a.hello()
It is possible with meta-programming.
Another alternative is Aquarium. Aquarium is a framework that implements Aspect-Oriented Programming (AOP) for Ruby. AOP allow you to implement functionality across normal object and method boundaries. Your use case, applying a pre-action on every method, is a basic task of AOP.

Ruby call method on all following def calls?

I've seen some code that makes a class method such that you can write
class_method :instance_method,
to alias instance_method and call it from in a wrapper method every time it is called. Is there a way to be able to call class_method and have it apply to all the following definition calls (like how private works)?
I don't quite understand your question. In the future, please provide a specification of what it is exactly that you are trying to do, preferably in the form of an executable testsuite, so that we can check for ourselves whether our answers really answer your question.
Are you perhaps talking about something like this?
module MethodHook
private
def class_method(m=nil)
return if #__recursing__ # prevent infinite recursion
return #__class_method__ = true unless m
#__recursing__ = true
old_m = instance_method(m)
define_method(m) do |*args, &block|
puts "before #{m}(#{args.join(', ')})" # wrap wrap wrap
old_m.bind(self).(*args, &block)
puts "after #{m}" # more wrapping
end
#__recursing__ = nil
end
def method_added(m)
class_method(m) if #__class_method__
super
end
end
Use like this:
class Foo
extend MethodHook
def unwrapped
puts __method__
end
class_method
def wrapped
puts __method__
end
end
f = Foo.new
f.unwrapped
# unwrapped
f.wrapped
# before wrapped()
# wrapped
# after wrapped
class Foo
class_method(:unwrapped)
end
f.unwrapped
# before unwrapped()
# wrapped
# after unwrapped

Resources