在嵌套模型上验证之前

Before validation on nested model

我有一个嵌套模型 items,我正在尝试将两列相乘 costquantity 以设置最后一列 price。 我需要在保存表单之前设置列 price,我还需要验证模型。如果在保存前未设置 costquantity,则 before_validation 回调显然会中断。 关于如何将这些列相乘并验证模型的任何想法?

这是我的代码item.rb

class Item < ActiveRecord::Base
belongs_to :invoice

 validates :invoice_id, presence: true
 validates :name, presence: true
 validates_presence_of :quantity, :cost, :price

 before_validation :set_price

 def set_price
    self.price = cost * quantity.round(2)
 end
end

这是我的父模型invoice.rb

class Invoice < ActiveRecord::Base
belongs_to :user
has_many :items

accepts_nested_attributes_for :items, :reject_if => :all_blank, :allow_destroy => true

validates :sender, presence: true
validates_associated :items

before_save :increment_invoice_number, :set_amount

private

def increment_invoice_number
    if published == true
        self.invoice_number = user.invoices.where(:published => true).count + 1
    end
end

def set_amount
    self.amount = items.map(&:price).sum             
end

结束

如果您总是要在每次保存时更新价格,则不需要验证它的存在,因此只需将其从验证中删除并添加一个 before_save 回调,它将被调用 验证之后,您将保证拥有有效的成本和数量字段。 link Rails 指南中讨论此问题的地方: http://guides.rubyonrails.org/active_record_callbacks.html#available-callbacks

这是一个例子:

class Item < ActiveRecord::Base
  belongs_to :invoice

  validates :invoice_id, presence: true
  validates :name, presence: true
  validates_presence_of :quantity, :cost

  before_save :set_price

  def set_price
    self.price = cost * quantity.round(2)
  end
end

如果您想在创建或更新时仅设置价格,您可以改用before_createbefore_update回调。或者,您可以可选地设置价格(如果尚未设置):

self.price ||= cost * quantity.round(2)

这是您要实现的目标吗?

更新: 经过一些讨论(见评论),处理这个问题的最佳方法似乎是更新 [=12] 中的所有子 items =] 父 invoice 上的回调,因为每次保存 invoice 时都会调用它。下面是一个示例实现 - 我还从您的原始代码中删除了一些验证语句,并使用新语法组合了所有内容。此外,无论何时通过 nested_attributes 关联更新,items 都将被验证。

class Item < ActiveRecord::Base
  belongs_to :invoice

  validates :invoice_id, :name, :quantity, :cost, presence: true

  def set_price
    self.price = cost * quantity.round(2)
  end
end

class Invoice < ActiveRecord::Base
  belongs_to :user
  has_many :items

  accepts_nested_attributes_for :items, :reject_if => :all_blank, :allow_destroy => true

  validates :sender, presence: true

  before_save :increment_invoice_number, :update_prices, :set_amount

  private

  def increment_invoice_number
    if published == true
      self.invoice_number = user.invoices.where(:published => true).count + 1
    end
  end

  def update_prices
    items.each(&:set_price)
  end

  def set_amount
    self.amount = items.map(&:price).sum             
  end
end