Rails ActiveRecord自定义验证器返回false但接受了model.save而没有回滚

时间:2017-01-04 10:19:58

标签: ruby-on-rails validation activerecord

我有一个模型,定义了100平方米到200平方米的尺寸范围。我写了一个验证器:

class SizeRange < ActiveRecord::Base
    validate :non_overlapping

    def non_overlapping
        lower_in_range = SizeRange.where(lower_bound: lower_bound..upper_bound)
        upper_in_range = SizeRange.where(upper_bound: lower_bound..upper_bound)
        if lower_in_range.present?
            errors.add(:lower_bound, 'blablabla')
        end
        if upper_in_range.present?
            errors.add(:upper_bound, 'blablabla')
        end
    end
end

我的猜测是,当我尝试保存一个新的模型,其下限或上限似乎在另一个SizeRange实例的范围内时,验证器会将模型标记为无效,并且保存操作将被中止。 真正发生的是我的模型被保存并分配了一个id,但是当我调用model.valid时?它返回false(所以我的验证器似乎应该做它应该做的)。

有什么我可以做错的,或者我不明白验证器是如何工作的?我可以强制验证器中止保存操作吗?

另一个问题: 有没有办法通过数据库约束强制执行约束?我想我更喜欢数据库方面的解决方案。

感谢您的帮助!

4 个答案:

答案 0 :(得分:1)

model.save

将被默默接受并返回false。它不会抛出任何异常。

你应该致电:

model.save!

验证失败

答案 1 :(得分:0)

每次出错后都应该返回false.add取消保存记录

答案 2 :(得分:0)

原来问题是我的验证器功能不完善。

我检查了一个导致混淆的案例:

  • 数据库中已有200到400的SizeRange
  • 创建200到400之间的新版本,例如SizeRange.new({:lower_bound=>250, :upper_bound=>350})

所以我希望它无效(model.valid? - &gt; false)但它当然是有效的。所以model.save没有回滚,我在model.valid上测试了吗? NOW会返回false,因为新保存的实例会违反约束,因为它是针对自身进行测试的。

所以有两个问题:

  • model.valid?如果它已经有一个id
  • ,也会对同一个实例进行测试
  • 验证者不会验证我认为会是什么

所以我最终重写了我的验证器功能:

def non_overlapping
    sr = id.present? ? SizeRange.where.not(id: id) : SizeRange

    ranges = sr.all
    lower_overlappings = ranges.map { |r| lower_bound.between?(r.lower_bound, r.upper_bound)}
    upper_overlappings = ranges.map { |r| upper_bound.between?(r.lower_bound, r.upper_bound)}

    if lower_overlappings.any?
        errors.add(:lower_bound, 'lower bla bla')
    end
    if upper_overlappings.any?
        errors.add(:lower_bound, 'upper bla bla')
    end
end

第一行处理第一个问题,其余部分处理预期的验证。

感谢您的帮助,并对此感到抱歉。

答案 3 :(得分:0)

你应该使用开始和救援:

class SizeRange < ActiveRecord::Base
  validate :non_overlapping

  private
    def non_overlapping
      lower_in_range = SizeRange.where(lower_bound: lower_bound..upper_bound)
      upper_in_range = SizeRange.where(upper_bound: lower_bound..upper_bound)

      begin
        raise("Lower Bound Met!") if lower_in_range.present?
      rescue => ex
        errors.add(:lower_bound, "#{ex.message} (#{ex.class})")
      end
      begin
        raise("Lower Bound Met!") if upper_in_range.present?
      rescue => ex
        errors.add(:upper_bound, "#{ex.message} (#{ex.class})")
      end
    end
end