Rails 7 adds better support for custom enum types in PostgreSQL


PostgreSQL provides in-built support for enumerated types that Rails can then take advantage of. However, it is often a pain to make use of this feature since it’s tedious to create custom enum types in PostgreSQL via ActiveRecord migrations.

Before

The previous method of creating custom enum types is done by executing direct SQL statements.

# db/migrate/*_create_articles.rb
def up
  execute <<-SQL
    CREATE TYPE status AS ENUM ('draft', 'published', 'archived', 'trashed');
  SQL

  create_table :articles do |t|
    t.column :current_status, :status
  end
end

Arguably this is not convenient in reality!

After

Often many Rails + PostgreSQL developers use structure.sql instead of schema.rb to make it easier to manage custom enum types. Fortunately, Rails 7 adds support for custom enum types in PostgreSQL. This change removes that friction by introducing create_enum to add a new enum type and t.enum to add a column. Enum types work well with ActiveRecord::Enum and now it’s easier than ever to utilize them.

def up
  # note that enums cannot be dropped
  create_enum :status, ["draft", "published", "archived", "trashed"]

  change_table :articles do |t|
    t.enum :current_status, enum_type: "status", default: "draft", null: false
  end
end

The enum definitions and enum columns get added to the schema.rb, so we can load them into a test database, and they’ll work correctly.

ActiveRecord::Schema.define(version: 2022_01_03_113555) do

  # These are the extensions that must be enabled to support this database.
  enable_extension "plpgsql"

  # Custom types defined in this database.
  # Note that some types may not work with other database engines. Be careful if changing the database.
  create_enum "status", ["draft", "published", "archived", "trashed"]

  create_table "articles", force: :cascade do |t|
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.enum "current_status", default: "draft", null: false, enum_type: "status"
  end
end

It is important to note that this is not compatible with any other database adapters. So if one decides to change the database at any point in the future, this would have to be a manual migration.

Join Our Newsletter