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 +4 -4
- data/.circleci/config.yml +37 -1
- data/Appraisals +1 -0
- data/CHANGELOG.md +35 -0
- data/README.md +35 -2
- data/gemfiles/rails_8.0.gemfile +7 -0
- data/lib/counter_culture/configuration.rb +38 -0
- data/lib/counter_culture/counter.rb +75 -9
- data/lib/counter_culture/reconciler.rb +42 -11
- data/lib/counter_culture/version.rb +1 -1
- data/lib/counter_culture/with_connection.rb +25 -11
- data/lib/counter_culture.rb +9 -3
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c568fc84225757b834a008eb60dbadfe5dea148078c855fe76a2b5711b0c5b1e
|
4
|
+
data.tar.gz: 3abc94f197b60fb6778eadb98ad402e6b961155c492970111b85bbf1bd171237
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
|
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.
|
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,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
|
-
|
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
|
-
|
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}.#{
|
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
|
-
|
194
|
-
|
195
|
-
|
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
|
-
|
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
|
-
|
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
|
-
"#{
|
114
|
-
"#{
|
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
|
-
|
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
|
-
|
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 #{
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
371
|
+
@connection_handler.call(reading: false) do |_conn|
|
372
|
+
yield
|
373
|
+
end
|
343
374
|
end
|
344
375
|
end
|
345
376
|
end
|
@@ -6,28 +6,42 @@ module CounterCulture
|
|
6
6
|
|
7
7
|
attr_reader :recipient
|
8
8
|
|
9
|
-
def call
|
10
|
-
if
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
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
|
26
|
-
|
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
|
data/lib/counter_culture.rb
CHANGED
@@ -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
|
-
|
39
|
-
|
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.
|
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:
|
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
|