验证部分的总和等于父/子记录的总和

时间:2012-07-19 07:30:30

标签: ruby-on-rails ruby-on-rails-3 validation associations

我有2个型号:

Invoice has_many :lines

Line belongs_to :invoice

我希望确保给定Line的{​​{1}}的总和与相关Invoice的总和相匹配。

我试过这个:

Invoice

但是

  1. 它不适用于新对象(只是更新)
  2. 即使是更新它也会系统地抛出错误
  3. 我也看过一个关于AssociatedValidator的问题,但我还是没能理解如何使用它:(

3 个答案:

答案 0 :(得分:2)

目前尚不清楚您想要验证的内容,因为您的示例与之前描述的内容不同。

我认为这样的事情应该有效,使用before_add callback

class Invoice < AR::Base
  has_many :lines, :before_add => :validate_total

  def validate_total(invoice, line)
    totals = invoice.lines.sum(:line_value)

    if totals + line.line_value > invoice.total
      invoice.errors.add(:total, " should be lower or equal to the total amount of the invoice")
      return false # I think you can alternatively raise an exception here
  end
  ...

答案 1 :(得分:1)

我可能会将其解释错误,但如果totalinvoices表中的列,我建议将其删除。相反,将它作为一种方法,让方法加起来Line价格加上任何调整。否则,您在数据库中有重复。这样你无论如何都不需要验证任何东西:)

更一般地说,在ActiveRecord中添加关联模型的验证效果不佳。在某些情况下,几乎不可能,在另一些情况下 - 很难做到正确。我想你已经看到它容易出错了。我建议避免它并尝试设计您的数据库,以便您不需要(在这种情况下使用Invoice#total作为方法)。

答案 2 :(得分:0)

花了一些时间才找到使用accepts_nested_attributes_for出现的问题。但是答案只是说很难,即使不是不可能!accepts_nested_attributes_for是一种有点复杂的方法,但是它可以工作-除非您试图基于子模型的计算来验证模型。我可能已经找到一种克服计算问题的方法。

我正在研究基于Web的双向记帐应用程序,该应用程序具有以下基本模型;

class Account < ApplicationRecord
  has_many :splits
  has_many :entries, through: :splits
end

class Entry < ApplicationRecord
  has_many :splits, -> {order(:account_id)}, dependent: :destroy, inverse_of: :entry

  validate  :balanced?
end

class Split < ApplicationRecord
  belongs_to :entry, inverse_of: :splits
  belongs_to :account

  validates_associated :account
  validates_associated :entry
end

条目(交易)必须至少有两个拆分,拆分中的金额属性(或借方/贷方)之和必须等于0。尽管validate :balanced?会照顾好它,但显然JavaScript错误允许输入不平衡。我尚未找到该错误,但是由于条目不平衡,所以我无法更新它,因为valid?在我尝试添加的新拆分中不起作用(返回false)。

分类帐accepts_nested_attributes_for表单已退出一些Javascript,该Javascript不应允许提交不平衡的事务。均衡?没有在创建时设置错误,但是在更新时存在错误。我修复它的方法不是使用无效的验证,而是依靠与@entry.update(entry_params)一起调用的方法:

class Entry < ApplicationRecord
  has_many :splits, -> {order(:account_id)}, dependent: :destroy, inverse_of: :entry

  # validate  :balanced? # took this out since its after the fact, balanced? method can still be called

  accepts_nested_attributes_for :splits, 
    :reject_if =>  proc { |att| att[:amount].to_i.zero? && att['account_id'].to_i.zero?},
    allow_destroy: true

    def valid_params?(params)
      split_sum = 0
      params_hash = params.to_h
      params_hash[:splits_attributes].each{|k,s| split_sum += s[:amount].to_i if s[:_destroy].to_i.zero?}
      unless split_sum.zero?
        errors.add(:amount, "Unbalanced: debits, credits must balance")
        return false
      else
        return true
      end
    end
  end
end

# update action from Entry Controller

def update
  respond_to do |format|
    if @entry.valid_params?(entry_params) && @entry.update(entry_params)
      format.html { redirect_to account_path(session[:current_acct]), notice: 'Entry was successfully updated.' }
      format.json { render :show, status: :ok, location: @entry }
    else
      # ... errr
    end
  end
end

同样,这无非是验证参数,而不是在这种情况下不起作用的模型验证。

这可能与答案2大致相同,但不使用回调,而只是在控制器中调用