2

私は2つのモデルを持っています:

Invoice has_many :lines

Line belongs_to :invoice

Line与えられInvoiceたのの合計が関連するの合計と一致することを確認したいと思いInvoiceます。

私はこれを試しました:

validate :total_amount
def total_amount
    inv_id = self.invoice_id
    target_amount = Invoice.find(inv_id).total
    total_lines = Line.where(invoice_id: inv_id).sum(:line_value)

    errors.add(:total, " should be lower or equal to the total amount of the invoice") if total_lines > target_amount
end

だが

  1. 新しいオブジェクトでは機能しません(更新のみ)
  2. 更新の場合でも、体系的にエラーをスローします

私はAssociatedValidatorについて話している質問も見ましたが、それを使用する方法を理解することができませんでした:(

4

3 に答える 3

2

あなたの例はそれ以前に説明していたものとは異なるため、正確に何を検証したいかは明確ではありません。

before_addコールバックを使用すると、このようなものが機能するはずです:

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
  ...
于 2012-07-19T07:49:43.443 に答える
1

間違って解釈している可能性がありますが、がテーブルtotalの列である場合は、invoices削除することをお勧めします。代わりに、それをメソッドとして持ち、メソッドにLine価格と調整を合計させます。それ以外の場合は、データベースに重複があります。そうすれば、とにかく何も検証する必要はありません:)

より一般的な注意点として、に関連するモデルに検証を追加することActiveRecordはあまりうまく機能していません。場合によっては、それがほとんど不可能な場合もあれば、正しく理解するのがかなり難しい場合もあります。あなたはそれが簡単にうまくいかないのを見たと思います。私はそれを避けて、あなたがそうする必要がないようにあなたのデータベースを設計することを試みることを提案します(Invoice#totalこの場合はメソッドとして持っています)。

于 2012-07-19T09:29:55.240 に答える
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

エントリ(トランザクション)には、少なくとも2つのスプリットが必要です。スプリットのAmount属性(または借方/クレジット)の合計は0に等しくなければなりません。私validate :balanced?はそれを処理しますが、明らかなJavascriptエラーにより不均衡なエントリが許可されました。まだバグを追跡していませんが、エントリのバランスが取れてvalid?いなかったため、追加しようとした新しいスプリットでは機能しない(falseを返す)ため、更新できませんでした。

元帳accepts_nested_attributes_forフォーム​​は、不均衡なトランザクションの送信を許可しないはずの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とほぼ同じかもしれませんが、コールバックを使用せず、コントローラーを呼び出すだけです。

于 2018-12-30T18:48:55.233 に答える