counter_culture 3.10.2 → 3.11.1

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: dc2af460731ae5d0d71c452bff1969ede450961b0383de35624015058e73bbce
4
- data.tar.gz: fe4cd5de448b52f11b2b5d9409202ad115bb6e87d5008ed83eb3708fbd177745
3
+ metadata.gz: 593e3704f40322fade2c263f93bb6d88749b397d641586b30f4d616ee5c9ed0c
4
+ data.tar.gz: b183412de943dbe2390a4eef636ca8aae9fea6aeac6ac298177545d26b08413e
5
5
  SHA512:
6
- metadata.gz: 360b2c83a7e377c2a9974cfbd812d5df0f0d3921c93aabf5b4c0426e5f7aab156fe1cd7350607be17694b243f6cc823000b8eb2bb342fb65401ce9071dcc688e
7
- data.tar.gz: 39a199f34ca2275c4804ab1f03a0dac80105675ef43dc473f3d9d4c479b57eb7088d32a36a67a47df00ae26e1d81d3454cf0de465209d357c181cca66de18d1c
6
+ metadata.gz: c0a74d87a9d56ec4b19d7fe9a7aaf9bd2698d527387251a52b866e534117386005e830eef556b96bbc67122a125ebf942a4531be5b6f2d35a075b55b3133b052
7
+ data.tar.gz: 4cd891261ed5402cffa7eea851e2400f3b6d7ec8c4d617b36a3b0dd680e00f17b9b1a909a55bb94db1dbe636ca4b5d2d929d02df851e00869bb316a703fa8928
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 3.11.1 (June 27, 2025)
2
+
3
+ Bugfixes:
4
+ - Don't attempt to modify frozen association object (#416)
5
+
6
+ ## 3.11.0 (June 26, 2025)
7
+
8
+ New features:
9
+ - Support for composite primary keys in Rails v7.2+ (#413)
10
+
1
11
  ## 3.10.2 (June 24, 2025)
2
12
 
3
13
  Bugfixes:
@@ -11,6 +11,10 @@ module CounterCulture
11
11
  @configuration = Configuration.new
12
12
  end
13
13
 
14
+ def self.supports_composite_keys?
15
+ Gem::Requirement.new('>= 7.2.0').satisfied_by?(ActiveRecord.version)
16
+ end
17
+
14
18
  class Configuration
15
19
  attr_reader :use_read_replica
16
20
 
@@ -113,7 +113,9 @@ module CounterCulture
113
113
  end
114
114
 
115
115
  if @with_papertrail
116
- instance = klass.where(primary_key => id_to_change).first
116
+ conditions = primary_key_conditions(primary_key, id_to_change)
117
+ instance = klass.where(conditions).first
118
+
117
119
  if instance
118
120
  if instance.paper_trail.respond_to?(:save_with_version)
119
121
  # touch_with_version is deprecated starting in PaperTrail 9.0.0
@@ -137,7 +139,8 @@ module CounterCulture
137
139
 
138
140
  unless Thread.current[:aggregate_counter_updates]
139
141
  execute_now_or_after_commit(obj) do
140
- klass.where(primary_key => id_to_change).update_all updates.join(', ')
142
+ conditions = primary_key_conditions(primary_key, id_to_change)
143
+ klass.where(conditions).update_all updates.join(', ')
141
144
  unless options[:was]
142
145
  assign_to_associated_object(obj, relation, change_counter_column, operator, delta_magnitude)
143
146
  end
@@ -174,7 +177,7 @@ module CounterCulture
174
177
 
175
178
  # the string to pass to order() in order to sort by primary key
176
179
  def full_primary_key(klass)
177
- "#{klass.quoted_table_name}.#{klass.quoted_primary_key}"
180
+ Array.wrap(klass.quoted_primary_key).map { |pk| "#{klass.quoted_table_name}.#{pk}" }.join(', ')
178
181
  end
179
182
 
180
183
  # gets the value of the foreign key on the given relation
@@ -188,23 +191,24 @@ module CounterCulture
188
191
  original_relation = relation
189
192
  relation = relation.is_a?(Enumerable) ? relation.dup : [relation]
190
193
 
191
- if was
194
+ value = if was
192
195
  first = relation.shift
193
196
  foreign_key_value = attribute_was(obj, relation_foreign_key(first))
194
197
  klass = relation_klass(first, source: obj, was: was)
195
198
  if foreign_key_value
196
- value = klass.where(
197
- "#{klass.table_name}.#{relation_primary_key(first, source: obj, was: was)} = ?",
198
- foreign_key_value).first
199
+ primary_key = relation_primary_key(first, source: obj, was: was)
200
+ conditions = primary_key_conditions(primary_key, foreign_key_value)
201
+ klass.where(conditions).first
199
202
  end
200
203
  else
201
- value = obj
204
+ obj
202
205
  end
203
206
  while !value.nil? && relation.size > 0
204
207
  value = value.send(relation.shift)
205
208
  end
206
209
 
207
- return value.try(relation_primary_key(original_relation, source: obj, was: was).try(:to_sym))
210
+ primary_key = relation_primary_key(original_relation, source: obj, was: was)
211
+ Array.wrap(primary_key).map { |pk| value.try(pk&.to_sym) }.compact.presence
208
212
  end
209
213
 
210
214
  # gets the reflect object on the given relation
@@ -310,6 +314,7 @@ module CounterCulture
310
314
  return reflect.options[:primary_key] if reflect.options.key?(:primary_key)
311
315
  return relation_klass(relation, source: source, was: was).try(:primary_key)
312
316
  end
317
+
313
318
  reflect.association_primary_key(klass)
314
319
  end
315
320
 
@@ -373,7 +378,7 @@ module CounterCulture
373
378
  association_name = relation_reflect(relation).name
374
379
 
375
380
  association_object = association_object_for_assign(obj, association_name)
376
- return if association_object.blank?
381
+ return if association_object.blank? || association_object.frozen?
377
382
 
378
383
  association_object.assign_attributes(
379
384
  change_counter_column =>
@@ -441,6 +446,12 @@ module CounterCulture
441
446
  end
442
447
  end
443
448
 
449
+ def primary_key_conditions(primary_key, fk_value)
450
+ Array.wrap(primary_key)
451
+ .zip(Array.wrap(fk_value))
452
+ .to_h
453
+ end
454
+
444
455
  def counter_update_snippet(update, klass, id_to_change, operator, delta_magnitude)
445
456
  if Thread.current[:aggregate_counter_updates]
446
457
  remember_counter_update(
@@ -111,8 +111,8 @@ module CounterCulture
111
111
 
112
112
  # select join column and count (from above) as well as cache column ('column_name') for later comparison
113
113
  counts_query = scope.select(
114
- "#{relation_class_table_name}.#{relation_class.primary_key}, " \
115
- "#{relation_class_table_name}.#{relation_reflect(relation).association_primary_key(relation_class)}, " \
114
+ "#{primary_key_select}, " \
115
+ "#{association_primary_key_select}, " \
116
116
  "#{count_select} AS count, " \
117
117
  "MAX(#{relation_class_table_name}.#{column_name}) AS #{column_name}"
118
118
  )
@@ -173,7 +173,8 @@ module CounterCulture
173
173
  end
174
174
 
175
175
  with_writing_db_connection do
176
- 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(', '))
177
178
  end
178
179
  end
179
180
  end
@@ -199,11 +200,12 @@ module CounterCulture
199
200
  def track_change(record, column_name, count)
200
201
  @changes_holder << {
201
202
  :entity => relation_class.name,
202
- relation_class.primary_key.to_sym => record.send(relation_class.primary_key),
203
203
  :what => column_name,
204
204
  :wrong => record.send(column_name),
205
205
  :right => count
206
- }
206
+ }.tap do |h|
207
+ Array.wrap(relation_class.primary_key).each { |pk| h[pk.to_sym] = record.send(pk) }
208
+ end
207
209
  end
208
210
 
209
211
  def count_select
@@ -217,10 +219,23 @@ module CounterCulture
217
219
  @count_select = "SUM(COALESCE(#{self_table_name}.#{delta_column}, 0))"
218
220
  end
219
221
  else
220
- @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}"
221
225
  end
