Ruby’s Swiss Army Knife: The Enumerable Module
’Ruby’s Swiss Army Knife: The Enumerable Module’ is an article by Natasha Postolovski’s, a self-taught developer, now working as a software developer at ThoughtWorks in Australia. You can follow her on Twitter at @npostolovski.
Incredibly useful, though un-intuitively named, Ruby’s Enumerable module is a kind of Swiss Army Knife, capable of solving a variety of problems with precise, compact tools. In many cases where people write Ruby that isn’t idiomatic, more akin to Java or Python in style, it’s because they don’t have a deep understanding of the elegant tools offered by Enumerable.
Before we dive into what those tools are, let’s step back a little bit. Enumerable is one of Ruby’s modules. In Ruby, a module is a collection of methods, classes and/or constants inside a particular namespace. A namespace is a unique environment or naming scheme that prevents collisions in behavior. For example, I can have two methods called #each
in my program, and use them at the same time, as long as they are in a different namespace.
The Enumerable module can be mixed into any class that creates objects that may need to be compared, sorted, examined, or categorized. If you’ve worked with Arrays or Hashes in Ruby, you may have already performed these kinds of operations on them, iterating over them with #each
or sorting array items with #sort
. Enumerable allows you to quickly implement these kinds of behaviors in your own classes.
Before we dive into creating a class that utilizes Enumerable, let’s take a look at five Enumerable methods that will give you some idea of the power of this module. The following five methods (which I sometimes call “The Ects”) are critical for writing idiomatic Ruby code. They are #collect
, #select
, #reject
, #detect
, and #inject
. They can solve problems in one line that would otherwise require writing more complex conditional logic from scratch. A deep knowledge of each of these methods will make you a much better Ruby programmer.
You Might Also Want to Read: Become a Developer with these 20+ Resources
Going beyond #each
Learning #each
is often the moment when programmers coming from other languages start to appreciate the uniqueness of Ruby. Instead of writing the following code:
names = ['Lee', 'Tania', 'Louis']
for name in names
puts name
end
You can write:
names = ['Lee', 'Tania', 'Louis']
names.each do |name|
puts name
end
Or, even more succinctly:
names = ['Lee', 'Tania', 'Louis']
names.each { |name| puts name }
While some programmers feel Ruby’s #each
syntax is more readable than a for
loop, it’s not necessarily less verbose. Even so, using #each
is the most common way for Rubyists to handle iteration. Many people learning Ruby will stop here. Having learned #each
, they’ll add conditional logic to #each
blocks to perform tasks that “The Ects” are built to handle. If your code is littered with usage of the #each
method, you will probably benefit from learning about some of the other methods in Enumerable.
#collect
Also known by another name you may be familiar with — #map
— #collect
allows you to loop over objects and add the return value of each loop to an array.
You’ll see many beginner Ruby programmers do this instead:
names = ['Lee', 'Tania', 'Louis']
uppercase_names = []
names.each do |name|
uppercase_names << name.upcase end uppercase_names #=> ["LEE", "TANIA", "LOUIS"]
You can achieve the same thing using #collect
as follows:
names = ['Lee', 'Tania', 'Louis']
uppercase_names = names.collect { |name| name.upcase }
uppercase_names #=> ['LEE', 'TANIA', 'LOUIS']
#select
The #select
method allows you loop over a collection and return a list of objects for which a particular expression returns true. In other words, take a collection of objects and ‘select’ those that meet a certain criteria, discarding the rest. Here’s a more verbose example, inspired by the song Molly Mallone, using our friend #each
:
cockles_and_mussels = ['alive', 'dead', 'dead', 'alive', 'alive', 'dead']
alive_alive_oh = []
cockles_and_mussels.each do |cockle_or_mussel|
if cockle_or_mussel == 'alive'
alive_alive_oh << cockle_or_mussel end end alive_alive_oh #=> ["alive", "alive", "alive"]
Here’s what a solution looks like using #select
:
cockles_and_mussels = ['alive', 'dead', 'dead', 'alive', 'alive', 'dead']
alive_alive_oh = cockles_and_mussels.select do |cockle_or_mussel|
cockle_or_mussel == 'alive'
end
alive_alive_oh #=> ['alive', 'alive', 'alive']
You can see that any object passed into the block that is evaluated as part of a true/false expression and returns true
will be added to an array.
#reject
The #reject
method is very similar to #select
, but the inverse. It will leave behind any objects for which the expression returns true
, and add only those that return false
to the resulting array.
Here’s the above example, this time using #reject
:
cockles_and_mussels = ['alive', 'dead', 'dead', 'alive', 'alive', 'dead']
alive_alive_oh = cockles_and_mussels.reject do |cockle_or_mussel|
cockle_or_mussel == 'dead'
end
alive_alive_oh #=> ['alive', 'alive', 'alive']
Choosing between #select
and #reject
is often a matter of style. Both can be used to solve similar problems effectively.
#detect
The #detect
method (also implemented as #find
) is similar to #select
, but instead of returning a collection of objects that match the given criteria, it will “detect” the first matching element it finds and return only that object.
songs = [
{ title: 'Mad World', artist: 'Gary Jules', is_sad: true },
{ title: 'California Gurls', artist: 'Katy Perry', is_sad: false },
{ title: 'Needle in the Hay', artist: 'Elliott Smith', is_sad: true },
{ title: 'Happy', artist: 'Pharrell Williams', is_sad: false }
]
sad_song_to_play_now = songs.detect { |song| song[:is_sad] }
sad_song_to_play_now #=> { title: 'Mad World', artist: 'Gary Jules', is_sad: true }
#inject
The #inject
method is wonderfully useful, though often misunderstood. It’s an excellent tool for building up data structures, or adding values together. It’s often used to sum up numbers into a total. Here’s an example of that, and then we’ll dive into a slightly different usage:
shopping_cart = [
{ name: 'Vermillion Ink', price: 12.99 },
{ name: 'Azure Ink', price: 9.99 },
{ name: 'LAMY Safari Fountain Pen', price: 49.95 }
]
order_total = shopping_cart.inject(0) do |total, item|
total + item[:price]
end
order_total #=> 72.93
I should note that this example is slightly problematic. For simplicity’s sake I’m using floats to represent monetary values, but this can cause problems. In the real world it’s much better to use a class better suited to monetary values, such as BigDecimal.
Unlike the other “Ect” methods, #inject
passes two values to the block. The left-hand value is the accumulator. It starts at 0 (the argument to inject is the starting value) and will accumulate the result of the expression in the block. The right-hand argument is the object being iterated over.
We can also use #inject
to build up data structures. Let’s say we have an array of some employee data:
customer = [['full_name', 'Lois Lane'], ['position', 'Journalist']]
This looks like the kind of data you might extract from a CSV file. It’s in a format we can work with, but we can do better. Let’s use #inject
to construct a Hash from this data.
customer = [['full_name', 'Lois Lane'], ['position', 'Journalist']]
customer.inject({}) do |result, element|
result[element.first] = element.last
result
end
customer #=> { "full_name"=>"Lois Lane", "position"=>"Journalist" }
This is a really useful tool. You might have noticed that we are passing an argument to #inject
: an empty hash. This will be used as the initial value of the “result” or accumulator variable. We start with this object and then build it up with each successive iteration over the elements in the array.
A few other useful Enumerable methods:
#any
The #any
method returns true
if any element in the collection match the given expression.
pet_names = ['pluto', 'scooby', 'nyan']
find_scooby = pet_names.any? { | pet | pet == 'scooby' }
find_scooby #=> true
#all
The #all
method returns true
if all elements in the collection match the given expression.
ages = [ 19, 59, 70, 23, 140 ]
valid = ages.all? { | age | age > 0 && age <= 122 } valid #=> false
#each_with_index
A slight enhancement to the #each
method, #each_with_index
iterates over the element in the collection, as well as providing its index.
online_opponents = Hash.new
%w(joe87 potatahead coolguy415 ).each_with_index do |item, index|
online_opponents[item] = index
end
online_opponents #=> {"joe87"=>0, "potatahead"=>1, "coolguy415"=>2}
#include?
The #include?
method will return true if any elements in the collection are equal to the given object. Object equality is tested using `==
` (this post provides a good explanation of the different types of equality in Ruby).
superhero_names = ['Wonder Woman', 'Batman', 'Superman']
awesome = superhero_names.include? 'Wonder Woman'
awesome #=> true
Making Your Own Classes Enumerable
In the previous examples we’ve been calling Enumerable methods on instances of the Array class. While this is powerful on its own, Enumerable becomes even cooler when you include the module in a class of your own creation, assuming that class is a collection and is well-suited to the kinds of behaviours provided by the module.
Let’s say you want to have a class that represents a football team. Seems like a good candidate for Enumerable, right? To unlock the magic, we need to include the module and define an #each
method on the class. As you can see, this #each
method delegates to Enumerable’s implementation of #each
, which is included in the Array class. Nice!
class FootballTeam
include Enumerable
attr_accessor :players
def initialize
@players = []
end
def each &block
@players.each { |player| block.call(player) }
end
end
With this small addition, we can treat our FootballTeam
class like the collection it really is, using Enumerable methods like #map
.
irb(main):002:0> require 'football_team.rb'
=> true
irb(main):003:0> football_team = FootballTeam.new
=> #
irb(main):004:0> football_team.players = ['Mesut Özil', 'Leo Messi', 'Xavi Alonso']
=> ["Mesut Özil", "Leo Messi", "Xavi Alonso"]
irb(main):005:0> football_team.map { |player| player.upcase }
=> ["MESUT ÖZIL", "LEO MESSI", "XAVI ALONSO"]
irb(main):006:0>
This pattern can help make your code a little bit more object-oriented. Rather than using basic data structures like arrays to represent collections, you can enrich them with behaviours that suit their purpose, without losing all the benefits gained by having access to Enumerable methods.
Conclusion
I hope I’ve encouraged you to play around with the Enumerable module. Next time you reach for the #each
method to solve a problem, take a moment to consider whether one of #collect
, #select
, #reject
, #detect,
or `#inject
` could solve the problem in a more elegant way. And if you’re working with a class that represents a collection, consider enriching the class by including Enumerable.