轨道 5 验证关联范围内的计数



我有一个名为Person的模型,以及一个名为Contract的模型。一个人has_many合同合同belongs_to一个人

合约start_dateend_date。 如果当前日期介于两者之间,则合同被视为有效。

我在合约上有一个名为">活动">的范围,它会相应地返回记录。 一个人可以拥有任意数量的非活动合同,但只能有一个有效的合同。

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

  • 与已具有有效合同的关联人员创建新的有效合同。
  • 将现有有效合同上的关联人员更改为已具有有效合同的人员。
  • 当关联的人员已具有有效合同时,将不处于活动状态的合同更改为具有使其处于活动状态的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

对我来说,这感觉有点像黑客。 我不关心独特性,我关心大小;碰巧独特性起作用。如果我想允许不超过 2 个有效合约怎么办?

此外,当我尝试添加多个活动合同时,我得到的验证错误说"人员已被占用",这是误导性的。当然,我可以添加自定义消息,但这似乎表明我做错了。

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

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记录无效。

如果我正确理解了完整的场景,您不仅应该检查唯一性 ->活动。否则,当您的有效合约变为非活动状态时 - 可能有两个不活跃的合约现在处于活动状态。 而且我同意验证应该发生在人身上(这甚至应该解决您改变关系的问题(。

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

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

通过这种方式,您可以从一开始就确保一个人永远不会获得重叠的合同。

最新更新