Suppose an object can yield values one at a time. In Ruby, the usual way to give it collection-style operations is to include Enumerable and define an instance method named each. That method yields values to a block; Enumerable uses those yields to implement methods such as find, map, select, and first. The object need not store its values in an array. section 10.0 section 10.1
Listing 10.1 An Enumerable class and its deployment of the each method
class Rainbow
include Enumerable
def each
yield "red"
yield "orange"
yield "yellow"
yield "green"
yield "blue"
yield "indigo"
yield "violet"
end
end
A small class yields colors directly from each. Including Enumerable makes methods such as find available without defining them in the class. section 10.1
The meaning of an element comes from each, not from Enumerable. An array yields its members in sequence; a hash yields key/value pairs as two-element arrays; a file handle yields lines. A range can participate when it can iterate, but a range starting at a float cannot support iteration-based queries. This is why the same Enumerable method can receive different kinds of block arguments on different objects. section 10.1 section 10.2
Once each is in place, the shared operations serve several jobs:
- Test values with all?, any?, one?, or none?, or find the first value passing a block. section 10.2 section 10.1
- Filter with select or grep; grep tests elements with ===, so a regular expression, class, or range can supply the criterion. section 10.3 section 10.3.3
- Take or drop an initial run of values, attach ordinal positions with each_with_index, or sort values whose comparisons support it. section 10.4.2 section 10.5.2 section 10.8
A search built on each
With a color-yielding object that includes Enumerable, `r.find { |color| color.start_with?('y') }` returns `"yellow"`. find calls each, tests each yielded color with the block, and stops at the first match; the class does not need its own find method. section 10.1
Shared methods do not erase class-specific behavior. Hash#select returns a hash, whereas Enumerable’s select returns an array. An enumerator attached to a hash’s each uses Enumerable’s select and returns an array of key/value pairs. Nor must an object itself include Enumerable to expose its yielded values: an Enumerator can attach its own each to another object’s yielding method and provide Enumerable operations there. section 10.10.1 section 10.10.4
Iteration can also produce values indefinitely. Enumerable has first but no general last: an endlessly yielding each has no final value. Operations such as map and select that wait for iteration to finish cannot return a completed result from that endless stream. Choose an operation that stops when its goal is met, or provide a stopping condition. section 10.4.1 section 10.10.4
