我正在运行Rails 5.1.4,我的模型看起来像这样:
class Quota < ActiveRecord::Base
belongs_to :domain, optional: true
belongs_to :project, optional: true
end
配额应该属于域OR项目,但不能同时属于两者(因此设置optional: true
)。
但是,如果提供了无效的项目或域ID,我似乎无法弄清楚如何使rails抛出错误。
以下是发生的事情:
q = Quota.create!(domain_id: nil, project_id: 'invalid_id')
q.project_id # -> nil
即使我明确传递了project_id,如果它与有效项目不匹配,它也会神奇地清除它。
我尝试添加自定义验证方法,但是在调用验证方法时,它已经设置为nil。它甚至不使用project_id=
方法;我查了一下。
如果ID无效而不是将其设置为nil,是否有办法让Rails引发错误? (虽然仍允许零值)
答案 0 :(得分:1)
我能想出的最佳解决方案是:
class Quota < ActiveRecord::Base
belongs_to :domain, optional: true
belongs_to :project, optional: true
validate :validate_associations
def project_id=(val)
Project.find(val) unless val.nil?
super
end
def domain_id=(val)
Domain.find(val) unless val.nil?
super
end
private
def validate_associations
errors.add(:base, 'Specify a domain or a project, not both') if domain && project
errors.add(:base, 'Must specify a domain or a project') if domain.nil? && project.nil?
end
end
感谢@ vane-trajkov帮助解决问题。我发现在设置domain_id或project_id时我确实需要使用find
方法,因为Rails很乐意将其设置为无效的ID。使用project=
和domain=
可以正常工作,因为它们几乎可以确保ID已经设置为有效值。
答案 1 :(得分:0)
这是一种可能的解决方案
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
)的设置方案。这样我们就可以避免两次击中数据库,实际上,我们只需要按一次数据库。
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引发错误 而不是将其设置为零? (虽然仍允许零值)
使用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
的值为nil,因为属性无效且没有实例化对象。这是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
将保留属性值和验证错误(如果有)。