switchman 3.3.6 → 3.4.0

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: 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