Let's step through it:
arr = [10, 20, 30, 40]
enum0 = arr.each
#=> #<Enumerator: [10, 20, 30, 40]:each>
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: [10, 20, 30, 40]:each>:with_index>
We can see the contents of enum1 by converting it to an array:
enum1.to_a
#=> [[10, 0], [20, 1], [30, 2], [40, 3]]
This tells us that Enumerator#each (which will invoke Array#each) will pass the four elements of the enumerator enum1 into the block, assigning them in turn to the block variables. The first is:
elmt, i = enum1.next
#=> [10, 0]
puts elmt, i
# 10
# 0
elmt == 20
#=> false
so arr.delete_at(i) is not executed.
Neither arr nor enum1 have been altered:
arr
#=> [10, 20, 30, 40]
enum1.to_a
#=> [[10, 0], [20, 1], [30, 2], [40, 3]]
each now passes the next element of enum1 into the block:
elmt, i = enum1.next
#=> [20, 1]
elmt == 20
#=> true
so we execute:
arr.delete_at(i)
#=> [10, 20, 30, 40].delete_at(1)
#=> 20
arr
#=> [10, 30, 40]
enum1.to_a
#=> [[10, 0], [30, 1], [40, 2]]
Ah! So the enumerator has been changed as well as arr. That makes perfect sense, because when the enumerator is created a reference to the original receiver is established, together with rules for what is to be be done with it. Changes to the receiver will therefore affect the enumerator.
We can use Enumerator#peek to see what will be the next element of enum1 that each will pass into the block:
enum1.peek
#=> [40, 2]
So you see that each moves on to the next indexed position, oblivious to the fact that an earlier element has been removed, causing the later elements to each shift down by one position, causing each to skip [30,1].
elmt, i = enum1.next
#=> [40, 2]
elmt == 20
#=> false
arr
#=> [10, 30, 40]
enum1.to_a
#=> [[10, 0], [30, 1], [40, 2]]
At this point each reaches the end of the enumerator, so it's job is finished. It therefore returns the original receiver, arr, but that has been modified, so we get:
[10, 30, 40]
A better example might be:
arr = [10, 20, 20, 40]
where:
[10, 20, 40]
would be returned.
Array(or generally anyEnumerable) while iterating over it produces undefined behaviour (at least in MRI). To cite Matz (the creator of Ruby): "It is undefined behavior of iterators if the block modifies iterating object. It may crash or fall in to infinite loop."