我该如何在Rails迁移中添加检查约束?

41

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

6个回答

56

Rails迁移不提供添加约束的方法,但您仍然可以通过迁移来实现,只需通过execute()传递实际的SQL语句即可。

创建迁移文件:

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

4
请注意,如果您添加了约束条件,您需要在 config/application.rb 中设置 config.active_record.schema_format = :sql,因为"db/schema.rb 无法表达特定于数据库的项目,如触发器、存储过程或检查约束条件。" - Paul Fioravanti
现在有一个宝石支持将约束条件存储在 db/schema.rb 中:active_record-postgres-constraints。更多细节请参见我的回答。目前仅支持postgres。 - Isaac Betesh

43

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_constraintremove_check_constraint的更多详细信息。


7
这应该成为新的被接受的答案(或者至少被标记为现代解决方案)。谢谢! - Asfand Qazi

5
你可以使用Migration Validators宝石来实现。在这里查看详细信息:https://github.com/vprokopchuk256/mv-core 有了这个宝石,你就可以在数据库级别上定义包含验证:
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"]

5

截至2021年5月,此答案已过时

我刚刚发布了一个针对此问题的gem:active_record-postgres-constraints。正如README中所描述的那样,您可以将其与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。

1
该宝石不再受支持,因为Rails 6.1+正式提供了选项(请参见Marian13的答案),如作者也解释的那样。我注意到这个宝石在运行rails app:update时会引起麻烦。感谢@Isaac Betesh提供的宝石!它很有用。 - Masa Sakano

4
我刚刚解决了一个关于PostgreSQL CHECK约束的问题。
Nilesh的解决方案并不完整;db/schema.rb文件中不包含约束条件,因此测试和使用db:setup进行的任何部署都不会得到约束条件。按照 http://guides.rubyonrails.org/migrations.html#types-of-schema-dumps 的要求:

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

也就是说,在config/application.rb中设置:
```ruby config.active_record.schema_format = :sql ```
config.active_record.schema_format = :sql

不幸的是,如果您正在使用PostgreSQL,则在加载结果转储时可能会出现错误,请参见ERROR:必须是语言plpgsql的所有者的讨论。我不想在那个讨论中走下PostgreSQL配置路径;另外,在任何情况下,我都喜欢有一个可读的db/schema.rb文件。所以这就排除了在迁移文件中使用自定义SQL。

Valera建议的https://github.com/vprokopchuk256/mv-core gem似乎很有前途,但它只支持有限的约束集(当我尝试使用它时出现错误,但这可能是由于与我包含的其他gems不兼容所致)。

我采用的解决方案(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后运行特定脚本?(您不能简单地缓存选择的结果,因为验证/约束添加发生在可能被回滚的事务中,因此您需要每次检查)。

这是一个有趣的想法,但不可扩展。正如你所说,在每个验证之前它都会执行额外的选择。如果你最终有很多约束条件,随着规模的扩大,这将成为性能问题。我认为你链接的关于在初始数据库连接后触发脚本的帖子可能是更好的方法来做到这一点。然而,话虽如此,有人可能会认为尝试解决约束问题不应该在Rails内部完成,而应该在Rails之外完成 - Rails旨在成为数据库无关的。请参见:https://dev59.com/0XE85IYBdhLWcg3w1HKF - rmcsharry

2

您可以使用Sequel宝石库 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接