Merge commit 'mislav/counter_cache'
This commit is contained in:
commit
564ab4776c
|
@ -16,11 +16,15 @@ module ActiveRecord
|
||||||
def reset_counters(id, *counters)
|
def reset_counters(id, *counters)
|
||||||
object = find(id)
|
object = find(id)
|
||||||
counters.each do |association|
|
counters.each do |association|
|
||||||
child_class = reflect_on_association(association).klass
|
child_class = reflect_on_association(association.to_sym).klass
|
||||||
counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column
|
belongs_name = self.name.demodulize.underscore.to_sym
|
||||||
|
counter_name = child_class.reflect_on_association(belongs_name).counter_cache_column
|
||||||
|
|
||||||
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")
|
self.unscoped.where(arel_table[self.primary_key].eq(object.id)).arel.update({
|
||||||
|
arel_table[counter_name] => object.send(association).count
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
# A generic "counter updater" implementation, intended primarily to be
|
# A generic "counter updater" implementation, intended primarily to be
|
||||||
|
@ -53,19 +57,13 @@ module ActiveRecord
|
||||||
# # SET comment_count = comment_count + 1,
|
# # SET comment_count = comment_count + 1,
|
||||||
# # WHERE id IN (10, 15)
|
# # WHERE id IN (10, 15)
|
||||||
def update_counters(id, counters)
|
def update_counters(id, counters)
|
||||||
updates = counters.inject([]) { |list, (counter_name, increment)|
|
updates = counters.map do |counter_name, value|
|
||||||
sign = increment < 0 ? "-" : "+"
|
operator = value < 0 ? '-' : '+'
|
||||||
list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}"
|
quoted_column = connection.quote_column_name(counter_name)
|
||||||
}.join(", ")
|
"#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
|
||||||
|
|
||||||
if id.is_a?(Array)
|
|
||||||
ids_list = id.map {|i| quote_value(i)}.join(', ')
|
|
||||||
condition = "IN (#{ids_list})"
|
|
||||||
else
|
|
||||||
condition = "= #{quote_value(id)}"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}")
|
update_all(updates.join(', '), primary_key => id )
|
||||||
end
|
end
|
||||||
|
|
||||||
# Increment a number field by one, usually representing a count.
|
# Increment a number field by one, usually representing a count.
|
||||||
|
|
|
@ -708,55 +708,6 @@ class BasicsTest < ActiveRecord::TestCase
|
||||||
assert Topic.find(2).approved?
|
assert Topic.find(2).approved?
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_increment_counter
|
|
||||||
Topic.increment_counter("replies_count", 1)
|
|
||||||
assert_equal 2, Topic.find(1).replies_count
|
|
||||||
|
|
||||||
Topic.increment_counter("replies_count", 1)
|
|
||||||
assert_equal 3, Topic.find(1).replies_count
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_decrement_counter
|
|
||||||
Topic.decrement_counter("replies_count", 2)
|
|
||||||
assert_equal(-1, Topic.find(2).replies_count)
|
|
||||||
|
|
||||||
Topic.decrement_counter("replies_count", 2)
|
|
||||||
assert_equal(-2, Topic.find(2).replies_count)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_reset_counters
|
|
||||||
assert_equal 1, Topic.find(1).replies_count
|
|
||||||
|
|
||||||
Topic.increment_counter("replies_count", 1)
|
|
||||||
assert_equal 2, Topic.find(1).replies_count
|
|
||||||
|
|
||||||
Topic.reset_counters(1, :replies)
|
|
||||||
assert_equal 1, Topic.find(1).replies_count
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_update_counter
|
|
||||||
category = categories(:general)
|
|
||||||
assert_nil category.categorizations_count
|
|
||||||
assert_equal 2, category.categorizations.count
|
|
||||||
|
|
||||||
Category.update_counters(category.id, "categorizations_count" => category.categorizations.count)
|
|
||||||
category.reload
|
|
||||||
assert_not_nil category.categorizations_count
|
|
||||||
assert_equal 2, category.categorizations_count
|
|
||||||
|
|
||||||
Category.update_counters(category.id, "categorizations_count" => category.categorizations.count)
|
|
||||||
category.reload
|
|
||||||
assert_not_nil category.categorizations_count
|
|
||||||
assert_equal 4, category.categorizations_count
|
|
||||||
|
|
||||||
category_2 = categories(:technology)
|
|
||||||
count_1, count_2 = (category.categorizations_count || 0), (category_2.categorizations_count || 0)
|
|
||||||
Category.update_counters([category.id, category_2.id], "categorizations_count" => 2)
|
|
||||||
category.reload; category_2.reload
|
|
||||||
assert_equal count_1 + 2, category.categorizations_count
|
|
||||||
assert_equal count_2 + 2, category_2.categorizations_count
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_update_all
|
def test_update_all
|
||||||
assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'")
|
assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'")
|
||||||
assert_equal "bulk updated!", Topic.find(1).content
|
assert_equal "bulk updated!", Topic.find(1).content
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
require 'cases/helper'
|
||||||
|
require 'models/topic'
|
||||||
|
require 'models/reply'
|
||||||
|
require 'models/category'
|
||||||
|
require 'models/categorization'
|
||||||
|
|
||||||
|
class CounterCacheTest < ActiveRecord::TestCase
|
||||||
|
fixtures :topics, :categories, :categorizations
|
||||||
|
|
||||||
|
class SpecialTopic < ::Topic
|
||||||
|
has_many :special_replies, :foreign_key => 'parent_id'
|
||||||
|
end
|
||||||
|
|
||||||
|
class SpecialReply < ::Reply
|
||||||
|
belongs_to :special_topic, :foreign_key => 'parent_id', :counter_cache => 'replies_count'
|
||||||
|
end
|
||||||
|
|
||||||
|
setup do
|
||||||
|
@topic = Topic.find(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "increment counter" do
|
||||||
|
assert_difference '@topic.reload.replies_count' do
|
||||||
|
Topic.increment_counter(:replies_count, @topic.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "decrement counter" do
|
||||||
|
assert_difference '@topic.reload.replies_count', -1 do
|
||||||
|
Topic.decrement_counter(:replies_count, @topic.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "reset counters" do
|
||||||
|
# throw the count off by 1
|
||||||
|
Topic.increment_counter(:replies_count, @topic.id)
|
||||||
|
|
||||||
|
# check that it gets reset
|
||||||
|
assert_difference '@topic.reload.replies_count', -1 do
|
||||||
|
Topic.reset_counters(@topic.id, :replies)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "reset counters with string argument" do
|
||||||
|
Topic.increment_counter('replies_count', @topic.id)
|
||||||
|
|
||||||
|
assert_difference '@topic.reload.replies_count', -1 do
|
||||||
|
Topic.reset_counters(@topic.id, 'replies')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "reset counters with modularized and camelized classnames" do
|
||||||
|
special = SpecialTopic.create!(:title => 'Special')
|
||||||
|
SpecialTopic.increment_counter(:replies_count, special.id)
|
||||||
|
|
||||||
|
assert_difference 'special.reload.replies_count', -1 do
|
||||||
|
SpecialTopic.reset_counters(special.id, :special_replies)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update counter with initial null value" do
|
||||||
|
category = categories(:general)
|
||||||
|
assert_equal 2, category.categorizations.count
|
||||||
|
assert_nil category.categorizations_count
|
||||||
|
|
||||||
|
Category.update_counters(category.id, :categorizations_count => category.categorizations.count)
|
||||||
|
assert_equal 2, category.reload.categorizations_count
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update counter for decrement" do
|
||||||
|
assert_difference '@topic.reload.replies_count', -3 do
|
||||||
|
Topic.update_counters(@topic.id, :replies_count => -3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update counters of multiple records" do
|
||||||
|
t1, t2 = topics(:first, :second)
|
||||||
|
|
||||||
|
assert_difference ['t1.reload.replies_count', 't2.reload.replies_count'], 2 do
|
||||||
|
Topic.update_counters([t1.id, t2.id], :replies_count => 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue