seymore_strongboy
seymore_strongboy

Reputation: 964

Rails ActiveRecord :pluck and :map return two different values - why?

I have an ActiveRecord model (using STI) MonetaryChange::PaymentPromise that has subtotal_cents as an attribute / column (an integer).

My controller is updating that model/attribute, but for some reason, I'm getting the old/original value when I call map and the updated (correct) value when I call pluck. For example:

V410MonetaryChange::PaymentPromise.where(id: 1).pluck(:subtotal_cents)
=> [24600] # this is the correct updated value

V410MonetaryChange::PaymentPromise.where(id: 1).map(&:subtotal_cents)
=> [12300] # this is the original value, but it should have been updated to 24600

I notice that the SQL that Rails generates for the pluck method is much simpler:

(0.5ms)  SELECT "v410_monetary_changes"."subtotal_cents" 
FROM "v410_monetary_changes" 
WHERE "v410_monetary_changes"."type" IN ('V410MonetaryChange::PaymentPromise') 
AND "v410_monetary_changes"."deleted_at" IS NULL 
AND "v410_monetary_changes"."id" = $1  [["id", 1]]

but the SQL for the map method loads a whole bunch of associations after the first query:

  V410MonetaryChange::PaymentPromise Load (0.6ms)  SELECT "v410_monetary_changes".* FROM "v410_monetary_changes" WHERE "v410_monetary_changes"."type" IN ('V410MonetaryChange::PaymentPromise') AND "v410_monetary_changes"."deleted_at" IS NULL AND "v410_monetary_changes"."id" = $1  [["id", 1]]
  V410OrderedSku Load (0.3ms)  SELECT  "v410_ordered_skus".* FROM "v410_ordered_skus" WHERE "v410_ordered_skus"."deleted_at" IS NULL AND "v410_ordered_skus"."id" = $1 LIMIT 1  [["id", 1]]
  V410OrderedSkusV410Sku Load (0.2ms)  SELECT  "v410_ordered_skus_v410_skus".* FROM "v410_ordered_skus_v410_skus" WHERE "v410_ordered_skus_v410_skus"."v410_ordered_sku_id" = $1 LIMIT 1  [["v410_ordered_sku_id", 1]]
  V410Sku Load (0.2ms)  SELECT  "v410_skus".* FROM "v410_skus" WHERE "v410_skus"."id" = $1 LIMIT 1  [["id", 1]]

Any idea why this is the case, and how I can resolve this?

I tried adding .reload to my query, but the results were the same:

V410MonetaryChange::PaymentPromise.where(id: 1).reload.map(&:subtotal_cents)
=> [12300]

Edit 1: I can confirm that the database has the correct value (24600). When I open a psql console, and run the query:

SELECT "v410_monetary_changes"."subtotal_cents" 
    FROM "v410_monetary_changes" 
    WHERE "v410_monetary_changes"."type" IN ('V410MonetaryChange::PaymentPromise') 
    AND "v410_monetary_changes"."deleted_at" IS NULL 
    AND "v410_monetary_changes"."id" = 1;

I get:

 subtotal_cents 
----------------
          24600
(1 row)

So Rails must be doing something with the associations that is causing it to give me an old version of the record. Any ideas why?

Upvotes: 1

Views: 1651

Answers (1)

seymore_strongboy
seymore_strongboy

Reputation: 964

OK, important Rails lesson learned today!

I recently added an after_initialize callback to this model, which set the default values (including subtotal_cents) for newly initialized records.

What I didn't realise is that this callback was re-initializing (ie overwriting in memory after load) existing records with the default values.

My fix was to change

after_initialize :set_default_currency_amounts

to

after_initialize :set_default_currency_amounts, unless: Proc.new { |pp| pp.persisted? }

In my case, this is why the SQL was so long - my defaults required the loading of some associated models to get the default value for subtotal_cents. This was the clue that helped me solve this.

Thank you to everyone for your help!

Upvotes: 2

Related Questions