Rails:验证3列的唯一组合


68

嗨,我不会验证表格中3列的唯一组合。

假设我有一个名为cars的表,其值是:brand,:model_name和:fuel_type。

然后,我想要的是基于这3条记录的组合来验证记录是否唯一。示例:

    brand    model_name    fuel_type
    Audi     A4            Gas
    Audi     A4            Diesel
    Audi     A6            Gas

应该都有效。但是,另一个带有“ Audi,A6,Gas”的记录无效。

我知道此验证,但我怀疑它是否确实可以执行我想要的操作。

    validates_uniqueness_of :brand, :scope => {:model_name, :fuel_type}

4
不用怀疑 您的验证是正确的
2011年

嗯,好吧-只是认为所做的是检查:brand在:model_name,:fuel_type的组中是否唯一
Niels Kristian

1
可能的Rails
potashin

Answers:


121

您的代码段中存在语法错误。正确的验证是:

validates_uniqueness_of :car_model_name, :scope => [:brand_id, :fuel_type_id]

甚至在ruby 1.9.x中更短:

validates_uniqueness_of :car_model_name, scope: [:brand_id, :fuel_type_id]

使用滑轨4时,您可以使用:

validates :car_model_name, uniqueness: { scope: [:brand_id, :fuel_type_id] }

轨道5可以使用

validates_uniqueness_of :car_model_name, scope: %i[brand_id fuel_type_id]

2
Rails 4示例真的正确吗?我认为应该是这样的:( validates :brand_id, uniqueness: { scope: [:fuel_type_id] }即不要引用模型名称)。
krsyoung '16

14
:model_name引用OP有关汽车模型的问题,不是模型类:P
bkunzi01 '16

1
哦,是的,这是令人误解的:(
Krzysztof Witczak

16

根据您的需求,您还可以添加约束(作为表创建迁移的一部分或作为单独的约束),而不是模型验证:

add_index :the_table_name, [:brand, :model_name, :fuel_type], :unique => true

在多个数据库连接同时执行写操作的情况下,在数据库级别添加唯一约束是有意义的。


1
当我尝试此操作时,似乎唯一的约束被单独施加在所有列上
Daniel Hitzel

这是可行的,但是如果有人尝试保存重复的条目,则服务器将以内部服务器错误(500)消息进行响应,这不是所希望的行为。为避免这种情况,您需要在模型级别添加验证,这样,它将像其他任何验证一样以一条错误消息进行响应。
tafuentesc

1
@tafuentesc,您编写控制器,以便决定服务器响应是什么。您可以使用rescue(try-catch)包装代码,并以所需的方式对Duplicate错误做出响应。
亚历山大

正如@tafuentesc所提到的,强烈建议ActiveRecord::RecordNotUnique至少在Rails 5中在模型中添加验证以防止引发错误。验证可能类似于validates_uniqueness_of :model_name, scope: %i[fuel_type brand], message: "the given name already exists"
alexventuraio

5

使用新的哈希模式向Rails 4正确的代码

validates :column_name, uniqueness: {scope: [:brand_id, :fuel_type_id]}

4

我会这样写的:

validates_uniqueness_of :model_name, :scope => {:brand_id, :fuel_type_id}

因为这对我来说更有意义:

  • 对于“品牌”和“燃料类型”的组合,不应重复使用“型号名称”,
  • “型号名称”和“燃料类型”的组合不应有重复的“品牌”

但这是主观意见。

当然,如果brand和fuel_type与其他模型有关系(如果不是,则删除“ _id”部分)。使用唯一性验证,您无法检查非数据库列,因此必须验证模型中的外键。

您需要定义要验证的属性-您不能一次验证所有属性,如果需要的话,则需要为每个属性创建单独的验证,因此当用户犯错并尝试创建重复的记录时,您将向他显示错误。无效字段附近的表格。


嗨,我认为您是正确的,但是我不清楚逻辑如何工作。我可以验证“每种品牌和燃料类型的唯一型号”,但我不明白为什么说“每种型号和燃料类型的唯一品牌”是错误的。嗯
tidelake

是的,我和@tidelake有相同的问题。为什么每次回应说validate model_name:scope => {:brand_id, :fuel_type_id}?为什么不能以同样的方式OP提到验证:brand:scope => {:model_name, :fuel_type}?为什么建议的订单与OP的问题不同?我看不出是什么问题或所有答案都说要验证model_name而不是brand!!!
alexventuraio

2

结合使用此验证方法ActiveRecord::Validations#save并不能保证没有重复的记录插入,因为在应用程序级别进行的唯一性检查本质上容易出现竞争状况。

如果您使用具有“可序列化”隔离级别的事务,甚至可能发生这种情况。解决此问题的最佳方法是使用来向数据库表添加唯一索引ActiveRecord::ConnectionAdapters::SchemaStatements#add_index。在极少数情况下发生竞争情况,数据库将保证该字段的唯一性。


1

将其他答案拼凑在一起并自己尝试,这就是您要寻找的语法:

validates :brand, uniqueness: { scope: [:model_name, :fuel_type] }

我不确定为什么其他答案会添加_id到范围中的字段中。仅当这些字段表示其他模型时才需要这样做,但是我没有在问题中看到任何指示。此外,这些字段可以按任何顺序排列。这将完成相同的操作,只有错误将在:model_name属性上而不是:brand

validates :model_name, uniqueness: { scope: [:fuel_type, :brand] }

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.