ActiveRecord::PGEnum Build Status

The enum feature in Rails has bad developer ergonomics. It uses integer types at the DB layer, which means trying to understand SQL output is a pain.

Using the easy form of the helper syntax is a minor footgun:

enum status: %w[new active archived]

It's not obvious that the above code is order-dependent, but if you decide to add a new enum anywhere but the end, you're in trouble.

If you choose the use varchar fields instead, now you have to write annoying check constraints and lose the efficient storage.

enum status: { new: "new", active: "active", archived: "archived" }

Nobody has time to write that nonsense.

Enumerated Types: The Best of Both Worlds

Did you know you can define your own types in PostgreSQL? You can, and this type system also supports enumeration.

CREATE TYPE status_type AS ENUM ('new', 'active', 'archived');

Not only does this give you full type safety at the DB layer, the implementation is highly efficient. An enum value only takes up four bytes.

The best part is that PostgreSQL supports inserting new values at any point of the list without having to migrate your data.

ALTER TYPE status_type ADD VALUE 'pending' BEFORE 'active';

Version support

Every version of Rails with an enum macro is supported. This means 4.1 through master. Yes, this was annoying and difficult.

The monkeypatches in this library are extremely narrow and contained; the dirty hacks I had to do to make 4.1 work, for instance, have no impact on 6.0.

Monkeypatching Rails internals is scary. So this library has a comprehensive test suite that runs against every known minor version.

Installation

Add this line to your application's Gemfile:

gem 'activerecord-pg_enum'

And then execute:

$ bundle

Usage

Migrations

Defining a new ENUM

class AddContactMethodType < ActiveRecord::Migration[5.2]
  def up
    create_enum "contact_method_type", %w[Email Phone]
  end

  def down
    drop_enum "contact_method_type"
  end
end

Adding a value to an existing ENUM

class AddSMSToContactMethodType < ActiveRecord::Migration[5.2]
  def up
    add_enum_value "contact_method_type", "SMS", before: "Phone"
  end

  def down
    raise ActiveRecord::IrreversibleMigration
  end
end

Adding an enum column to a table

class AddStatusToOrder < ActiveRecord::Migration[5.2]
  def change
    change_table :orders do |t|
      t.enum :status, as: "status_type", default: "new"
    end
  end
end

Module Builder

class ContactInfo < ActiveRecord::Base
  include PGEnum(contact_method: %w[Email SMS Phone])
end

The generated module calls the official enum method converting array syntax into strings. The above example is equivalent to:

class ContactInfo < ActiveRecord::Base
  enum contact_method: { Email: "Email", SMS: "SMS", Phone: "Phone" }
end

There's no technical reason why you couldn't detect enum columns at startup time and automatically do this wireup, but I feel that the benefit of self-documenting outweighs the convenience.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run appraise rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

Test a specific version with appraise 6.0 rake spec. This is usually necessary because different versions have different Ruby version support.

To install this gem onto your local machine, run bundle exec rake install.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/alassek/activerecord-pg_enum.

License

The gem is available as open source under the terms of the MIT License.