Sometimes when I’m fumbling around in irb on an API I don’t know, or with Active Record queries, I get an Enumerator object back when I don’t want to. So if I save this Enumerator as a variable, what the heck do I do with it? And why do I get an Enumerator anyway?
So what we’ll do is play around with a hash of elements. Elements have a name, color and what happens when you touch them attributes. We’ll iterate through them and print out the values as normal but we’ll also store the Enumerator by itself and iterate through it later.
# enumerator test
# ie: why the ---- do i get an enumerable back when I'm cluelessly hacking?
elements = [
{ :name => "earth", :color => "brown", :touch => "I'm dirty." },
{ :name => "wind", :color => "white", :touch => "I'm flying." },
{ :name => "fire", :color => "red", :touch => "I'm on fire!" },
{ :name => "water", :color => "blue", :touch => "I'm wet." },
{ :name => "ice", :color => "blue", :touch => "Brr!"}
]
# Here is the wtf moment. What are we supposed to do with an Enumerator?!
puts "What do you use a Ruby enumerator for?"
puts elements.find
# Fix it you dummy, you didn't pass in a block.
puts "\nYou don't get an enumerator if you pass a block in:"
puts elements.find{|e| e[:name] == "wind"}
# ok so ruby devs aren't dumb, wtf is enumerable
enum = elements.each
color_enum = enum.collect { |e| e[:touch] }
puts "\nDelayed enum search: "
puts color_enum.collect { |c| c }.join(", ")
puts "\nStandard collect with no enumerator: "
puts elements.collect{|e| e[:color]}.join(",")
# => ["brown", "white", "red", "blue", "blue"]
map = elements.collect {|e| {e[:name].to_sym => e[:color]} }
# [{:earth=>"brown"}, {:wind=>"white"}, {:fire=>"red"}, {:water=>"blue"}, {:ice=>"blue"}]
# No! This is an array of hashes. Not what we want.
h = {}
elements.each {|e| k=e[:name]; v=e[:color]; h[k]=v}
# {"earth"=>"brown", "wind"=>"white", "fire"=>"red", "water"=>"blue", "ice"=>"blue"}
# YES! One hash, one deep. Perfect.
puts "\nNice hash associating element to color: "
puts h
Run this and you get:
What do you use a Ruby enumerator for? #<Enumerator:0x00000100859c28></p> You don't get an enumerator if you pass a block in: {:name=>"wind", :color=>"white", :touch=>"I'm flying."} Delayed enum search: I'm dirty., I'm flying., I'm on fire!, I'm wet., Brr! Standard collect with no enumerator: brown,white,red,blue,blue Nice hash associating element to color: {"earth"=>"brown", "wind"=>"white", "fire"=>"red", "water"=>"blue", "ice"=>"blue"}
Very simple example but for me, this was confusing and helped me to understand that many things can return an Enumerator and many methods can work with an Enumerator. But what we’re doing here is just really seeing that the find method returns an Enumerator. What if we want to do something more original?
Let’s create a class that mixes-in the Enumerable module.
class StagesOfLife
include Enumerable
attr_accessor :stages
def initialize
@stages = {
:nothing => 0,
:teenager => 5,
:egg => 1,
:dead => 7,
:adult => 6,
:baby => 2,
:toddler => 3,
:kid => 4
}
end
def each
@sorted_stages = @stages.sort{|a,b| a[1]<=>b[1]}
@sorted_stages.each do |s|
yield s[0]
end
end
def max
@stages.sort{|a,b| a[1]<=>b[1]}.last[0]
end
def min
@stages.sort{|a,b| a[1]<=>b[1]}.first[0]
end
end
stages_of_life = StagesOfLife.new
puts stages_of_life.max
# => dead
puts stages_of_life.min
# => nothing
puts "\nEach block"
stages_of_life.each do |s|
puts "Stage: #{s}"
end
# Each block
# Stage: nothing
# Stage: egg
# Stage: baby
# Stage: toddler
# Stage: kid
# Stage: teenager
# Stage: adult
# Stage: dead
print "\nAs array: "
puts stages_of_life.collect {|s| s}.join(", ")
# => As array: nothing, egg, baby, toddler, kid, teenager, adult, dead
I’ve included the output in the comments above. We define our stages of life and include a value 0-7 for each stage. I used a value in the @stages hash as the sort key so we could implement a custom sort for the #max and #min methods. Also notice that we have to sort our hash by value in #each because otherwise our hash would have to be defined in order (ie: nothing, egg, baby …).
So there you go, a very basic tour of Enumerable in Ruby. If this is interesting also look at how to mixin and implement your own Comparable.