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

发布于 2024-10-11 09:27:37 字数 84 浏览 3 评论 0原文

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

I need to add a new integer column to an existing table in my Rails app. The column can only have values 1, 2, 3, so I'd like to add a check constraint to the table/column. How do I specify this constraint within a Rails migration?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

酷炫老祖宗 2024-10-18 09:27:37

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

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
爱的故事 2024-10-18 09:27:37

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

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.

满天都是小星星 2024-10-18 09:27:37

您可以使用迁移验证器 gem 来完成此操作。请在此处查看详细信息: 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"]

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"]
謌踐踏愛綪 2024-10-18 09:27:37

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

我刚刚为此发布了一个 gem: active_record-postgres-限制。正如 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。

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.

徒留西风 2024-10-18 09:27:37

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

Nilesh 的解决方案并不十分完整; db/schema.rb 文件不会包含约束,因此使用 db:setup 的测试和任何部署都不会获得约束。根据 http://guides.rubyonrails.org/migrations.html#架构转储类型

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

即,在 config/application.rb 中设置

config.active_record.schema_format = :sql

不幸的是,如果您使用 PostgreSQL,则在加载结果转储时可能会收到错误,请参阅 错误:必须是语言 plpgsql 的所有者。我不想在讨论中深入探讨 PostgreSQL 配置路径;另外,无论如何我喜欢有一个可读的 db/schema.rb 文件。因此,这对我来说排除了迁移文件中的自定义 SQL。

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

我采用的解决方案(黑客)是让模型代码插入约束。因为它有点像验证,所以这就是我所说的:

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

I have just worked through getting a PostgreSQL CHECK constraint to work.

Nilesh's solution is not quite complete; the db/schema.rb file won't include the constraint, so tests and any deployments that use db:setup won't get the constraint. As per http://guides.rubyonrails.org/migrations.html#types-of-schema-dumps

While in a migration you can execute custom SQL statements, the
schema dumper cannot reconstitute those statements from the database.
If you are using features like this, then you should set the schema
format to :sql.

I.e., in config/application.rb set

config.active_record.schema_format = :sql

Unfortunately, if you're using PostgreSQL you may get an error when loading the resultant dump, see discussion at ERROR: must be owner of language plpgsql. I didn't want to go down the PostgreSQL configuration path in that discussion; plus in any case i'm fond of having a readable db/schema.rb file. So that ruled out custom SQL in the migration file for me.

The https://github.com/vprokopchuk256/mv-core gem suggested by Valera seems promising, but it only supports a limited set of constraints (and I got an error when I tried to use it, though that may be due to incompatibilities with other gems I'm including).

The solution (hack) I went with is to have the model code insert the constraint. Since it's kindof like a validation, that's where I put it:

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

Of course this does an extra select before each validation; if that's a problem a solution would be to put it in an "after connect" monkey patch such as discussed in How to run specific script after connected to oracle using rails? (You can't simply cache the result of the select, because the validation/constraint addition happens within a transaction that might get rolled back, so you need to check each time.)

幽梦紫曦~ 2024-10-18 09:27:37

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

You can use 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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文