222
226
  end
223
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
+
224
239
  def self_table_name
225
240
  return @self_table_name if @self_table_name
226
241
 
@@ -271,8 +286,17 @@ module CounterCulture
271
286
  [target_table_key, source_table_key]
272
287
  end
273
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 ')
274
297
  joins_sql = "LEFT JOIN #{target_table} AS #{target_table_alias} "\
275
- "ON #{source_table}.#{source_table_key} = #{target_table_alias}.#{target_table_key}"
298
+ "ON #{join_conditions}"
299
+
276
300
  # adds 'type' condition to JOIN clause if the current model is a
277
301
  # child in a Single Table Inheritance
278
302
  if reflect.active_record.column_names.include?('type') &&
@@ -289,7 +313,9 @@ module CounterCulture
289
313
  # conditions must be applied to the join on which we are counting
290
314
  if where
291
315
  if where.respond_to?(:to_sql)
292
- 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})"
293
319
  else
294
320
  joins_sql += " AND (#{model.send(:sanitize_sql_for_conditions, where)})"
295
321
  end
@@ -1,3 +1,3 @@
1
1
  module CounterCulture
2
- VERSION = '3.10.2'.freeze
2
+ VERSION = '3.11.1'.freeze
3
3
  end
@@ -35,9 +35,14 @@ module CounterCulture
35
35
  end.compact
36
36
 
37
37
  if update_snippets.any?
38
- klass
39
- .where(Thread.current[:primary_key_map][klass] => rec_id)
40
- .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(', '))
41
46
  end
42
47
  end
43
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.10.2
4
+ version: 3.11.1
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: 2025-06-24 00:00:00.000000000 Z
11
+ date: 2025-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord