counter_culture 3.10.1 → 3.11.0

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: 157dbe49de1382e3c20686dbb3b22327af960831612683cb08b59db7ccf8dd8e
4
- data.tar.gz: 88db04f7a2d879c9b25255f627d13f823f7bf487cf42fb5efc7d9c764d890b59
3
+ metadata.gz: dbbb4f3f637d653c94426d45846cb656d5f199db3706f9d0b2187ca6a67e2be0
4
+ data.tar.gz: 55d928ec95802a130b82b9c25b2a970eca01ee9c07d9b7346731b9e6fb17aaa0
5
5
  SHA512:
6
- metadata.gz: f0ddda7335aa711ca57fa20e36e4b0a3a12086263281994093749407662b2b0c5ab32851ce14c3f3b44b960a11a758b2f4462fe88c93b5f8b65bde4a05eaa2b0
7
- data.tar.gz: 020d0e8e5084419a67c14a102d77ba76d1b445fb980fa3e441e009114f0a4e6074d47e11689aadc16edf5efdee2b4f3b2e7db15787cb0a57de125a31faecd4eb
6
+ metadata.gz: 89d37375626c427c86481ea57f0de65e59939982436d13968e223956473d94ab29a5b270715c577348bc8b194f46d6beaae80d8a6fc5554f04e9bf221934bad2
7
+ data.tar.gz: f6a7886d1d5e72b8e0e414e1b83e47b24ff87d2214597565a526f8797c605ee662d2cd3c3fdc3f540368c424559a2eedad2c634e8713725ed2f7921ebab3b701
data/CHANGELOG.md CHANGED
@@ -1,6 +1,16 @@
1
+ ## 3.11.0 (June 26, 2025)
2
+
3
+ New features:
4
+ - Support for composite primary keys in Rails v7.2+ (#413)
5
+
6
+ ## 3.10.2 (June 24, 2025)
7
+
8
+ Bugfixes:
9
+ - Fix incorrect in-memory counter updates on `belongs_to` association (#415)
10
+
1
11
  ## 3.10.1 (April 30, 2025)
2
12
 
3
- Bigfixes:
13
+ Bugfixes:
4
14
  - Fix issue when using `delegate` instead of `has_many :through` (#411)
5
15
 
6
16
  ## 3.10.0 (April 15, 2025)
@@ -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,8 +139,11 @@ 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(', ')
141
- assign_to_associated_object(obj, relation, change_counter_column, operator, delta_magnitude)
142
+ conditions = primary_key_conditions(primary_key, id_to_change)
143
+ klass.where(conditions).update_all updates.join(', ')
144
+ unless options[:was]
145
+ assign_to_associated_object(obj, relation, change_counter_column, operator, delta_magnitude)
146
+ end
142
147
  end
143
148
  end
144
149
  end
@@ -172,7 +177,7 @@ module CounterCulture
172
177
 
173
178
  # the string to pass to order() in order to sort by primary key
174
179
  def full_primary_key(klass)
175
- "#{klass.quoted_table_name}.#{klass.quoted_primary_key}"
180
+ Array.wrap(klass.quoted_primary_key).map { |pk| "#{klass.quoted_table_name}.#{pk}" }.join(', ')
176
181
  end
177
182
 
178
183
  # gets the value of the foreign key on the given relation
@@ -186,23 +191,24 @@ module CounterCulture
186
191
  original_relation = relation
187
192
  relation = relation.is_a?(Enumerable) ? relation.dup : [relation]
188
193
 
189
- if was
194
+ value = if was
190
195
  first = relation.shift
191
196
  foreign_key_value = attribute_was(obj, relation_foreign_key(first))
192
197
  klass = relation_klass(first, source: obj, was: was)
193
198
  if foreign_key_value
194
- value = klass.where(
195
- "#{klass.table_name}.#{relation_primary_key(first, source: obj, was: was)} = ?",
196
- 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
197
202
  end
198
203
  else
199
- value = obj
204
+ obj
200
205
  end
201
206
  while !value.nil? && relation.size > 0
202
207
  value = value.send(relation.shift)
203
208
  end
204
209
 
205
- 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
206
212
  end
207
213
 
208
214
  # gets the reflect object on the given relation
@@ -308,6 +314,7 @@ module CounterCulture
308
314
  return reflect.options[:primary_key] if reflect.options.key?(:primary_key)
309
315
  return relation_klass(relation, source: source, was: was).try(:primary_key)
310
316
  end
317
+
311
318
  reflect.association_primary_key(klass)
312
319
  end
313
320
 
@@ -439,6 +446,12 @@ module CounterCulture
439
446
  end
440
447
  end
441
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
+
442
455
  def counter_update_snippet(update, klass, id_to_change, operator, delta_magnitude)
443
456
  if Thread.current[:aggregate_counter_updates]
444
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.1'.freeze
2
+ VERSION = '3.11.0'.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.1
4
+ version: 3.11.0
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-04-30 00:00:00.000000000 Z
11
+ date: 2025-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord