这是一种可能的解决方案
class Quota < ApplicationRecord
belongs_to :domain, optional: true
belongs_to :project, optional: true
validate :present_domain_or_project?
validates :domain, presence: true, unless: Proc.new { |q| q.project_id.present? }
validates :project, presence: true, unless: Proc.new { |q| q.domain_id.present? }
private
def present_domain_or_project?
if domain_id.present? && project_id.present?
errors.add(:base, "Specify a domain or a project, not both")
end
end
end
在第一个块中,我们定义关联并指定optional: true,因此我们超越了Rails 5 验证关联存在的新行为。
belongs_to :domain, optional: true
belongs_to :project, optional: true
然后,我们要做的第一件事就是简单地消除设置了两个关联属性(project_id 和domain_id)的场景。这样我们就避免了两次击中 DB,实际上,我们只需要击中一次 DB。
validate :present_domain_or_project?
...
private
def present_domain_or_project?
if domain_id.present? && project_id.present?
errors.add(:base, "Specify a domain or a project, not both")
end
end
最后一部分是检查一个关联是否存在(有效)而另一个则不存在
validates :domain, presence: true, unless: Proc.new { |q| q.project_id.present? }
validates :project, presence: true, unless: Proc.new { |q| q.domain_id.present? }
关于:
如果 ID 无效,有没有办法让 Rails 引发错误
而不是将其设置为零? (同时仍然允许 nil 值)
使用create! 方法时,如果验证失败,Rails 会引发 RecordInvalid 错误。应捕获并适当处理异常。
begin
q = Quota.create!(domain_id: nil, project_id: 'invalid_id')
rescue ActiveRecord::RecordInvalid => invalid
p invalid.record
p invalid.record.errors
end
invalid 对象应包含失败的模型属性以及验证错误。请注意,在此块之后,q 的值为零,因为属性无效并且没有实例化对象。这是 Rails 中预定义的正常行为。
另一种方法是结合使用new 和save 方法。使用new 方法,可以在不保存的情况下实例化对象,并且对save 的调用将触发验证并将记录提交到数据库(如果有效)。
q = Quota.new(domain_id: nil, project_id: 'invalid_id')
if q.save
# quota model passes validations and is saved in DB
else
# quota model fails validations and it not saved in DB
p q
p q.errors
end
这里的对象实例 - q 将保存属性值和验证错误(如果有)。