Enumerator is related to iterators. The significant difference between the two is that Enumerator is an object and iterator is a method. Basically, Enumerator is a simple enumerable object with each method. It uses Enumerable module to define such methods like: take, drop, map, select etc. directly on top of its each method. The most interesting thing is how each method of Enumerator is defined. Enumerator doesn’t have built-in each method. Every time you create Enumerator, you have to specify this method. After you’ve did it, Enumerator knows how to use other enumerable methods (map, reduce etc.)

You can define each method algorithm in one of two ways:

  • call Enumerator.new with code block, which contains the each logic
  • attach enumerator to other object

Enumerator.new with code block

Lets’ see how we can create Enumerator calling new method on it. This is a simple example:

e = Enumerator.new do |y|
  y << 'a'
  y << 'b'
  y << 'c'
  y << 'd'
end

y is an instance of Enumerator::Yielder, which is automatically passed to the block. In the block we teach yielder what should happens when an each is called. Let’s try to call some enumerable methods on our enumerator in irb console:

e.to_a
#=> ["a", "b", "c", "d"] 
e.take(2)
#=> ["a", "b"] 
e.map(&:upcase)
#=> ["A", "B", "C", "D"]

As you can see each method works as expected. You can provide to Enumerator block any logic you like. For example, if you want to work on the whole alphabet, you can pass something like this:

e = Enumerator.new do |y|
  ('a'..'z').each { |letter| y << letter }
end

I even found so crazy and useless example:

a = [1,2,3,4,5,6]

e = Enumerator.new do |y|

  sum = 0

  until a.empty?
    sum += a.pop
    y << sum
  end

end

e.take(3) #=> [6, 11, 15] 
a #=> [1, 2, 3] 
e.map { |n| n*2 } #=> [6, 10, 12] 
a #=> []

Attach enumerator to other object

The second method to work with enumerators is more common. What you have to do here, is hooking up the enumerator to another object iterator (the most often to the each method but it can be any method that yields values). This will give the enumerator the basic knowledge what to do when each logic is called. To use this approach we call enum_for on the object from which we want to borrow iteration logic. As a first argument, we pass the name of the method onto which the enumerator will attach its each method. Let’s see the example:

a = [1,2,3,4]

e = a.enum_for(:select)
e.each { |n| n > 2 }

When we run this code in irb console, we get [3,4]. Here we use select method as a foundation of enumerator each method. After defined that, we can pass to each method block the condition we normally use for select method and it works :)

We can also pass additional arguments to enum_for method. Example:

a = [1,2,3,4,5]

e = a.enum_for(:inject, 'Countdown:')
e.each { |string, n| string + " #{n}..." }

#=> "Countdown: 5... 4... 3... 2... 1..."

Enumerator uses

Protecting objects with enumerators

One situation when enumerators can be useful is when you’d like to protect object from changes. Let’s say that you have an method that takes an array as an argument:

def some_method(arr)
  #some code here
end

Then the method can change the passed object:

def some_method(arr)
  arr.pop
end

array = [1,2,3,4]
some_method(array)
puts array # [1,2,3]

What can you do if you’d like to protect array argument form being changed? Yes! You can use Enumerator for this:

array = [1,2,3,4]
some_method(Enumerator.new(array))
puts array # undefined method `pop' for #<Enumerator: [1, 2, 3, 4]:each> (NoMethodError)

Enumerator can remember state

Enumerator is an object, so it remembers its state. It allows us to do something along these lines:

arr = %w{Paris London Rome}
e = arr.to_enum
puts e.next # Paris
puts e.next # London
puts e.next # Rome
puts e.rewind # returns to the beginning
puts e.next # Paris

Add enumerability to object

Enumerator can add enumerability to objects that don’t have it but they possess an iterator method. Let’s say we have a class:

module MyModule
  class Car

    BRAND = %w{ BMW Ford Toyota Ferrari }

    def wiki
      BRAND.each { |brand| yield(brand) }
    end

  end
end

cars = MyModule::Car.new
puts cars.map(&:upcase) # it won't work: undefined method `map' for #<MyModule::Car:0x007fc819886628>

Our class doesn’t mixin Enumerable module, so it can use such method as map. So what can we do? The easiest solution is mixin Enumerable module, of course. But we can also use Enumerator:

e = cars.to_enum(:wiki)
puts e.map(&:upcase)

mapwithindex – here you are!

Enumerable module has a very handy method – eachwithindex:

brands = %w{ BMW Ford Toyota Ferrari }
brands.each_with_index do |brand, index|

  puts "Brand #{index+1}: #{brand}"

end

It will be good to have similar method, that passes index as an argument but with ability of map method. There is no mapwithindex method but Enumerator class has with_index method which we can use instead:

brands.map.with_index { |brand, index| [brand, index] }
# [["BMW", 0], ["Ford", 1], ["Toyota", 2], ["Ferrari", 3]]

How it works? If we call an enumerable method without block, it returns Enumerable object. When we get one, we can use it to call with_index method.

As you can see, there are a lot of situations, when Enumerator can be really useful. For further reading about Enumerator and Enumerable module, I recommend article “Building Enumerable & Enumerator in Ruby”.

