验证多列的唯一性

re5*_*5et 188 validation activerecord ruby-on-rails ruby-on-rails-3 rails-activerecord

是否有一种轨道方式来验证实际记录是唯一的而不仅仅是一列?例如,友谊模型/表不应该具有多个相同的记录,例如:

user_id: 10 | friend_id: 20
user_id: 10 | friend_id: 20
Run Code Online (Sandbox Code Playgroud)

Dyl*_*kow 316

您可以validates_uniqueness_of按如下方式调用呼叫范围.

validates_uniqueness_of :user_id, :scope => :friend_id
Run Code Online (Sandbox Code Playgroud)

  • 只是想补充一点,您可以传递多个范围参数,以防您需要在两个以上的字段上验证唯一性.即:scope => [:friend_id,:group_id] (83认同)
  • 在Rails 4中,这变成:validates:user_id,uniqueness:{scope :: friend_id} (70认同)
  • 很奇怪,你不能说`validates_uniqueness_of [:user_id,:friend_id]`.也许这需要修补? (27认同)
  • Alexey,validates_uniqueness_of [:user_id,:friend_id]将对列出的每个字段进行验证 - 并记录并预期行为 (11认同)
  • 你可能想要添加一个自定义错误消息,例如:message =>'已经是这个朋友了. (3认同)
  • 还应该使用一个独特的SQL索引,因为rails的验证器中有太多的魔法只能依赖IMO,例如尝试验证一个布尔值 (2认同)

pot*_*hin 130

您可以使用在一列上validates进行验证uniqueness:

validates :user_id, uniqueness: {scope: :friend_id}
Run Code Online (Sandbox Code Playgroud)

多列验证的语法类似,但您应该提供一个字段数组:

validates :attr, uniqueness: {scope: [:attr1, ... , :attrn]}
Run Code Online (Sandbox Code Playgroud)

但是,上面显示的验证方法具有竞争条件,无法确保一致性.请考虑以下示例:

  1. 数据库表记录应该是n个字段唯一的;

  2. 多个(两个或多个)并发请求,由每个单独的进程(应用程序服务器,后台工作服务器或您正在使用的任何服务器)处理,访问数据库以在表中插入相同的记录;

  3. 并行的每个进程验证是否存在具有相同n个字段的记录;

  4. 成功传递每个请求的验证,每个进程在表中创建具有相同数据的记录.

要避免这种行为,应该为db表添加一个唯一约束.您可以add_index通过运行以下迁移为一个(或多个)字段设置帮助程序:

class AddUniqueConstraints < ActiveRecord::Migration
  def change
   add_index :table_name, [:field1, ... , :fieldn], unique: true
  end
end
Run Code Online (Sandbox Code Playgroud)

警告:即使在设置了唯一约束之后,两个或多个并发请求也会尝试将相同的数据写入db,但是不会创建重复记录,而是会引发ActiveRecord::RecordNotUnique异常,您应该单独处理:

begin
# writing to database
rescue ActiveRecord::RecordNotUnique => e
# handling the case when record already exists
end 
Run Code Online (Sandbox Code Playgroud)