On Saturday 24 October 2009, Daniel Moore wrote:
> |
> |## Enumerable ObjectSpace (#222)
> |
> |Kaixo Rubyists,
> |
> |This week's quiz was suggested by Trans[1].
> |
> |ObjectSpace has the method #each_object. I've always wanted to alias
> |that to #each and make ObjectSpace Enumerable. But there's a catch,
> |#each_object takes an argument and Enumerable does not pass arguments
> |on to the #each method. So how does one resolve this issue? What's the
> |easiest way to make ObjectSpace Enumerable?
> |
> |One could always take the brute force approach and completely
> |re-implement Enumerable module to pass along the arguments. But I'm
> |hoping there are much more elegant solutions to be had.
> |
> |Have fun!
> |
> |
> |[1]: http://rubyworks.github.com

Here's my solution. It uses an instance variable to keep trace of the argument 
given to the enumerable method.

In more detail, here's how it worsk: after extending Enumerable, in redefines 
each method added by it allowing any number of arguments (indeed, all that is 
needed is to add one argument. I believe it can be done that way but I don't 
think it's worth the effort). The first argument is then removed from the 
array and inserted at the end of the array contained in the @__chosen_class 
instance variable (which is created if it doesn't already exist). Then, super 
is called, passing it the new argument list (that is, the one with the first 
argument removed) and the block.

The each method is then defined to either accept one argument, so that you can 
do

ObjectSpace.each(Array){...}

or to use the last element of @__chosen_class.

The reason to store the class in an array is to allow nested calls, that is 
something like this:

ObjectSpace.select(cls1) do |o|
  ...
  ObjectSpace.any?(cls2){...}
	...
end

When ObjectSpace.any? is called, it'll push cls2 at the end of 
@__chosen_class, so that its each will use that class, while each for the 
outer block will use cls1. I enclosed the call to super in a begin/ensure 
clause to be sure that the class pushed in the array is removed at the end of 
the method call.

module ObjectSpace
  
  methods = Enumerable.instance_methods - self.public_methods
  
  extend Enumerable
  
  methods.each do |m|
    
    instance_eval <<-STR
      
      def #{m} *args, &blk
        @__chosen_class ||= []
        @__chosen_class << args.shift
        begin super *args, &blk
        ensure @__chosen_class.pop
        end
      end

    STR
    
  end
  
  
  def self.each cls = nil
    each_object(cls || @__chosen_class[-1]){|o| yield o}
  end
  
end

Stefano