Application monitoring that helps developers get it done.
Deploy apps to servers that you own and control.
In addition to virtual columns, there is another higher-order column type I want to explore: the enum.
Jumping into our IRB session playground, we have a slightly different schema definition. This time, I’m using the create_enum helper alongside create_table to define a new enum called status with a set of possible values in our posts table.
Now, I define a new column called status as an enum column type, specifying:
For those familiar with MySQL or PostgreSQL, this is the expected behavior when working with enums in Rails.
Unfortunately, SQLite does not support the enum column type, so the SQLite adapter in Active Record fails to recognize this. However, we can still define and use enums in Active Record while maintaining similar behavior in the database.
Let’s look at how we can achieve this manually.
Instead of defining an enum column, we use a regular string column with constraints:
create_table :posts, force: true do |t|
t.string :category, null: false, default: "story", index: true
t.check_constraint "category IN ('story', 'ask', 'show', 'poll', 'job')", name: "category_check"
end
Once we run this schema definition, everything executes successfully. We now have:
Now, we define our Post model and use the Active Record enum helper:
class Post < ApplicationRecord
enum category: { story: "story", ask: "ask", show: "show", poll: "poll", job: "job" }, prefix: true
end
In MySQL and PostgreSQL, enums are often stored as integers for compact storage, but since SQLite doesn’t support native enums, I prefer to store them as strings.
Using strings instead of integers provides:
However, if storage efficiency is critical, you can store enums as integers and modify the check constraint accordingly.
Defining an enum in Active Record provides several useful methods:
This will return the stored enum values set:
Post.categories
# => {"story"=>"story", "ask"=>"ask", ....}
This will return a subset, finding all of the posts that have a category of poll:
Post.category_poll
Generates:
SELECT * FROM posts WHERE category = 'poll'
We can also find records that are NOT a specific category:
Post.not_category_ask
When creating a new post, the default value is automatically set:
post = Post.create!
post.category
# => "story"
To change the category:
post.category_ask!
To check a post’s category:
post.category_story?
# => true or false
If we try to assign an invalid value, Active Record raises an error:
post.category_default?
# => undefined method 'category_default' for an instance of Post
If we bypass Active Record and attempt to insert an invalid value:
post.update_columns(category: "foo")
Since update_columns skips validations, Active Record won’t catch this—but SQLite still enforces our check constraint:
SQLite3::ConstraintException: CHECK constraint failed: category_check
This ensures that our database remains in a valid state, even if Active Record validations are bypassed.
While SQLite lacks native enum support, we can still achieve full enum-like functionality with just a little extra setup.