Reputation: 21599
I understand that it is possible to decorate methods with before and after hooks in ruby, but is it possible to do it for each line of a given method?
For example, I have an automation test and I want to verify that after each step there no error shown on the page. The error is shown as a red div and is not visible to Ruby as raise
or anything like that, so I have to check for it manually (there are several other use cases as well).
I understand that it might be possible using set_trace_func
. But I think this may bring more problems than benefits since it works over entire call tree (and requires me to filter it myself).
UPDATE (clarification):
I am interested in intercepting all actions (or calls) that are performed within a given method. This means unspecified number of classes is called, so I can't just intercept any given set of classes/methods.
Upvotes: 4
Views: 773
Reputation: 774
What about (just tries)
class User
def initialize(name)
@name = name
end
def say_hello
puts "hello #{@name}"
end
def say_hi(friend)
puts "hi #{@name} from #{friend}"
end
def say_bye(a, b = 'Anna')
puts "bye #{a} and #{b}"
end
end
User.class_eval do
User.instance_methods(false).each do |method|
original = instance_method(method)
define_method method do |*options|
parameters = original.parameters
if parameters.empty?
original.bind(self).call
else
original.bind(self).call(*options)
end
puts __method__
end
end
end
user = User.new("John")
user.say_hello
user.say_hi("Bob")
user.say_bye("Bob")
user.say_bye("Bob", "Lisa")
outputs:
hello John
say_hello
hi John from Bob
say_hi
bye Bob and Anna
say_bye
bye Bob and Lisa
say_bye
Upvotes: 0
Reputation: 65232
It sounds like you want tracing on every method call on every object, but only during the span of every call to one particular method. In that case, you could just redefine the method to turn on- and off instrumentation. First, use the universal instrumentation suggsted in Pinochle's answer, then redefine the method in question as follows:
# original definition, in /lib/foo.rb:
class Foo
def bar(baz)
do_stuff
end
end
# redefinition, in /test/add_instrumentation_to_foo.rb:
Foo.class_eval do
original_bar = instance_method(:bar)
def bar(baz)
TracedObject.install!
original_bar.bind(self).call(baz)
TracedObject.uninstall!
end
end
You'd need to write the install!
and uninstall
methods, but they should be pretty trivial: just set or unset a class variable and check for it in the instrumentation logic.
Upvotes: 2
Reputation: 5563
It is, and you can get a full description of how to do it in section 8.9 of The Ruby Programming Language. Running the code on each invocation of the method involves sending the method to a TracedObject
class that has an implementation for method_missing
. Whenever it receives a message, it invokes method_missing
and executes whatever code you have assigned to it. (This is, of course, for general tracing).
That's the general description of the procedure for doing it, you can consult the book for details.
Upvotes: 2