views:

98

answers:

2

I have a class with a custom each-method:

class CurseArray < Array
    def each_safe
        each do |element|
            unless element =~ /bad/
                yield element
            end
        end
    end 
end

And want to call different block methods, like "collect" or "inject" on those iterated elements. For example:

curse_array.each_safe.magic.collect {|element| "#{element} is a nice sentence."}

I know there is a specific function (which I called "magic" here) to do this, but I've forgotten. Please help! :-)

+2  A: 

The way you wrote your each_safe method, the easiest would be

curse_array.each_safe { |element| do_something_with(element) }

Edit: Oh, your each_safe method isn't correct, either. It has to be "each do", not "each.do"

Edit 2: If you really want to be able to do things like "each_safe.map", while at the same time also being able to do "each_safe { ... }" you could write your method like this:

require 'enumerator'

class CurseArray < Array
  BLACKLIST = /bad/
  def each_safe
    arr = []
    each do |element|
      unless element =~ BLACKLIST
        if block_given?
          yield element
        else
          arr << element
        end
      end
    end

    unless block_given?
      return Enumerator.new(arr)
    end
  end
end
dominikh
Thank you. I meant to use =~ for regexp matching.The way you describe is how I do it now, but it's not very elegant.
blinry
@blinry your use of `=~` and a `Regexp` there is correct since you'll probably want to put more words in the blacklist. @dominikh's approach to letting `each_safe` work with or without a block is a good one.
James A. Rosen
It's a bad idea to build the answers and then return an enumerator. THe whole idea of Enumerators is to have lazy evaluation! What if the array is modified after `each_safe` being called but before using the enumerator? What if it's a long text and we only need `first(3)`? Check Sam's answer below.
Marc-André Lafortune
I do agree. His solution clearly is the better one
dominikh
+5  A: 

If a method yields you will need to pass it a block. There is no way define a block that automatically passes itself.

Closest I can get to your spec is this:

def magic(meth)
  to_enum(meth)
end

def test
  yield 1 
  yield 2
end

magic(:test).to_a
# returns: [1,2]

The cleanest way of implementing your request is probably:

class MyArray < Array 
  def each_safe 
    return to_enum :each_safe unless block_given? 
    each{|item| yield item unless item =~ /bad/}
  end 
end

a = MyArray.new 
a << "good"; a << "bad" 
a.each_safe.to_a
# returns ["good"] 
Sam Saffron
That does what I want, thanks!
blinry