Skip to content

Commit 564ab47

Browse files
committed
Merge commit 'mislav/counter_cache'
2 parents 668f7dd + bc84bd1 commit 564ab47

File tree

3 files changed

+95
-63
lines changed

3 files changed

+95
-63
lines changed

activerecord/lib/active_record/counter_cache.rb

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@ module CounterCache
1616
def reset_counters(id, *counters)
1717
object = find(id)
1818
counters.each do |association|
19-
child_class = reflect_on_association(association).klass
20-
counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column
19+
child_class = reflect_on_association(association.to_sym).klass
20+
belongs_name = self.name.demodulize.underscore.to_sym
21+
counter_name = child_class.reflect_on_association(belongs_name).counter_cache_column
2122

22-
connection.update("UPDATE #{quoted_table_name} SET #{connection.quote_column_name(counter_name)} = #{object.send(association).count} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}", "#{name} UPDATE")
23+
self.unscoped.where(arel_table[self.primary_key].eq(object.id)).arel.update({
24+
arel_table[counter_name] => object.send(association).count
25+
})
2326
end
27+
return true
2428
end
2529

2630
# A generic "counter updater" implementation, intended primarily to be
@@ -53,19 +57,13 @@ def reset_counters(id, *counters)
5357
# # SET comment_count = comment_count + 1,
5458
# # WHERE id IN (10, 15)
5559
def update_counters(id, counters)
56-
updates = counters.inject([]) { |list, (counter_name, increment)|
57-
sign = increment < 0 ? "-" : "+"
58-
list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}"
59-
}.join(", ")
60-
61-
if id.is_a?(Array)
62-
ids_list = id.map {|i| quote_value(i)}.join(', ')
63-
condition = "IN (#{ids_list})"
64-
else
65-
condition = "= #{quote_value(id)}"
60+
updates = counters.map do |counter_name, value|
61+
operator = value < 0 ? '-' : '+'
62+
quoted_column = connection.quote_column_name(counter_name)
63+
"#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
6664
end
6765

68-
update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}")
66+
update_all(updates.join(', '), primary_key => id )
6967
end
7068

7169
# Increment a number field by one, usually representing a count.

activerecord/test/cases/base_test.rb

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -708,55 +708,6 @@ def test_boolean_attributes
708708
assert Topic.find(2).approved?
709709
end
710710

711-
def test_increment_counter
712-
Topic.increment_counter("replies_count", 1)
713-
assert_equal 2, Topic.find(1).replies_count
714-
715-
Topic.increment_counter("replies_count", 1)
716-
assert_equal 3, Topic.find(1).replies_count
717-
end
718-
719-
def test_decrement_counter
720-
Topic.decrement_counter("replies_count", 2)
721-
assert_equal(-1, Topic.find(2).replies_count)
722-
723-
Topic.decrement_counter("replies_count", 2)
724-
assert_equal(-2, Topic.find(2).replies_count)
725-
end
726-
727-
def test_reset_counters
728-
assert_equal 1, Topic.find(1).replies_count
729-
730-
Topic.increment_counter("replies_count", 1)
731-
assert_equal 2, Topic.find(1).replies_count
732-
733-
Topic.reset_counters(1, :replies)
734-
assert_equal 1, Topic.find(1).replies_count
735-
end
736-
737-
def test_update_counter
738-
category = categories(:general)
739-
assert_nil category.categorizations_count
740-
assert_equal 2, category.categorizations.count
741-
742-
Category.update_counters(category.id, "categorizations_count" => category.categorizations.count)
743-
category.reload
744-
assert_not_nil category.categorizations_count
745-
assert_equal 2, category.categorizations_count
746-
747-
Category.update_counters(category.id, "categorizations_count" => category.categorizations.count)
748-
category.reload
749-
assert_not_nil category.categorizations_count
750-
assert_equal 4, category.categorizations_count
751-
752-
category_2 = categories(:technology)
753-
count_1, count_2 = (category.categorizations_count || 0), (category_2.categorizations_count || 0)
754-
Category.update_counters([category.id, category_2.id], "categorizations_count" => 2)
755-
category.reload; category_2.reload
756-
assert_equal count_1 + 2, category.categorizations_count
757-
assert_equal count_2 + 2, category_2.categorizations_count
758-
end
759-
760711
def test_update_all
761712
assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'")
762713
assert_equal "bulk updated!", Topic.find(1).content
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
require 'cases/helper'
2+
require 'models/topic'
3+
require 'models/reply'
4+
require 'models/category'
5+
require 'models/categorization'
6+
7+
class CounterCacheTest < ActiveRecord::TestCase
8+
fixtures :topics, :categories, :categorizations
9+
10+
class SpecialTopic < ::Topic
11+
has_many :special_replies, :foreign_key => 'parent_id'
12+
end
13+
14+
class SpecialReply < ::Reply
15+
belongs_to :special_topic, :foreign_key => 'parent_id', :counter_cache => 'replies_count'
16+
end
17+
18+
setup do
19+
@topic = Topic.find(1)
20+
end
21+
22+
test "increment counter" do
23+
assert_difference '@topic.reload.replies_count' do
24+
Topic.increment_counter(:replies_count, @topic.id)
25+
end
26+
end
27+
28+
test "decrement counter" do
29+
assert_difference '@topic.reload.replies_count', -1 do
30+
Topic.decrement_counter(:replies_count, @topic.id)
31+
end
32+
end
33+
34+
test "reset counters" do
35+
# throw the count off by 1
36+
Topic.increment_counter(:replies_count, @topic.id)
37+
38+
# check that it gets reset
39+
assert_difference '@topic.reload.replies_count', -1 do
40+
Topic.reset_counters(@topic.id, :replies)
41+
end
42+
end
43+
44+
test "reset counters with string argument" do
45+
Topic.increment_counter('replies_count', @topic.id)
46+
47+
assert_difference '@topic.reload.replies_count', -1 do
48+
Topic.reset_counters(@topic.id, 'replies')
49+
end
50+
end
51+
52+
test "reset counters with modularized and camelized classnames" do
53+
special = SpecialTopic.create!(:title => 'Special')
54+
SpecialTopic.increment_counter(:replies_count, special.id)
55+
56+
assert_difference 'special.reload.replies_count', -1 do
57+
SpecialTopic.reset_counters(special.id, :special_replies)
58+
end
59+
end
60+
61+
test "update counter with initial null value" do
62+
category = categories(:general)
63+
assert_equal 2, category.categorizations.count
64+
assert_nil category.categorizations_count
65+
66+
Category.update_counters(category.id, :categorizations_count => category.categorizations.count)
67+
assert_equal 2, category.reload.categorizations_count
68+
end
69+
70+
test "update counter for decrement" do
71+
assert_difference '@topic.reload.replies_count', -3 do
72+
Topic.update_counters(@topic.id, :replies_count => -3)
73+
end
74+
end
75+
76+
test "update counters of multiple records" do
77+
t1, t2 = topics(:first, :second)
78+
79+
assert_difference ['t1.reload.replies_count', 't2.reload.replies_count'], 2 do
80+
Topic.update_counters([t1.id, t2.id], :replies_count => 2)
81+
end
82+
end
83+
end

0 commit comments

Comments
 (0)