views:

227

answers:

2

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.

+2  A: 

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.

Pinochle
Andrey Shchekin
If you're interested in a single method, you merely filter for that inside of `method_missing`; but you did state that you don't like the idea of filtering. You're right that this would be an inelegant to difficult solution for a method that messages on several classes.Perhaps you should edit the question to add that second requirement (about several classes).
Pinochle
I am interested in outgoing calls from single method, not the ingoing calls to it. Or I want to intercept each line of the original method, as described in the original question.
Andrey Shchekin
Updated question a bit.
Andrey Shchekin
Ha, you're going to kill me, but I think what you are looking for is to be found in 8.11.3. of "The Ruby Programming Language", but you're still left with the problem of having to set up the tracing in each class where the methods are being used.At the very least, you could use this as a starting point towards a solution --- it would give you line-by-line tracing.
Pinochle
This is cool and I now know what the eigenclass is. But it still requires me to call trace! on each instance used within the method. After some consideration I think I'll go with set_trace_func -- I'll post a sample as a reply when I'm done with it.
Andrey Shchekin
Pinochle
+2  A: 

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.

James A. Rosen
If I read 8.11.3 correctly, then there should not be a global TracedObject.install! available, since it's not a self.install!, it is available only on instance level.
Andrey Shchekin