How do I add a check constraint in a Rails migration?

Rails migration does not provide any way to add Constraints, but you can still do it via migration but by passing actual SQL to execute()

Create Migration file:

ruby script/generate Migration AddConstraint

Now, in the migration file:

class AddConstraint < ActiveRecord::Migration
  def self.up
    execute "ALTER TABLE table_name ADD CONSTRAINT check_constraint_name CHECK (check_column_name IN (1, 2, 3) )"
  end

  def self.down
    execute "ALTER TABLE table_name DROP CONSTRAINT check_constraint_name"
  end
end

Rails 6.1+ Check Constraints

Rails 6.1 added basic support for check constraints to database migrations.

So now, a migration for adding a check constraint which restricts integer column values only to 1, 2, and 3 can be written as follows:

class AddConstraint < ActiveRecord::Migration
  def up
    add_check_constraint :table_name, 'check_column_name IN (1, 2, 3)', name: 'check_constraint_name'
  end

  def down
    remove_check_constraint :table_name, name: 'check_constraint_name'
  end
end

Here is a link to the relative PR where you can find more details about add_check_constraint and remove_check_constraint.


This answer is obsolete as of May 2021

I just published a gem for this: active_record-postgres-constraints. As the README there describes, you can use it with a db/schema.rb file, and it adds support for the following methods in migrations:

create_table TABLE_NAME do |t|
  # Add columns
  t.check_constraint conditions
  # conditions can be a String, Array or Hash
end

add_check_constraint TABLE_NAME, conditions
remove_check_constraint TABLE_NAME, CONSTRAINT_NAME

Note that at this time, only postgres is supported.


You can do it with Migration Validators gem. See details here: https://github.com/vprokopchuk256/mv-core

With that gem you'll be able to define inclusion validation on db level:

def change
  change_table :table_name do |t|
    t.integer :column_name, inclusion: [1, 2, 3]
  end
end

moreover you is able to define how that validation should be defined and even error message that should be shown:

def change
  change_table :posts do |t|
    t.integer :priority, 
              inclusion: { in: [1, 2, 3], 
                           as: :trigger, 
                           message: "can't be anything else than 1, 2, or 3" }
  end
end

you can even level up that validation from migration right to your model:

class Post < ActiveRecord::Base 
  enforce_migration_validations
end

and then validation defines in migration will be also defined as ActiveModel validation in your model:

Post.new(priority: 3).valid? 
=> true

Post.new(priority: 4).valid?
=> false

Post.new(priority: 4).errors.full_messages
=> ["Priority can't be anything else than 1, 2, or 3"]