switchman 3.3.7 → 3.4.1

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: 07ef0eeff511916fa7913f453f2b3cde7d5317dfee9e03b58cf3645b2830c845
4
- data.tar.gz: 2c2c12374387e6856674f14cf965087a0d27c1ccdacc6470b69aec0eb77af22f
3
+ metadata.gz: e080637567925bab42e7d6c87a2963486b16fb5a09eab87ef61fd8056d773ddf
4
+ data.tar.gz: 2694d7353b3cb7df6976aa812897540f1a9fc2188c5a6d2cc15bebfbbabb1371
5
5
  SHA512:
6
- metadata.gz: d62c36fe017fbf801312eb13295e19fbb66c5e9b2aa23996400c3654acca130525580728602328377f4b601d5a9ba09847ef6082948f29af7d1217d7471ffa70
7
- data.tar.gz: b6afb2bd1e0f69d93dd7b1e7a739cd7a1b6896eee43be063f8452d102de21991f8ab49da3fecc65859e24d66dd54e19f49c6352e22a50dcbd1add00518f154c0
6
+ metadata.gz: b3318e8323ebda712ad41873279793ddd5b0d5849a536c9f2dc0fcd3addefb35ff4d364882f27eb59d0c8e27e7c5d79e5c5edc6de66a73024746f79fed23170f
7
+ data.tar.gz: a2d2a1b1682febaffe2856dbdee8eca77241d5781e60fe037677f39c5c77b30d4d4ec5d6b0ff5e8c6cdd947fa79171fc99f0b19ffc3a5a77185bb400069ab537
@@ -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
@@ -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,53 @@ module Switchman
169
171
  end
170
172
  end
171
173
 
174
+ def destroy_shadow_records(target_shards: [Shard.current])
175
+ raise Errors::ShadowRecordError, 'Cannot be called on a shadow record.' if shadow_record?
176
+ raise Errors::MethodUnsupportedForUnshardedTableError, 'Cannot be called on a record belonging to an unsharded table.' unless self.class.sharded_column?(self.class.primary_key)
177
+
178
+ Array(target_shards).each do |target_shard|
179
+ next if target_shard == shard
180
+
181
+ target_shard.activate { self.class.where('id = ?', global_id).delete_all }
182
+ end
183
+ end
184
+
185
+ # Returns "the shard that this record was actually loaded from" , as
186
+ # opposed to "the shard this record belongs on", which might be
187
+ # different if this is a shadow record.
188
+ def loaded_from_shard
189
+ @loaded_from_shard || fallback_shard
190
+ end
191
+
172
192
  def shard
173
- @shard || Shard.current(self.class.connection_class_for_self) || Shard.default
193
+ @shard || fallback_shard
174
194
  end
175
195
 
176
196
  def shard=(new_shard)
177
197
  raise ::ActiveRecord::ReadOnlyRecord if !new_record? || @shard_set_in_stone
178
198
 
179
- return if shard == new_shard
199
+ if shard == new_shard
200
+ @loaded_from_shard = new_shard
201
+ return
202
+ end
180
203
 
181
204
  attributes.each do |attr, value|
182
205
  self[attr] = Shard.relative_id_for(value, shard, new_shard) if self.class.sharded_column?(attr)
183
206
  end
207
+ @loaded_from_shard = new_shard
184
208
  @shard = new_shard
185
209
  end
186
210
 
187
211
  def save(*, **)
212
+ raise Errors::ManuallyCreatedShadowRecordError if creating_shadow_record?
213
+
188
214
  @shard_set_in_stone = true
189
215
  super
190
216
  end
191
217
 
192
218
  def save!(*, **)
219
+ raise Errors::ManuallyCreatedShadowRecordError if creating_shadow_record?
220
+
193
221
  @shard_set_in_stone = true
194
222
  super
195
223
  end
@@ -221,7 +249,7 @@ module Switchman
221
249
  end
222
250
 
223
251
  def hash
224
- self.class.sharded_primary_key? ? self.class.hash ^ global_id.hash : super
252
+ self.class.sharded_primary_key? ? [self.class, global_id].hash : super
225
253
  end
226
254
 
227
255
  def to_param
@@ -270,6 +298,16 @@ module Switchman
270
298
  self.class.connection_class_for_self
271
299
  end
272
300
  end
301
+
302
+ private
303
+
304
+ def fallback_shard
305
+ Shard.current(self.class.connection_class_for_self) || Shard.default
306
+ end
307
+
308
+ def creating_shadow_record?
309
+ new_record? && shadow_record?
310
+ end
273
311
  end
274
312
  end
275
313
  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(*, **)
@@ -2,8 +2,18 @@
2
2
 
3
3
  module Switchman
4
4
  module Errors
5
+ class ManuallyCreatedShadowRecordError < RuntimeError
6
+ def initialize(msg = "It looks like you're trying to manually create a shadow record. Please use Switchman::ActiveRecord::Base#save_shadow_record instead.")
7
+ super
8
+ end
9
+ end
10
+
5
11
  class NonExistentShardError < RuntimeError; end
6
12
 
7
13
  class ParallelShardExecError < RuntimeError; end
14
+
15
+ class ShadowRecordError < RuntimeError; end
16
+
17
+ class UnshardedTableError < RuntimeError; end
8
18
  end
9
19
  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.7'
4
+ VERSION = '3.4.1'
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.7
4
+ version: 3.4.1
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-22 00:00:00.000000000 Z
13
+ date: 2023-04-04 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