After almost two years and running over 1500 km I’ve decided to buy a new running shoes. In fact, my husband promised me a new shoes after running my first half-marathon in March. Eventually we went shopping yesterday. I’m lucky because I have shop for runners just around the corner :) Until now I’ve run in Nike shoes – Nike Eclipse. There were great, they’ve never let me down. But, you know, it’s time to something new. I decided to buy more natural shoes, without too much stabilisations systems and other stuff with scary names. I choose Brooks Pure Cadence 2 and I fell in love with them after my yesterday training. They’re light, my feet feel really comfortable, no grazes, no squeezes. Of course, it wasn’t long run, just 7 kilometers (my usual distance on Tuesday) but I’m sure that on Sunday (18 kilometers to run) my shoes won’t fail as well :)

brooks pure cadence

Last three days I spent at Front Trends conference in Warsaw. I must admit that it was the best event I took part into. I’m not a big fun of such events because I like to get practical knowledge, not only ensures that JavaScript and HTML5 is the best what happened in the web. It’s also hard for me to sit down and listen lectures for too long, maybe as I finished my school education so far ago. Anyway, the event was great and surprised me positively.

Front Trends poster

Speakers

When I first read the schedule I thought: ‘Well, who the f… are those people?’ Yep, accept two or three people I didn’t recognise anyone on the speaker list. I was disappointed as I was sure that for example Addy Osmani would be one of the speaker (I love this guy! :) . But it turned out that the speakers were really great. Smart, amusing and really, really wise people. I respect and admire what they do for web community. I hope one day I also give something so freshy and practical as they did.

Venue

I think it was the weakest element of the conference. The event took place into the Soho Factory. First of all it’s hard to get there. No subway, only one tram from the city center. The surroundings terrible (the right side of the Warsaw isn’t so nice – old houses, garbage everywhere etc.). Speeches took place into depot. There were a lot of room and fresh air there (it’s a pros) but it wasn’t warm on Wednesday, so I really got cold (I hate that!).

Food and drinks

I’m not gourmet. In fact I’m rather boring if you ask me about food but I must admit that food was delicious and there was a lot of it. I’m also not a big drinker but they served beer during conference so if somebody needed to take a courage there was a possibility.

Light talks

On the second stage, there were also additional speeches – light talks. I must say that in this point the organizers failed. There were the wi-fi problems all the time, so speakers were fighting technicals problems all the time instead being focused what they want to say. It was annoying for them and for audience. I hope these problems won’t repeat next time. Otherwise it’s pointless to organize such speeches.

To wrap up, there were some pros and cons of this conference but for me the most important is that I got actual knowledge and motivation to work harder. I think that web technologies will have great future because we have such wise and hard-working people on the board :)

Front Trends free time
Resting with my teammate at Front Trends conference

Here are links to (almost) all presentations from Front Trends.

In Ruby every method you define can take list of the arguments. You define them as a comma-separated list after the method name:

def my_method(a,b,c)
  #method code here
end

You can define required and optional arguments for the method. If you call the method with wrong number of the required arguments, Ruby will complain that there is an error:

def my_method(a,b,c)
  #method code here
end
 
my_method 1 
#=> ArgumentError: wrong number of arguments (1 for 3)

Any number of arguments

Sometimes you don’t want to define the exact number of the arguments. Ruby allows you to define such methods. To do this, you put an asterisk (*: a star) in front of the name of the single argument:

def my_method(*args)
  #method code
end

my_method 1
my_method 1,2

The *args notation means that the method can take zero, one or more arguments. After calling the method, the arguments are converted into the array. So you can get them traversing the array:

def my_method(*args)
  puts args[0]
end

my_method 1,2 
#=> puts 1

Default value fot an argument

You can also define default values for method’s arguments:

def my_method(a,b=2)
  puts a+b
end

my_method 1 # puts 3, as b has default value
my_method 1,1 # puts 2, as b has now value 1

In these example, in line 5 we don’t give second argument, so b equals 2. In line 6 we overwrite the default value and b equals 1.

Let’s play with arguments

Let’s play a little bit with arguments to understand how Ruby works. What would happend if we call the method like this:

my_method(a,*b,c)
  puts a, b, c
end

my_method 1,2,3,4

Ruby tries to give the value all the arguments. The argument with asterisk gets the lowest priority. So first of all, the a and c arguments get values. If there are more arguments to allocate, they will be put into b arguments (it’s an array, as you remember). So calling the method this way, we gets:

1
[2,3]
4

But if we call the method with any 2 arguments:

my_method 1,2

we get:

1
[]
2

In this case, Ruby sets value for the a and c arguments. Nothing leave for b argument, so we get empty array.

Let’s do our example even more complicated and add default value for an argument.

def my_method(a, b=2, *c, d)
  puts a,b,c,d
end

So right now we have two mandatory arguments (a and d), one argument with default value (b) and optional argument (c). So what will happen if we call out method like this:

my_method 1,3,2,4,5

As we already know, the mandatory arguments are given the values in the first place. After that Ruby give arguments value from left to right. So first the b argument gets value and then c. So we’ll get:

1
3
[2,4]
5

If we call the method with only two arguments, we get:

my_method 1,3

# results in
1
2
[]
3

Order of the arguments makes difference!

You must be aware of the order of the arguments in a metod definition. Let’s see one more example:

def my_method a, *b, c=1
  #method code
end

This will give us the syntax error. Let’s think about it: first a argument gets value, then Ruby gives value for the rest of argument from left to right. So there is no way that c gets value because all velues are put into b array. So always remember to put asterisk argument AFTER arguments with default values.