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:
enumis a way to refer to the Rails’ActiveRecord::Enumclass unambiguously and should be the prefered word.
So a good way of answering the question “What is an enum?” is:
“
enumis 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
#eachmethod- External iteration where the calling code controls the traversal by manually calling
#nextor#cycleEnumerator has both
#each,#next, and#cyclemethods so it implements both patternsFor 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
ablemeans “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:
- In a Rails context,
enumrefers toActiveRecord::Enum. - Enumeration is the concept of traversing over or being traversible. Keep in mind that this is not a ruby concept!
Enumerableis a ruby class that provides access to most of the methods used by an Enumeration.Enumeratoris a ruby class that allows anenumerableto be iterated over. The best example is theeachmethod.
And that’s it! Congratulations for reading that far!
Suddenly “enum” don’t sound so scary anymore, right?!