The Enumerable module in Ruby Core is very powerful. It provides methods such as sort, min, max and others for working specifically with collections. Its implemented by collection classes such as Array out of the box but what if you need to make your own custom collection class? In this case you will need to include the Enumerable module. Here’s how.
Lets consider a Coffee Shop that has a number of different blends of coffee on offer. Imagine that the shop wants to sort these different blends by strength. Also consider that we may need to quickly determine the stongest or the weakest coffee. Enumerable module to the rescue!
First of all, lets get some coffee brewed by implementing our Coffee class!
class Coffee
attr_accessor :name
attr_accessor :strength
def initialize(name, strength)
@name = name
@strength = strength
end
end
Lets make the coffee!
laos = Coffee.new("Laos", 10)
angkor = Coffee.new("Angkor", 7)
nescafe = Coffee.new("Nescafe", 1)
Now, for starters, lets just add this to an array and try and sort it.#
my_favorite_coffee = [laos, angkor, nescafe]
puts my_favorite_coffee.sort
This will blow up with the error in ‘sort’: comparison of Coffee with Coffee failed. This is becuase the Enumerable sort method (being called on the Array instance) does not have any idea how to sort a collection of Coffee objects. In order to fix this we need to implement the spaceship operator method on the Coffee class.
The Ruby spaceship operator is used to compare two objects. It returns only three possible values: -1, 0, 1. It works as follows
This simple logic is then used by the Enumerable Modules sort method.
So without further ado lets implement our own spaceship operator method for our Coffee class. In our specific case we need to sort on the strength attribute.
def <=>(other_coffee)
self.strength <=> other_coffee.strength
end
Now if we run the program again it does not fail but it does not work so well becuase we see three Coffee object strings like this output: <Coffee:0x007f9ada820f10>
. What we really want is the human readable detail about the coffee name and strength. In order to do this we need to override the to_s
method on the Coffee class.
Once we override the to_s
method like so
def to_s
"<name: #{name}, strength: #{strength}>"
end
Then our output is now as follows, which is much better!
<name: Nescafe, strength: 1>
<name: Angkor, strength: 7>
<name: Laos, strength: 10>
So now that we know we have provided enough detail to sort our Coffee objects by strength within a generic Array, we need to be able to do the same within our CoffeeShop class too. This is achieved by implementing the Enumerable module in the CoffeeShop class. There are two things we need to do in our CoffeeShop class to achive this:
each
method that yields each Coffee instance to the callerHere is our completed CoffeeShop class:
class CoffeeShop
include Enumerable
attr_accessor :coffees
def initialize(*coffees)
@coffees = coffees
end
def each
@coffees.map{|coffee| yield coffee}
end
end
We can use it as follows and we get the same result as we do with calling sort on the Array.
laos = Coffee.new("Laos", 10) angkor = Coffee.new("Angkor", 7) nescafe = Coffee.new("Nescafe", 1) cs = CoffeeShop.new(nescafe, laos, angkor) puts cs.sort
So that is the basics of implementing Enumerable in Ruby. Here is a Gist that contains the full source code.
Rotati is a full service Web and Mobile application consultancy. We use Ruby and JavaScript as our tools of choice.