activerecord-batch_touching 1.0.pre.beta4 → 1.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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f994be54523c59f7878cbcf822ca03552bb3a365215cb3de7f23c05541381c75
|
4
|
+
data.tar.gz: aeab5dbe2bebca4b0b88d2a77ce033b7a288969fe6bddb18c74e332ec729a6bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0808dfbb8f5ea2fedbd51feaf8c453f9224699017d5c33c54a8c08ff02396b52524b67691937ba9b48b838f84257abf0eab3280500bb976945b7eec7302fc89a'
|
7
|
+
data.tar.gz: 4237add83e7f90d3c6852f3221daa31e798eedc2a2b780193e2eab71ae8d17c831c8d2076d899bfed1e84cf830285d501d820fd34ca942223e0f7b53dc5876f2
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
# = Active Record Batch Touching
|
3
5
|
module BatchTouching
|
4
|
-
|
5
6
|
# Tracking of the touch state. This class has no class-level data, so you can
|
6
7
|
# store per-thread instances in thread-local variables.
|
7
8
|
class State # :nodoc:
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "activerecord/batch_touching/version"
|
2
4
|
require "activerecord/batch_touching/state"
|
3
5
|
|
@@ -20,9 +22,17 @@ module ActiveRecord
|
|
20
22
|
# Person.first.touch
|
21
23
|
# end
|
22
24
|
def transaction(requires_new: nil, isolation: nil, joinable: true, &block)
|
23
|
-
super(requires_new: requires_new, isolation: isolation, joinable: joinable) do
|
25
|
+
result = super(requires_new: requires_new, isolation: isolation, joinable: joinable) do
|
24
26
|
BatchTouching.start(requires_new: requires_new, &block)
|
25
27
|
end
|
28
|
+
|
29
|
+
if BatchTouching.should_apply_touches?
|
30
|
+
super() do
|
31
|
+
BatchTouching.apply_touches
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
result
|
26
36
|
end
|
27
37
|
end
|
28
38
|
|
@@ -67,7 +77,7 @@ module ActiveRecord
|
|
67
77
|
|
68
78
|
# Disable batch touching for a block
|
69
79
|
def disable
|
70
|
-
Thread.current[:batch_touching_disabled] =
|
80
|
+
Thread.current[:batch_touching_disabled] = true
|
71
81
|
yield
|
72
82
|
ensure
|
73
83
|
Thread.current[:batch_touching_disabled] = false
|
@@ -77,12 +87,18 @@ module ActiveRecord
|
|
77
87
|
Thread.current[:batch_touching_disabled] || @disabled
|
78
88
|
end
|
79
89
|
|
80
|
-
|
81
|
-
|
82
|
-
|
90
|
+
# Start batching all touches. When done, apply them. (Unless nested.)
|
91
|
+
def start(requires_new:)
|
92
|
+
states.push State.new
|
93
|
+
yield.tap do
|
94
|
+
gather_touches if states.length == 1
|
95
|
+
end
|
96
|
+
ensure
|
97
|
+
merge_transactions unless $! && requires_new
|
83
98
|
|
84
|
-
|
85
|
-
|
99
|
+
# Decrement nesting even if +gather_touches+ raised an error. To ensure the stack of States
|
100
|
+
# is empty after the top-level transaction exits.
|
101
|
+
states.pop
|
86
102
|
end
|
87
103
|
|
88
104
|
delegate :add_record, to: :current_state
|
@@ -91,18 +107,40 @@ module ActiveRecord
|
|
91
107
|
states.present? && !disabled?
|
92
108
|
end
|
93
109
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
110
|
+
def should_apply_touches?
|
111
|
+
batched_touches.present?
|
112
|
+
end
|
113
|
+
|
114
|
+
def apply_touches
|
115
|
+
batched_touches&.each do |(klass, columns), records|
|
116
|
+
records.reject!(&:destroyed?)
|
117
|
+
touch_records klass, columns, records, batched_touches_time
|
99
118
|
end
|
100
|
-
ensure
|
101
|
-
merge_transactions unless $! && options[:requires_new]
|
102
119
|
|
103
|
-
|
104
|
-
|
105
|
-
|
120
|
+
reset!
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def reset!
|
126
|
+
Thread.current[:batch_touching_batch] = nil
|
127
|
+
Thread.current[:batch_touching_time] = nil
|
128
|
+
end
|
129
|
+
|
130
|
+
def batched_touches
|
131
|
+
Thread.current[:batch_touching_batch]
|
132
|
+
end
|
133
|
+
|
134
|
+
def batched_touches_time
|
135
|
+
Thread.current[:batch_touching_time]
|
136
|
+
end
|
137
|
+
|
138
|
+
def states
|
139
|
+
Thread.current[:batch_touching_states] ||= []
|
140
|
+
end
|
141
|
+
|
142
|
+
def current_state
|
143
|
+
states.last
|
106
144
|
end
|
107
145
|
|
108
146
|
# When exiting a nested transaction, merge the nested transaction's
|
@@ -111,56 +149,60 @@ module ActiveRecord
|
|
111
149
|
states[-2].merge!(current_state) if states.length > 1
|
112
150
|
end
|
113
151
|
|
114
|
-
#
|
115
|
-
def
|
152
|
+
# Gather all the touches that were batched.
|
153
|
+
def gather_touches
|
116
154
|
current_time = ActiveRecord::Base.current_time_from_proper_timezone
|
117
|
-
|
155
|
+
already_touched = Set.new
|
118
156
|
all_states = State.new
|
119
157
|
while current_state.more_records?
|
120
158
|
all_states.merge!(current_state)
|
121
159
|
state_records = current_state.records
|
122
160
|
current_state.clear_records!
|
123
161
|
state_records.each do |(_klass, columns), records|
|
124
|
-
soft_touch_records(columns, records, current_time,
|
162
|
+
soft_touch_records(columns, records, current_time, already_touched)
|
125
163
|
end
|
126
164
|
end
|
127
165
|
|
128
166
|
# Sort by class name. Having a consistent order can help mitigate deadlocks.
|
129
|
-
sorted_records = all_states.records.keys.sort_by { |k| k.first.name }.
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
167
|
+
sorted_records = all_states.records.keys.sort_by { |k| k.first.name }.to_h { |k| [k, all_states.records[k]] }
|
168
|
+
|
169
|
+
# Save results to a thread so that we can reference these later in their own transaction.
|
170
|
+
Thread.current[:batch_touching_batch] = sorted_records
|
171
|
+
Thread.current[:batch_touching_time] = current_time
|
172
|
+
end
|
173
|
+
|
174
|
+
# Perform DB update to touch records
|
175
|
+
def touch_records(klass, columns, records, time)
|
176
|
+
return if columns.blank? || records.blank?
|
177
|
+
|
178
|
+
sql = columns.map { |column| "#{klass.connection.quote_column_name(column)} = :time" }.join(", ")
|
179
|
+
sql += ", #{klass.locking_column} = #{klass.locking_column} + 1" if klass.locking_enabled?
|
180
|
+
|
181
|
+
klass.unscoped.where(klass.primary_key => records.to_a).update_all([sql, time: time])
|
134
182
|
end
|
135
183
|
|
136
|
-
#
|
137
|
-
|
138
|
-
|
184
|
+
# Set new timestamp in memory, without updating the DB just yet.
|
185
|
+
def soft_touch_records(columns, records, time, already_touched)
|
186
|
+
return if columns.blank? || records.blank?
|
187
|
+
|
139
188
|
records.each do |record|
|
140
|
-
record.
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
clear_attribute_changes(columns)
|
148
|
-
end
|
149
|
-
unless callbacks_run.include?(record)
|
150
|
-
record._run_touch_callbacks
|
151
|
-
callbacks_run.add(record)
|
152
|
-
end
|
153
|
-
end
|
189
|
+
next if record.destroyed? || already_touched.include?(record)
|
190
|
+
|
191
|
+
soft_touch_record(columns, record, time)
|
192
|
+
|
193
|
+
# Running callbacks also allows us to collect more touches (i.e. touch: true for associations).
|
194
|
+
record._run_touch_callbacks
|
195
|
+
already_touched.add(record)
|
154
196
|
end
|
155
197
|
end
|
156
198
|
|
157
|
-
|
158
|
-
|
159
|
-
if
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
199
|
+
def soft_touch_record(columns, record, time)
|
200
|
+
columns.each { |column| record.write_attribute column, time }
|
201
|
+
if record.locking_enabled?
|
202
|
+
record[record.class.locking_column] += 1
|
203
|
+
record.clear_attribute_changes(columns + [record.class.locking_column])
|
204
|
+
else
|
205
|
+
record.clear_attribute_changes(columns)
|
164
206
|
end
|
165
207
|
end
|
166
208
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-batch_touching
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: '1.1'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Morearty
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2022-07-
|
12
|
+
date: 2022-07-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: '0'
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
57
|
+
name: rspec-rails
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
60
|
- - ">="
|
@@ -68,7 +68,7 @@ dependencies:
|
|
68
68
|
- !ruby/object:Gem::Version
|
69
69
|
version: '0'
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
|
-
name:
|
71
|
+
name: rubocop
|
72
72
|
requirement: !ruby/object:Gem::Requirement
|
73
73
|
requirements:
|
74
74
|
- - ">="
|
@@ -82,7 +82,35 @@ dependencies:
|
|
82
82
|
- !ruby/object:Gem::Version
|
83
83
|
version: '0'
|
84
84
|
- !ruby/object:Gem::Dependency
|
85
|
-
name:
|
85
|
+
name: rubocop-performance
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: rubocop-rake
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: rubocop-rspec
|
86
114
|
requirement: !ruby/object:Gem::Requirement
|
87
115
|
requirements:
|
88
116
|
- - ">="
|
@@ -123,6 +151,34 @@ dependencies:
|
|
123
151
|
- - ">="
|
124
152
|
- !ruby/object:Gem::Version
|
125
153
|
version: '0'
|
154
|
+
- !ruby/object:Gem::Dependency
|
155
|
+
name: sqlite3
|
156
|
+
requirement: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
type: :development
|
162
|
+
prerelease: false
|
163
|
+
version_requirements: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '0'
|
168
|
+
- !ruby/object:Gem::Dependency
|
169
|
+
name: timecop
|
170
|
+
requirement: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - ">="
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
type: :development
|
176
|
+
prerelease: false
|
177
|
+
version_requirements: !ruby/object:Gem::Requirement
|
178
|
+
requirements:
|
179
|
+
- - ">="
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
126
182
|
description: Batch up your ActiveRecord "touch" operations for better performance.
|
127
183
|
All accumulated "touch" calls will be consolidated into as few database round trips
|
128
184
|
as possible.
|
@@ -136,10 +192,11 @@ files:
|
|
136
192
|
- lib/activerecord/batch_touching.rb
|
137
193
|
- lib/activerecord/batch_touching/state.rb
|
138
194
|
- lib/activerecord/batch_touching/version.rb
|
139
|
-
homepage:
|
195
|
+
homepage: https://github.com/ProductPlan/activerecord-batch_touching
|
140
196
|
licenses:
|
141
197
|
- MIT
|
142
|
-
metadata:
|
198
|
+
metadata:
|
199
|
+
rubygems_mfa_required: 'true'
|
143
200
|
post_install_message:
|
144
201
|
rdoc_options: []
|
145
202
|
require_paths:
|
@@ -148,12 +205,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
148
205
|
requirements:
|
149
206
|
- - ">="
|
150
207
|
- !ruby/object:Gem::Version
|
151
|
-
version:
|
208
|
+
version: 2.7.0
|
152
209
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
210
|
requirements:
|
154
|
-
- - "
|
211
|
+
- - ">="
|
155
212
|
- !ruby/object:Gem::Version
|
156
|
-
version:
|
213
|
+
version: '0'
|
157
214
|
requirements: []
|
158
215
|
rubygems_version: 3.3.7
|
159
216
|
signing_key:
|