Ruby on Rails - before_save calculation on accepts_nested_attributes_for
Asked Answered
I

2

8

I have an invoice model, that "has many" invoice items. I have a form that allows you to create/edit an invoice, and this form accepts nested attributes for the invoice items.

In my invoice model, there is a "total_amount" field that is a calculated field (the sum of "amount" from the invoice items).

I want to calculate this amount when an invoice is created, or updated. I am trying to do this in the before_save event of my invoice model. The code below almost works, however the total that gets saved is always one step behind. I.e. if I have an invoice with a total of $20, and I edit this invoice and change the invoice items to total $15, then save my invoice, the total doesn't change. If I open the same invoice, and then save it again, the total is updated properly.

I assume that my line below that calculates the sum is accessing the line items that are saved in the database already, and not those that have just been changed and are about to be saved. I don't however know how to access those.

class Invoice < ActiveRecord::Base
  has_many :invoice_items, :dependent => :destroy

  accepts_nested_attributes_for :invoice_items, :allow_destroy => true 

  before_save :record_total_amount

  private
    def record_total_amount
      self.total_amount = self.invoice_items.sum('amount')
    end
end

Any help would be much appreciated.

Inge answered 28/2, 2011 at 6:25 Comment(2)
Does total_amount have to be saved? You could just recalculate it whenever requested by defining it as an instance method.Maffei
I am saving it for performance reasonsInge
I
5

I solved this problem, I had to replace the calculation line with this one:

self.total_amount = invoice_items.map(&:amount).sum
Inge answered 28/2, 2011 at 6:48 Comment(0)
D
4

Try this:

self.total_amount = invoice_items.reject(&:marked_for_destruction?).map(&:amount).sum

Droit answered 17/3, 2013 at 15:28 Comment(1)
you can also replace .map with .sum directly: self.total_amount = invoice_items.reject(&:marked_for_destruction?).sum(&:amount)Hireling

© 2022 - 2024 — McMap. All rights reserved.