validation在另一个表上具有条件时,数据库中的唯一性validation

当validation有条件但我的要求已经改变时,我在数据库中的唯一性validation中询问了类似的问题,因此这个问题。

当有多个进程时,在Rails中使用唯一性validation是不安全的,除非在数据库上也强制执行约束(在我的情况下是PostgreSQL数据库,所以请参阅http://robots.thoughtbot.com/the-perils-of-uniqueness-validations )。

在我的例子中,唯一性validation是有条件的:只有在另一个模型上的另一个属性变为真时才应该强制执行。 所以我有

class Parent < ActiveRecord::Base # attribute is_published end class Child < ActiveRecord::Base belongs_to :parent validates_uniqueness_of :text, if: :parent_is_published? def parent_is_published? self.parent.is_published end end 

所以模型Child有两个属性: parent_id (与Parent关联)和text (文本属性)。 模型Parent有一个属性: is_published (布尔值)。 如果其parent.is_published为true,则所有类型为Child模型的text应该是唯一的。

使用http://robots.thoughtbot.com/the-perils-of-uniqueness-validations中建议的唯一索引太过限制,因为无论is_published的值如何,它都会强制执行约束。

是否有人知道PostgreSQL数据库上依赖于另一个表的“条件”索引? validation具有条件时,数据库中唯一性validation的解决方案是您的条件取决于同一个表上的属性。 还是另一种解决方法?

不幸的是,没有像上一个问题那样简单和干净的解决方案。

这应该做的工作:

  • 将一个冗余标志is_published添加到Child表中

     ALTER TABLE child ADD column is_published boolean NOT NULL; 

    插入时将其设置为DEFAULT FALSE或父列中通常具有的任何值。
    它必须是NOT NULL才能避免使用NULL值的漏洞和外键中的默认MATCH SIMPLE行为:
    仅当第三列为NOT NULL时,双列外键约束

  • parent(parent_id, is_published)上添加(看似毫无意义的)唯一约束parent(parent_id, is_published)

     ALTER TABLE parent ADD CONSTRAINT parent_fk_uni UNIQUE (parent_id, is_published); 

    由于parent_id是主键,因此组合在任何一种方式都是唯一的。 但这是以下fk约束所必需的。

  • 不使用简单的外键约束引用parent(parent_id) (parent_id, is_published)使用ON UPDATE CASCADE(parent_id, is_published)上创建多列外键。
    这样, child.is_published的状态由系统自动维护和强制执行,并且比使用自定义触发器实现的更可靠:

     ALTER TABLE child ADD CONSTRAINT child_special_fkey FOREIGN KEY (parent_id, is_published) REFERENCES parent (parent_id, is_published) ON UPDATE CASCADE; 
  • 然后像在上一个答案中一样添加部分UNIQUE索引 。

     CREATE UNIQUE INDEX child_txt_is_published_idx ON child (text) WHERE is_published; 

当然,在child表中插入行时,您必须立即使用parent.is_published的当前状态。 但重点是:强制执行参照完整性。

完整的架构

或者,不是调整现有的架构,而是完整的布局:

 CREATE TABLE parent( parent_id serial PRIMARY KEY , is_published bool NOT NULL DEFAULT FALSE --, more columns ... , UNIQUE (parent_id, is_published) -- required for fk ); CREATE TABLE child ( child_id serial PRIMARY KEY , parent_id integer NOT NULL , is_published bool NOT NULL DEFAULT FALSE , txt text , FOREIGN KEY (parent_id, is_published) REFERENCES parent (parent_id, is_published) ON UPDATE CASCADE ); CREATE UNIQUE INDEX child_txt_is_published_idx ON child (text) WHERE is_published;