Skip to content

Instantly share code, notes, and snippets.

@latentflip
Created November 18, 2011 13:19
Show Gist options
  • Save latentflip/1376443 to your computer and use it in GitHub Desktop.
Save latentflip/1376443 to your computer and use it in GitHub Desktop.
Before save and active record association aggregations

So today I learned that if you have:

class Invoice < ActiveRecord::Base
  has_many :transactions
  before_save :cache_transaction_total

  def cache_transaction_total
    self.transaction_total = transactions.sum(:total) || 0
  end
end

class Transaction < ActiveRecord::Base
  belongs_to :invoice
end

And you open a rails console and do something innocuous like:

$ i = Invoice.create

The query that aggregates the transactions in the before_save callback is something like:

SELECT SUM("transactions"."total") AS sum_id 
FROM "transactions" WHERE "transactions"."invoice_id" IS NULL

Which rather than aggregating all the (non-existant) transactions associated to the new invoice, aggregates all the transactions which aren't associated to an invoice at all - which gives a very different result. In my case, most transactions are not listed to an invoice so instead of caching 0 it caches something more like 1,000,000.

I guess the solution is to run the update in an after_save callback instead -- but does this behaviour not seem completely different to what we would expect?

Thoughts?

@latentflip
Copy link
Author

Yeah I guess I see why it's happening I just thought that AR would catch that the id was null and not try and do the aggregation. After all, if you do

$ i = Invoice.new
$ i.transactions

it doesn't go ahead and

SELECT * FROM transactions WHERE transactions.invoice_id IS NULL

@lenary
Copy link

lenary commented Nov 18, 2011

actually on second thoughts, how about just have it in before_update... that would make more sense... (and stops you having to think about inception-callbacks). S

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment