【问题标题】:Rails 5 Postgres: Is there a DB constraint for inclusion?Rails 5 Postgres:是否存在包含数据库约束?
【发布时间】:2020-01-16 13:42:10
【问题描述】:

我正在尝试(尽可能)确保我的每个模型验证都有等效的数据库约束。我的很多模型都有一个status 列,我使用inclusion: 验证来确保状态值在我的白名单数组中。

validates :status, inclusion: { in: %w[pending active inactive] }

我可以添加到 create_table 迁移中的数据库的等效约束吗?

(使用带有 Postgres 的 Rails 5.2.4.1)

【问题讨论】:

    标签: sql ruby-on-rails ruby postgresql activerecord


    【解决方案1】:

    Postgres 有一个native enum type,它实际上在很多方面都比一个约束要好。

    枚举(enum)类型是包含静态、有序的数据类型 一组值。它们等价于 a 中支持的枚举类型 编程语言的数量。枚举类型的一个示例可能是 星期几,或一组数据的状态值。

    这解决了对 ActiveRecord::Enum 整数列的流行批评之一,您无法仅通过查看数据库来判断状态的实际含义。它也不像仅仅使用一个字符串列,因为这些值被映射到一个查找表并且每个只占用 4 位磁盘。

    class AddStatusToProjects < ActiveRecord::Migration[5.2]
      def up
        execute <<-SQL
          CREATE TYPE project_status AS ENUM ('pending', 'active', 'inactive');
        SQL
        add_column :project, :status, :project_status
      end
    
      def down
        remove_column :projects, :status
        execute <<-SQL
          DROP TYPE project_status;
        SQL
      end
    end
    

    然后为模型中的 Enum 设置显式映射:

    class Project < ApplicationRecord
      enum status: {
        'pending' => :pending,
        'active' => :active,
        'inactive' => :inactive
      }
      # ...
    end
    

    Postgres 将强制这些值对该类型有效:

    irb(main):022:0> Project.create!(status: 'foo')
       (0.3ms)  BEGIN
      Project Create (3.6ms)  INSERT INTO "projects" ("created_at", "updated_at", "status") VALUES ($1, $2, $3) RETURNING "id"  [["created_at", "2020-01-16 14:15:47.579769"], ["updated_at", "2020-01-16 14:15:47.579769"], ["status", "foo"]]
       (0.4ms)  ROLLBACK
    Traceback (most recent call last):
            1: from (irb):22
    ActiveRecord::StatementInvalid (PG::InvalidTextRepresentation: ERROR:  invalid input value for enum project_status: "foo")
    

    最大的缺点是您需要使用 SQL 模式而不是 ruby​​ 模式,因为它无法转储表。但这对于 Postgres 的许多功能来说都是如此。

    【讨论】:

    • 我喜欢应用程序代码中的枚举。但我讨厌他们在数据库代码中。它们是非关系型的。它们广泛不受支持,并且在它们支持的地方,支持是不一致的。您不能使用正常的插入和删除语句对它们进行操作。 (PostgreSQL 根本不支持从枚举中删除“行”。)
    • @MikeSherrill'CatRecall' 这些都是真正有效的担忧,你肯定让我重新考虑。 Postgres 实际上不支持从 Enum 类型中删除值,因为它没有处理可能具有该值并因此变得无效的记录的机制。有一种解决方法是声明一个新的枚举类型并添加一个新列,但是它很乱。
    【解决方案2】:

    如果我理解正确,您正在寻找的数据库约束是检查约束或外键约束。

    对于检查约束,您可以像这样运行 SQL。

    alter table your_table
      add constraint constraint_name
      check (status in ('pending', 'active', 'inactive');
    

    对于外键约束,您首先要构建一个有效状态表。

    create table statuses (
      status varchar(15) primary key
    );
    insert into statuses values 
    ('pending'), ('active'), ('inactive');
    

    在引用表中。 . .

    create table your_table (
      other_columns char(1) primary key,  
      status varchar(15) not null
        references statuses (status)
    );
    

    检查约束更能抵抗更改(您必须更改数据库架构以添加或删除有效状态);外键更容易更改(只需添加或删除一行),但更容易“篡改”。无论您使用哪一个,相对较少的人应该有足够的权限来更改这些约束。真的很少。

    但是,恕我直言,Rails 忽略了一个您可能需要考虑的问题更大的数据库约束。

    这个助手在对象被保存之前验证属性的值是唯一的。它不会在数据库中创建唯一性约束。 . . (来自Uniqueness helper documentation

    【讨论】:

    • 如果状态列表不是开发人员关心的问题并且需要由 GUI 修改,则使用外键是一个不错的选择。 primary key 将在 statuses.status 上强制执行唯一性,所以我不确定问题出在哪里。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-04
    • 2015-01-25
    相关资源
    最近更新 更多