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"]