Skip to content

Commit 33e6051

Browse files
authored
Merge pull request rails#27543 from kamipo/fix_update_counters_of_multiple_records_with_touch
Fix update counters of multiple records with touch: true
2 parents 4bf0447 + 059a476 commit 33e6051

File tree

4 files changed

+50
-41
lines changed

4 files changed

+50
-41
lines changed

activerecord/lib/active_record/counter_cache.rb

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def reset_counters(id, *counters, touch: nil)
4646
counter_name = reflection.counter_cache_column
4747

4848
updates = { counter_name.to_sym => object.send(counter_association).count(:all) }
49-
updates.merge!(touch_updates(object, touch)) if touch
49+
updates.merge!(touch_updates(touch)) if touch
5050

5151
unscoped.where(primary_key => object.id).update_all(updates)
5252
end
@@ -105,8 +105,7 @@ def update_counters(id, counters)
105105
end
106106

107107
if touch
108-
object = find(id)
109-
updates << object.class.send(:sanitize_sql_for_assignment, touch_updates(object, touch))
108+
updates << sanitize_sql_for_assignment(touch_updates(touch))
110109
end
111110

112111
unscoped.where(primary_key => id).update_all updates.join(", ")
@@ -165,9 +164,9 @@ def decrement_counter(counter_name, id, touch: nil)
165164
end
166165

167166
private
168-
def touch_updates(object, touch)
169-
touch = object.send(:timestamp_attributes_for_update_in_model) if touch == true
170-
touch_time = object.send(:current_time_from_proper_timezone)
167+
def touch_updates(touch)
168+
touch = timestamp_attributes_for_update_in_model if touch == true
169+
touch_time = current_time_from_proper_timezone
171170
Array(touch).map { |column| [ column, touch_time ] }.to_h
172171
end
173172
end

activerecord/lib/active_record/timestamp.rb

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,41 @@ def initialize_dup(other) # :nodoc:
5252
clear_timestamp_attributes
5353
end
5454

55+
class_methods do
56+
private
57+
def timestamp_attributes_for_create_in_model
58+
timestamp_attributes_for_create.select { |c| column_names.include?(c) }
59+
end
60+
61+
def timestamp_attributes_for_update_in_model
62+
timestamp_attributes_for_update.select { |c| column_names.include?(c) }
63+
end
64+
65+
def all_timestamp_attributes_in_model
66+
timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
67+
end
68+
69+
def timestamp_attributes_for_create
70+
["created_at", "created_on"]
71+
end
72+
73+
def timestamp_attributes_for_update
74+
["updated_at", "updated_on"]
75+
end
76+
77+
def current_time_from_proper_timezone
78+
default_timezone == :utc ? Time.now.utc : Time.now
79+
end
80+
end
81+
5582
private
5683

5784
def _create_record
5885
if record_timestamps
5986
current_time = current_time_from_proper_timezone
6087

61-
all_timestamp_attributes.each do |column|
62-
if has_attribute?(column) && !attribute_present?(column)
88+
all_timestamp_attributes_in_model.each do |column|
89+
if !attribute_present?(column)
6390
write_attribute(column, current_time)
6491
end
6592
end
@@ -85,41 +112,29 @@ def should_record_timestamps?
85112
end
86113

87114
def timestamp_attributes_for_create_in_model
88-
timestamp_attributes_for_create.select { |c| self.class.column_names.include?(c) }
115+
self.class.send(:timestamp_attributes_for_create_in_model)
89116
end
90117

91118
def timestamp_attributes_for_update_in_model
92-
timestamp_attributes_for_update.select { |c| self.class.column_names.include?(c) }
119+
self.class.send(:timestamp_attributes_for_update_in_model)
93120
end
94121

95122
def all_timestamp_attributes_in_model
96-
timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
97-
end
98-
99-
def timestamp_attributes_for_update
100-
["updated_at", "updated_on"]
101-
end
102-
103-
def timestamp_attributes_for_create
104-
["created_at", "created_on"]
123+
self.class.send(:all_timestamp_attributes_in_model)
105124
end
106125

107-
def all_timestamp_attributes
108-
timestamp_attributes_for_create + timestamp_attributes_for_update
126+
def current_time_from_proper_timezone
127+
self.class.send(:current_time_from_proper_timezone)
109128
end
110129

111-
def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update)
130+
def max_updated_column_timestamp(timestamp_names = self.class.send(:timestamp_attributes_for_update))
112131
timestamp_names
113132
.map { |attr| self[attr] }
114133
.compact
115134
.map(&:to_time)
116135
.max
117136
end
118137

119-
def current_time_from_proper_timezone
120-
self.class.default_timezone == :utc ? Time.now.utc : Time.now
121-
end
122-
123138
# Clear attributes and changed_attributes
124139
def clear_timestamp_attributes
125140
all_timestamp_attributes_in_model.each do |attribute_name|

activerecord/test/cases/counter_cache_test.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,16 @@ class ::SpecialReply < ::Reply
227227
end
228228
end
229229

230+
test "update counters of multiple records with touch: true" do
231+
t1, t2 = topics(:first, :second)
232+
233+
assert_touching t1, :updated_at do
234+
assert_difference ["t1.reload.replies_count", "t2.reload.replies_count"], 2 do
235+
Topic.update_counters([t1.id, t2.id], replies_count: 2, touch: true)
236+
end
237+
end
238+
end
239+
230240
test "update multiple counters with touch: true" do
231241
assert_touching @topic, :updated_at do
232242
Topic.update_counters(@topic.id, replies_count: 2, unique_replies_count: 2, touch: true)

activerecord/test/cases/timestamp_test.rb

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -430,21 +430,6 @@ def test_timestamp_column_values_are_present_in_the_callbacks
430430
assert_not_equal person.born_at, nil
431431
end
432432

433-
def test_timestamp_attributes_for_create
434-
toy = Toy.first
435-
assert_equal ["created_at", "created_on"], toy.send(:timestamp_attributes_for_create)
436-
end
437-
438-
def test_timestamp_attributes_for_update
439-
toy = Toy.first
440-
assert_equal ["updated_at", "updated_on"], toy.send(:timestamp_attributes_for_update)
441-
end
442-
443-
def test_all_timestamp_attributes
444-
toy = Toy.first
445-
assert_equal ["created_at", "created_on", "updated_at", "updated_on"], toy.send(:all_timestamp_attributes)
446-
end
447-
448433
def test_timestamp_attributes_for_create_in_model
449434
toy = Toy.first
450435
assert_equal ["created_at"], toy.send(:timestamp_attributes_for_create_in_model)

0 commit comments

Comments
 (0)