Examples of creating an enum type, renaming a value, deleting a value
Rails 7 introduced a new way to create enums in PostgreSQL, by adding create_enum method. See official documentation here.
I will explore the following tasks:
All examples were tested on PostgreSQL 12, Ruby 3.1.2 and Rails 7.1.0.alpha
The migration will look like this:
def up
create_enum :user_status, ["pending", "active", "archived"]
create_table :users, force: true do |t|
t.enum :status, enum_type: "user_status", default: "pending", null: false
end
end
The User model might look something like this:
class User < ActiveRecord::Base
enum status: {
pending: 'pending',
active: 'active',
archived: 'archived',
disabled: 'disabled',
waiting: 'waiting'
}, _prefix: true
end
And then you can execute things like:
User.status_pending.count # returning the number of Users with status pending
user = User.first
user.status_pending? # will return true if the status is `pending`
A rollback for this might look like this:
def down
drop_table :users
execute <<-SQL
DROP TYPE user_status;
SQL
end
You can do this by executing the following migration with raw SQL:
disable_ddl_transaction
def up
execute <<-SQL
ALTER TYPE user_status ADD VALUE IF NOT EXISTS 'disabled' AFTER 'active';
SQL
end
For creating a rollback, see the section about how to delete a value from an enum. Things are a bit more complicated.
Let’s say you now want to rename pending to waiting and do that in Rails and the database.
The migration might look something like the following:
disable_ddl_transaction
def up
execute <<-SQL
ALTER TYPE user_status RENAME VALUE 'pending' TO 'waiting';
SQL
# don't forget to change the default status if you renamed the default one
change_column_default :users, :status, from: 'pending', to: 'waiting'
end
This is a more complex operation if you want to delete a value.
First, you need to do the following two things:
Then your migration for this might look like this. Say I want to remove waiting status and replace it with pending
def up
# First, make sure no records are using the status that you want to remove
User.status_waiting.update_all(status: 'pending')
# change default to nil
change_column_default :users, :status, nil
execute <<-SQL
--- Rename the old enum
ALTER TYPE user_status RENAME TO user_status_old;
--- Create the new enum as you will like it to be
CREATE TYPE user_status AS ENUM('pending', 'active', 'archived');
--- Alter the table to update it to use the new enum
ALTER TABLE users ALTER COLUMN status TYPE user_status USING users::text::user_status;
--- Drop the old status
DROP TYPE user_status_old;
SQL
# make the default status pending
change_column_default :users, :status, 'pending'
end
⚠️ This migration cannot be undone. You can write a rollback to recreate the enum that was initially, but that will not update back the Users that were in waiting. That information is lost.
If you want to keep that information, then the way to do this is:
backup_status that should be of type string (because you probably don’t want to create a new enum type, and you don’t want to link the backup to the enum type that you are just about to change)status to backup_statuswaiting from the user_status enum as described abovebackup_statusIf you want to play with the code, I create a single file Rails app here: postgres_enums_in_rails_7.rb
If you like this content, follow me on Twitter @lucianghinda, where I tweet or retweet about Ruby and Ruby web frameworks.
I also publish a free newsletter with Ruby and Rails fresh content at newsletter.shortruby.com — in case you want to stay up to date with what is happening in Ruby and Rails world.