switchman 3.3.7 → 3.4.1

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