I’ve often been asked by developers how they can improve. One low-hanging fruit is to get to know the tools that Ruby provides us. In this post, I’ll discuss Ruby’s Enumerable and how it can help us to be stronger engineers.
Rubyists should read the Enumerable documentation, reference it often, and become familiar with the tools it provides to speed up their code. Enumerable is a module included in commonly used data structures like Array and Hash as well as less common but still useful Set, Dir, and Range among others.
Enumerable provides a consistent and powerful set of methods for working with collections of data. This includes methods for iterating, filtering, transforming, and reducing data, as well as methods for searching, sorting, and grouping data. By using these methods, developers can write clean and concise code that is easy to read and understand.
Beyond Ruby’s classes that already include Enumerable, it’s easy to include in your own classes as it only requires that you define
#each. Many classes provided by gems also include Enumerable. Perhaps one of the most impactful would be Rails’ ActiveRecord::Association, returned from query methods like
order. This means that we can use Enumerable methods with a wide range of data types and structures, and can easily apply knowledge of methods to different scenarios and problem domains.
Enumerable is highly efficient and performant. Many of the methods provided by Enumerable are implemented in C, likely making something like
filter_map faster than running similar code written in Ruby to combine
compact. In addition, Enumerable methods can make use of lazy evaluation, which allows them to process data on-demand and avoid unnecessarily loading an entire collection into memory at once. This can help developers write efficient and scalable code that can handle large, or even infinite, amounts of data.
Let’s take a look at an example of using a naïve approach to iterating over an array to find the longest string and an Enumerable method that is designed to make it easier and faster.
# Given an array of strings, find the longest string in the array # using a naive approach def longest_string(strings) longest = "" strings.each do |str| longest = str if str.length > longest.length end longest end # using Enumerable#max_by def longest_string(strings) strings.max_by(&:length) end
In this example, the naive approach iterates over the array of strings using
Array#each, and compares each string to the current longest string to find the longest one. This approach works, but it is verbose and can be difficult to read and understand.
In contrast, the Enumerable approach uses the
Enumerable#max_by method to find the longest string in the array. This method takes a block that specifies how to compare the strings, and returns the string that is “maximal” according to the comparison. In this case, the block uses the
String#length method to compare the lengths of the strings, and
max_by returns the string with the longest length.
Familiarity with Enumerable’s powerful can be used to solve common problems in a more concise and elegant way than simply using the most basic building blocks it provides. It lets us avoid things like chaining the more common methods such as
flat_map or writing imperative code (telling the computer how to do something) in favor of declarative code (telling it what to do). Regularly referencing the documentation lets us build the “muscle memory” to check for the right tool for the job, even if we don’t know what it is off the top of our heads.
Prompt for ChatGPT
One thing I feel would be really helpful for junior and even intermediate Rubyists is to become really familiar with the methods provided by Ruby's Enumerable module, which is included in Arrays, Hashes, and Sets among other things. How would you convince developers to read up on that, with a few examples of naive approaches to problems with each or map being replaced with more appropriate Enumerable methods?