繁体   English   中英

rails 5验证关联范围中的计数

[英]rails 5 validate count in associated scope

我有一个名为Person的模型和一个名为Contract的模型。 一个 的has_many 合同 ,并belongs_to的一个 合同

合同具有开始日期结束日期 如果当前日期介于此日期之间,则合同被视为有效

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

我正在尝试找出添加验证的最佳方法,以防止出现以下任何一种情况:

  • 与已经拥有有效合同的关联个人创建新的有效合同。
  • 将现有有效合同上的关联人员更改为已经具有有效合同的人员。
  • 当关联的人已经具有有效合同时,将无效的合同更改为具有使其生效的开始日期或结束日期。
  • 创建一个具有多个活动合同的人。

这是我目前正在做的,并且似乎可以正常工作:

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM