× {{alert.msg}} Never ask again
Receive New Tutorials
GET IT FREE

Ruby’s Swiss Army Knife: The Enumerable Module

– {{showDate(postTime)}}

enumerable

’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.


Other tutorials you might be interested in:




Questions about this tutorial?  Get Live 1:1 help from Ruby on Rails experts!
K M Rakibul Islam (Rakib)
K M Rakibul Islam (Rakib)
5.0
Top Ruby on Rails Mentor of January, February, March, April, May, June, July & August 2017 (8 months in a row!) | Experienced (7+ years) and friendly Ruby on Rails Developer (with passion for helping others learn) | 775+ Sessions
I am the Top Ruby on Rails Mentor of January, February, March, April, May, June, July and August 2016 (8 months in a row) and January, February,...
Hire this Expert
Arup Rakshit
Arup Rakshit
5.0
Passionate Ruby/Rails and JS developer
I am a freelance web developer working out of India. My weapons of choices are : HTML, CSS, Javascript, SQL, Ruby, Ruby on Rails. I literally spend...
Hire this Expert

Or Become a Codementor!

Live 1:1 help from expert developers

Codementor is your live 1:1 expert mentor helping you in real time.

comments powered by Disqus
Codementor is your live 1:1 expert helping you in real time