activerecord-batch_touching 1.0.pre.beta2 → 1.0.pre.rc1
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: 06aa6d889559fed96fed448ffb100324496a79d1e97e147607067f1a3275ab92
|
4
|
+
data.tar.gz: 5a737bed02b7812f0feba746bf2ea19bfece091959bdaa25a465dae634b1c38c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b0add569ae81b020e75b5168b00e550d4f69a67cf8e24e5adbb6f45808339ac0d126f69a59bbd0c8620dd073fc762a9cc98283362f9534f3e20197b95da9dc33
|
7
|
+
data.tar.gz: 2d23e326e341927fa8196c566b3101f8850f56632ab93bdb9bf6506e87a451f9c6141801e40c2c6f2743e7a9cfacc535c555161d06003467e1bd29fa59c0f673
|
@@ -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
|
|
@@ -55,34 +57,58 @@ module ActiveRecord
|
|
55
57
|
end
|
56
58
|
|
57
59
|
class << self
|
58
|
-
|
59
|
-
|
60
|
+
# Disable batch touching globally
|
61
|
+
def disable!
|
62
|
+
@disabled = true
|
60
63
|
end
|
61
64
|
|
62
|
-
|
63
|
-
|
65
|
+
# Enable batch touching globally
|
66
|
+
def enable!
|
67
|
+
@disabled = false
|
64
68
|
end
|
65
69
|
|
66
|
-
|
70
|
+
# Disable batch touching for a block
|
71
|
+
def disable
|
72
|
+
Thread.current[:batch_touching_disabled] = true
|
73
|
+
yield
|
74
|
+
ensure
|
75
|
+
Thread.current[:batch_touching_disabled] = false
|
76
|
+
end
|
67
77
|
|
68
|
-
def
|
69
|
-
|
78
|
+
def disabled?
|
79
|
+
Thread.current[:batch_touching_disabled] || @disabled
|
70
80
|
end
|
71
81
|
|
72
82
|
# Start batching all touches. When done, apply them. (Unless nested.)
|
73
|
-
def start(
|
83
|
+
def start(requires_new:)
|
74
84
|
states.push State.new
|
75
85
|
yield.tap do
|
76
86
|
apply_touches if states.length == 1
|
77
87
|
end
|
78
88
|
ensure
|
79
|
-
merge_transactions unless $! &&
|
89
|
+
merge_transactions unless $! && requires_new
|
80
90
|
|
81
91
|
# Decrement nesting even if +apply_touches+ raised an error. To ensure the stack of States
|
82
92
|
# is empty after the top-level transaction exits.
|
83
93
|
states.pop
|
84
94
|
end
|
85
95
|
|
96
|
+
delegate :add_record, to: :current_state
|
97
|
+
|
98
|
+
def batch_touching?
|
99
|
+
states.present? && !disabled?
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def states
|
105
|
+
Thread.current[:batch_touching_states] ||= []
|
106
|
+
end
|
107
|
+
|
108
|
+
def current_state
|
109
|
+
states.last
|
110
|
+
end
|
111
|
+
|
86
112
|
# When exiting a nested transaction, merge the nested transaction's
|
87
113
|
# touched records with the outer transaction's touched records.
|
88
114
|
def merge_transactions
|
@@ -91,50 +117,58 @@ module ActiveRecord
|
|
91
117
|
|
92
118
|
# Apply the touches that were batched. We're in a transaction already so there's no need to open one.
|
93
119
|
def apply_touches
|
94
|
-
|
120
|
+
current_time = ActiveRecord::Base.current_time_from_proper_timezone
|
121
|
+
already_touched = Set.new
|
95
122
|
all_states = State.new
|
96
123
|
while current_state.more_records?
|
97
124
|
all_states.merge!(current_state)
|
98
125
|
state_records = current_state.records
|
99
126
|
current_state.clear_records!
|
100
|
-
state_records.each do |
|
101
|
-
|
102
|
-
records.each do |record|
|
103
|
-
unless callbacks_run.include?(record)
|
104
|
-
record._run_touch_callbacks
|
105
|
-
callbacks_run.add(record)
|
106
|
-
end
|
107
|
-
end
|
127
|
+
state_records.each do |(_klass, columns), records|
|
128
|
+
soft_touch_records(columns, records, current_time, already_touched)
|
108
129
|
end
|
109
130
|
end
|
110
131
|
|
111
132
|
# Sort by class name. Having a consistent order can help mitigate deadlocks.
|
112
|
-
sorted_records = all_states.records.keys.sort_by { |k| k.first.name }.
|
133
|
+
sorted_records = all_states.records.keys.sort_by { |k| k.first.name }.to_h { |k| [k, all_states.records[k]] }
|
113
134
|
sorted_records.each do |(klass, columns), records|
|
114
|
-
|
135
|
+
records.reject!(&:destroyed?)
|
136
|
+
touch_records klass, columns, records, current_time
|
115
137
|
end
|
116
138
|
end
|
117
139
|
|
118
|
-
#
|
119
|
-
def touch_records(klass, columns, records)
|
120
|
-
if columns.
|
121
|
-
current_time = records.first.send(:current_time_from_proper_timezone)
|
122
|
-
|
123
|
-
records.each do |record|
|
124
|
-
record.instance_eval do
|
125
|
-
columns.each { |column| write_attribute column, current_time }
|
126
|
-
if locking_enabled?
|
127
|
-
self[self.class.locking_column] += 1
|
128
|
-
clear_attribute_change(self.class.locking_column)
|
129
|
-
end
|
130
|
-
clear_attribute_changes(columns)
|
131
|
-
end
|
132
|
-
end
|
140
|
+
# Perform DB update to touch records
|
141
|
+
def touch_records(klass, columns, records, time)
|
142
|
+
return if columns.blank? || records.blank?
|
133
143
|
|
134
|
-
|
135
|
-
|
144
|
+
sql = columns.map { |column| "#{klass.connection.quote_column_name(column)} = :time" }.join(", ")
|
145
|
+
sql += ", #{klass.locking_column} = #{klass.locking_column} + 1" if klass.locking_enabled?
|
146
|
+
|
147
|
+
klass.unscoped.where(klass.primary_key => records.to_a).update_all([sql, time: time])
|
148
|
+
end
|
149
|
+
|
150
|
+
# Set new timestamp in memory, without updating the DB just yet.
|
151
|
+
def soft_touch_records(columns, records, time, already_touched)
|
152
|
+
return if columns.blank? || records.blank?
|
153
|
+
|
154
|
+
records.each do |record|
|
155
|
+
next if record.destroyed? || already_touched.include?(record)
|
156
|
+
|
157
|
+
soft_touch_record(columns, record, time)
|
158
|
+
|
159
|
+
# Running callbacks also allows us to collect more touches (i.e. touch: true for associations).
|
160
|
+
record._run_touch_callbacks
|
161
|
+
already_touched.add(record)
|
162
|
+
end
|
163
|
+
end
|
136
164
|
|
137
|
-
|
165
|
+
def soft_touch_record(columns, record, time)
|
166
|
+
columns.each { |column| record.write_attribute column, time }
|
167
|
+
if record.locking_enabled?
|
168
|
+
record[record.class.locking_column] += 1
|
169
|
+
record.clear_attribute_changes(columns + [record.class.locking_column])
|
170
|
+
else
|
171
|
+
record.clear_attribute_changes(columns)
|
138
172
|
end
|
139
173
|
end
|
140
174
|
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.0.pre.
|
4
|
+
version: 1.0.pre.rc1
|
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-03 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/irphilli/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,7 +205,7 @@ 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
|
- - ">"
|