switchman 3.3.6 → 3.4.0

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: a0e25e9a338b74ed4340f605d27978bba42bd45ef47ddac2bf7c44047db8a014
4
- data.tar.gz: a6f6cdb8f1686213391d38f1eb172fefa9346012f3ce055b152696f1dcd05975
3
+ metadata.gz: be3660461c9c52c63b304de0bb3e5a18e3fe3cdfe7d5f61802b2db2b232f97be
4
+ data.tar.gz: 47248f2cb8c6c35396d537e7bd1f938399811d11eb1a07a96778654ccf5dc3f9
5
5
  SHA512:
6
- metadata.gz: fe38ed259e6d881d9f60ecd0dd96b65a3dd154b750beb3b5ee5b8af03e7683f056238feb464fc32c5156841b439200c6ff0c9bf5959dc108b5bb91c298bd450a
7
- data.tar.gz: b74decf8b11511749f80ffc792b2a98dbf6cdf36670d1f2db84a80975138aee8f0f4c11dab8883b9be93413324fb7f574589d6788781cd8e664dea34fe964cbd
6
+ metadata.gz: 60d5b3eb13458b61bbdc8d2a729e10f870284d35fa0694612d793ed8ff9fd15667777dda2e4d28e4fb435a707aff0ca483e51032b396d7496b5d3792e6d6be16
7
+ data.tar.gz: fa52c0f4ddf8fea8602917d64f165602ee14ffb9ea72f609a7f9d11e2ac511b5ba3cae9a8ae9915791c268adc0c9f9ef12f142817d0e472bf1c3a17b1f3e262c
@@ -50,7 +50,7 @@ module Switchman
50
50
  def shard
51
51
  if @owner.class.sharded_column?(@reflection.foreign_key) &&
52
52
  (foreign_id = @owner[@reflection.foreign_key])
53
- Shard.shard_for(foreign_id, @owner.shard)
53
+ Shard.shard_for(foreign_id, @owner.loaded_from_shard)
54
54
  else
55
55
  super
56
56
  end
@@ -46,7 +46,6 @@ module Switchman
46
46
  raise if connection.open_transactions.positive?
47
47
  end
48
48
 
49
- # rubocop:disable Naming/MethodParameterName
50
49
  def define_cached_method(owner, name, namespace:, as:, &block)
51
50
  if ::Rails.version < '7.0'
52
51
  yield owner
@@ -55,7 +54,6 @@ module Switchman
55
54
  owner.define_cached_method(name, namespace: namespace, as: as, &block)
56
55
  end
57
56
  end
58
- # rubocop:enable Naming/MethodParameterName
59
57
 
60
58
  def define_method_global_attribute(attr_name, owner:)
61
59
  if sharded_column?(attr_name)
@@ -136,7 +134,7 @@ module Switchman
136
134
 
137
135
  abs_raw_value = raw_value.abs
