Steps to reproduce
The following gist contains a failing unit test:
https://gist.github.com/malikolivier/87904c9ed078dca186cf455a09a4f30e
Expected behavior
Test should succeed, counter_cache should be decremented once.
Actual behavior
Test fails, counter_cache is decremented twice.
System configuration
Rails version: 5.1.5 (master)
Ruby version: 2.4.3
More details
Database setup: A lecture is given by a person in an institution. An institution can hold several lectures and a person can give several lectures.
Each person and institution has a cache counter counting the number of lectures associated.
Run the file included in the gist:
ruby decrement_counter_cache_test.rb
This file moves a lecture to another institution. We thus except the counter showing the number of lectures in the original institution to be decremented.
You will get the following output:
D, [2018-02-22T19:01:14.445281 #20237] DEBUG -- : Institution Load (0.0ms) SELECT "institutions".* FROM "institutions" WHERE "institutions"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
D, [2018-02-22T19:01:14.446164 #20237] DEBUG -- : Institution Update All (0.1ms) UPDATE "institutions" SET "lectures_count" = COALESCE("lectures_count", 0) + 1 WHERE "institutions"."id" IS NULL
D, [2018-02-22T19:01:14.446526 #20237] DEBUG -- : Institution Update All (0.0ms) UPDATE "institutions" SET "lectures_count" = COALESCE("lectures_count", 0) - 1 WHERE "institutions"."id" = ? [["id", 1]] <- First decrement
D, [2018-02-22T19:01:14.446999 #20237] DEBUG -- : Institution Create (0.0ms) INSERT INTO "institutions" ("address") VALUES (?) [["address", "New address"]]
D, [2018-02-22T19:01:14.447429 #20237] DEBUG -- : Lecture Update (0.0ms) UPDATE "lectures" SET "institution_id" = ? WHERE "lectures"."id" = ? [["institution_id", 2], ["id", 1]]
D, [2018-02-22T19:01:14.447822 #20237] DEBUG -- : Institution Update All (0.0ms) UPDATE "institutions" SET "lectures_count" = COALESCE("lectures_count", 0) + 1 WHERE "institutions"."id" = ? [["id", 2]]
D, [2018-02-22T19:01:14.448094 #20237] DEBUG -- : Institution Update All (0.0ms) UPDATE "institutions" SET "lectures_count" = COALESCE("lectures_count", 0) - 1 WHERE "institutions"."id" = ? [["id", 1]] <- Second decrement
D, [2018-02-22T19:01:14.448218 #20237] DEBUG -- : (0.0ms) commit transaction
As you can see, institutions.lectures_count is decremented twice. Now very interestingly, if you change this line to
belongs_to :person#, counter_cache: true
the unit test will succeed. The second decrement will not occur.
Conclusion: It seems that the creation of a second cache counter to another record interferes with the the way other cache counters are computed, causing a superfluous decrement. This happens while updating a record's attributes in bulk. A quick survey seemed to show that assign_attributes decrements the counter once, and save! decrements it a second time in the following code: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/persistence.rb#L429-L430.
May or may not be related to #31491 and #31493.
Steps to reproduce
The following gist contains a failing unit test:
https://gist.github.com/malikolivier/87904c9ed078dca186cf455a09a4f30e
Expected behavior
Test should succeed, counter_cache should be decremented once.
Actual behavior
Test fails, counter_cache is decremented twice.
System configuration
Rails version: 5.1.5 (master)
Ruby version: 2.4.3
More details
Database setup: A lecture is given by a person in an institution. An institution can hold several lectures and a person can give several lectures.
Each person and institution has a cache counter counting the number of lectures associated.
Run the file included in the gist:
This file moves a lecture to another institution. We thus except the counter showing the number of lectures in the original institution to be decremented.
You will get the following output:
As you can see,
institutions.lectures_countis decremented twice. Now very interestingly, if you change this line tothe unit test will succeed. The second decrement will not occur.
Conclusion: It seems that the creation of a second cache counter to another record interferes with the the way other cache counters are computed, causing a superfluous decrement. This happens while updating a record's attributes in bulk. A quick survey seemed to show that
assign_attributesdecrements the counter once, andsave!decrements it a second time in the following code: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/persistence.rb#L429-L430.May or may not be related to #31491 and #31493.