问题描述
在 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
中> 类,并在您更改 Account
或 User
中的任何验证时保持该类为最新.
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 对象上,如何检查唯一性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!