【问题标题】:rails 5 validate count in associated scoperails 5 验证相关范围内的计数
【发布时间】:2018-08-27 15:09:04
【问题描述】:

我有一个名为 Person 的模型和一个名为 Contract 的模型。 Person has_many Contracts,以及Contract belongs_to Person。

合同有一个start_date和一个end_date。如果当前日期介于两者之间,则合同被视为有效

我在合同上有一个名为“active”的范围,它会相应地返回记录。一个人可以有任意数量的非活动合同,但应该只有一个活动合同。

我正在尝试找出添加验证以防止任何这些情况的最佳方法:

  • 与已拥有有效合同的关联人员创建新的有效合同。
  • 将现有活动合同中的关联人员更改为已拥有活动合同的人员。
  • 当关联的 Person 已经有一个活动的合同时,将一个不活动的合同更改为具有使其活动的 start_date 或 end_date。
  • 创建一个关联多个活动合同的人员。

这就是我目前正在做的事情,它似乎有效:

class Contract < ApplicationRecord
  belongs_to :person
  validates_uniqueness_of :person_id, conditions: -> { active }
  scope :active, -> { where("start_date <= ? AND end_date >= ?", Date.today, Date.today) }
end

对我来说,这感觉有点像 hack。我不关心独特性,我关心的是大小;它只是碰巧唯一性起作用。如果我想允许不超过 2 个有效合同怎么办?

此外,当我尝试添加多个活动合同时返回的验证错误显示“人员已被占用”,这具有误导性。当然,我可以添加自定义消息,但这似乎是我做错了的另一种迹象。

【问题讨论】:

  • 将逻辑委托给 Person 怎么样?在 Person 上创建一个类似于“active_contract_count”的范围,当需要创建合约时,从 Person 的“create_contracts(contracts)”之类的方法中创建,该方法在尝试创建合约之前测试 active_contract_count,然后还查看每个合约和测试他们的.active?如果它们在指定的日期范围内,则返回。
  • @bkimble 也许,但是验证对现有合约的更新呢?尝试通过人员模型执行所有创建/更新合同操作似乎会变得混乱。它可能会防止出现不需要的情况,但如果我理解正确,你建议我不要使用内置的-在 Rails 验证功能中?在我看来,那时我最好使用 validates_with 和自定义验证器类。

标签: ruby-on-rails


【解决方案1】:

对于唯一性误导消息,您始终可以自定义验证消息

validates :person_id, uniqueness: {scope: :active, message: 'Some custom message'}

对于非标准验证,您需要自定义验证器或自定义验证方法https://guides.rubyonrails.org/active_record_validations.html#custom-methods。当某些情况发生时,您只需要为属性添加错误(如果与属性无关,则为 :base),条件可能是检查日期、用户等

class Contract < ApplicationRecord
  validate :allow_only_two_active_contracts

private
  def allow_only_two_active_contracts
    person_contracts = person.contracts.active.where.not(id: self.id).count #count all active contracts of the person except this one, not sure if the where.not is necessary

    errors.add(:person_id, 'This person already has two active contracts') if person_contracts >= 2
  end
end

ActiveRecord 尝试验证您的合约的内容,它将调用该方法,并且在所有验证之后,如果 errors 发现记录无效。

【讨论】:

  • 谢谢,是的,看来这可能是最好的选择。作为记录,我认为你的答案的逻辑是不完整的——如果 person_contracts >= 2 && self.active,我们只想添加错误? (如果正在验证的合约不活跃,那么我们不应该阻止保存它)。
  • 哦,是的,当然,您对应用程序有更多的上下文和知识来添加正确的条件,我的只是一个关于如何使用它的示例。
【解决方案2】:

如果我正确理解了整个场景,您不应该只检查唯一性 -> 活动。否则,当您的有效合同变为无效时 - 可能有两个合同,它们是无效的,现在变得有效。 我同意验证应该发生在这个人身上(这甚至可以解决你改变关系的问题)。

所以我认为你想要更多类似的东西:

class Person < ApplicationRecord
  validates :contracts, :not_overlapping
end

class NotOverlappingValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if value.blank?
    mark_overlapping_error to_ranges(value)
  end

  def mark_overlapping_error(ranges)
    ranges[0..-2].each_with_index do |range, index|
      # check if successive contracts are overlapping in their active interval
      next unless range.overlaps?(ranges[index + 1])
      # add some custom error about the overlapping
      return record.errors.add attribute, :overlaps 
    end
  end

  def to_ranges(contracts)
    # mapping contracts to their activity-interval
    value.sort_by(&:start_date).map do |contract|
      (contract.start_date..contract.end_date)
    end
  end
end

这样你从一开始就确保一个人永远不会得到重叠的合同。

【讨论】:

  • 谢谢,我喜欢这个答案,因为它将逻辑转向关注 Person,但仍将合同验证逻辑保留在其自己的类中。这个解决方案更灵活,但我接受了 arieljuod 的回答,因为它更直接地解决了我的问题。
猜你喜欢
  • 2015-06-04
  • 2018-06-24
  • 2017-06-16
  • 1970-01-01
  • 1970-01-01
  • 2017-04-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多