Skip to content

Commit

Permalink
Fix counter caches to work for models with composite primary keys
Browse files Browse the repository at this point in the history
  • Loading branch information
fatkodima committed Feb 8, 2024
1 parent 257c630 commit ea38048
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 6 deletions.
11 changes: 8 additions & 3 deletions activerecord/lib/active_record/counter_cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def reset_counters(id, *counters, touch: nil)
updates.merge!(touch_updates)
end

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

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

Expand Down Expand Up @@ -212,14 +213,18 @@ def destroy_row
if affected_rows > 0
counter_cached_association_names.each do |association_name|
association = association(association_name)
foreign_key = association.reflection.foreign_key.to_sym
unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key

unless destroyed_by_association && _foreign_keys_equal?(destroyed_by_association.foreign_key, association.reflection.foreign_key)
association.decrement_counters
end
end
end

affected_rows
end

def _foreign_keys_equal?(fkey1, fkey2)
fkey1 == fkey2 || Array(fkey1).map(&:to_sym) == Array(fkey2).map(&:to_sym)
end
end
end
45 changes: 43 additions & 2 deletions activerecord/test/cases/counter_cache_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
require "models/subscriber"
require "models/subscription"
require "models/book"
require "models/cpk"
require "active_support/core_ext/enumerable"

class CounterCacheTest < ActiveRecord::TestCase
fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers, :people, :friendships, :subscribers, :subscriptions, :books
fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers, :people, :friendships, :subscribers, :subscriptions, :books,
:cpk_orders, :cpk_books

class ::SpecialTopic < ::Topic
has_many :special_replies, foreign_key: "parent_id"
Expand All @@ -42,11 +44,25 @@ class ::SpecialReply < ::Reply
end

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

test "increment counter for cpk model" do
order = Cpk::Order.first
assert_difference -> { order.reload.books_count } do
Cpk::Order.increment_counter(:books_count, order.id)
end
end

test "increment counter for multiple cpk model records" do
order1, order2 = Cpk::Order.first(2)
assert_difference [-> { order1.reload.books_count }, -> { order2.reload.books_count }] do
Cpk::Order.increment_counter(:books_count, [order1.id, order2.id])
end
end

test "decrement counter" do
assert_difference "@topic.reload.replies_count", -1 do
Topic.decrement_counter(:replies_count, @topic.id)
Expand All @@ -59,6 +75,13 @@ class ::SpecialReply < ::Reply
end
end

test "decrement counter for cpk model" do
order = Cpk::Order.first
assert_difference -> { order.reload.books_count }, -1 do
Cpk::Order.decrement_counter(:books_count, order.id)
end
end

test "reset counters" do
# throw the count off by 1
Topic.increment_counter(:replies_count, @topic.id)
Expand Down Expand Up @@ -148,6 +171,17 @@ class ::SpecialReply < ::Reply
end
end

test "reset counters for cpk model" do
order = Cpk::Order.first
# throw the count off by 1
Cpk::Order.increment_counter(:books_count, order.id)

# check that it gets reset
assert_difference -> { order.reload.books_count }, -1 do
Cpk::Order.reset_counters(order.id, :books)
end
end

test "update counter with initial null value" do
category = categories(:general)
assert_equal 2, category.categorizations.count
Expand Down Expand Up @@ -177,6 +211,13 @@ class ::SpecialReply < ::Reply
end
end

test "update counter for decrement for cpk model" do
order = Cpk::Order.first
assert_difference -> { order.reload.books_count }, -3 do
Cpk::Order.update_counters(order.id, books_count: -3)
end
end

test "update other counters on parent destroy" do
david, joanna = dog_lovers(:david, :joanna)
_ = joanna # squelch a warning
Expand Down
2 changes: 1 addition & 1 deletion activerecord/test/models/cpk/book.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class Book < ActiveRecord::Base
attr_accessor :fail_destroy

self.table_name = :cpk_books
belongs_to :order, autosave: true, query_constraints: [:shop_id, :order_id]
belongs_to :order, autosave: true, query_constraints: [:shop_id, :order_id], counter_cache: true
belongs_to :author, class_name: "Cpk::Author"

has_many :chapters, query_constraints: [:author_id, :book_id]
Expand Down
1 change: 1 addition & 0 deletions activerecord/test/schema/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@
create_table :cpk_orders, force: true do |t|
t.integer :shop_id
t.string :status
t.integer :books_count, default: 0
end

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

0 comments on commit ea38048

Please sign in to comment.