counter_culture 3.11.4 → 3.12.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 +4 -4
- data/.circleci/config.yml +64 -93
- data/CHANGELOG.md +9 -0
- data/README.md +1 -1
- data/gemfiles/rails_8.1.gemfile +8 -0
- data/lib/counter_culture/counter.rb +41 -72
- data/lib/counter_culture/reconciler.rb +1 -1
- data/lib/counter_culture/version.rb +1 -1
- data/lib/counter_culture.rb +18 -7
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4d37272100e5c67ce47b96f522793380e863db895f97387ce02dcc609ae1cb24
|
|
4
|
+
data.tar.gz: 24580fa8abe60d089e5993e8a95c3c1740124ad058d5bdbba2c9766a83a635ba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 673ede183fe87f83356185732c651bf9a9348030b4daaee26ff63cb2774b09b08c0dadb720a5ac58d331586ae085bdbc558dba2000f6ef70e7f0ec781fdb6c46
|
|
7
|
+
data.tar.gz: 6df694a1661bc69582106cb429a557f4110cf7d2336534d33d7e64dce120e000489ec94a1c6982308593f9b1853ed0926cad63c6b09998293bf3139d01eaf93b
|
data/.circleci/config.yml
CHANGED
|
@@ -13,23 +13,17 @@ jobs:
|
|
|
13
13
|
database:
|
|
14
14
|
type: string
|
|
15
15
|
docker:
|
|
16
|
-
- image:
|
|
16
|
+
- image: ruby:<< parameters.ruby-version >>
|
|
17
17
|
- image: cimg/postgres:14.1
|
|
18
18
|
- image: cimg/mysql:8.0
|
|
19
19
|
steps:
|
|
20
20
|
- checkout
|
|
21
|
-
- run:
|
|
22
|
-
name: gem update --system --no-ri --no-rdoc
|
|
23
|
-
# bundles sporadically fail with a weird Psych error if I don't do
|
|
24
|
-
# this. It's annoying since it's slow but it's better than
|
|
25
|
-
# non-deterministic build failures.
|
|
26
|
-
command: sudo gem update --system
|
|
27
21
|
- run:
|
|
28
22
|
name: bundle update --no-ri --no-rdoc
|
|
29
23
|
command: BUNDLE_GEMFILE=gemfiles/rails_<< parameters.rails-version >>.gemfile bundle update
|
|
30
24
|
- run:
|
|
31
25
|
name: install dockerize
|
|
32
|
-
command: wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz &&
|
|
26
|
+
command: wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
|
|
33
27
|
environment:
|
|
34
28
|
DOCKERIZE_VERSION: v0.3.0
|
|
35
29
|
- run:
|
|
@@ -48,124 +42,101 @@ workflows:
|
|
|
48
42
|
- test:
|
|
49
43
|
matrix:
|
|
50
44
|
parameters:
|
|
51
|
-
ruby-version: ["
|
|
52
|
-
rails-version: ["
|
|
45
|
+
ruby-version: ["3.0", "3.1", "3.2", "3.3", "4.0"]
|
|
46
|
+
rails-version: ["6.0", "6.1", "7.0", "7.1", "7.2", "8.0", "8.1"]
|
|
53
47
|
database: ["postgresql", "sqlite3", "mysql2"]
|
|
54
48
|
exclude:
|
|
49
|
+
# Rails 7.2 requires Ruby 3.1+
|
|
55
50
|
- ruby-version: "3.0"
|
|
56
|
-
rails-version: "
|
|
51
|
+
rails-version: "7.2"
|
|
57
52
|
database: "postgresql"
|
|
58
53
|
- ruby-version: "3.0"
|
|
59
|
-
rails-version: "
|
|
54
|
+
rails-version: "7.2"
|
|
55
|
+
database: "sqlite3"
|
|
56
|
+
- ruby-version: "3.0"
|
|
57
|
+
rails-version: "7.2"
|
|
60
58
|
database: "mysql2"
|
|
59
|
+
# Rails 8.0 requires Ruby 3.2+
|
|
61
60
|
- ruby-version: "3.0"
|
|
62
|
-
rails-version: "
|
|
61
|
+
rails-version: "8.0"
|
|
62
|
+
database: "postgresql"
|
|
63
|
+
- ruby-version: "3.0"
|
|
64
|
+
rails-version: "8.0"
|
|
63
65
|
database: "sqlite3"
|
|
66
|
+
- ruby-version: "3.0"
|
|
67
|
+
rails-version: "8.0"
|
|
68
|
+
database: "mysql2"
|
|
64
69
|
- ruby-version: "3.1"
|
|
65
|
-
rails-version: "
|
|
70
|
+
rails-version: "8.0"
|
|
66
71
|
database: "postgresql"
|
|
67
72
|
- ruby-version: "3.1"
|
|
68
|
-
rails-version: "
|
|
69
|
-
database: "mysql2"
|
|
70
|
-
- ruby-version: "3.1"
|
|
71
|
-
rails-version: "5.2"
|
|
73
|
+
rails-version: "8.0"
|
|
72
74
|
database: "sqlite3"
|
|
73
|
-
- ruby-version: "3.
|
|
74
|
-
rails-version: "
|
|
75
|
+
- ruby-version: "3.1"
|
|
76
|
+
rails-version: "8.0"
|
|
77
|
+
database: "mysql2"
|
|
78
|
+
# Rails 8.1 requires Ruby 3.2+
|
|
79
|
+
- ruby-version: "3.0"
|
|
80
|
+
rails-version: "8.1"
|
|
75
81
|
database: "postgresql"
|
|
76
|
-
- ruby-version: "3.
|
|
77
|
-
rails-version: "
|
|
82
|
+
- ruby-version: "3.0"
|
|
83
|
+
rails-version: "8.1"
|
|
84
|
+
database: "sqlite3"
|
|
85
|
+
- ruby-version: "3.0"
|
|
86
|
+
rails-version: "8.1"
|
|
78
87
|
database: "mysql2"
|
|
79
|
-
- ruby-version: "3.
|
|
80
|
-
rails-version: "
|
|
88
|
+
- ruby-version: "3.1"
|
|
89
|
+
rails-version: "8.1"
|
|
90
|
+
database: "postgresql"
|
|
91
|
+
- ruby-version: "3.1"
|
|
92
|
+
rails-version: "8.1"
|
|
81
93
|
database: "sqlite3"
|
|
82
|
-
- ruby-version: "3.
|
|
83
|
-
rails-version: "
|
|
94
|
+
- ruby-version: "3.1"
|
|
95
|
+
rails-version: "8.1"
|
|
96
|
+
database: "mysql2"
|
|
97
|
+
# Ruby 4.0 requires Rails 8.0+
|
|
98
|
+
- ruby-version: "4.0"
|
|
99
|
+
rails-version: "6.0"
|
|
84
100
|
database: "postgresql"
|
|
85
|
-
- ruby-version: "
|
|
86
|
-
rails-version: "
|
|
101
|
+
- ruby-version: "4.0"
|
|
102
|
+
rails-version: "6.0"
|
|
103
|
+
database: "sqlite3"
|
|
104
|
+
- ruby-version: "4.0"
|
|
105
|
+
rails-version: "6.0"
|
|
87
106
|
database: "mysql2"
|
|
88
|
-
- ruby-version: "
|
|
89
|
-
rails-version: "
|
|
107
|
+
- ruby-version: "4.0"
|
|
108
|
+
rails-version: "6.1"
|
|
109
|
+
database: "postgresql"
|
|
110
|
+
- ruby-version: "4.0"
|
|
111
|
+
rails-version: "6.1"
|
|
90
112
|
database: "sqlite3"
|
|
91
|
-
- ruby-version: "
|
|
113
|
+
- ruby-version: "4.0"
|
|
114
|
+
rails-version: "6.1"
|
|
115
|
+
database: "mysql2"
|
|
116
|
+
- ruby-version: "4.0"
|
|
92
117
|
rails-version: "7.0"
|
|
93
118
|
database: "postgresql"
|
|
94
|
-
- ruby-version: "
|
|
119
|
+
- ruby-version: "4.0"
|
|
95
120
|
rails-version: "7.0"
|
|
96
121
|
database: "sqlite3"
|
|
97
|
-
- ruby-version: "
|
|
122
|
+
- ruby-version: "4.0"
|
|
98
123
|
rails-version: "7.0"
|
|
99
124
|
database: "mysql2"
|
|
100
|
-
- ruby-version: "
|
|
125
|
+
- ruby-version: "4.0"
|
|
101
126
|
rails-version: "7.1"
|
|
102
127
|
database: "postgresql"
|
|
103
|
-
- ruby-version: "
|
|
128
|
+
- ruby-version: "4.0"
|
|
104
129
|
rails-version: "7.1"
|
|
105
130
|
database: "sqlite3"
|
|
106
|
-
- ruby-version: "
|
|
131
|
+
- ruby-version: "4.0"
|
|
107
132
|
rails-version: "7.1"
|
|
108
133
|
database: "mysql2"
|
|
109
|
-
- ruby-version: "
|
|
134
|
+
- ruby-version: "4.0"
|
|
110
135
|
rails-version: "7.2"
|
|
111
136
|
database: "postgresql"
|
|
112
|
-
- ruby-version: "
|
|
137
|
+
- ruby-version: "4.0"
|
|
113
138
|
rails-version: "7.2"
|
|
114
139
|
database: "sqlite3"
|
|
115
|
-
- ruby-version: "
|
|
140
|
+
- ruby-version: "4.0"
|
|
116
141
|
rails-version: "7.2"
|
|
117
142
|
database: "mysql2"
|
|
118
|
-
- ruby-version: "2.7"
|
|
119
|
-
rails-version: "7.2"
|
|
120
|
-
database: "postgresql"
|
|
121
|
-
- ruby-version: "2.7"
|
|
122
|
-
rails-version: "7.2"
|
|
123
|
-
database: "sqlite3"
|
|
124
|
-
- ruby-version: "2.7"
|
|
125
|
-
rails-version: "7.2"
|
|
126
|
-
database: "mysql2"
|
|
127
|
-
- ruby-version: "3.0"
|
|
128
|
-
rails-version: "7.2"
|
|
129
|
-
database: "postgresql"
|
|
130
|
-
- ruby-version: "3.0"
|
|
131
|
-
rails-version: "7.2"
|
|
132
|
-
database: "sqlite3"
|
|
133
|
-
- ruby-version: "3.0"
|
|
134
|
-
rails-version: "7.2"
|
|
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/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## 3.12.1 (January 6, 2026)
|
|
2
|
+
|
|
3
|
+
Bugfixes:
|
|
4
|
+
- Fix Rails 8.1+ compatibility by using Arel-based updates to avoid ambiguous column errors with UPDATE...FROM syntax (#425)
|
|
5
|
+
|
|
6
|
+
Changes:
|
|
7
|
+
- Dropped support for Ruby 2.6, 2.7 and Rails 5.2 (all EOL)
|
|
8
|
+
- Added support and testing for Ruby 4.0 and Rails 8.1 (#425)
|
|
9
|
+
|
|
1
10
|
## 3.11.4 (November 5, 2025)
|
|
2
11
|
|
|
3
12
|
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
|
|
10
|
+
Tested against Ruby 3.0, 3.1, 3.2, 3.3 and 4.0 and against the latest patch releases of Rails 6.0, 6.1, 7.0, 7.1, 7.2, 8.0 and 8.1.
|
|
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
|
|
|
@@ -59,32 +59,20 @@ module CounterCulture
|
|
|
59
59
|
end
|
|
60
60
|
return if delta_magnitude.zero?
|
|
61
61
|
|
|
62
|
-
# increment or decrement?
|
|
63
|
-
operator = options[:increment] ? '+' : '-'
|
|
64
|
-
|
|
65
62
|
klass = relation_klass(relation, source: obj, was: options[:was])
|
|
66
63
|
|
|
67
|
-
# MySQL throws an ambiguous column error if any joins are present and we don't include the
|
|
68
|
-
# table name. We isolate this change to MySQL because sqlite has the opposite behavior and
|
|
69
|
-
# throws an exception if the table name is present after UPDATE.
|
|
70
|
-
quoted_column = WithConnection.new(klass).call do |connection|
|
|
71
|
-
if connection.adapter_name == 'Mysql2'
|
|
72
|
-
"#{klass.quoted_table_name}.#{connection.quote_column_name(change_counter_column)}"
|
|
73
|
-
else
|
|
74
|
-
"#{connection.quote_column_name(change_counter_column)}"
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
64
|
column_type = klass.type_for_attribute(change_counter_column).type
|
|
79
65
|
|
|
80
|
-
#
|
|
81
|
-
|
|
66
|
+
# Calculate signed delta for both paths
|
|
67
|
+
signed_delta = options[:increment] ? delta_magnitude : -delta_magnitude
|
|
82
68
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
69
|
+
if Thread.current[:aggregate_counter_updates]
|
|
70
|
+
# Store structured data for later Arel assembly
|
|
71
|
+
remember_counter_update(klass, id_to_change, change_counter_column, signed_delta, column_type)
|
|
86
72
|
else
|
|
87
|
-
|
|
73
|
+
# Build Arel expression for non-aggregate case
|
|
74
|
+
counter_expr = Counter.build_arel_counter_expr(klass, change_counter_column, signed_delta, column_type)
|
|
75
|
+
arel_updates = { change_counter_column => counter_expr }
|
|
88
76
|
end
|
|
89
77
|
|
|
90
78
|
# and this will update the timestamp, if so desired
|
|
@@ -97,12 +85,11 @@ module CounterCulture
|
|
|
97
85
|
timestamp_columns << touch
|
|
98
86
|
end
|
|
99
87
|
timestamp_columns.each do |timestamp_column|
|
|
100
|
-
|
|
101
|
-
klass,
|
|
102
|
-
|
|
103
|
-
timestamp_column
|
|
104
|
-
|
|
105
|
-
)
|
|
88
|
+
if Thread.current[:aggregate_counter_updates]
|
|
89
|
+
remember_timestamp_update(klass, id_to_change, timestamp_column)
|
|
90
|
+
else
|
|
91
|
+
arel_updates[timestamp_column] = current_time
|
|
92
|
+
end
|
|
106
93
|
end
|
|
107
94
|
end
|
|
108
95
|
|
|
@@ -149,7 +136,9 @@ module CounterCulture
|
|
|
149
136
|
unless Thread.current[:aggregate_counter_updates]
|
|
150
137
|
execute_now_or_after_commit(obj) do
|
|
151
138
|
conditions = primary_key_conditions(primary_key, id_to_change)
|
|
152
|
-
|
|
139
|
+
# Use Arel-based updates which let Rails handle column qualification properly,
|
|
140
|
+
# avoiding ambiguous column errors in Rails 8.1+ UPDATE...FROM syntax.
|
|
141
|
+
klass.where(conditions).distinct(false).update_all(arel_updates)
|
|
153
142
|
# Determine if we should update the in-memory counter on the associated object.
|
|
154
143
|
# When updating the old counter (was: true), we need to carefully consider two scenarios:
|
|
155
144
|
# 1) The belongs_to relation changed (e.g., moving a child from parent A to parent B):
|
|
@@ -171,6 +160,7 @@ module CounterCulture
|
|
|
171
160
|
end
|
|
172
161
|
|
|
173
162
|
if should_update_counter
|
|
163
|
+
operator = options[:increment] ? '+' : '-'
|
|
174
164
|
assign_to_associated_object(obj, relation, change_counter_column, operator, delta_magnitude)
|
|
175
165
|
end
|
|
176
166
|
end
|
|
@@ -454,68 +444,47 @@ module CounterCulture
|
|
|
454
444
|
(association_object.public_send(change_counter_column) || 0).public_send(operator, delta_magnitude)
|
|
455
445
|
end
|
|
456
446
|
|
|
457
|
-
def assemble_money_counter_update(klass, id_to_change, quoted_column, operator, delta_magnitude)
|
|
458
|
-
counter_update_snippet(
|
|
459
|
-
"#{quoted_column} = COALESCE(CAST(#{quoted_column} as NUMERIC), 0)",
|
|
460
|
-
klass,
|
|
461
|
-
id_to_change,
|
|
462
|
-
operator,
|
|
463
|
-
delta_magnitude
|
|
464
|
-
)
|
|
465
|
-
end
|
|
466
|
-
|
|
467
|
-
def assemble_counter_update(klass, id_to_change, quoted_column, operator, delta_magnitude)
|
|
468
|
-
counter_update_snippet(
|
|
469
|
-
"#{quoted_column} = COALESCE(#{quoted_column}, 0)",
|
|
470
|
-
klass,
|
|
471
|
-
id_to_change,
|
|
472
|
-
operator,
|
|
473
|
-
delta_magnitude
|
|
474
|
-
)
|
|
475
|
-
end
|
|
476
|
-
|
|
477
|
-
def assemble_timestamp_update(klass, id_to_change, timestamp_column, value)
|
|
478
|
-
update = "#{timestamp_column} ="
|
|
479
|
-
|
|
480
|
-
if Thread.current[:aggregate_counter_updates]
|
|
481
|
-
remember_timestamp_update(klass, id_to_change, update, value)
|
|
482
|
-
else
|
|
483
|
-
"#{update} '#{value.call}'"
|
|
484
|
-
end
|
|
485
|
-
end
|
|
486
|
-
|
|
487
447
|
def primary_key_conditions(primary_key, fk_value)
|
|
488
448
|
Array.wrap(primary_key)
|
|
489
449
|
.zip(Array.wrap(fk_value))
|
|
490
450
|
.to_h
|
|
491
451
|
end
|
|
492
452
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
453
|
+
# Builds an Arel expression for counter updates: COALESCE(col, 0) +/- delta
|
|
454
|
+
# This is a class method so it can be called from CounterCulture.aggregate_counter_updates
|
|
455
|
+
def self.build_arel_counter_expr(klass, column, delta, column_type)
|
|
456
|
+
arel_column = klass.arel_table[column]
|
|
457
|
+
arel_delta = Arel::Nodes.build_quoted(delta.abs)
|
|
458
|
+
|
|
459
|
+
coalesce = if column_type == :money
|
|
460
|
+
Arel::Nodes::NamedFunction.new(
|
|
461
|
+
'COALESCE',
|
|
462
|
+
[Arel::Nodes::NamedFunction.new('CAST', [arel_column.as('NUMERIC')]), 0]
|
|
500
463
|
)
|
|
501
464
|
else
|
|
502
|
-
|
|
465
|
+
Arel::Nodes::NamedFunction.new('COALESCE', [arel_column, 0])
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
if delta > 0
|
|
469
|
+
Arel::Nodes::Addition.new(coalesce, arel_delta)
|
|
470
|
+
else
|
|
471
|
+
Arel::Nodes::Subtraction.new(coalesce, arel_delta)
|
|
503
472
|
end
|
|
504
473
|
end
|
|
505
474
|
|
|
506
|
-
def remember_counter_update(klass, id,
|
|
475
|
+
def remember_counter_update(klass, id, column, delta, column_type)
|
|
507
476
|
Thread.current[:aggregated_updates][klass] ||= {}
|
|
508
|
-
Thread.current[:aggregated_updates][klass][id] ||= {}
|
|
509
|
-
Thread.current[:aggregated_updates][klass][id][
|
|
477
|
+
Thread.current[:aggregated_updates][klass][id] ||= { counters: {}, timestamps: [] }
|
|
478
|
+
Thread.current[:aggregated_updates][klass][id][:counters][column] ||= { delta: 0, type: column_type }
|
|
510
479
|
|
|
511
|
-
Thread.current[:aggregated_updates][klass][id][
|
|
480
|
+
Thread.current[:aggregated_updates][klass][id][:counters][column][:delta] += delta
|
|
512
481
|
end
|
|
513
482
|
|
|
514
|
-
def remember_timestamp_update(klass, id,
|
|
483
|
+
def remember_timestamp_update(klass, id, column)
|
|
515
484
|
Thread.current[:aggregated_updates][klass] ||= {}
|
|
516
|
-
Thread.current[:aggregated_updates][klass][id] ||= {}
|
|
485
|
+
Thread.current[:aggregated_updates][klass][id] ||= { counters: {}, timestamps: [] }
|
|
517
486
|
|
|
518
|
-
Thread.current[:aggregated_updates][klass][id][
|
|
487
|
+
Thread.current[:aggregated_updates][klass][id][:timestamps] << column
|
|
519
488
|
end
|
|
520
489
|
end
|
|
521
490
|
end
|
|
@@ -174,7 +174,7 @@ module CounterCulture
|
|
|
174
174
|
|
|
175
175
|
with_writing_db_connection do
|
|
176
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
|
+
relation_class.where(conditions).distinct(false).update_all(updates.join(', '))
|
|
178
178
|
end
|
|
179
179
|
end
|
|
180
180
|
end
|
data/lib/counter_culture.rb
CHANGED
|
@@ -26,15 +26,26 @@ module CounterCulture
|
|
|
26
26
|
|
|
27
27
|
result = yield
|
|
28
28
|
|
|
29
|
-
# aggregate the updates for each target record and execute SQL queries
|
|
29
|
+
# aggregate the updates for each target record and execute SQL queries using Arel
|
|
30
30
|
Thread.current[:aggregated_updates].each do |klass, attrs|
|
|
31
31
|
attrs.each do |rec_id, updates|
|
|
32
|
-
|
|
33
|
-
value = value.call if value.is_a?(Proc)
|
|
34
|
-
%Q{#{operation} #{value.is_a?(String) ? "'#{value}'" : value}} unless value == 0
|
|
35
|
-
end.compact
|
|
32
|
+
arel_updates = {}
|
|
36
33
|
|
|
37
|
-
|
|
34
|
+
# Build counter updates
|
|
35
|
+
updates[:counters].each do |column, info|
|
|
36
|
+
next if info[:delta] == 0
|
|
37
|
+
arel_updates[column] = Counter.build_arel_counter_expr(klass, column, info[:delta], info[:type])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Build timestamp updates (compute timestamp at execution time)
|
|
41
|
+
if updates[:timestamps].any?
|
|
42
|
+
current_time = klass.send(:current_time_from_proper_timezone)
|
|
43
|
+
updates[:timestamps].each do |column|
|
|
44
|
+
arel_updates[column] = current_time
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if arel_updates.any?
|
|
38
49
|
primary_key = Thread.current[:primary_key_map][klass]
|
|
39
50
|
|
|
40
51
|
conditions =
|
|
@@ -42,7 +53,7 @@ module CounterCulture
|
|
|
42
53
|
.zip(Array.wrap(rec_id))
|
|
43
54
|
.to_h
|
|
44
55
|
|
|
45
|
-
klass.where(conditions).
|
|
56
|
+
klass.where(conditions).distinct(false).update_all(arel_updates)
|
|
46
57
|
end
|
|
47
58
|
end
|
|
48
59
|
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.12.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:
|
|
11
|
+
date: 2026-01-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -320,6 +320,7 @@ files:
|
|
|
320
320
|
- gemfiles/rails_7.1.gemfile
|
|
321
321
|
- gemfiles/rails_7.2.gemfile
|
|
322
322
|
- gemfiles/rails_8.0.gemfile
|
|
323
|
+
- gemfiles/rails_8.1.gemfile
|
|
323
324
|
- lib/counter_culture.rb
|
|
324
325
|
- lib/counter_culture/configuration.rb
|
|
325
326
|
- lib/counter_culture/counter.rb
|