使你的数据库和应用程序的验证相匹配是个好主意。如果你的模型中有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
这个规则说:"在这个账户的范围内,这个项目的名称必须是唯一的"。换句话说,name 和account_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"