本文介绍了在 ActiveModel 对象上,如何检查唯一性?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 Bryan Helmkamp 的优秀博客文章7 种重构 Fat ActiveRecord 模型的模式",他提到使用 Form Objects 来抽象多层表单并停止使用 accepts_nested_attributes_for.

In Bryan Helmkamp's excellent blog post called "7 Patterns to Refactor Fat ActiveRecord Models", he mentions using Form Objects to abstract away multi-layer forms and stop using accepts_nested_attributes_for.

下面 寻求解决方案.

see below for a solution.

我几乎完全复制了他的代码示例,因为我有同样的问题要解决:

I've almost exactly duplicated his code sample, as I had the same problem to solve:

class Signup
  include Virtus

  extend ActiveModel::Naming
  include ActiveModel::Conversion
  include ActiveModel::Validations

  attr_reader :user
  attr_reader :account

  attribute :name, String
  attribute :account_name, String
  attribute :email, String

  validates :email, presence: true
  validates :account_name,
    uniqueness: { case_sensitive: false },
    length: 3..40,
    format: { with: /^([a-z0-9\-]+)$/i }

  # Forms are never themselves persisted
  def persisted?
    false
  end

  def save
    if valid?
      persist!
      true
    else
      false
    end
  end

private

  def persist!
    @account = Account.create!(name: account_name)
    @user = @account.users.create!(name: name, email: email)
  end
end

我的一段代码中的一个不同之处是,我需要验证帐户名称(和用户电子邮件)的唯一性.但是,ActiveModel::Validations 没有 uniqueness 验证器,因为它应该是 ActiveRecord 的非数据库支持变体.

One of the things different in my piece of code, is that I need to validate the uniqueness of the account name (and user e-mail). However, ActiveModel::Validations doesn't have a uniqueness validator, as it's supposed to be a non-database backed variant of ActiveRecord.

我认为有三种方法可以解决这个问题:

I figured there are three ways to handle this:

  • 编写我自己的方法来检查这个(感觉多余)
  • 包括 ActiveRecord::Validations::UniquenessValidator(试过这个,没有让它工作)
  • 或者在数据存储层添加约束

我更喜欢使用最后一个.但后来我一直在想如何我将实现这一点.

I would prefer to use the last one. But then I'm kept wondering how I would implement this.

我可以做一些像(元编程,我需要修改其他一些区域):

  def persist!
    @account = Account.create!(name: account_name)
    @user = @account.users.create!(name: name, email: email)
  rescue ActiveRecord::RecordNotUnique
    errors.add(:name, "not unique" )
    false
  end

但是现在我在我的班级中运行了两个检查,首先我使用 valid? 然后我使用 rescue 语句来处理数据存储约束.

But now I have two checks running in my class, first I use valid? and then I use a rescue statement for the data storage constraints.

有人知道处理这个问题的好方法吗?最好为此编写我自己的验证器(但是我会对数据库有两个查询,理想情况下一个就足够了).

Does anyone know of a good way to handle this issue? Would it be better to perhaps write my own validator for this (but then I'd have two queries to the database, where ideally one would be enough).

推荐答案

Bryan 非常友好地在他的博文中评论我的问题.在他的帮助下,我想出了以下自定义验证器:

Bryan was kind enough to comment on my question to his blog post. With his help, I've come up with the following custom validator:

class UniquenessValidator < ActiveRecord::Validations::UniquenessValidator
  def setup(klass)
    super
    @klass = options[:model] if options[:model]
  end

  def validate_each(record, attribute, value)
    # UniquenessValidator can't be used outside of ActiveRecord instances, here
    # we return the exact same error, unless the 'model' option is given.
    #
    if ! options[:model] && ! record.class.ancestors.include?(ActiveRecord::Base)
      raise ArgumentError, "Unknown validator: 'UniquenessValidator'"

    # If we're inside an ActiveRecord class, and `model` isn't set, use the
    # default behaviour of the validator.
    #
    elsif ! options[:model]
      super

    # Custom validator options. The validator can be called in any class, as
    # long as it includes `ActiveModel::Validations`. You can tell the validator
    # which ActiveRecord based class to check against, using the `model`
    # option. Also, if you are using a different attribute name, you can set the
    # correct one for the ActiveRecord class using the `attribute` option.
    #
    else
      record_org, attribute_org = record, attribute

      attribute = options[:attribute].to_sym if options[:attribute]
      record = options[:model].new(attribute => value)

      super

      if record.errors.any?
        record_org.errors.add(attribute_org, :taken,
          options.except(:case_sensitive, :scope).merge(value: value))
      end
    end
  end
end

您可以像这样在 ActiveModel 类中使用它:

You can use it in your ActiveModel classes like so:

  validates :account_name,
    uniqueness: { case_sensitive: false, model: Account, attribute: 'name' }

您会遇到的唯一问题是您的自定义 model 类是否也有验证.当您调用 Signup.new.save 时,这些验证不会运行,因此您必须以其他方式检查它们.您始终可以在上述 persist! 方法中使用 save(validate: false),但是您必须确保所有验证都在 Signup 中> 类,并在您更改 AccountUser 中的任何验证时保持该类为最新.

The only problem you'll have with this, is if your custom model class has validations as well. Those validations aren't run when you call Signup.new.save, so you will have to check those some other way. You can always use save(validate: false) inside the above persist! method, but then you have to make sure all validations are in the Signup class, and keep that class up to date, when you change any validations in Account or User.

这篇关于在 ActiveModel 对象上,如何检查唯一性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-23 16:29