Skip to content

Instantly share code, notes, and snippets.

@jaydorsey
Last active December 27, 2021 22:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaydorsey/b24a8429342781cda385d7c72e8d895e to your computer and use it in GitHub Desktop.
Save jaydorsey/b24a8429342781cda385d7c72e8d895e to your computer and use it in GitHub Desktop.
Converting a boolean column to an enum in ActiveRecord/Rails
# frozen_string_literal: true
# db/migrate/20211227213338_create_users.rb
#
# This is an example of what the Users table might have been created as
# or would look like before the migration from a boolean field to an enum
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :name, null: false
t.string :email, null: false, unique: true
# Realistically, this would probably be "active" not status; you'll want to do
# a rename at the appropriate time. Assuming all the code changes & migration
# have been run before doing the actual conversion which is run in the other migration
t.boolean :status, null: false, default: false, comment: 'true is active, false is inactive'
t.timestamps
end
end
end
# frozen_string_literal: true
# db/migrate/20211227213604_convert_user_status_to_enum.rb
#
# Scenario:
# - You have a Users table with a boolean active column
# - You realize you need an extra "state" of pending
# - You want to convert your boolean to an enum & coerce the existing data
#
# Not in scope:
# - Renaming the original field from active -> status
class ConvertUserStatusToEnum < ActiveRecord::Migration[7.0]
def up
execute <<-SQL
-- Creates a new postgres type named user_status_fields which allows
-- for additional states aside from true/false
CREATE TYPE user_status_fields AS ENUM ('active', 'inactive', 'pending')
SQL
change_column_default :users, :status, nil
change_column :users, :status, <<~SQL
user_status_fields
USING
(
CASE status
WHEN true THEN 'active'::user_status_fields
WHEN false THEN 'inactive'::user_status_fields
END
)
SQL
change_column_comment :users, :status, from: 'true is active, false is inactive',
to: 'user_status_fields enum value'
change_column_default :users, :status, :inactive
end
def down
change_column_default :users, :status, nil
change_column :users, :status, <<~SQL
boolean
USING
(
CASE status
WHEN 'active'::user_status_fields THEN true
WHEN 'inactive'::user_status_fields THEN false
-- You just have to decide what pending means when you roll back
WHEN 'pending'::user_status_fields THEN false
END
)
SQL
change_column_default :users, :status, false
change_column_comment :users, :status, from: 'user_status_fields enum value',
to: 'true is active, false is inactive'
execute <<-SQL
DROP TYPE user_status_fields
SQL
end
end
# frozen_string_literal: true
# app/models/user.rb
class User < ApplicationRecord
# The keys must be strings, not symbols, for ActiveRecord to recognize
# them. You get the standard scopes just like a regular enum, like
# User.active & User.pending
enum status: { active: 'active', inactive: 'inactive', pending: 'pending' }
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment