Rails belongs_to 可选时不验证 id
Rails belongs_to not validating id when optional
我是 运行 Rails 5.1.4,我有一个看起来像这样的模型:
class Quota < ActiveRecord::Base
belongs_to :domain, optional: true
belongs_to :project, optional: true
end
配额应属于域或项目,但不能同时属于两者(因此设置 optional: true
)。
但是,如果提供的项目或域 ID 无效,我似乎无法弄清楚如何使 rails 抛出错误。
事情是这样的:
q = Quota.create!(domain_id: nil, project_id: 'invalid_id')
q.project_id # -> nil
即使我明确传递 project_id,如果它与有效项目不匹配,它也会神奇地清除它。
我尝试添加自定义验证方法,但在调用验证方法时,它已经设置为 nil。它甚至也不使用 project_id=
方法;我检查了。
有没有办法让 Rails 在 ID 无效时引发错误而不是将其设置为 nil? (同时仍然允许 nil 值)
我能想到的最佳解决方案是:
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 已设置为有效值。
这是一种可能的解决方案
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? }
关于:
Is there a way to get Rails to raise an error if the ID is invalid
instead of setting it to nil? (while still allowing a nil value)
使用 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
将保存属性值和验证错误(如果有)。
我是 运行 Rails 5.1.4,我有一个看起来像这样的模型:
class Quota < ActiveRecord::Base
belongs_to :domain, optional: true
belongs_to :project, optional: true
end
配额应属于域或项目,但不能同时属于两者(因此设置 optional: true
)。
但是,如果提供的项目或域 ID 无效,我似乎无法弄清楚如何使 rails 抛出错误。
事情是这样的:
q = Quota.create!(domain_id: nil, project_id: 'invalid_id')
q.project_id # -> nil
即使我明确传递 project_id,如果它与有效项目不匹配,它也会神奇地清除它。
我尝试添加自定义验证方法,但在调用验证方法时,它已经设置为 nil。它甚至也不使用 project_id=
方法;我检查了。
有没有办法让 Rails 在 ID 无效时引发错误而不是将其设置为 nil? (同时仍然允许 nil 值)
我能想到的最佳解决方案是:
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 已设置为有效值。
这是一种可能的解决方案
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? }
关于:
Is there a way to get Rails to raise an error if the ID is invalid instead of setting it to nil? (while still allowing a nil value)
使用 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
将保存属性值和验证错误(如果有)。