Postgres has a native enum type which is actually better than a constraint in a lot of ways.
Enumerated (enum) types are data types that comprise a static, ordered
set of values. They are equivalent to the enum types supported in a
number of programming languages. An example of an enum type might be
the days of the week, or a set of status values for a piece of data.
This addresses one the popular criticisms of ActiveRecord::Enum integer columns which that you can't tell just from looking the database what the statuses actually mean. Its also not like just using a string column since the values are mapped to a lookup table and only occupy 4 bites on disk each.
class AddStatusToProjects < ActiveRecord::Migration[5.2]
def up
execute <<-SQL
CREATE TYPE project_status AS ENUM ('pending', 'active', 'inactive');
SQL
add_column :project, :status, :project_status
end
def down
remove_column :projects, :status
execute <<-SQL
DROP TYPE project_status;
SQL
end
end
Then setup an explicit mapping for the Enum in your model:
class Project < ApplicationRecord
enum status: {
'pending' => :pending,
'active' => :active,
'inactive' => :inactive
}
# ...
end
Postgres will enforce that the values are valid for the type:
irb(main):022:0> Project.create!(status: 'foo')
(0.3ms) BEGIN
Project Create (3.6ms) INSERT INTO "projects" ("created_at", "updated_at", "status") VALUES ($1, $2, $3) RETURNING "id" [["created_at", "2020-01-16 14:15:47.579769"], ["updated_at", "2020-01-16 14:15:47.579769"], ["status", "foo"]]
(0.4ms) ROLLBACK
Traceback (most recent call last):
1: from (irb):22
ActiveRecord::StatementInvalid (PG::InvalidTextRepresentation: ERROR: invalid input value for enum project_status: "foo")
The biggest con is that you need to use a SQL schema instead of a ruby schema as it cannot dump the table. But thats true for a lot of Postgres features.