counter_culture 3.8.2 → 3.11.2

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: 13f043a04b7cca0b364e7692c19425b049b45b4e965ed5caac0dbac2f91622c1
4
- data.tar.gz: 50dfedf030e3c3823829aedc5e3b2a08fe65b63b50fc439d229afb5ab3fa5d5d
3
+ metadata.gz: c568fc84225757b834a008eb60dbadfe5dea148078c855fe76a2b5711b0c5b1e
4
+ data.tar.gz: 3abc94f197b60fb6778eadb98ad402e6b961155c492970111b85bbf1bd171237
5
5
  SHA512:
6
- metadata.gz: 604bd858a6660d3e83b74b339a2675366b5d7908c87c37241ed9eb9a2b245ddb4d02b19d6782d3c30df793cd895be45e22455e34f9aca88e5fee462f472c2a48
7
- data.tar.gz: a5828bb5f0bd66b6717401b47f8494687a371546c6f3df89c93dd9a524a055e074aeffc3fa6c02e0b2e2cd25c3e706e76c2ef654e22a0b3f48cfb3d0c0c401b7
6
+ metadata.gz: adfd25e90ca300e1d6bc19296b343a8fbd9a272076e48591dddc513ffa4173dff10ef2d26cec6bd08bdfbc372ccf1b94e97cee7d7827205523efe0278d1bffea
7
+ data.tar.gz: 733126919913da347057de9c81cbbdf7b4b45e9ea1bbbecad12586a06432801e37b1c8833078cc93ce11fdc0ad3b201c5484822dcebc708f970d2106c022d35b
data/.circleci/config.yml CHANGED
@@ -49,7 +49,7 @@ workflows:
49
49
  matrix:
50
50
  parameters:
51
51
  ruby-version: ["2.6", "2.7", "3.0", "3.1", "3.2", "3.3"]
52
- rails-version: ["5.2", "6.0", "6.1", "7.0", "7.1", "7.2"]
52
+ rails-version: ["5.2", "6.0", "6.1", "7.0", "7.1", "7.2", "8.0"]
53
53
  database: ["postgresql", "sqlite3", "mysql2"]
54
54
  exclude:
55
55
  - ruby-version: "3.0"
@@ -133,3 +133,39 @@ workflows:
133
133
  - ruby-version: "3.0"
134
134
  rails-version: "7.2"
135
135
  database: "mysql2"
136
+ - ruby-version: "2.6"
137
+ rails-version: "8.0"
138
+ database: "postgresql"
139
+ - ruby-version: "2.6"
140
+ rails-version: "8.0"
141
+ database: "sqlite3"
142
+ - ruby-version: "2.6"
143
+ rails-version: "8.0"
144
+ database: "mysql2"
145
+ - ruby-version: "2.7"
146
+ rails-version: "8.0"
147
+ database: "postgresql"
148
+ - ruby-version: "2.7"
149
+ rails-version: "8.0"
150
+ database: "sqlite3"
151
+ - ruby-version: "2.7"
152
+ rails-version: "8.0"
153
+ database: "mysql2"
154
+ - ruby-version: "3.0"
155
+ rails-version: "8.0"
156
+ database: "postgresql"
157
+ - ruby-version: "3.0"
158
+ rails-version: "8.0"
159
+ database: "sqlite3"
160
+ - ruby-version: "3.0"
161
+ rails-version: "8.0"
162
+ database: "mysql2"
163
+ - ruby-version: "3.1"
164
+ rails-version: "8.0"
165
+ database: "postgresql"
166
+ - ruby-version: "3.1"
167
+ rails-version: "8.0"
168
+ database: "sqlite3"
169
+ - ruby-version: "3.1"
170
+ rails-version: "8.0"
171
+ database: "mysql2"
data/Appraisals CHANGED
@@ -5,6 +5,7 @@
5
5
  7.0
6
6
  7.1
7
7
  7.2
8
+ 8.0
8
9
  ].each do |rails_version|
9
10
  appraise "rails-#{rails_version}" do
10
11
  gem 'activerecord', "~> #{rails_version}.0"
