【问题标题】:Rails belongs_to not validating id when optionalRails belongs_to 在可选时不验证 id
【发布时间】:2017-12-11 23:35:28
【问题描述】:

我正在运行 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= 方法;我查过了。

如果 ID 无效而不是将其设置为 nil,是否有办法让 Rails 引发错误? (同时仍然允许 nil 值)

【问题讨论】:

    标签: ruby-on-rails activerecord ruby-on-rails-5 rails-models


    【解决方案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 已设置为有效值。

    【讨论】:

      【解决方案2】:

      这是一种可能的解决方案

      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_iddomain_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 中预定义的正常行为。

      另一种方法是结合使用newsave 方法。使用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 将保存属性值和验证错误(如果有)。

      【讨论】:

      • 感谢@vane-trajkov,但我认为这并不能解决我验证关联 ID 的主要问题。
      • 也许我遗漏了一些东西,但它确实验证了关联。例如,您传递 domain_id=1 它将检查数据库中是否存在 id=1 的域。验证存在:真正的规则可以解决问题
      • 在我的“这是发生了什么”部分,它显示了一些非常奇怪的行为,如果 id 没有映射到有效的项目,它只是将用户的给定值覆盖为 nil。当关联不是可选的时,它通常会在那个时候抛出一个错误,但是当它是可选的时,它只是忽略无效的 id 并清除该字段。在我看来,Rails 不是很直观。
      • 我已经更新了答案,添加了关于“发生了什么”部分的解释。另外,如果这没有帮助,您能否也澄清一下您的问题?访问无效属性的用例是什么?你能粘贴你的完整代码来处理配额的创建和访问属性吗?
      猜你喜欢
      • 2013-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多