Reputation: 4965
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! :-)
Upvotes: 4
Views: 531
Reputation: 67850
The selected solution uses the common idiom to_enum :method_name unless block_given?
which it's ok, but there are alternatives:
Leave your "unfriendly" yielder method untouched, use enum_for
when calling it.
Use a lazy Enumerator
.
Use lazy arrays (needs Ruby 2.0 or gem enumerable-lazy).
Here's a demo code:
class CurseArray < Array
def each_safe
each do |element|
unless element =~ /bad/
yield element
end
end
end
def each_safe2
Enumerator.new do |enum|
each do |element|
unless element =~ /bad/
enum.yield element
end
end
end
end
def each_safe3
lazy.map do |element|
unless element =~ /bad/
element
end
end.reject(&:nil?)
end
end
xs = CurseArray.new(["good1", "bad1", "good2"])
xs.enum_for(:each_safe).select { |x| x.length > 1 }
xs.each_safe2.select { |x| x.length > 1 }
xs.each_safe3.select { |x| x.length > 1 }.to_a
Upvotes: 0
Reputation: 18430
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
Upvotes: 2
Reputation: 131112
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"]
Upvotes: 6