138
136
  current_shard = ::Switchman::Shard.current(#{attr_connection_class})
139
- same_shard = shard == current_shard
137
+ same_shard = loaded_from_shard == current_shard
140
138
  return raw_value if same_shard && abs_raw_value < ::Switchman::Shard::IDS_PER_SHARD
141
139
 
142
140
  value_shard_id = abs_raw_value / ::Switchman::Shard::IDS_PER_SHARD
@@ -144,9 +142,9 @@ module Switchman
144
142
  # of a local id
145
143
  return raw_value % ::Switchman::Shard::IDS_PER_SHARD if value_shard_id == current_shard.id
146
144
  return raw_value if !same_shard && abs_raw_value > ::Switchman::Shard::IDS_PER_SHARD
147
- return shard.global_id_for(raw_value) if !same_shard && abs_raw_value < ::Switchman::Shard::IDS_PER_SHARD
145
+ return loaded_from_shard.global_id_for(raw_value) if !same_shard && abs_raw_value < ::Switchman::Shard::IDS_PER_SHARD
148
146
 
149
- ::Switchman::Shard.relative_id_for(raw_value, shard, current_shard)
147
+ ::Switchman::Shard.relative_id_for(raw_value, loaded_from_shard, current_shard)
150
148
  end
151
149
  RUBY
152
150
  end
@@ -173,7 +171,7 @@ module Switchman
173
171
  def build_sharded_setter(attr_name, attr_field, attr_connection_class)
174
172
  <<-RUBY
175
173
  def #{attr_name}=(new_value)
176
- self.original_#{attr_field} = ::Switchman::Shard.relative_id_for(new_value, ::Switchman::Shard.current(#{attr_connection_class}), shard)
174
+ self.original_#{attr_field} = ::Switchman::Shard.relative_id_for(new_value, ::Switchman::Shard.current(#{attr_connection_class}), loaded_from_shard)
177
175
  end
178
176
  RUBY
179
177
  end
@@ -251,7 +249,7 @@ module Switchman
251
249
 
252
250
  def local_attribute(attr_name)
253
251
  if self.class.sharded_column?(attr_name)
254
- ::Switchman::Shard.local_id_for(attribute(attr_name), shard).first
252
+ ::Switchman::Shard.local_id_for(attribute(attr_name)).first
255
253
  else
256
254
  attribute(attr_name)
257
255
  end
@@ -142,6 +142,8 @@ module Switchman
142
142
  else
143
143
  Shard.current(self.class.connection_class_for_self)
144
144
  end
145
+
146
+ @loaded_from_shard ||= Shard.current(self.class.connection_class_for_self)
145
147
  readonly! if shadow_record? && !Switchman.config[:writable_shadow_records]
146
148
  super
147
149
  end
@@ -169,27 +171,42 @@ module Switchman
169
171
  end
170
172
  end
171
173
 
174
+ # Returns "the shard that this record was actually loaded from" , as
175
+ # opposed to "the shard this record belongs on", which might be
176
+ # different if this is a shadow record.
177
+ def loaded_from_shard
178
+ @loaded_from_shard || fallback_shard
179
+ end
180
+
172
181
  def shard
173
- @shard || Shard.current(self.class.connection_class_for_self) || Shard.default
182
+ @shard || fallback_shard
174
183
  end
175
184
 
176
185
  def shard=(new_shard)
177
186
  raise ::ActiveRecord::ReadOnlyRecord if !new_record? || @shard_set_in_stone
178
187
 
179
- return if shard == new_shard
188
+ if shard == new_shard
189
+ @loaded_from_shard = new_shard
190
+ return
191
+ end
180
192
 
181
193
  attributes.each do |attr, value|
182
194
  self[attr] = Shard.relative_id_for(value, shard, new_shard) if self.class.sharded_column?(attr)
183
195
  end
196
+ @loaded_from_shard = new_shard
184
197
  @shard = new_shard
185
198
  end
186
199
 
187
200
  def save(*, **)
201
+ raise Errors::ManuallyCreatedShadowRecordError if creating_shadow_record?
202
+
188
203
  @shard_set_in_stone = true
189
204
  super
190
205
  end
191
206
 
192
207
  def save!(*, **)
208
+ raise Errors::ManuallyCreatedShadowRecordError if creating_shadow_record?
209
+
193
210
  @shard_set_in_stone = true
194
211
  super
195
212
  end
@@ -221,7 +238,7 @@ module Switchman
221
238
  end
222
239
 
223
240
  def hash
224
- self.class.sharded_primary_key? ? self.class.hash ^ global_id.hash : super
241
+ self.class.sharded_primary_key? ? [self.class, global_id].hash : super
225
242
  end
226
243
 
227
244
  def to_param
@@ -270,6 +287,16 @@ module Switchman
270
287
  self.class.connection_class_for_self
271
288
  end
272
289
  end
290
+
291
+ private
292
+
293
+ def fallback_shard
294
+ Shard.current(self.class.connection_class_for_self) || Shard.default
295
+ end
296
+
297
+ def creating_shadow_record?
298
+ new_record? && shadow_record?
299
+ end
273
300
  end
274
301
  end
275
302
  end
@@ -44,6 +44,23 @@ module Switchman
44
44
  end
45
45
 
46
46
  module MigrationContext
47
+ def migrate(...)
48
+ connection = ::ActiveRecord::Base.connection
49
+ connection_pool = ::ActiveRecord::Base.connection_pool
50
+ previous_schema_cache = connection_pool.get_schema_cache(connection)
51
+ temporary_schema_cache = ::ActiveRecord::ConnectionAdapters::SchemaCache.new(connection)
52
+
53
+ reset_column_information
54
+ connection_pool.set_schema_cache(temporary_schema_cache)
55
+
56
+ begin
57
+ super(...)
58
+ ensure
59
+ connection_pool.set_schema_cache(previous_schema_cache)
60
+ reset_column_information
61
+ end
62
+ end
63
+
47
64
  def migrations
48
65
  return @migrations if instance_variable_defined?(:@migrations)
49
66
 
@@ -51,6 +68,12 @@ module Switchman
51
68
  key = Digest::MD5.hexdigest(migration_files.sort.join(','))
52
69
  @migrations = migrations_cache[key] ||= super
53
70
  end
71
+
72
+ private
73
+
74
+ def reset_column_information
75
+ ::ActiveRecord::Base.descendants.reject { |m| m <= UnshardedRecord }.each(&:reset_column_information)
76
+ end
54
77
  end
55
78
  end
56
79
  end
@@ -16,6 +16,14 @@ module Switchman
16
16
  db = shard.database_server
17
17
  db.unguard { super }
18
18
  end
19
+
20
+ def reload(*)
21
+ res = super
22
+ # When a shadow record is reloaded the real record is returned. So
23
+ # we need to ensure the loaded_from_shard is set correctly after a reload.
24
+ @loaded_from_shard = @shard
25
+ res
26
+ end
19
27
  end
20
28
  end
21
29
  end
@@ -4,7 +4,7 @@ module Switchman
4
4
  module ActiveRecord
5
5
  module Relation
6
6
  def self.prepended(klass)
7
- klass::SINGLE_VALUE_METHODS.concat %i[shard shard_source]
7
+ klass::SINGLE_VALUE_METHODS.push(:shard, :shard_source)
8
8
  end
9
9
 
10
10
  def initialize(*, **)
@@ -221,10 +221,8 @@ module Switchman
221
221
 
222
222
  unless schema == false
223
223
  shard.activate do
224
- with_empty_caches do
225
- ::ActiveRecord::Base.connection.transaction(requires_new: true) do
226
- ::ActiveRecord::Base.connection.migration_context.migrate
227
- end
224
+ ::ActiveRecord::Base.connection.transaction(requires_new: true) do
225
+ ::ActiveRecord::Base.connection.migration_context.migrate
228
226
  end
229
227
 
230
228
  ::ActiveRecord::Base.descendants.reject do |m|
@@ -240,7 +238,6 @@ module Switchman
240
238
  rescue
241
239
  shard&.destroy
242
240
  shard&.drop_database rescue nil unless schema_already_existed
243
- reset_column_information unless schema == false rescue nil
244
241
  raise
245
242
  ensure
246
243
  self.class.creating_new_shard = false
@@ -272,26 +269,5 @@ module Switchman
272
269
  end
273
270
  @primary_shard
274
271
  end
275
-
276
- private
277
-
278
- def reset_column_information
279
- ::ActiveRecord::Base.descendants.reject { |m| m <= UnshardedRecord }.each(&:reset_column_information)
280
- end
281
-
282
- def with_empty_caches
283
- connection = ::ActiveRecord::Base.connection
284
- connection_pool = ::ActiveRecord::Base.connection_pool
285
- previous_schema_cache = connection_pool.get_schema_cache(connection)
286
- temporary_schema_cache = ::ActiveRecord::ConnectionAdapters::SchemaCache.new(connection)
287
-
288
- reset_column_information
289
- connection_pool.set_schema_cache(temporary_schema_cache)
290
-
291
- yield
292
-
293
- connection_pool.set_schema_cache(previous_schema_cache)
294
- reset_column_information
295
- end
296
272
  end
297
273
  end
@@ -5,5 +5,11 @@ module Switchman
5
5
  class NonExistentShardError < RuntimeError; end
6
6
 
7
7
  class ParallelShardExecError < RuntimeError; end
8
+
9
+ class ManuallyCreatedShadowRecordError < RuntimeError
10
+ def initialize(msg = "It looks like you're trying to manually create a shadow record. Please use Switchman::ActiveRecord::Base#save_shadow_record instead.")
11
+ super
12
+ end
13
+ end
8
14
  end
9
15
  end
@@ -65,4 +65,4 @@ module Switchman
65
65
  end
66
66
  end
67
67
 
68
- ::Parallel::UndumpableException.prepend(::Switchman::Parallel::UndumpableException)
68
+ Parallel::UndumpableException.prepend(Switchman::Parallel::UndumpableException)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Switchman
4
- VERSION = '3.3.6'
4
+ VERSION = '3.4.0'
5
5
  end
@@ -3,9 +3,9 @@
3
3
  # In rails 7.0+ if you have only 1 db in the env it doesn't try to do explicit activation
4
4
  # (and for rails purposes we only have one db per env because each database server is a separate env)
5
5
  if Rails.version < '7.0'
6
- task_prefix = ::Rake::Task.task_defined?('app:db:migrate') ? 'app:db' : 'db'
7
- ::Rake::Task["#{task_prefix}:migrate"].clear_actions.enhance do
8
- ::ActiveRecord::Tasks::DatabaseTasks.migrate
6
+ task_prefix = Rake::Task.task_defined?('app:db:migrate') ? 'app:db' : 'db'
7
+ Rake::Task["#{task_prefix}:migrate"].clear_actions.enhance do
8
+ ActiveRecord::Tasks::DatabaseTasks.migrate
9
9
  # Ensure this doesn't blow up when running inside the dummy app
10
10
  Rake::Task["#{task_prefix}:_dump"].invoke
11
11
  end
@@ -19,8 +19,8 @@ module Switchman
19
19
  end
20
20
 
21
21
  def self.scope(base_scope = Shard,
22
- database_server: ENV['DATABASE_SERVER'],
23
- shard: ENV['SHARD'])
22
+ database_server: ENV.fetch('DATABASE_SERVER', nil),
23
+ shard: ENV.fetch('SHARD', nil))
24
24
  servers = DatabaseServer.all
25
25
 
26
26
  if database_server
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: switchman
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.6
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-03-21 00:00:00.000000000 Z
13
+ date: 2023-03-31 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -192,6 +192,20 @@ dependencies:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
194
  version: '1.10'
195
+ - !ruby/object:Gem::Dependency
196
+ name: rubocop-performance
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: '1.16'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: '1.16'
195
209
  - !ruby/object:Gem::Dependency
196
210
  name: rubocop-rake
197
211
  requirement: !ruby/object:Gem::Requirement