rubyyield-keyword

What does the "yield" keyword do in Ruby?


I encountered the following Ruby code:

class MyClass
    attr_accessor :items
    ...
    def each
        @items.each{|item| yield item}
    end
    ...
end

What does the each method do? In particular, I don't understand what yield does.


Solution

  • This is an example fleshing out your sample code:

    class MyClass
      attr_accessor :items
    
      def initialize(ary=[])
        @items = ary
      end
    
      def each
        @items.each do |item| 
          yield item
        end
      end
    end
    
    my_class = MyClass.new(%w[a b c d])
    my_class.each do |y|
      puts y
    end
    # >> a
    # >> b
    # >> c
    # >> d
    

    each loops over a collection. In this case it's looping over each item in the @items array, initialized/created when I did the new(%w[a b c d]) statement.

    yield item in the MyClass.each method passes item to the block attached to my_class.each. The item being yielded is assigned to the local y.

    Does that help?

    Now, here's a bit more about how each works. Using the same class definition, here's some code:

    my_class = MyClass.new(%w[a b c d])
    
    # This points to the `each` Enumerator/method of the @items array in your instance via
    #  the accessor you defined, not the method "each" you've defined.
    my_class_iterator = my_class.items.each # => #<Enumerator: ["a", "b", "c", "d"]:each>
    
    # get the next item on the array
    my_class_iterator.next # => "a"
    
    # get the next item on the array
    my_class_iterator.next # => "b"
    
    # get the next item on the array
    my_class_iterator.next # => "c"
    
    # get the next item on the array
    my_class_iterator.next # => "d"
    
    # get the next item on the array
    my_class_iterator.next # => 
    # ~> -:21:in `next': iteration reached an end (StopIteration)
    # ~>    from -:21:in `<main>'
    

    Notice that on the last next the iterator fell off the end of the array. This is the potential pitfall for NOT using a block because if you don't know how many elements are in the array you can ask for too many items and get an exception.

    Using each with a block will iterate over the @items receiver and stop when it reaches the last item, avoiding the error, and keeping things nice and clean.