From: Chris Pine Date: 2003-01-04T11:36:31+09:00 Subject: examining a method Hello, I was thinking recently about how to examine an interface or, more simply, a single method. I don't know if anyone has tried something like this before, but I wanted to make an object which I could pass into a method and see what that method does to it to get an idea of what that method was expecting. I thought that maybe I should delete most of the methods it gets from Object (or Kernel), and then rewrite the ones I didn't delete to keep track of those method calls. Here's what I have so far: class Expectation (public_instance_methods(true) + protected_instance_methods(true) + private_instance_methods(true)).each do |meth| next if meth == 'initialize' next if meth == '__send__' next if meth == '__id__' undef_method meth end (public_instance_methods(true) + protected_instance_methods(true) + private_instance_methods(true)).each do |meth| module_eval <<-END_DEFINITION def #{meth} (*args, &block) method_missing :#{meth}, *args, &block super *args, &block end END_DEFINITION end def initialize (*args, &block) if !@calls @calls = {} @creator = args[0] @depth = (nil == @creator) ? 0 : args[1] else method_missing :initialize, *args, &block end end def inspect (*args, &block) # I don't want to keep track of `inspect' calls. return ' '*@depth + "expects nothing\n" if @calls.empty? str = '' @calls.each do |meth, calls| calls.each do |call| str += ' '*@depth str += "expects obj.#{meth} (#{call[0].join(', ')}) " if call[2] str += "to yield objects like\n" call[3].each do |blockParam| str += blockParam.inspect end str += ' '*@depth + 'and ' end str += "to return object like\n" str += call[1].inspect end end str end def method_missing symbol, *args, &block # I don't see how I could keep track of `method_missing' calls. @calls[symbol] = [] if !@calls[symbol] retVal = Expectation.new (self, @depth+1) if block blockParams = [] block.arity.abs.times do blockParams << Expectation.new(self, @depth+1) end block.call(*blockParams) @calls[symbol] << [args, retVal, block, blockParams] else @calls[symbol] << [args, retVal] end retVal end end puts expect = Expectation.new expect+5 == 2 expect.foo('bar'){|x| puts x} p expect Thoughts? Suggestions? Reasons why this is a terrible idea and I should stop now? I think something like this could possibly be useful for trying to determine exactly what an interface does. In a sense, it's like what you do when you're building mock objects for unit tests, except that it sort of builds itself as you use it. It would be much nicer if the output was more readable... any suggestions? Improvements? Oh brave new world, that has such programs in it... Chris PS: Have I mentioned how much I love this language? :) Ahhh, Ruby!