On 5/10/06, Logan C. [email protected] wrote:
Yours also breaks when someone else overrides the same method and
aliases it to old_#{meth} (Which, unfortunately is pretty common.)
So does yours in many cases ;-). Imagine any code that uses
Binding.of_caller, debug, or profile to any significant extent… Here
are a few more advanced solutions (lacking in polish a little but that
is a task for the reader):
module Kernel
tracers = []
legacy_tracer = nil
set_trace_func lambda {|*args|
legacy_tracer.call(*args) if legacy_tracer
Might want to check line and file to match LINE and FILE
by the correct difference.
tracers.dup.each {|tracer| tracer.call(*args)} unless args[3] ==
:trace
}
define_method(:set_trace_func) {|proc|
legacy_tracer = proc
nil
}
Not thread safe but easy to make so.
define_method(:trace) {|tracer, block|
tracers << tracer
result = block.call
lambda {} == lambda {} work around just in case.
tracers.reject! {|x| x.object_id == tracer.object_id}
result
}
end
The above allows one to trace regions of code easily (still a bit
ugly… maybe keyword arguments and {||} style block construction would
help):
def selective_trace
result_of_the_traced_block = trace(lambda {|*a|
puts “Tracing code here”
}, lambda {
# code to trace here. You could pass a method in also.
# The Symbol#to_proc code might make that look even better.
})
end
… Not so much greater but it at least doesn’t let others clobber
tracers.
The other snippet is a cleanup of the original tracer code using
another technique. The unfortunate thing is that the clean version
only works in 1.9+ (block with blocks). There are workarounds I will
show but first the nice looking code:
class Module
def add_tracer(meth)
old_proc = instance_method(meth)
define_method(meth) { |*args, &block|
puts caller[1] + ": " + self.class.name + "##{meth} called"
old_proc.bind(self).call(*args, &block)
}
end
end
Note the lack of text eval (which is really a big plus IMO). 1.8 is a
sad story in this regard however (I wish Matz would allow 1.8 to take
block args in blocks somehow ~.^). The preservation of the method is
done in a hash rather than a lexical closure in 1.8’s case:
class Module
Simple tracer storage. This could be more elaborate.
class << self
attr_reader :tracers
end
@tracers = {}
Very similar to original code. Doesn’t clobber for
single level tracing (as in only calling add_tracer on
:foo once will preserve foo). More elaborate storage
could provide safety from multi-tracing but I couldn’t
think of a good use case.
def add_tracer(meth)
Module.tracers[meth] = instance_method(meth)
module_eval %{
def #{meth}(*args, &block)
puts caller[0] + ": " + self.class.name + “##{meth} called”
Module.tracers[:#{meth}].bind(self).call(*args, &block)
end
}
end
end
Note that this also resorts to text eval again. There are some really
complex (a.k.a. ugly) hacks to avoid this but I would rather have a
simple example. The conclusion is that there are plenty better options
than using alias and similar to accomplish this. Your best bet is
probably the one that best models the type and lifetime of your hook.
That can vary quite a bit from code to code so use your best judgement
and always have a generous amount of test code to ensure things stay
in line.
At least we can all look forward to the eventual release of Rite and
the 2.0 language. The older slides from his “How Ruby Sucks”
presentation (RUbyConf 03) [1] and the newer “Wild and Weird Ideas”
presentation (RubyConf 05) [2] show some possible ways 2.0 might
support wrapping method calls. If that doesn’t happen at least blocks
can take blocks right now in 1.9 ^^.
Brian.
[1] http://www.rubyist.net/~matz/slides/rc2003/mgp00028.html
[2] http://www.rubyist.net/~matz/slides/rc2005/mgp00033.html