The Inimitable Enumerable
If you do find yourself creating an aggregate class and equipping it with an internal iterator, you should probably consider including the Enumerable mixin module in your class. Enumerable is like one of those late-night gadget commercials: To mix in Enumerable, you need only make sure that your internal iterator method is named each and that the individual elements that you are going to iterate over have a reasonable implementation of the <=> comparison operator. For this one low, low price, Enumerable will add to your class a whole range of useful methods. Among the handy things you get from Enumerable are include?(obj), which returns true if the object supplied as a parameter is part of your aggregate object, plus min and max, which return exactly what you would expect.
The Enumerable mixin also includes more exotic methods such as all?, which takes a block and returns true if the block returns true for all of the elements. The Array class includes Enumerable, so we can write one line of code to return true if all the strings in an array are less than four characters long:
a = [ 'joe', 'sam', 'george' ] a.all? {|element| element.length < 4}
The string 'george' is longer than four characters, so the call to all? in this example will evaluate to false. Along the same lines of all? we have any?, which will return true if the block returns true for any of the iterator elements. Since 'joe' and 'sam' are both less than four characters long, the following will return true:
a.any? {|element| element.length < 4}
Finally, Enumerable supplies your class with a sort method, which returns all of the subitems in an array, sorted.
To see just how easy it is to include all of this functionality in one of your own classes, imagine that you have two classes: one that models a single financial account and one that manages a portfolio of accounts.
class Account attr_accessor :name, :balance def initialize(name, balance) @name = name @balance = balance end def <=>(other) balance <=> other.balance end end class Portfolio include Enumerable def initialize @accounts = [] end def each(&block) @accounts.each(&block) end def add_account(account) @accounts << account end end
By simply mixing the Enumerable module into Portfolio and defining an each method, we have equipped Portfolio with all kinds of Enumerable goodness. For example, we can now find out whether any of the accounts in our portfolio has a balance of at least $2,000:
my_portfolio.any? {|account| account.balance > 2000}
We can also find out whether all of our accounts contain at least $10:
my_portfolio.all? {|account| account.balance > = 10}