When your model is using a callback like before_save
or before_validation
to calculate an aggregated value from its children, it needs to skip those children that are #marked_for_destruction?
. Otherwise you will include children that have been ticked for deletion in a nested form.
Wrong way
class Invoice < ApplicationRecord
has_many :invoice_items
accepts_nested_attributes_for :invoice_items, :allow_destroy => true # the critical code 1/2
before_save :calculate_and_store_amount # the critical code 2/2
private
def calculate_and_store_amount
# may include amounts of items you marked for destruction
self.amount = invoice_items.collect(&:amount).sum
end
end
Right way
class Invoice < ApplicationRecord
has_many :invoice_items
accepts_nested_attributes_for :invoice_items, :allow_destroy => true # the critical code 1/2
before_save :calculate_and_store_amount # the critical code 2/2
private
def calculate_and_store_amount
# does not include amounts of items you marked for destruction
self.amount = invoice_items.reject(&:marked_for_destruction?).sum(&:amount)
end
end
How to test the correct behaviour in rspec
it 'ignores invoice items marked for destruction in a nested update' do
invoice = Invoice.make
item1 = InvoiceItem.make(:invoice => invoice, :amount => BigDecimal.new("10.0"))
item2 = InvoiceItem.make(:invoice => invoice, :amount => BigDecimal.new("21.0"))
invoice.update_attributes!(
:invoice_items_attributes => {
'0' => {
'id' => item1.id,
'_destroy' => '1'
}
}
)
expect(invoice.amount.to_s).to eq(BigDecimal.new("21.0").to_s)
end
Posted by Martin Straub to makandra dev (2012-02-23 14:45)