Skip to content

Commit ea38048

Browse files
committed
Fix counter caches to work for models with composite primary keys
1 parent 257c630 commit ea38048

File tree

4 files changed

+53
-6
lines changed

4 files changed

+53
-6
lines changed

activerecord/lib/active_record/counter_cache.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def reset_counters(id, *counters, touch: nil)
6666
updates.merge!(touch_updates)
6767
end
6868

69-
unscoped.where(primary_key => object.id).update_all(updates) if updates.any?
69+
unscoped.where(primary_key => [object.id]).update_all(updates) if updates.any?
7070

7171
true
7272
end
@@ -113,6 +113,7 @@ def reset_counters(id, *counters, touch: nil)
113113
# # `updated_at` = '2016-10-13T09:59:23-05:00'
114114
# # WHERE id IN (10, 15)
115115
def update_counters(id, counters)
116+
id = [id] if composite_primary_key? && id.is_a?(Array) && !id[0].is_a?(Array)
116117
unscoped.where!(primary_key => id).update_counters(counters)
117118
end
118119

@@ -212,14 +213,18 @@ def destroy_row
212213
if affected_rows > 0
213214
counter_cached_association_names.each do |association_name|
214215
association = association(association_name)
215-
foreign_key = association.reflection.foreign_key.to_sym
216-
unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
216+
217+
unless destroyed_by_association && _foreign_keys_equal?(destroyed_by_association.foreign_key, association.reflection.foreign_key)
217218
association.decrement_counters
218219
end
219220
end
220221
end
221222

222223
affected_rows
223224
end
225+
226+
def _foreign_keys_equal?(fkey1, fkey2)
227+
fkey1 == fkey2 || Array(fkey1).map(&:to_sym) == Array(fkey2).map(&:to_sym)
228+
end
224229
end
225230
end

activerecord/test/cases/counter_cache_test.rb

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
require "models/subscriber"
1818
require "models/subscription"
1919
require "models/book"
20+
require "models/cpk"
2021
require "active_support/core_ext/enumerable"
2122

2223
class CounterCacheTest < ActiveRecord::TestCase
23-
fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers, :people, :friendships, :subscribers, :subscriptions, :books
24+
fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers, :people, :friendships, :subscribers, :subscriptions, :books,
25+
:cpk_orders, :cpk_books
2426

2527
class ::SpecialTopic < ::Topic
2628
has_many :special_replies, foreign_key: "parent_id"
@@ -42,11 +44,25 @@ class ::SpecialReply < ::Reply
4244
end
4345

4446
test "increment counter by specific amount" do
45-
assert_difference "@topic.reload.replies_count", +2 do
47+
assert_difference -> { @topic.reload.replies_count }, +2 do
4648
Topic.increment_counter(:replies_count, @topic.id, by: 2)
4749
end
4850
end
4951

52+
test "increment counter for cpk model" do
53+
order = Cpk::Order.first
54+
assert_difference -> { order.reload.books_count } do
55+
Cpk::Order.increment_counter(:books_count, order.id)
56+
end
57+
end
58+
59+
test "increment counter for multiple cpk model records" do
60+
order1, order2 = Cpk::Order.first(2)
61+
assert_difference [-> { order1.reload.books_count }, -> { order2.reload.books_count }] do
62+
Cpk::Order.increment_counter(:books_count, [order1.id, order2.id])
63+
end
64+
end
65+
5066
test "decrement counter" do
5167
assert_difference "@topic.reload.replies_count", -1 do
5268
Topic.decrement_counter(:replies_count, @topic.id)
@@ -59,6 +75,13 @@ class ::SpecialReply < ::Reply
5975
end
6076
end
6177

78+
test "decrement counter for cpk model" do
79+
order = Cpk::Order.first
80+
assert_difference -> { order.reload.books_count }, -1 do
81+
Cpk::Order.decrement_counter(:books_count, order.id)
82+
end
83+
end
84+
6285
test "reset counters" do
6386
# throw the count off by 1
6487
Topic.increment_counter(:replies_count, @topic.id)
@@ -148,6 +171,17 @@ class ::SpecialReply < ::Reply
148171
end
149172
end
150173

174+
test "reset counters for cpk model" do
175+
order = Cpk::Order.first
176+
# throw the count off by 1
177+
Cpk::Order.increment_counter(:books_count, order.id)
178+
179+
# check that it gets reset
180+
assert_difference -> { order.reload.books_count }, -1 do
181+
Cpk::Order.reset_counters(order.id, :books)
182+
end
183+
end
184+
151185
test "update counter with initial null value" do
152186
category = categories(:general)
153187
assert_equal 2, category.categorizations.count
@@ -177,6 +211,13 @@ class ::SpecialReply < ::Reply
177211
end
178212
end
179213

214+
test "update counter for decrement for cpk model" do
215+
order = Cpk::Order.first
216+
assert_difference -> { order.reload.books_count }, -3 do
217+
Cpk::Order.update_counters(order.id, books_count: -3)
218+
end
219+
end
220+
180221
test "update other counters on parent destroy" do
181222
david, joanna = dog_lovers(:david, :joanna)
182223
_ = joanna # squelch a warning

activerecord/test/models/cpk/book.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class Book < ActiveRecord::Base
55
attr_accessor :fail_destroy
66

77
self.table_name = :cpk_books
8-
belongs_to :order, autosave: true, query_constraints: [:shop_id, :order_id]
8+
belongs_to :order, autosave: true, query_constraints: [:shop_id, :order_id], counter_cache: true
99
belongs_to :author, class_name: "Cpk::Author"
1010

1111
has_many :chapters, query_constraints: [:author_id, :book_id]

activerecord/test/schema/schema.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@
285285
create_table :cpk_orders, force: true do |t|
286286
t.integer :shop_id
287287
t.string :status
288+
t.integer :books_count, default: 0
288289
end
289290

290291
create_table :cpk_order_tags, primary_key: [:order_id, :tag_id], force: true do |t|

0 commit comments

Comments
 (0)