Rails ActiveRecord - 使用命名范围验证唯一性(不像其他列那样的范围)

Rails ActiveRecord - vaildate uniqueness with named scope (not the scope like other columns)

想象一个用户 table:

id email access_token refresh_token country_id city_id discarded_at
1 james.dean@example.com 11111 22222 1 1 2021-01-31 00:57:25
2 james.bond@example.com 33333 44444 1 3 NULL
3 james.franco@example.com 55555 66666 2 4 NULL
4 james.corden@example.com 77777 88888 1 5 NULL
5 james.dean@example.com 11111 22222 1 1 NULL

通常,验证其他列的唯一性是这样的

validates :emails, :access_token, :refresh_token, uniqueness: { scope: %i[country_id city_id] }

现在的问题是 - 如何为命名范围执行此操作?

例如,有一个命名范围 scope :kept, -> { where(discarded_at: nil) },那么 emailaccess_tokenrefresh_token 对于所有 kept 用户应该是唯一的。

(上面的例子,即ID=5是有效的,因为ID=1已经被丢弃,所以email,ID=5的access_token和refresh_token是有效的)

class User < ApplicationRecord
  scope :kept, -> { where(discarded_at: nil) }

   # maybe something like this?
  validates :email, :access_token, :refresh_token, uniqueness: { where(discarded_at: nil) }
end

您可以使用 conditions 选项(如概述 here

validates_uniqueness_of :email, :access_token, :refresh_token, conditions: -> { where(discarded_at: nil) }

只是为了进一步扩展@AbM 的回答以及@r4cc00n 的评论。

完整答案如下:

  • 使用新的 validates 语法
  • 需要allow_nil
  • 添加数据库级索引

型号

validates :email, :access_token, :refresh_token,
          uniqueness: { allow_nil: true, conditions: -> { where(discarded_at: nil) } }

迁移

class CreateUniqueIndiceToUsers < ActiveRecord::Migration[6.0]
  def change
    add_index :users, :email, unique: true, where: "(discarded_at IS NULL)"
    add_index :users, :access_token, unique: true, where: "(discarded_at IS NULL)"
    add_index :users, :refresh_token, unique: true, where: "(discarded_at IS NULL)"
  end
end