activerecord-batch_touching 1.0.pre.beta4 → 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: 8e3ae09116c725e745e467afa1d834b6b2a28bcae4f01d5346e598235bb7d0a8
4
- data.tar.gz: 842acced5ccc6df3149f4ae60b2b7f574f1bf04a553a67b54d6832c1501b94ee
3
+ metadata.gz: 06aa6d889559fed96fed448ffb100324496a79d1e97e147607067f1a3275ab92
4
+ data.tar.gz: 5a737bed02b7812f0feba746bf2ea19bfece091959bdaa25a465dae634b1c38c
5
5
  SHA512:
6
- metadata.gz: 5339c3f783b5b4e8f667a47437fa26e507353d648fd99f637ea73925c28d07d626c7eeb9a8850552ae4182ebb1d36ae9acb5acbe0e2d42e7b0d9f1664a0d86d7
7
- data.tar.gz: bf5025309b66d19d7571ef34a3701eb23d9d8c054b68619bb6ad1f881439b37eb99e2a6fdd67e53dbadccf585ea41ae2703ee09fe10d3a81dd9e422675aa8b56
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
  module Activerecord
2
4
  module BatchTouching
3
5
  VERSION = "1.0"
@@ -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
 
@@ -67,7 +69,7 @@ module ActiveRecord
67
69
 
68
70
  # Disable batch touching for a block
69
71
  def disable
70
- Thread.current[:batch_touching_disabled] = false
72
+ Thread.current[:batch_touching_disabled] = true
71
73
  yield
72
74
  ensure
73
75
  Thread.current[:batch_touching_disabled] = false
@@ -77,34 +79,36 @@ module ActiveRecord
77
79
  Thread.current[:batch_touching_disabled] || @disabled
78
80
  end
79
81
 
80
- def states
81
- Thread.current[:batch_touching_states] ||= []
82
- end
83
-
84
- def current_state
85
- states.last
86
- end
87
-
88
- delegate :add_record, to: :current_state
89
-
90
- def batch_touching?
91
- states.present? && !disabled?
92
- end
93
-
94
82
  # Start batching all touches. When done, apply them. (Unless nested.)
95
- def start(options = {})
83
+ def start(requires_new:)
96
84
  states.push State.new
97
85
  yield.tap do
98
86
  apply_touches if states.length == 1
99
87
  end
100
88
  ensure
101
- merge_transactions unless $! && options[:requires_new]
89
+ merge_transactions unless $! && requires_new
102
90
 
103
91
  # Decrement nesting even if +apply_touches+ raised an error. To ensure the stack of States
104
92
  # is empty after the top-level transaction exits.
105
93
  states.pop
106
94
  end
107
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
+
108
112
  # When exiting a nested transaction, merge the nested transaction's
109
113
  # touched records with the outer transaction's touched records.
110
114
  def merge_transactions
@@ -114,53 +118,57 @@ module ActiveRecord
114
118
  # Apply the touches that were batched. We're in a transaction already so there's no need to open one.
115
119
  def apply_touches
116
120
  current_time = ActiveRecord::Base.current_time_from_proper_timezone
117
- callbacks_run = Set.new
121
+ already_touched = Set.new
118
122
  all_states = State.new
119
123
  while current_state.more_records?
120
124
  all_states.merge!(current_state)
121
125
  state_records = current_state.records
122
126
  current_state.clear_records!
123
127
  state_records.each do |(_klass, columns), records|
124
- soft_touch_records(columns, records, current_time, callbacks_run)
128
+ soft_touch_records(columns, records, current_time, already_touched)
125
129
  end
126
130
  end
127
131
 
128
132
  # 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 }.map { |k| [k, all_states.records[k]] }.to_h
133
+ sorted_records = all_states.records.keys.sort_by { |k| k.first.name }.to_h { |k| [k, all_states.records[k]] }
130
134
  sorted_records.each do |(klass, columns), records|
131
135
  records.reject!(&:destroyed?)
132
- touch_records klass, columns, records, current_time if records.present?
136
+ touch_records klass, columns, records, current_time
133
137
  end
134
138
  end
135
139
 
136
- # Only set new timestamp in memory.
137
- # Running callbacks also allows us to collect more touches (i.e. touch: true for associations).
138
- def soft_touch_records(columns, records, time, callbacks_run)
140
+ # Perform DB update to touch records
141
+ def touch_records(klass, columns, records, time)
142
+ return if columns.blank? || records.blank?
143
+
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
+
139
154
  records.each do |record|
140
- record.instance_eval do
141
- unless destroyed?
142
- columns.each { |column| write_attribute column, time }
143
- if locking_enabled?
144
- self[self.class.locking_column] += 1
145
- clear_attribute_change(self.class.locking_column)
146
- end
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
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)
154
162
  end
155
163
  end
156
164
 
157
- # Touch the specified records--non-empty set of instances of the same class.
158
- def touch_records(klass, columns, records, time)
159
- if columns.present?
160
- sql = columns.map { |column| "#{klass.connection.quote_column_name(column)} = :time" }.join(", ")
161
- sql += ", #{klass.locking_column} = #{klass.locking_column} + 1" if klass.locking_enabled?
162
-
163
- klass.unscoped.where(klass.primary_key => records.to_a).update_all([sql, time: time])
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)
164
172
  end
165
173
  end
166
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.beta4
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-01 00:00:00.000000000 Z
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: sqlite3
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: timecop
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: rspec-rails
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: '0'
208
+ version: 2.7.0
152
209
  required_rubygems_version: !ruby/object:Gem::Requirement
153
210
  requirements:
154
211
  - - ">"