High Leverage Rails
Introduction
Introduction to this course
Why use Ruby on Rails
Why use SQLite
Ruby on Rails + SQLite
Powering Your App with SQLite
Creating tables
Timestamps
Column types
Typeof
Ruby types
Creating table introduction
Creating table advanced
Inserting data
Updating data
Upserting data
Reading data
Virtual columns
Enums
Introduction to JSON
Indexing JSON
JSON table functions
Building a Modern Rails Application
Creating a new Rails application
Installing Solid Queue
Installing Solid Cache
Installing Solid Cable
Dockerfile
Application overview
Authentication
Base styles
Registration
Scaffolding posts
Polishing posts
Scaffolding comments
Polishing comments
Polishing models
Polishing controllers
Creating new post
Updating post
Reviewing MVP
Tagging posts
Custom tags
Friendly URLs
Full text search
Deploying & Operating Your App
Backups
Check Litestream locally
Verifying backups
Deployment options
Deploying with Hatchbox
Deployment constraints
Vertical scaling
Database access
Migrations
Locked video

Please purchase the course to watch this video.

Video thumbnail
Powering Your App with SQLite
Enums

Full Course

$
129
$179
USD, one-time fee
This course came at the perfect time. I’ve recently gotten back into Rails after an 18-year hiatus, and this was a perfect refresher and shows just how much you can accomplish with Rails right out of the box.
Garrett Winder
Garrett Winder

Move fast and fix things

Application monitoring that helps developers get it done.

Rails hosting made simple

Deploy apps to servers that you own and control.

Summary

Learn how to implement enums in Active Record with SQLite, even without native enum support. Discover how to enforce enum-like behavior using string columns with check constraints while leveraging Rails’ enum helper for flexible application logic. This approach enhances data integrity, query performance, and usability in Rails applications using SQLite.

Video Transcript

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:

  • The enum name I want to use.
  • A default value.
  • That it cannot be null.

For those familiar with MySQL or PostgreSQL, this is the expected behavior when working with enums in Rails.

SQLite Does Not Support Native Enums

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.

Creating an Enum-Like Column in SQLite

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:

  1. Strict Validation – Ensures category can only contain the specified values.
  2. Indexing – Improves performance on enum queries.
  3. Mimics Enum Behavior – Provides database-level integrity similar to an actual enum.

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

Why Use Strings Instead of Integers?

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:

  • Readability – Easier to inspect data directly in the database.
  • Consistency – Avoids needing to map integers to application values.
  • Less Complexity – Eliminates the risk of mismatched enum mappings.

However, if storage efficiency is critical, you can store enums as integers and modify the check constraint accordingly.

Using Active Record Enum Methods

Defining an enum in Active Record provides several useful methods:

  1. Returning stored values

This will return the stored enum values set:

Post.categories
# => {"story"=>"story", "ask"=>"ask", ....}
  1. Querying Enums

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
  1. Creating and Updating Enum Values

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

Database-Level Protection Against Invalid Values

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.