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 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
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:
- In a Rails context,
enum
refers toActiveRecord::Enum
. - Enumeration is the concept of traversing over or being traversible. Keep in mind that this is not a ruby concept!
Enumerable
is a ruby class that provides access to most of the methods used by an Enumeration.Enumerator
is a ruby class that allows anenumerable
to be iterated over. The best example is theeach
method.
And that’s it! Congratulations for reading that far!
Suddenly “enum” don’t sound so scary anymore, right?!