【问题标题】:How do I add a check constraint in a Rails migration?如何在 Rails 迁移中添加检查约束?
【发布时间】:2011-06-07 13:31:00
【问题描述】:

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

【问题讨论】:

    标签: mysql ruby-on-rails ruby database-migration


    【解决方案1】:

    自 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。

    【讨论】:

    • 不再支持上述 gem,因为 Rails 6.1+ 正式提供选项(参见 Marian13 的回答)为the author too explains。我观察到 gem 在运行 rails app:update 时会出现问题。谢谢@Isaac Betesh 的宝石!很有用。
    【解决方案2】:

    Rails 6.1+ 检查约束

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

    所以现在,添加检查约束的迁移将整数列值限制为仅 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
    

    这里是a link to the relative PR,您可以在其中找到有关add_check_constraintremove_check_constraint 的更多详细信息。

    【讨论】:

    • 这应该是新接受的答案(或至少被标记为现代解决方案)。谢谢!
    【解决方案3】:

    你可以使用Sequel gem 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
    

    【讨论】:

      【解决方案4】:

      您可以使用 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"]
      

      【讨论】:

        【解决方案5】:

        我刚刚完成了一个 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 看起来很有希望,但它只支持一组有限的约束(当我尝试使用它时出现错误,尽管这可能是由于与我所包含的其他 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
        

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

        【讨论】:

        • 这是一个有趣的想法,但不可扩展。正如您所说,它在每次验证之前都会进行额外的选择。如果您最终遇到很多限制,这将成为您扩展时令人头疼的性能问题。我认为您链接到的关于在初始数据库连接后触发脚本的帖子可能是执行此操作的更好方法。然而,话虽如此,有人可能会争辩说,尝试解决约束问题不应该在 Rails 内部完成,而应在 Rails 外部完成——Rails 意味着与数据库无关。见:stackoverflow.com/questions/2589509/…
        【解决方案6】:

        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
        

        【讨论】:

        猜你喜欢
        • 2016-11-08
        • 1970-01-01
        • 2011-08-06
        • 1970-01-01
        • 2014-05-02
        • 1970-01-01
        • 2020-03-12
        相关资源
        最近更新 更多