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;