From: Kevin Ballard Date: 2005-10-19T02:36:58+09:00 Subject: Re: Unit testing an each function Ok, here's a new version. It now generalizes the wrapping, so it's trivial to add it to new enumerable classes: class EnumerableProxy < Object instance_methods.each { |meth| undef_method(meth) unless meth.to_s =~ /^__/ } def initialize(obj, meth, *args) @obj = obj @meth = meth @args = args end def method_missing(meth, *args, &block) @obj.send(@meth, *@args) { |item| item.send meth, *args, &block } end end module Enumerable @@meth_cache = Hash.new def self.meth_cache @@meth_cache end # I'm using a string for module_eval instead of a block # because using a block necessitates using define_method, # and methods created with define_method can't handle a block # (because Proc's can't handle blocks) def self.wrap_meth(klass, meth) meth_sym = meth.to_sym.inspect klass.module_eval <<-END meth = instance_method(#{meth_sym}) Enumerable.meth_cache[#{klass.name}] ||= Hash.new Enumerable.meth_cache[#{klass.name}][#{meth_sym}] = meth def #{meth.to_s}(&blk) if blk meth = Enumerable.meth_cache[#{klass.name}][#{meth_sym}] meth.bind(self).call(&blk) else EnumerableProxy.new(self, #{meth_sym}) end end END end wrap_meth self, :collect end Enumerable.wrap_meth Array, :each Enumerable.wrap_meth Array, :collect Enumerable.wrap_meth Array, :collect! Unfortunately, due to limitations in Proc (documented in the comment above), I had to use the ugly string form of module_eval. I also had to use a method cache on Enumerable itself instead of the individual classes, becuase when I tried it on the individual classes it wasn't working right (seemed to be accessing Enumerable anyway, so I was getting cross-class method binding issues). Be careful when using irb with this, as it the proxy can cause confusion (since the proxy forwards #inspect along with everything else). Normally calling #collect without a block is basically the same as calling #to_a, so "foo".collect normally returns ["foo"]. However, once you've loaded this code, "foo".collect returns an EnumerableProxy object, which irb will display as just "foo" because it'll forward the #inspect message irb sends on to "foo". Usage of this is pretty easy. Wrap any methods you want with Enumerable.wrap_method class, :method. Of course it only makes sense on enumerable methods. Feel free to wrap any more of Enumerable's built-ins, and any classes you want to use #each with you have to wrap separately (along with any class-specific implementations of Enumerable methods, like Array#collect). Interestingly, this seems to work perfectly fine on destructive methods like Array#collect!. At this point I'm considering wrapping this up in a gem for distribution. Any thoughts?