activerecord-batch_touching 1.0.pre.beta4 → 1.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
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
  - - ">"