39

我需要在我的 Rails 应用程序中的现有表中添加一个新的整数列。该列只能有值 1、2、3,所以我想为表/列添加一个检查约束。如何在 Rails 迁移中指定此约束?

4

6 回答 6

53

Rails 迁移不提供任何添加约束的方法,但您仍然可以通过迁移来完成,但通过将实际 SQL 传递给 execute()

创建迁移文件:

ruby script/generate Migration AddConstraint

现在,在迁移文件中:

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
于 2011-01-12T11:14:17.240 回答
27

Rails 6.1+ 检查约束

Rails 6.1 添加了对数据库迁移检查约束的基本支持

因此,现在,添加检查约束的迁移将整数列值仅限制为 1、2 和 3,可以编写如下:

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

这是相关 PR 的链接,您可以在其中找到有关add_check_constraint和的更多详细信息remove_check_constraint

于 2020-08-05T19:37:08.743 回答
5

您可以使用 Migration Validators gem 来完成。在此处查看详细信息:https ://github.com/vprokopchuk256/mv-core

使用该 gem,您将能够在 db 级别定义包含验证:

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

此外,您可以定义应该如何定义验证,甚至应该显示错误消息:

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

您甚至可以将验证从迁移权升级到您的模型:

class Post < ActiveRecord::Base 
  enforce_migration_validations
end

然后在迁移中定义的验证也将在您的模型中定义为 ActiveModel 验证:

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"]
于 2015-01-28T16:36:33.810 回答
5

自 2021 年 5 月起,此答案已过时

我刚刚为此发布了一个 gem:active_record-postgres-constraints。正如那里的自述文件所述,您可以将它与 db/schema.rb 文件一起使用,它在迁移中添加了对以下方法的支持:

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

请注意,此时仅支持 postgres。

于 2017-03-17T13:49:49.073 回答
4

我刚刚完成了一个 PostgreSQL CHECK 约束的工作。

Nilesh 的解决方案并不完全;db/schema.rb 文件不包含约束,因此使用 db:setup 的测试和任何部署都不会得到约束。根据http://guides.rubyonrails.org/migrations.html#types-of-schema-dumps

虽然在迁移中您可以执行自定义 SQL 语句,但模式转储程序无法从数据库中重构这些语句。如果您正在使用这样的功能,那么您应该将架构格式设置为 :sql。

即,在 config/application.rb 中设置

config.active_record.schema_format = :sql

不幸的是,如果您使用的是 PostgreSQL,则在加载结果转储时可能会出现错误,请参阅ERROR: must be owner of language plpgsql 中的讨论。我不想在那次讨论中沿着 PostgreSQL 配置路径走下去。另外,在任何情况下,我都喜欢拥有一个可读的 db/schema.rb 文件。所以这为我排除了迁移文件中的自定义 SQL。

Valera 建议的https://github.com/vprokopchuk256/mv-core gem 看起来很有希望,但它只支持一组有限的约束(我在尝试使用它时遇到了一个错误,尽管这可能是由于不兼容造成的与我包括的其他宝石)。

我采用的解决方案(hack)是让模型代码插入约束。因为它有点像验证,所以我把它放在这里:

class MyModel < ActiveRecord::Base

    validates :my_constraint

    def my_constraint
        unless MyModel.connection.execute("SELECT * FROM information_schema.check_constraints WHERE constraint_name = 'my_constraint'").any?
            MyModel.connection.execute("ALTER TABLE my_models ADD CONSTRAINT my_constraint CHECK ( ...the SQL expression goes here ... )")
        end
    end

当然,这会在每次验证之前进行额外的选择;如果这是一个问题,解决方案是将其放入“连接后”猴子补丁中,例如如何在使用 rails 连接到 oracle 后运行特定脚本?(您不能简单地缓存选择的结果,因为验证/约束添加发生在可能回滚的事务中,因此您需要每次都检查。)

于 2012-03-19T00:23:14.940 回答
2

您可以使用Sequelgem https://github.com/jeremyevans/sequel

Sequel.migration do
  change do
    create_table(:artists) do
      primary_key :id
      String :name
      constraint(:name_min_length){char_length(name) > 2}
    end
  end
end
于 2016-03-20T22:52:05.160 回答