How Rails Enums Work Behind the Scenes

Chaitali Khangar
Railsfactory
Published in
3 min readJan 31, 2024

In Rails, we often declare an enum to predefine the value set for the column in a database. But, have you ever thought about how Rails does its magic behind the curtain?

Let’s uncover this secret in this article. But before diving into this let’s understand a few basic things.

What is Enum?

Enum is a user-defined data of a set of named values representing distinct elements.

When we should declare Enum?

According to the official API document, we should declare an enum attribute where the values map to integers in the database.

You can use enums wisely for situations where you,

  • Have a limited set of well-defined values (like blog statuses: draft, published, public, private, archived)
  • Want improved code readability and type safety
  • Need to enforce consistency and prevent invalid data

How does Rails perform its magic?

Rails use the power of metaprogramming and declare things on the fly using “define_method”.

So, Rails’ secret sauce is Metaprogramming with define_method.

Now, let’s look at the code and understand the secret.

Consider a class that inherits from ApplicationRecord, like this:

class Blog < ApplicationRecord
enum status: {
public: 0,
draft: 1,
private: 2
}
end

For this enum status Rails produces methods and scopes.

The method generated by Rails for status Enum:

Class Method:

Blog.statuses — Will return the [‘public’, ‘draft’, ‘private’]

Instance Methods: This will be generated for all the enum keys

  1. blog.public? — will return true or false based on blog is public or not

Format: object.enum_key?

2. blog.public! — will update the blog status to ‘public’

Format: object.enum_key!

Let’s understand how Rails is generating these methods.

Code for defining a method that returns all enums keys:

singleton_class.define_method(name.pluralize) { enum_values }

Rails use the pluralize method to generate the ‘statuses’ method which returns an enum array.

Code for defining a predicate method (a method that ends with ?):

define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }

This will generate the following code for active enum value.

def public?() status_for_database == 0 end

In Rails, if we want to get the value of any attribute we can do that using.

‘Blog.first.status’ or ‘Blog.first.status_for_database’.

In the above example, it uses ‘Blog.first.status_for_database’ which will trigger an SQL query and find out the status of the first blog entry.

Code for defining a bang method (a method that ends with !):

define_method("#{value_method_name}!") { update!(name => value) }

This will generate the following code for the public enum value and we can use it to update the value in the database.

def public!() update!(status: 0) end

Now, that we understand how Rails defines the method for an enum on the fly, let’s check out how it defines the scope.

Scope Generated by Rails for status Enum: This will be generated for all the enum keys

  1. Blog.public — Scope for returning blog value with status as ‘public’.
  2. Blog.not_public — Scope for returning all blogs that do not have the status as ‘public’.
  3. Blog.private — Scope for returning blog value with status as ‘private’.
  4. Blog.not_private — Scope for returning all blogs that do not have the status as ‘private’.
  5. Blog.draft — Scope for returning blog value with status as ‘draft’.
  6. Blog.not_draft — Scope for returning all blogs that do not have the status as ‘not_draft’.

Format: Blog.enum_value

Let’s understand how Rails is generating these scopes.

klass.scope value_method_name, -> { where(name => value) }
klass.scope "not_#{value_method_name}", -> { where.not(name => value) }

Rails internally converts the above code into:

scope :public, -> { where(status: 0) }
scope :not_public, -> { where.not(status: 0) }

Now we understand how Rails utilizes ‘define_method’ to generate supercool Enum definition.

You can also try to leverage ‘define_method’ in the same way.

If you are interested in exploring enums in Rails, feel free to check the code here

Till we meet, next time Happy Coding!!

--

--