Search code examples
rubyenumerator

Easily create an Enumerator


When creating methods that yield, sometimes we want it to return an Enumerator if no block is given. The recommended way is basically return to_enum(:name_of_method, [args]) unless block_given?. However, it's a pain to have to type that for every method that does this. Ruby being ruby, I decided to create a make_enum method, similar to attr_accessor, which does this for me:

class Module # Put this in a mixin, but for the purposes of this experiment, it's in Module
  def make_enum *args
    args.each do |name|
      old_method = instance_method(name)
      define_method(name) do |*args, &block|
        next to_enum(name, *args) unless block
        old_method.bind(self).call(*args, &block)
      end
    end
  end
end

Now I can use it like so:

class Test
  def test
    yield 1
    yield 2
  end

  make_enum :test
end

t = Test.new
t.test { |n| puts n }
# 1
# 2
t.test.to_a #=> [1, 2]

And it works! But it doesn't work if make_enum is before the method definition.

How can I get this method to work before defining a method, so that the following works? Perhaps I need to make use of method_added?

class Test
  make_enum :test

  def test
    yield 1
    yield 2
  end
end

I don't know if it's a bad idea for it to be before the method, but my reason for thinking that it would be nice to do that is that it better matches the way we use attr_accessor and the like.


Solution

  • Whereas attr_ methods create instance methods newly, your make_enum modifies an existing method, which is rather similar to protected, private, and public methods. Note that these visibility methods are used either in the form:

    protected
    def foo; ... end
    

    or

    protected def foo; ... end
    

    or

    def foo; ... end
    protected :foo
    

    The latter two ways are already available with your make_enum. Especially, the second form is already possible (which Stefan also notes in the comment). You can do:

    make_enum def test; ... end
    

    If you want to do the first form, you should try to implement that in your make_enum definition.