counter_culture 3.13.1 → 3.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9731db7c2d482e50fef56c6bac5e7c9e1697ac1f2f4f20ff9bb819088d77e843
4
- data.tar.gz: 1fd97cbd3f4642eb8ff9ae375e1d50d244f1e0f034fe2fcd72e0593cd03641f5
3
+ metadata.gz: 5c24eb92adf8139552bb89de6d5f39f41f1fc46b89a9b23f66309385ab31cb6f
4
+ data.tar.gz: b3ec3a46a8bcaa1520b193a47909d3233c0193958849caac5cbe580acc621316
5
5
  SHA512:
6
- metadata.gz: f38aec03d1ad8259e26f26a6b51301e01f34a3ccc88c11086b0bc26eeecbfbd423c6e58913e1315918eb50368c3a07b50d22f5f3463ed673749e5ebc1df237ba
7
- data.tar.gz: 6f277537ab3465cf2e3ec6fdef625f611ba55cfc7285c438a6e0e8027dc634df84356004ec6c2569adb365ef95652cfb2f008b958c08948e2598dabce93015a1
6
+ metadata.gz: fd20102f237e5a2a3757567339afb5c60d5ef87e6d97c411a2e51a365749e1e45482a63553d631bf8337cbce997d657c708a34a7bf8a4a7d84b87dfd77d241b8
7
+ data.tar.gz: 533d50714673ce1e209db0e7ebbf29717668783014d3753d15306f6e0705c042a50821602ca948ed1444ec5adc76799c059e5735c5db0f416b6664f4fc414459
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 3.13.3 (June 25, 2026)
2
+
3
+ Bugfixes:
4
+ - Fix `counter_culture_fix_counts` raising a SQL syntax error for counter caches stored in reserved-word columns (e.g. `order`) by quoting identifiers in the reconciler (#434)
5
+
6
+ ## 3.13.2 (June 25, 2026)
7
+
8
+ Bugfixes:
9
+ - Fix multi-level counter caches not being updated in memory when creating or destroying records (#432)
10
+
1
11
  ## 3.13.1 (April 20, 2026)
2
12
 
3
13
  Bugfixes:
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # counter_culture [![Build Status](https://circleci.com/gh/magnusvk/counter_culture/tree/master.svg?style=svg)](https://circleci.com/gh/magnusvk/counter_culture/tree/master)
1
+ # counter_culture [![Build Status](https://circleci.com/gh/magnusvk/counter_culture/tree/main.svg?style=svg)](https://circleci.com/gh/magnusvk/counter_culture/tree/main)
2
2
 
3
3
  Turbo-charged counter caches for your Rails app. Huge improvements over the Rails standard counter caches:
4
4
 
@@ -410,9 +410,7 @@ module CounterCulture
410
410
 
411
411
  # update associated object counter attribute
412
412
  def assign_to_associated_object(obj, relation, change_counter_column, operator, delta_magnitude)
413
- association_name = relation_reflect(relation).name
414
-
415
- association_object = association_object_for_assign(obj, association_name)
413
+ association_object = association_object_for_assign(obj, relation)
416
414
  return if association_object.blank? || association_object.frozen?
417
415
 
418
416
  association_object.assign_attributes(
@@ -422,21 +420,23 @@ module CounterCulture
422
420
  association_object_clear_change(association_object, change_counter_column)
423
421
  end
424
422
 
425
- def association_object_for_assign(obj, association_name)
426
- if obj.class.reflect_on_all_associations.
427
- find { |assoc| assoc.name.to_sym == association_name.to_sym }.
428
- blank?
429
- # This means that this isn't defined as an association; either it
430
- # doesn't exist at all, or it uses delegate. In either case, we can't
431
- # update the count this way.
432
- return
433
- end
434
- if !obj.association(association_name).loaded?
435
- # If the association isn't loaded, no need to update the count
436
- return
423
+ def association_object_for_assign(obj, relation)
424
+ relation = relation.is_a?(Enumerable) ? relation.dup : [relation]
425
+
426
+ while !obj.nil? && relation.size > 0
427
+ cur_relation = relation.shift
428
+ # This isn't an association (e.g. it uses delegate), so we can't update the count this way
429
+ return nil if obj.class.reflect_on_association(cur_relation).nil?
430
+
431
+ if !obj.association(cur_relation).loaded?
432
+ # If the association isn't loaded, no need to update the count
433
+ return nil
434
+ end
435
+
436
+ obj = obj.public_send(cur_relation)
437
437
  end
438
438
 
439
- obj.public_send(association_name)
439
+ obj
440
440
  end
441
441
 
442
442
  def association_object_clear_change(association_object, change_counter_column)
@@ -108,13 +108,15 @@ module CounterCulture
108
108
  next unless column_name
109
109
 
110
110
  relation_class_table_name = quote_table_name(relation_class.table_name)
111
+ # quote the cache column so reserved-word/quoting-sensitive names work
112
+ quoted_column_name = quote_column_name(column_name)
111
113
 
112
114
  # select join column and count (from above) as well as cache column ('column_name') for later comparison
113
115
  counts_query = scope.select(
114
116
  "#{primary_key_select}, " \
115
117
  "#{association_primary_key_select}, " \
116
118
  "#{count_select} AS count, " \
117
- "MAX(#{relation_class_table_name}.#{column_name}) AS #{column_name}"
119
+ "MAX(#{relation_class_table_name}.#{quoted_column_name}) AS #{quoted_column_name}"
118
120
  )
119
121
 
120
122
  # we need to join together tables until we get back to the table this class itself lives in
@@ -155,9 +157,20 @@ module CounterCulture
155
157
 
156
158
  track_change(record, column_name, count)
157
159
 
158
- updates = []
159
- # this updates the actual counter
160
- updates << "#{column_name} = #{count}"
160
+ # Build a Hash of column => value updates and let Rails (Arel) qualify
161
+ # and escape the column names and cast/quote the values, consistent with
162
+ # the Arel-based updates used in Counter (see #425). This updates the
163
+ # actual counter.
164
+ updates = { column_name => count }
165
+
166
+ # Set lock_version = lock_version (no-op) to skip Rails auto-increment,
167
+ # just as Counter does (see #429); a Hash update_all would otherwise
168
+ # bump the optimistic locking column.
169
+ if relation_class.locking_enabled?
170
+ lock_column = relation_class.locking_column
171
+ updates[lock_column] = relation_class.arel_table[lock_column]
172
+ end
173
+
161
174
  # and here we update the timestamp, if so desired
162
175
  if options[:touch]
163
176
  current_time = record.send(:current_time_from_proper_timezone)
@@ -168,13 +181,13 @@ module CounterCulture
168
181
  timestamp_columns << options[:touch]
169
182
  end
170
183
  timestamp_columns.each do |timestamp_column|
171
- updates << "#{timestamp_column} = '#{current_time.to_formatted_s(:db)}'"
184
+ updates[timestamp_column] = current_time
172
185
  end
173
186
  end
174
187
 
175
188
  with_writing_db_connection do
176
189
  conditions = Array.wrap(relation_class.primary_key).map { |key| [key, record.send(key)] }.to_h
177
- relation_class.where(conditions).distinct(false).update_all(updates.join(', '))
190
+ relation_class.where(conditions).distinct(false).update_all(updates)
178
191
  end
179
192
  end
180
193
  end
@@ -351,6 +364,12 @@ module CounterCulture
351
364
  end
352
365
  end
353
366
 
367
+ def quote_column_name(column_name)
368
+ @connection_handler.call do |connection|
369
+ connection.quote_column_name(column_name)
370
+ end
371
+ end
372
+
354
373
  def parameterize(string)
355
374
  if ACTIVE_RECORD_VERSION < Gem::Version.new("5.0")
356
375
  string.parameterize('_')
@@ -1,3 +1,3 @@
1
1
  module CounterCulture
2
- VERSION = '3.13.1'.freeze
2
+ VERSION = '3.13.3'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: counter_culture
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.13.1
4
+ version: 3.13.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Magnus von Koeller
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-20 00:00:00.000000000 Z
11
+ date: 2026-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord