Ruby on Rails - How to Create Perfect Enum in 5 Steps
If you work on a project within the domain of Ruby on Rails, enum can be an attribute you’d want to understand and use frequently. Why is that? In short, enums (enumerations) make your work faster, more efficient and less erroneous.

When your project starts you probably design ERD diagram or a similar one. Then, each time when a client passes new requirements to you it’s necessary to modify it. That process helps to understand the specific domain and mirrors the reality. Entities that you model contain many attributes of various types. A quite popular requirement is to create an attribute that can be assigned to one of a few available values. In programming, that type is called enumeration or just enum.
As an example, that could be a type of delivery: "courier", "parcel station" or "personal". Rails supports enums from version 4.1.
This article consists of 3 sections:
Basic solution - introduce
ActiveRecord::Enum
, as simple as possible5 various steps to improve enums functioning
Ultimate solution - wrap all improvements into one implementation
To better understand the topic let’s add some real background. In our recent project, we worked on a system related to artworks. Artworks
were collected into Catalogs
. The catalog was one of the biggest models in our app. Among many attributes, we had 4 of the enum type.
state: ["incoming", "in_progress", "finished"]
auction_type: ["traditional", "live", "internet"]
status: ["published", "unpublished", "not_set"]
localization: ["home", "foreign", "none"]
Basic Solution
Adding enum to an existing model is a really simple task. First of all, you need to create an appropriate migration. Notice that column type is set to integer and this is how Rails keeps enums values in the database.
rails g migration add_status_to_catalogs status:integer
tsx
Next step is to declare enum attribute in the model.
tsx
Run the migrations and that’s it! From now you can take advantage of the whole bunch of extra methods.
For example, you can check if the current status is set to a specific value:
catalog.published? # false
or change the status to other value:
tsx
List all published catalogs:
Catalog.published
To see all provided methods check ActiveRecord::Enum.
This simple solution is great for a start, but you may run into some troubles when your project will grow. To be prepared, you can implement a few improvements making your enums easier to maintain:
1. Declare enum as a Hash not Array
Vulnerability before change: Mapping between declared values and integers kept in the database is based on order in the array.
In this example mapping would be as follow:
tsx
That approach is not flexible at all. Imagine that requirements just have changed. From now "foreign" localization should be split into "America" and "Asia". In that case, you should remove old value and add two new ones. But... you cannot remove unused "foreign" type, because it violates an order of remaining values. To avoid this situation you should declare your enum as a Hash
. There is not much to do:
tsx
This declaration doesn’t depend on an order so you will be able to implement the changes and get rid of unused enum value.
2. Integrate ActiveRecord::Enum with PostgreSQL enum
Vulnerability before change: Meaningless integer values in the database.
Working with attributes representing by integers in the database can be annoying. Imagine that you want to query something in rails console
or even you need to create a scope based on your enum field. Backing to our background, let's suppose that we want to return all Catalogs
which are still up to date. So we can write a where clause
like this:
Catalog.where.not(“state = ?”, “finished”)
We got an error as we expected:
tsx
This problem occurs only in the array format of where clause
because the second value is put directly into SQL where clause
and obviously "finished" is not an integer.
A similar case can appear when you implement complex SQL query
omitting ActiveRecord
layer. When the query hasn’t access to the model then you lose meaningful information about values and stay with pure integers without sense. In that case, you need to put an extra effort to make these integers meaningful again.
Another annoying situation can take place when you work with a legacy database like this. You have access to the database and you are interested only in the data kept there. You are not able to get immediate information from what you see. Always need to map these numbers into real values from the domain.
It’s worth to remember that when integer enum is split from its model like in the examples above then we lose information, unfortunately.
To convince you even more, there is also safety point. When declaring ActiveRecord::Enum
there is no guarantee that your data will be restricted only to provided values. Changes could be made by any SQL insertions. On the other hand, when you declare PostgreSQL enum
you gain constraint on database level. You need to decide how confident you want to be.
PostgreSQL is commonly used as a database in Ruby on Rails projects. You can use PostgreSQL enum
as a type of an attribute in the database table.
Let’s see how it looks this time.
rails g migration add_status_to_catalogs status:catalog_status
You need to change attribute type. I don’t recommend creating types like "status". It is likely that another status will appear in the future. Next, you need to change migration a little. Most of all it must be reversible and could execute SQL.
tsx
Declaration is similar to the previous one:
tsx
3. Add an index to enum attribute
Vulnerability before change: Queries performance.
This point is a simple one. It’s highly probable that your enum attribute is what can distinguish objects within a particular model. Like with our status: some Catalogs
are published and others are not. As a consequence, searching or filtering by that attribute will be a quite frequent task, so it is worth to add an index to that field. Let's modify our migration:
tsx
4. Use prefix or suffix option in your enums
Vulnerabilities before change:
Unintuitive scopes
Bad readability of helper methods
Prone to errors
Referring to our recent project again, we had a few enums in our Catalog
model:
state: ["incoming", "in_progress", "finished"]
auction_type: ["traditional", "live", "internet"]
status: ["published", "unpublished", "not_set"]
localization: ["home", "foreign", "none"]
To add prefix or suffix to enum it’s enough to add that option to the declaration, like so:
tsx
Let's see why it can be so helpful. In Catalog
model, we have 4 enums and 12 values among them. It creates 12 scopes, very unintuitive scopes.
tsx
Can you say with ease what these methods return? No, you need to remember all the time how the scopes look. It may be annoying, really.
tsx
That looks much better.
Let's suppose now that you need to add one more enum to your model. It should keep information about the order of each catalog inside the global catalog. The order of some catalogs may not be specified. Most important is to know which one is first and which is last. We can create another enum:
tsx
Let's open rails console
to test new scopes:
Catalog.order
We got an error. It’s self-explanatory.
tsx
Ok, we can fix it:
tsx
And again, another error:
tsx
Ok, that is obvious too. We forgot that value "none" was declared in another attribute as well.
Prefix or suffix options are a perfect fit to avoid these kinds of troubles. We can declare values just like we want, there is no reason to change words which are most descriptive. In that approach, scopes are more intuitive and meaningful. According to new attribute, the declaration should look like this:
tsx
5. Implement Value Object to handle an enum
Vulnerability before change: Fat model
I can recommend extracting enum attribute into separated Value Object in two cases:
Enum attribute is used among many models (at least 2)
Enum attribute has specific logic that complicates a model
Ok, let’s introduce sample situation. In our project auction houses (where artworks are sold) are placed all over the country. Poland divides into 16 regions, called voivodeships. Each AuctionHouse
model has specific Address
that contains Voivodeship
attribute. You can imagine that for some reason there will be a necessity to list only northern auction houses or these from most popular voivodeships. In that case it is necessary to put extra logic into our model what makes it fatter. To avoid it you can extract that logic into another class making it reusable and cleaner.
tsx
Then in your corresponding model, you need to overwrite this attribute. In our project it is Address
model. array_to_enum_hash
is just helper method converting the Array
of enum values into a Hash
.
tsx
Here is what you achieved. Entire logic related to voivodeships is encapsulated into a single class. You can extend it as you want and Address
model stayed thick.
Now, when you want to get voivodeship attribute, the object of Voivodeship
class is returned. This is your Value Object.
tsx
Take a look that both voivodeships have the same value, but as objects, they aren’t equal. Thanks to our helper method we can check the equality in that way:
tsx
And what is most powerful you can take advantage of all defined methods that representing requirements we specified earlier.
tsx
Ultimate solution
Ok, you have passed through 5 enum improvements. Now it’s time to sum up all steps and create ultimate solution. As an example, let's take status attribute
from Catalog
model. Implementation can look like this:
Migration Generation:
rails g migration add_status_to_catalogs status:catalog_status
Migration:
tsx
ValueObject:
tsx
Catalog model & enum declaration:
tsx
Custom web app for live art auctions' market leader
Artinfo needed a seamless, real-time online auction platform to modernize bidding and engage more users. We built a scalable web platform that enhanced data processing, increased participation, and boosted sales.
75k
Unique users a month
80%
Of phone bids transferred online
300
Online bidding yearly

