ActiveRecord: When aggregating nested children, always exclude children marked for destruction

Updated . Posted . Visible to the public. Repeats.

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
Profile picture of Martin Straub
Martin Straub
Last edit
Felix Eschey
Keywords
howto
License
Source code in this card is licensed under the MIT License.
Posted by Martin Straub to makandra dev (2012-02-23 14:45)