Rails唯一性验证的使用方法

225 阅读2分钟

使你的数据库和应用程序的验证相匹配是个好主意。如果你的模型中有validates :name, presence: true ,你应该把它与not null 数据库约束配对。唯一的验证应该与UNIQUE 数据库索引相匹配。

在现实世界的应用中,你经常有更复杂的验证,但你应该尽可能地继续这种做法。

我经常遇到的事情是需要有唯一的记录,但要在一定范围内。

想象一下,你正在建立一个典型的项目管理工具。你可能希望Project,这样它们就可以在你的用户界面中被区分开来--但你不希望这个名字是全球唯一的。如果我做了一个名为 "Onboarding "的项目,另一个客户不应该被限制使用这个名字。

幸运的是,Rails为我们提供了一个方便的功能,即验证作用域

使用方法

Rails唯一性验证规则的scope 选项允许我们指定额外的列,以便在检查唯一性时予以考虑:

class Project < ApplicationRecord
  belongs_to :account

  has_many :tasks

  validates :name, presence: true, uniqueness: { scope: :account_id }
end

这个规则说:"在这个账户的范围内,这个项目的名称必须是唯一的"。换句话说,nameaccount_id 的组合必须是唯一的 - 但你可以在不同的账户中拥有相同名称的项目。

正如我们前面所讨论的,你真的想用数据库约束来支持你的应用程序级别的验证。

在这种情况下,你会想做一个多列索引。你可以在正常的Rails迁移中做到这一点:

class CreateProject < ActiveRecord::Migration[6.0]
  def change
    create_table :projects do |t|
      ...
    end

    add_index :projects, [:name, :account_id], unique: true
  end
end

选项

你可以将多个列传递给scope

如果你正在构建一个餐饮应用,并希望强制规定一个客人每天只能在一家餐厅有一个预订:

class Reservation < ApplicationRecord
  belongs_to :guest
  belongs_to :restaurant

  validates :guest_id, uniqueness: {
    scope: [ :restaurant_id, :reservation_date ]
  }
end

你可能希望改变信息,因为默认的错误信息将是相当简洁的:"{字段}已经被占用":

validates :guest_id, uniqueness: {
  scope: [ :restaurant_id, :reservation_date ],
  message: "Only one reservation per guest per day is permitted"
}

注意:在PostgreSQL中,索引名称的默认限制是63个字符,所以如果你的模型或列名更长,你可能会发现自己需要改变索引名称。

add_index :reservations, [:guest_id, :restaurant_id, :reservation_date],
  unique: true,
  name: "idx_reserveration_guest_date_uniq"