Conclusion
That’s all - 5 steps to build a better implementation of enums in Rails.
Sometimes all steps will be necessary and another time only a few of them. You can adjust this solution to your needs. Hope that you have found something useful in this article. Let me know in the comments what’s your opinions about enums. Maybe you can recommend any further improvements?
Let’s Create a Great Website Together
We'll shape your web platform the way you win it!
More posts in this category
February 05, 2025 • 10 min read
READ MORELearn more about api first cmsAPI-first CMS: What Options You Have (Web Dev Agency's Take)
According to the 2024 State of the API Report by Postman, 74% of organizations are adopting API-first strategies. This statistic isn’t just impressive—it signals a major shift in how businesses operate. While API-first approaches have long been a staple in software development, they're now reshaping content management as well. More and more companies are realizing that traditional CMS platforms can't keep up with the demand for flexibility, speed, and seamless integrations.
January 23, 2025 • 15 min read
READ MORELearn more about best cms for saas top cloud based solutionsBest CMS for SaaS: Top Cloud-Based Solutions
Choosing the right Content Management System (CMS) is a critical decision for your SaaS business. Your unique needs require solutions that are not only flexible, scalable, and user-friendly but also tailored to meet the demands of a fast-paced, customer-focused industry. A CMS should simplify your workflows and help you deliver personalized, high-quality digital experiences.
December 12, 2024 • 10 min read
READ MORELearn more about headless cms for vueWe Picked the Best (Headless) CMS for Vue
Picture a digital experience where content effortlessly flows across platforms, development is agile, and performance is unmatched. By combining the power of Vue.js, a progressive JavaScript framework, with a modern headless CMS, you can achieve all this and more.