enum VS enum VS enum - Enumeration, Enumerator & Enumerable

During my apprenticeship at thoughtbot, I was asked by my mentor, Matheus Richard, the following question: “Do you know what enumerators are?” and I promptly replied: “Yes! Enums are basically a collection of options!”. And well, that was when my world fell apart - apparently, there are “3 types of enums”. But don’t worry, at least they are correlated!!

So, what about enum?

In Rails, when we say enum, we are essentially referring to the Rails’ ActiveRecord::Enum class. Since ActiveRecord is Rails’ ORM to communicate with its Database (normally Postgres!), it is also correlated to PostgreSQL’s Enumerated type. It’s worth mentioning that we can rely purely on Rails’ enum and just create an integer column in the database and let Rails handle the rest. Their biggest advantage is that they have the readability of a string column while keeping the efficiency and the simplicity of an integer column in the actual Database!

Important: enum is a way to refer to the Rails’ ActiveRecord::Enum class unambiguously and should be the prefered word.

So a good way of answering the question “What is an enum?” is:

enum is the Rails’ solution to work with collections of data in our Database.”

This is what an enum looks like in Rails:

# Here we have an Article Model that can be either a draft,
# a published article, or an archived article.
class Article < ApplicationRecord
  enum status: { draft: 0, published: 1, archived: 2 }
end

# Using enums gives us access to methods based on the Enumerations created:
article = Article.new(status: :draft)

article.draft?
# => true

article.published!
# This will change the status from draft to published

article.published?
# => true

article.draft?
# => false

Enumeration

Enumeration in plain english is the act of counting out or listing items. It works over a collection of elements. It is worth mentioning that Enumeration is a concept.

Different programming languages might have different definitions of an Enumeration. For instance, Ruby does not have a definition for Enumeration.

So please remember that Enumeration is not a Ruby class or class. It’s a concept that is often used in other programming languages, as opposed to enumerators and enumerables, which are present and defined in Ruby.

Enumerator

Now that we know what enumeration is, we can disclose what an enumerator is. In Ruby, enumerator is how we traverse collections. You are most likely already familiar with one of its most popular method: #each! Quoting Scout APM:

“The enumerator is a basic implementation of the iterator design pattern – so it allows the environment to access the list of items in a class without exposing other details (such as its implementation).”

To add to our conversation and to the previous quoting, I’ll quote Joël Quenneville:

There are two iterator design patterns:

  • Internal iteration where the collection handles the traversal itself and calls a block you pass such as with traditional calls to the #each method
  • External iteration where the calling code controls the traversal by manually calling #next or #cycle

Enumerator has both #each, #next, and #cycle methods so it implements both patterns

For more on this idea, Design Patterns in Ruby chapter 7 covers internal/external iterator patterns.

When we previously used the #each method in the example above, we were using it in its block form, where the elements are evaluated individually in the Enumeration.

Now, to better visualize it, instead of passing a block, let’s call the #each in a non-block form:

enum = [1, 2].each

puts enum
# => #<Enumerator:0x00005560a80557e0>
# The #each method returns an instance of enumerator!!
# And it's wrapping the iteration!

# Here we are doing an external iteration
enum = [1, 2].cycle

puts enum.next
# => 1

puts enum.next
# => 2

puts enum.next
# => 1

puts enum.next
# => 2
# Notice how we can create an infinite series of elements from our Array!

This allows us to have more control than the #each method, especially when dealing with scenarios where we don’t want to iterate through every element immediately.

Here we have two great use cases from Joël of when he had to create an enumerator:

But honestly, for us to manually build/write an enumerator, especially in a Rails app, is a somewhat rare occasion. Most of the time we will be using a collection’s implementation of #each or methods from the enumerable module. We rarely interact with instances of the enumerator class.

Enumerable

And finally, here is where the Ruby magic happens…

Popular methods such as #any?, #filter, and #reduce are methods from the enumerable module. Most of the methods we have in Hashes and Arrays are actually from it, and they both can be considered enumerables, as they inherit the module.

Basically, this is Ruby’s solution to make and to allow the developer to make a class enumerated, as in having an Enumeration.

Thinking in plain English, codes aside:

  • The suffix able means “capable of, fit for, or worthy”.
  • In this case, we could say that it “is capable of enumerating”.

“Capable of enumerating”…? As an Enumeration? Yes! That is right!

my_friends_professions = {
  "Jane" => "Software Engineer",
  "Merelyn" => "Born rich, sorry!"
  "Anthony" => "Designer",
}

# Here we are traversing the hash, iterating by it.
my_friends_professions.each do |key, value|
  puts "#{key} is a(n) #{value}"
end
# => "Jane is a(n) Software Engineer",
# => "Merelyn is a(n) Born rich, sorry!"
# => "Anthony is a(n) Designer",

# Here we are also traversing the Hash, to sort it.
my_friends_professions.sort
# => "Anthony is a(n) Designer",
# => "Jane is a(n) Software Engineer",
# => "Merelyn is a(n) Born rich, sorry!"

# And lastly, we are also traversing it to verify
# if the Hash has any pair of key/value.
my_friends_professions.none?
# => false

Let’s Recap what we’ve learned

To sum it all up in the most straightforward way as possible, We’re going to summarize it in 4 lines:

  1. In a Rails context, enum refers to ActiveRecord::Enum.
  2. Enumeration is the concept of traversing over or being traversible. Keep in mind that this is not a ruby concept!
  3. Enumerable is a ruby class that provides access to most of the methods used by an Enumeration.
  4. Enumerator is a ruby class that allows an enumerable to be iterated over. The best example is the each method.

And that’s it! Congratulations for reading that far!

Suddenly “enum” don’t sound so scary anymore, right?!

References