From: Brian Mitchell Date: 2006-05-11T11:15:06+09:00 Subject: Re: Module#add_tracer On 5/10/06, Logan Capaldo 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