data/CHANGELOG.md CHANGED
@@ -1,3 +1,38 @@
1
+ ## 3.11.2 (July 10, 2025)
2
+
3
+ Bugfixes:
4
+ - Fix regression for foreign_key_values by adding support for composite primary keys (#417)
5
+
6
+ ## 3.11.1 (June 27, 2025)
7
+
8
+ Bugfixes:
9
+ - Don't attempt to modify frozen association object (#416)
10
+
11
+ ## 3.11.0 (June 26, 2025)
12
+
13
+ New features:
14
+ - Support for composite primary keys in Rails v7.2+ (#413)
15
+
16
+ ## 3.10.2 (June 24, 2025)
17
+
18
+ Bugfixes:
19
+ - Fix incorrect in-memory counter updates on `belongs_to` association (#415)
20
+
21
+ ## 3.10.1 (April 30, 2025)
22
+
23
+ Bugfixes:
24
+ - Fix issue when using `delegate` instead of `has_many :through` (#411)
25
+
26
+ ## 3.10.0 (April 15, 2025)
27
+
28
+ New features:
29
+ - Update counter cache values without requiring a reload (#407)
30
+
31
+ ## 3.9.0 (March 20, 2025)
32
+
33
+ New features:
34
+ - Add global configuration for using a read replica (#405)
35
+
1
36
  ## 3.8.2 (October 21, 2024)
2
37
 
3
38
  Bugfixes:
data/README.md CHANGED
@@ -7,7 +7,7 @@ Turbo-charged counter caches for your Rails app. Huge improvements over the Rail
7
7
  * Supports dynamic column names, making it possible to split up the counter cache for different types of objects
8
8
  * Can keep a running count, or a running total
9
9
 
10
- Tested against Ruby 2.6, 2.7, 3.0, 3.1, 3.2 and 3.3 and against the latest patch releases of Rails 5.2, 6.0, 6.1, 7.0 and 7.1.
10
+ Tested against Ruby 2.6, 2.7, 3.0, 3.1, 3.2 and 3.3 and against the latest patch releases of Rails 5.2, 6.0, 6.1, 7.0, 7.1, 7.2 and 8.0.
11
11
 
12
12
  Please note that -- unlike Rails' built-in counter-caches -- counter_culture does not currently change the behavior of the `.size` method on ActiveRecord associations. If you want to avoid a database query and read the cached value, please use the attribute name containing the counter cache directly.
13
13
 
@@ -232,7 +232,7 @@ The ```:delta_column``` option supports all numeric column types, not just ```:i
232
232
  class Product < ActiveRecord::Base
233
233
  belongs_to :category
234
234
  counter_culture :category, foreign_key_values:
235
- proc {|category_id| [category_id, Category.find_by_id(category_id).try(:parent_category).try(:id)] }
235
+ proc {|category_id| [category_id, Category.find_by(id: category_id)&.parent_category&.id] }
236
236
  end
237
237
 
238
238
  class Category < ActiveRecord::Base
@@ -618,6 +618,39 @@ Image.counter_culture_fix_counts(polymorphic_classes: Product)
618
618
  Image.counter_culture_fix_counts(polymorphic_classes: [Product, Employee])
619
619
  ```
620
620
 
621
+ ## Using Read Replicas
622
+
623
+ When using `counter_culture_fix_counts`, you can configure counter_culture to use read replicas for counting operations, which can help reduce load on your primary database. Write operations will still use the primary database.
624
+
625
+ ### Global Configuration
626
+
627
+ You can enable read replica support globally:
628
+
629
+ ```ruby
630
+ # config/initializers/counter_culture.rb
631
+ CounterCulture.configure do |config|
632
+ config.use_read_replica = true
633
+ end
634
+ ```
635
+
636
+ ### Per-Call Configuration
637
+
638
+ You can also specify read replica usage per call (this takes precedence over global configuration):
639
+
640
+ ```ruby
641
+ Model.counter_culture_fix_counts(
642
+ db_connection_builder: proc { |reading, block|
643
+ if reading
644
+ ApplicationRecord.connected_to(role: :reading, &block)
645
+ else
646
+ ApplicationRecord.connected_to(role: :writing, &block)
647
+ end
648
+ }
649
+ )
650
+ ```
651
+
652
+ Note: This feature requires Rails 7.1 or higher for full read replica support. On older versions, it will fall back to using the primary database.
653
+
621
654
  ## Contributing to counter_culture
622
655
 
623
656
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 8.0.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,38 @@
1
+ module CounterCulture
2
+ def self.configuration
3
+ @configuration ||= Configuration.new
4
+ end
5
+
6
+ def self.configure
7
+ yield(configuration)
8
+ end
9
+
10
+ def self.reset_configuration
11
+ @configuration = Configuration.new
12
+ end
13
+
14
+ def self.supports_composite_keys?
15
+ Gem::Requirement.new('>= 7.2.0').satisfied_by?(ActiveRecord.version)
16
+ end
17
+
18
+ class Configuration
19
+ attr_reader :use_read_replica
20
+
21
+ def initialize
22
+ @use_read_replica = false
23
+ end
24
+
25
+ def use_read_replica=(value)
26
+ if value && !rails_supports_read_replica?
27
+ raise "Counter Culture's read replica support requires Rails 7.1 or higher"
28
+ end
29
+ @use_read_replica = value
30
+ end
31
+
32
+ private
33
+
34
+ def rails_supports_read_replica?
35
+ Gem::Requirement.new('>= 7.1.0').satisfied_by?(ActiveRecord.version)
36
+ end
37
+ end
38
+ end
@@ -108,12 +108,23 @@ module CounterCulture
108
108
 
109
109
  primary_key = relation_primary_key(relation, source: obj, was: options[:was])
110
110
 
111
+ if foreign_key_values && id_to_change.is_a?(Enumerable) && id_to_change.count > 1 &&
112
+ Array.wrap(primary_key).count == 1
113
+ # To keep this feature backwards compatible, we need to accommodate a case where
114
+ # `foreign_key_values` returns an array, but we're _not_ using composite primary
115
+ # keys. In that case, we need to wrap the `id_to_change` in one more array to keep
116
+ # it compatible with the `.zip` call in `primary_key_conditions`.
117
+ id_to_change = [id_to_change]
118
+ end
119
+
111
120
  if Thread.current[:aggregate_counter_updates]
112
121
  Thread.current[:primary_key_map][klass] ||= primary_key
113
122
  end
114
123
 
115
124
  if @with_papertrail
116
- instance = klass.where(primary_key => id_to_change).first
125
+ conditions = primary_key_conditions(primary_key, id_to_change)
126
+ instance = klass.where(conditions).first
127
+
117
128
  if instance
118
129
  if instance.paper_trail.respond_to?(:save_with_version)
119
130
  # touch_with_version is deprecated starting in PaperTrail 9.0.0
@@ -137,7 +148,11 @@ module CounterCulture
137
148
 
138
149
  unless Thread.current[:aggregate_counter_updates]
139
150
  execute_now_or_after_commit(obj) do
140
- klass.where(primary_key => id_to_change).update_all updates.join(', ')
151
+ conditions = primary_key_conditions(primary_key, id_to_change)
152
+ klass.where(conditions).update_all updates.join(', ')
153
+ unless options[:was]
154
+ assign_to_associated_object(obj, relation, change_counter_column, operator, delta_magnitude)
155
+ end
141
156
  end
142
157
  end
143
158
  end
@@ -171,7 +186,7 @@ module CounterCulture
171
186
 
172
187
  # the string to pass to order() in order to sort by primary key
173
188
  def full_primary_key(klass)
174
- "#{klass.quoted_table_name}.#{klass.quoted_primary_key}"
189
+ Array.wrap(klass.quoted_primary_key).map { |pk| "#{klass.quoted_table_name}.#{pk}" }.join(', ')
175
190
  end
176
191
 
177
192
  # gets the value of the foreign key on the given relation
@@ -185,23 +200,24 @@ module CounterCulture
185
200
  original_relation = relation
186
201
  relation = relation.is_a?(Enumerable) ? relation.dup : [relation]
187
202
 
188
- if was
203
+ value = if was
189
204
  first = relation.shift
190
205
  foreign_key_value = attribute_was(obj, relation_foreign_key(first))
191
206
  klass = relation_klass(first, source: obj, was: was)
192
207
  if foreign_key_value
193
- value = klass.where(
194
- "#{klass.table_name}.#{relation_primary_key(first, source: obj, was: was)} = ?",
195
- foreign_key_value).first
208
+ primary_key = relation_primary_key(first, source: obj, was: was)
209
+ conditions = primary_key_conditions(primary_key, foreign_key_value)
210
+ klass.where(conditions).first
196
211
  end
197
212
  else
198
- value = obj
213
+ obj
199
214
  end
200
215
  while !value.nil? && relation.size > 0
201
216
  value = value.send(relation.shift)
202
217
  end
203
218
 
204
- return value.try(relation_primary_key(original_relation, source: obj, was: was).try(:to_sym))
219
+ primary_key = relation_primary_key(original_relation, source: obj, was: was)
220
+ Array.wrap(primary_key).map { |pk| value.try(pk&.to_sym) }.compact.presence
205
221
  end
206
222
 
207
223
  # gets the reflect object on the given relation
@@ -307,6 +323,7 @@ module CounterCulture
307
323
  return reflect.options[:primary_key] if reflect.options.key?(:primary_key)
308
324
  return relation_klass(relation, source: source, was: was).try(:primary_key)
309
325
  end
326
+
310
327
  reflect.association_primary_key(klass)
311
328
  end
312
329
 
@@ -365,6 +382,49 @@ module CounterCulture
365
382
  obj.public_send("#{attr}#{changes_method}")
366
383
  end
367
384
 
385
+ # update associated object counter attribute
386
+ def assign_to_associated_object(obj, relation, change_counter_column, operator, delta_magnitude)
387
+ association_name = relation_reflect(relation).name
388
+
389
+ association_object = association_object_for_assign(obj, association_name)
390
+ return if association_object.blank? || association_object.frozen?
391
+
392
+ association_object.assign_attributes(
393
+ change_counter_column =>
394
+ association_object_new_counter(association_object, change_counter_column, operator, delta_magnitude)
395
+ )
396
+ association_object_clear_change(association_object, change_counter_column)
397
+ end
398
+
399
+ def association_object_for_assign(obj, association_name)
400
+ if obj.class.reflect_on_all_associations.
401
+ find { |assoc| assoc.name.to_sym == association_name.to_sym }.
402
+ blank?
403
+ # This means that this isn't defined as an association; either it
404
+ # doesn't exist at all, or it uses delegate. In either case, we can't
405
+ # update the count this way.
406
+ return
407
+ end
408
+ if !obj.association(association_name).loaded?
409
+ # If the association isn't loaded, no need to update the count
410
+ return
411
+ end
412
+
413
+ obj.public_send(association_name)
414
+ end
415
+
416
+ def association_object_clear_change(association_object, change_counter_column)
417
+ if ACTIVE_RECORD_VERSION >= Gem::Version.new("6.1.0")
418
+ association_object.public_send(:"clear_#{change_counter_column}_change")
419
+ else
420
+ association_object.send(:clear_attribute_change, change_counter_column)
421
+ end
422
+ end
423
+
424
+ def association_object_new_counter(association_object, change_counter_column, operator, delta_magnitude)
425
+ (association_object.public_send(change_counter_column) || 0).public_send(operator, delta_magnitude)
426
+ end
427
+
368
428
  def assemble_money_counter_update(klass, id_to_change, quoted_column, operator, delta_magnitude)
369
429
  counter_update_snippet(
370
430
  "#{quoted_column} = COALESCE(CAST(#{quoted_column} as NUMERIC), 0)",
@@ -395,6 +455,12 @@ module CounterCulture
395
455
  end
396
456
  end
397
457
 
458
+ def primary_key_conditions(primary_key, fk_value)
459
+ Array.wrap(primary_key)
460
+ .zip(Array.wrap(fk_value))
461
+ .to_h
462
+ end
463
+
398
464
  def counter_update_snippet(update, klass, id_to_change, operator, delta_magnitude)
399
465
  if Thread.current[:aggregate_counter_updates]
400
466
  remember_counter_update(
@@ -66,6 +66,7 @@ module CounterCulture
66
66
  @counter, @options, = counter, options
67
67
  @relation_class = relation_class
68
68
  @changes_holder = changes_holder
69
+ @connection_handler = WithConnection.new(relation_class)
69
70
  end
70
71
 
71
72
  def perform
@@ -110,8 +111,8 @@ module CounterCulture
110
111
 
111
112
  # select join column and count (from above) as well as cache column ('column_name') for later comparison
112
113
  counts_query = scope.select(
113
- "#{relation_class_table_name}.#{relation_class.primary_key}, " \
114
- "#{relation_class_table_name}.#{relation_reflect(relation).association_primary_key(relation_class)}, " \
114
+ "#{primary_key_select}, " \
115
+ "#{association_primary_key_select}, " \
115
116
  "#{count_select} AS count, " \
116
117
  "MAX(#{relation_class_table_name}.#{column_name}) AS #{column_name}"
117
118
  )
@@ -172,7 +173,8 @@ module CounterCulture
172
173
  end
173
174
 
174
175
  with_writing_db_connection do
175
- relation_class.where(relation_class.primary_key => record.send(relation_class.primary_key)).update_all(updates.join(', '))
176
+ conditions = Array.wrap(relation_class.primary_key).map { |key| [key, record.send(key)] }.to_h
177
+ relation_class.where(conditions).update_all(updates.join(', '))
176
178
  end
177
179
  end
178
180
  end
@@ -198,11 +200,12 @@ module CounterCulture
198
200
  def track_change(record, column_name, count)
199
201
  @changes_holder << {
200
202
  :entity => relation_class.name,
201
- relation_class.primary_key.to_sym => record.send(relation_class.primary_key),
202
203
  :what => column_name,
203
204
  :wrong => record.send(column_name),
204
205
  :right => count
205
- }
206
+ }.tap do |h|
207
+ Array.wrap(relation_class.primary_key).each { |pk| h[pk.to_sym] = record.send(pk) }
208
+ end
206
209
  end
207
210
 
208
211
  def count_select
@@ -216,10 +219,23 @@ module CounterCulture
216
219
  @count_select = "SUM(COALESCE(#{self_table_name}.#{delta_column}, 0))"
217
220
  end
218
221
  else
219
- @count_select = "COUNT(#{self_table_name}.#{model.primary_key})*#{delta_magnitude}"
222
+ primary_key = Array.wrap(model.primary_key).first
223
+ count_column = "#{self_table_name}.#{primary_key}"
224
+ @count_select = "COUNT(#{count_column})*#{delta_magnitude}"
220
225
  end
221
226
  end
222
227
 
228
+ def primary_key_select
229
+ relation_class_table_name = quote_table_name(relation_class.table_name)
230
+ Array.wrap(relation_class.primary_key).map { |pk| "#{relation_class_table_name}.#{pk}" }.join(', ')
231
+ end
232
+
233
+ def association_primary_key_select
234
+ relation_class_table_name = quote_table_name(relation_class.table_name)
235
+ association_primary_key = relation_reflect(relation).association_primary_key(relation_class)
236
+ Array.wrap(association_primary_key).map { |apk| "#{relation_class_table_name}.#{apk}" }.join(', ')
237
+ end
238
+
223
239
  def self_table_name
224
240
  return @self_table_name if @self_table_name
225
241
 
@@ -270,8 +286,17 @@ module CounterCulture
270
286
  [target_table_key, source_table_key]
271
287
  end
272
288
 
289
+ source_table_key = Array.wrap(source_table_key)
290
+ target_table_key = Array.wrap(target_table_key)
291
+
292
+ join_conditions =
293
+ source_table_key
294
+ .zip(target_table_key).map do |source_key, target_key|
295
+ "#{source_table}.#{source_key} = #{target_table_alias}.#{target_key}"
296
+ end.join(' AND ')
273
297
  joins_sql = "LEFT JOIN #{target_table} AS #{target_table_alias} "\
274
- "ON #{source_table}.#{source_table_key} = #{target_table_alias}.#{target_table_key}"
298
+ "ON #{join_conditions}"
299
+
275
300
  # adds 'type' condition to JOIN clause if the current model is a
276
301
  # child in a Single Table Inheritance
277
302
  if reflect.active_record.column_names.include?('type') &&
@@ -288,7 +313,9 @@ module CounterCulture
288
313
  # conditions must be applied to the join on which we are counting
289
314
  if where
290
315
  if where.respond_to?(:to_sql)
291
- joins_sql += " AND #{target_table_alias}.#{model.primary_key} IN (#{where.select(model.primary_key).to_sql})"
316
+ model_primary_key = Array.wrap(model.primary_key)
317
+ where_select = model_primary_key.map { |pk| "#{model.table_name}.#{pk}" }.join(', ')
318
+ joins_sql += " AND (#{target_table_alias}.#{model_primary_key.first}) IN (#{where.select(where_select).to_sql})"
292
319
  else
293
320
  joins_sql += " AND (#{model.send(:sanitize_sql_for_conditions, where)})"
294
321
  end
@@ -314,7 +341,7 @@ module CounterCulture
314
341
  # using Postgres with schema-namespaced tables. But then it's required,
315
342
  # and otherwise it's just a no-op, so why not do it?
316
343
  def quote_table_name(table_name)
317
- WithConnection.new(relation_class).call do |connection|
344
+ @connection_handler.call do |connection|
318
345
  connection.quote_table_name(table_name)
319
346
  end
320
347
  end
@@ -331,7 +358,9 @@ module CounterCulture
331
358
  if builder = options[:db_connection_builder]
332
359
  builder.call(true, block)
333
360
  else
334
- yield
361
+ @connection_handler.call(reading: true) do |_conn|
362
+ yield
363
+ end
335
364
  end
336
365
  end
337
366
 
@@ -339,7 +368,9 @@ module CounterCulture
339
368
  if builder = options[:db_connection_builder]
340
369
  builder.call(false, block)
341
370
  else
342
- yield
371
+ @connection_handler.call(reading: false) do |_conn|
372
+ yield
373
+ end
343
374
  end
344
375
  end
345
376
  end
@@ -1,3 +1,3 @@
1
1
  module CounterCulture
2
- VERSION = '3.8.2'.freeze
2
+ VERSION = '3.11.2'.freeze
3
3
  end
@@ -6,28 +6,42 @@ module CounterCulture
6
6
 
7
7
  attr_reader :recipient
8
8
 
9
- def call
10
- if rails_7_2_or_greater?
11
- recipient.with_connection do |connection|
12
- yield connection
13
- end
14
- elsif rails_7_1?
15
- recipient.connection_pool.with_connection do |connection|
16
- yield connection
9
+ def call(reading: false)
10
+ if rails_7_1_or_greater?
11
+ use_read_replica = CounterCulture.configuration.use_read_replica && reading
12
+ role = use_read_replica ? :reading : :writing
13
+
14
+ ActiveRecord::Base.connected_to(role: role) do
15
+ yield_with_connection { |conn| yield conn }
17
16
  end
18
17
  else
19
- yield recipient.connection
18
+ # For older Rails versions, just use normal connection
19
+ yield_with_connection { |conn| yield conn }
20
20
  end
21
21
  end
22
22
 
23
23
  private
24
24
 
25
- def rails_7_1?
26
- Gem::Requirement.new('~> 7.1.0').satisfied_by?(ActiveRecord.version)
25
+ def yield_with_connection
26
+ if rails_7_2_or_greater?
27
+ recipient.with_connection { |conn| yield conn }
28
+ elsif rails_7_1?
29
+ recipient.connection_pool.with_connection { |conn| yield conn }
30
+ else
31
+ yield recipient.connection
32
+ end
33
+ end
34
+
35
+ def rails_7_1_or_greater?
36
+ Gem::Requirement.new('>= 7.1.0').satisfied_by?(ActiveRecord.version)
27
37
  end
28
38
 
29
39
  def rails_7_2_or_greater?
30
40
  Gem::Requirement.new('>= 7.2.0').satisfied_by?(ActiveRecord.version)
31
41
  end
42
+
43
+ def rails_7_1?
44
+ Gem::Requirement.new('~> 7.1.0').satisfied_by?(ActiveRecord.version)
45
+ end
32
46
  end
33
47
  end
@@ -3,6 +3,7 @@ require 'active_support/lazy_load_hooks'
3
3
 
4
4
  require 'counter_culture/version'
5
5
  require 'counter_culture/extensions'
6
+ require 'counter_culture/configuration'
6
7
  require 'counter_culture/counter'
7
8
  require 'counter_culture/reconciler'
8
9
  require 'counter_culture/skip_updates'
@@ -34,9 +35,14 @@ module CounterCulture
34
35
  end.compact
35
36
 
36
37
  if update_snippets.any?
37
- klass
38
- .where(Thread.current[:primary_key_map][klass] => rec_id)
39
- .update_all(update_snippets.join(', '))
38
+ primary_key = Thread.current[:primary_key_map][klass]
39
+
40
+ conditions =
41
+ Array.wrap(primary_key)
42
+ .zip(Array.wrap(rec_id))
43
+ .to_h
44
+
45
+ klass.where(conditions).update_all(update_snippets.join(', '))
40
46
  end
41
47
  end
42
48
  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.8.2
4
+ version: 3.11.2
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: 2024-10-21 00:00:00.000000000 Z
11
+ date: 2025-07-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -319,7 +319,9 @@ files:
319
319
  - gemfiles/rails_7.0.gemfile
320
320
  - gemfiles/rails_7.1.gemfile
321
321
  - gemfiles/rails_7.2.gemfile
322
+ - gemfiles/rails_8.0.gemfile
322
323
  - lib/counter_culture.rb
324
+ - lib/counter_culture/configuration.rb
323
325
  - lib/counter_culture/counter.rb
324
326
  - lib/counter_culture/extensions.rb
325
327
  - lib/counter_culture/reconciler.rb