switchman 3.3.1 → 4.0.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 +4 -4
- data/Rakefile +15 -14
- data/db/migrate/20180828183945_add_default_shard_index.rb +1 -1
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
- data/lib/switchman/active_record/abstract_adapter.rb +4 -2
- data/lib/switchman/active_record/associations.rb +89 -49
- data/lib/switchman/active_record/attribute_methods.rb +72 -34
- data/lib/switchman/active_record/base.rb +145 -27
- data/lib/switchman/active_record/calculations.rb +96 -49
- data/lib/switchman/active_record/connection_handler.rb +18 -0
- data/lib/switchman/active_record/connection_pool.rb +24 -3
- data/lib/switchman/active_record/database_configurations.rb +37 -15
- data/lib/switchman/active_record/finder_methods.rb +44 -14
- data/lib/switchman/active_record/log_subscriber.rb +11 -5
- data/lib/switchman/active_record/migration.rb +45 -3
- data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
- data/lib/switchman/active_record/persistence.rb +30 -0
- data/lib/switchman/active_record/postgresql_adapter.rb +11 -10
- data/lib/switchman/active_record/predicate_builder.rb +2 -2
- data/lib/switchman/active_record/query_cache.rb +49 -20
- data/lib/switchman/active_record/query_methods.rb +93 -30
- data/lib/switchman/active_record/relation.rb +23 -12
- data/lib/switchman/active_record/spawn_methods.rb +2 -2
- data/lib/switchman/active_record/statement_cache.rb +2 -2
- data/lib/switchman/active_record/tasks/database_tasks.rb +6 -1
- data/lib/switchman/active_record/test_fixtures.rb +26 -16
- data/lib/switchman/active_support/cache.rb +9 -4
- data/lib/switchman/arel.rb +34 -18
- data/lib/switchman/call_super.rb +2 -8
- data/lib/switchman/database_server.rb +69 -31
- data/lib/switchman/default_shard.rb +14 -3
- data/lib/switchman/engine.rb +29 -22
- data/lib/switchman/environment.rb +2 -2
- data/lib/switchman/errors.rb +13 -0
- data/lib/switchman/guard_rail/relation.rb +1 -2
- data/lib/switchman/parallel.rb +6 -6
- data/lib/switchman/r_spec_helper.rb +12 -11
- data/lib/switchman/shard.rb +180 -68
- data/lib/switchman/sharded_instrumenter.rb +3 -3
- data/lib/switchman/shared_schema_cache.rb +11 -0
- data/lib/switchman/standard_error.rb +4 -0
- data/lib/switchman/test_helper.rb +2 -2
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +27 -15
- data/lib/tasks/switchman.rake +96 -60
- metadata +38 -48
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eb5bc75d858d97e55442dfdafcd1826d7040fe1bd4819bc3c2ee6053257a72ab
|
|
4
|
+
data.tar.gz: e73beaf3a5987f26e2402078f66c028831afc6924f21546d0ca1cb35f70e0831
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 273718d8bc773ded149ac5d81509a6d85c6858f17bc7c1a03f3dbdf15bbb96f101fea5842eddf87ba442aabf1f0bffe1a21421da4b9329548b0f40e51de26645
|
|
7
|
+
data.tar.gz: 9f81d1963fe213d334aed7377057fe29ecbdb637c6b2b8a33a49d52fcc0f726e1a8e87853a6e1030c8c2b0bbaff51e369d54cf8118f1c512ca0d62bf5684267c
|
data/Rakefile
CHANGED
|
@@ -2,37 +2,38 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
begin
|
|
5
|
-
require
|
|
5
|
+
require "bundler/setup"
|
|
6
6
|
rescue LoadError
|
|
7
|
-
puts
|
|
7
|
+
puts "You must `gem install bundler` and `bundle install` to run rake tasks"
|
|
8
8
|
end
|
|
9
9
|
begin
|
|
10
|
-
require
|
|
10
|
+
require "rdoc/task"
|
|
11
11
|
rescue LoadError
|
|
12
|
-
require
|
|
13
|
-
require
|
|
12
|
+
require "rdoc/rdoc"
|
|
13
|
+
require "rake/rdoctask"
|
|
14
14
|
RDoc::Task = Rake::RDocTask
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
RDoc::Task.new(:rdoc) do |rdoc|
|
|
18
|
-
rdoc.rdoc_dir =
|
|
19
|
-
rdoc.title =
|
|
20
|
-
rdoc.options <<
|
|
21
|
-
rdoc.rdoc_files.include(
|
|
18
|
+
rdoc.rdoc_dir = "rdoc"
|
|
19
|
+
rdoc.title = "Switchman"
|
|
20
|
+
rdoc.options << "--line-numbers"
|
|
21
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
load "./spec/tasks/coverage.rake"
|
|
25
|
+
APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
|
|
26
|
+
load "rails/tasks/engine.rake"
|
|
26
27
|
|
|
27
28
|
Bundler::GemHelper.install_tasks
|
|
28
29
|
|
|
29
|
-
require
|
|
30
|
+
require "rspec/core/rake_task"
|
|
30
31
|
RSpec::Core::RakeTask.new
|
|
31
32
|
|
|
32
|
-
require
|
|
33
|
+
require "rubocop/rake_task"
|
|
33
34
|
|
|
34
35
|
RuboCop::RakeTask.new do |task|
|
|
35
|
-
task.options = [
|
|
36
|
+
task.options = ["-S"]
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
task default: %i[spec]
|
|
@@ -5,7 +5,7 @@ class AddDefaultShardIndex < ActiveRecord::Migration[4.2]
|
|
|
5
5
|
Switchman::Shard.where(default: nil).update_all(default: false) if Switchman::Shard.current.default?
|
|
6
6
|
change_column_default :switchman_shards, :default, false
|
|
7
7
|
change_column_null :switchman_shards, :default, false
|
|
8
|
-
options = if connection.adapter_name ==
|
|
8
|
+
options = if connection.adapter_name == "PostgreSQL"
|
|
9
9
|
{ unique: true, where: '"default"' }
|
|
10
10
|
else
|
|
11
11
|
{}
|
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
class AddUniqueNameIndexes < ActiveRecord::Migration[4.2]
|
|
4
4
|
def change
|
|
5
5
|
add_index :switchman_shards, %i[database_server_id name], unique: true
|
|
6
|
-
add_index :switchman_shards,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
add_index :switchman_shards,
|
|
7
|
+
:database_server_id,
|
|
8
|
+
unique: true,
|
|
9
|
+
where: "name IS NULL",
|
|
10
|
+
name: "index_switchman_shards_unique_primary_shard"
|
|
11
|
+
add_index :switchman_shards,
|
|
12
|
+
"(true)",
|
|
13
|
+
unique: true,
|
|
14
|
+
where: "database_server_id IS NULL AND name IS NULL",
|
|
15
|
+
name: "index_switchman_shards_unique_primary_db_and_shard"
|
|
10
16
|
end
|
|
11
17
|
end
|
|
@@ -24,11 +24,23 @@ module Switchman
|
|
|
24
24
|
|
|
25
25
|
module CollectionAssociation
|
|
26
26
|
def find_target
|
|
27
|
-
shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards)
|
|
27
|
+
shards = if reflection.options[:multishard] && owner.respond_to?(:associated_shards)
|
|
28
|
+
owner.associated_shards
|
|
29
|
+
else
|
|
30
|
+
[shard]
|
|
31
|
+
end
|
|
28
32
|
# activate both the owner and the target's shard category, so that Reflection#join_id_for,
|
|
29
33
|
# when called for the owner, will be returned relative to shard the query will execute on
|
|
30
|
-
Shard.with_each_shard(shards,
|
|
31
|
-
|
|
34
|
+
Shard.with_each_shard(shards,
|
|
35
|
+
[klass.connection_class_for_self, owner.class.connection_class_for_self].uniq) do
|
|
36
|
+
if reflection.options[:multishard] && owner.respond_to?(:associated_shards) && reflection.has_scope?
|
|
37
|
+
# Prevent duplicate results when reflection has a scope (when it would use the skip_statement_cache? path)
|
|
38
|
+
# otherwise, the super call will set the shard_value to the object, causing it to iterate too many times
|
|
39
|
+
# over the associated shards
|
|
40
|
+
scope.shard(Shard.current(scope.klass.connection_class_for_self), :association).to_a
|
|
41
|
+
else
|
|
42
|
+
super
|
|
43
|
+
end
|
|
32
44
|
end
|
|
33
45
|
end
|
|
34
46
|
|
|
@@ -50,7 +62,7 @@ module Switchman
|
|
|
50
62
|
def shard
|
|
51
63
|
if @owner.class.sharded_column?(@reflection.foreign_key) &&
|
|
52
64
|
(foreign_id = @owner[@reflection.foreign_key])
|
|
53
|
-
Shard.shard_for(foreign_id, @owner.
|
|
65
|
+
Shard.shard_for(foreign_id, @owner.loaded_from_shard)
|
|
54
66
|
else
|
|
55
67
|
super
|
|
56
68
|
end
|
|
@@ -128,7 +140,12 @@ module Switchman
|
|
|
128
140
|
Shard.lookup(shard).activate do
|
|
129
141
|
scope_was = loader_query.scope
|
|
130
142
|
begin
|
|
131
|
-
loader_query.instance_variable_set(
|
|
143
|
+
loader_query.instance_variable_set(
|
|
144
|
+
:@scope,
|
|
145
|
+
loader_query.scope.shard(
|
|
146
|
+
Shard.current(loader_query.scope.model.connection_class_for_self)
|
|
147
|
+
)
|
|
148
|
+
)
|
|
132
149
|
ret += loader_query.load_records_for_keys(keys) do |record|
|
|
133
150
|
loaders.each { |l| l.set_inverse(record) }
|
|
134
151
|
end
|
|
@@ -186,40 +203,7 @@ module Switchman
|
|
|
186
203
|
# #compare_by_identity makes such owners different hash keys
|
|
187
204
|
@records_by_owner = {}.compare_by_identity
|
|
188
205
|
|
|
189
|
-
|
|
190
|
-
raw_records ||= loader_query.records_for([self])
|
|
191
|
-
elsif owner_keys.empty?
|
|
192
|
-
raw_records ||= []
|
|
193
|
-
else
|
|
194
|
-
# determine the shard to search for each owner
|
|
195
|
-
if reflection.macro == :belongs_to
|
|
196
|
-
# for belongs_to, it's the shard of the foreign_key
|
|
197
|
-
partition_proc = lambda do |owner|
|
|
198
|
-
if owner.class.sharded_column?(owner_key_name)
|
|
199
|
-
Shard.shard_for(owner[owner_key_name], owner.shard)
|
|
200
|
-
else
|
|
201
|
-
Shard.current
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
elsif !reflection.options[:multishard]
|
|
205
|
-
# for non-multishard associations, it's *just* the owner's shard
|
|
206
|
-
partition_proc = ->(owner) { owner.shard }
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
raw_records ||= Shard.partition_by_shard(owners, partition_proc) do |partitioned_owners|
|
|
210
|
-
relative_owner_keys = partitioned_owners.map do |owner|
|
|
211
|
-
key = owner[owner_key_name]
|
|
212
|
-
if key && owner.class.sharded_column?(owner_key_name)
|
|
213
|
-
key = Shard.relative_id_for(key, owner.shard,
|
|
214
|
-
Shard.current(klass.connection_class_for_self))
|
|
215
|
-
end
|
|
216
|
-
convert_key(key)
|
|
217
|
-
end
|
|
218
|
-
relative_owner_keys.compact!
|
|
219
|
-
relative_owner_keys.uniq!
|
|
220
|
-
records_for(relative_owner_keys)
|
|
221
|
-
end
|
|
222
|
-
end
|
|
206
|
+
raw_records ||= loader_query.records_for([self])
|
|
223
207
|
|
|
224
208
|
@preloaded_records = raw_records.select do |record|
|
|
225
209
|
assignments = false
|
|
@@ -273,17 +257,73 @@ module Switchman
|
|
|
273
257
|
end
|
|
274
258
|
|
|
275
259
|
module AutosaveAssociation
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
260
|
+
if ::Rails.version < "7.1"
|
|
261
|
+
def association_foreign_key_changed?(reflection, record, key)
|
|
262
|
+
return false if reflection.through_reflection?
|
|
263
|
+
|
|
264
|
+
# have to use send instead of _read_attribute because sharding
|
|
265
|
+
record.has_attribute?(reflection.foreign_key) && record.send(reflection.foreign_key) != key
|
|
266
|
+
end
|
|
281
267
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
268
|
+
def save_belongs_to_association(reflection)
|
|
269
|
+
# this seems counter-intuitive, but the autosave code will assign to attribute bypassing switchman,
|
|
270
|
+
# after reading the id attribute _without_ bypassing switchman. So we need Shard.current for the
|
|
271
|
+
# category of the associated record to match Shard.current for the category of self
|
|
272
|
+
shard.activate(connection_class_for_self_for_reflection(reflection)) { super }
|
|
273
|
+
end
|
|
274
|
+
else
|
|
275
|
+
def association_foreign_key_changed?(reflection, record, key)
|
|
276
|
+
return false if reflection.through_reflection?
|
|
277
|
+
|
|
278
|
+
foreign_key = Array(reflection.foreign_key)
|
|
279
|
+
return false unless foreign_key.all? { |k| record._has_attribute?(k) }
|
|
280
|
+
|
|
281
|
+
# have to use send instead of _read_attribute because sharding
|
|
282
|
+
foreign_key.map { |k| record.send(k) } != Array(key)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def save_belongs_to_association(reflection)
|
|
286
|
+
association = association_instance_get(reflection.name)
|
|
287
|
+
return unless association&.loaded? && !association.stale_target?
|
|
288
|
+
|
|
289
|
+
record = association.load_target
|
|
290
|
+
return unless record && !record.destroyed?
|
|
291
|
+
|
|
292
|
+
autosave = reflection.options[:autosave]
|
|
293
|
+
|
|
294
|
+
if autosave && record.marked_for_destruction?
|
|
295
|
+
foreign_key = Array(reflection.foreign_key)
|
|
296
|
+
foreign_key.each { |key| self[key] = nil }
|
|
297
|
+
record.destroy
|
|
298
|
+
elsif autosave != false
|
|
299
|
+
if record.new_record? || (autosave && record.changed_for_autosave?)
|
|
300
|
+
saved = record.save(validate: !autosave)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
if association.updated?
|
|
304
|
+
primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
|
|
305
|
+
foreign_key = Array(reflection.foreign_key)
|
|
306
|
+
|
|
307
|
+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
|
308
|
+
primary_key_foreign_key_pairs.each do |pk, fk|
|
|
309
|
+
# Notable change: add relative_id_for here
|
|
310
|
+
association_id = if record.class.sharded_column?(pk)
|
|
311
|
+
Shard.relative_id_for(
|
|
312
|
+
record._read_attribute(pk),
|
|
313
|
+
record.shard,
|
|
314
|
+
shard
|
|
315
|
+
)
|
|
316
|
+
else
|
|
317
|
+
record._read_attribute(pk)
|
|
318
|
+
end
|
|
319
|
+
self[fk] = association_id unless self[fk] == association_id
|
|
320
|
+
end
|
|
321
|
+
association.loaded!
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
saved if autosave
|
|
325
|
+
end
|
|
326
|
+
end
|
|
287
327
|
end
|
|
288
328
|
end
|
|
289
329
|
end
|
|
@@ -31,8 +31,16 @@ module Switchman
|
|
|
31
31
|
# and not the silly one in AR::AttributeMethods::PrimaryKey
|
|
32
32
|
return unless sharded_column?(@primary_key)
|
|
33
33
|
|
|
34
|
-
class_eval(
|
|
35
|
-
|
|
34
|
+
class_eval(
|
|
35
|
+
build_sharded_getter("id",
|
|
36
|
+
"_read_attribute(@primary_key)",
|
|
37
|
+
"::#{connection_class_for_self.name}"),
|
|
38
|
+
__FILE__,
|
|
39
|
+
__LINE__
|
|
40
|
+
)
|
|
41
|
+
class_eval(build_sharded_setter("id", @primary_key, "::#{connection_class_for_self.name}"),
|
|
42
|
+
__FILE__,
|
|
43
|
+
__LINE__)
|
|
36
44
|
end
|
|
37
45
|
|
|
38
46
|
protected
|
|
@@ -46,20 +54,16 @@ module Switchman
|
|
|
46
54
|
raise if connection.open_transactions.positive?
|
|
47
55
|
end
|
|
48
56
|
|
|
49
|
-
# rubocop:disable Naming/MethodParameterName
|
|
50
57
|
def define_cached_method(owner, name, namespace:, as:, &block)
|
|
51
|
-
|
|
52
|
-
yield owner
|
|
53
|
-
owner.rename_method(as, name)
|
|
54
|
-
else
|
|
55
|
-
owner.define_cached_method(name, namespace: namespace, as: as, &block)
|
|
56
|
-
end
|
|
58
|
+
owner.define_cached_method(name, namespace: namespace, as: as, &block)
|
|
57
59
|
end
|
|
58
|
-
# rubocop:enable Naming/MethodParameterName
|
|
59
60
|
|
|
60
61
|
def define_method_global_attribute(attr_name, owner:)
|
|
61
62
|
if sharded_column?(attr_name)
|
|
62
|
-
define_cached_method(owner,
|
|
63
|
+
define_cached_method(owner,
|
|
64
|
+
"global_#{attr_name}",
|
|
65
|
+
as: "sharded_global_#{attr_name}",
|
|
66
|
+
namespace: :switchman) do |batch|
|
|
63
67
|
batch << <<-RUBY
|
|
64
68
|
def sharded_global_#{attr_name}
|
|
65
69
|
raw_value = original_#{attr_name}
|
|
@@ -71,13 +75,16 @@ module Switchman
|
|
|
71
75
|
RUBY
|
|
72
76
|
end
|
|
73
77
|
else
|
|
74
|
-
define_method_unsharded_column(attr_name,
|
|
78
|
+
define_method_unsharded_column(attr_name, "global", owner)
|
|
75
79
|
end
|
|
76
80
|
end
|
|
77
81
|
|
|
78
82
|
def define_method_local_attribute(attr_name, owner:)
|
|
79
83
|
if sharded_column?(attr_name)
|
|
80
|
-
define_cached_method(owner,
|
|
84
|
+
define_cached_method(owner,
|
|
85
|
+
"local_#{attr_name}",
|
|
86
|
+
as: "sharded_local_#{attr_name}",
|
|
87
|
+
namespace: :switchman) do |batch|
|
|
81
88
|
batch << <<-RUBY
|
|
82
89
|
def sharded_local_#{attr_name}
|
|
83
90
|
raw_value = original_#{attr_name}
|
|
@@ -87,7 +94,7 @@ module Switchman
|
|
|
87
94
|
RUBY
|
|
88
95
|
end
|
|
89
96
|
else
|
|
90
|
-
define_method_unsharded_column(attr_name,
|
|
97
|
+
define_method_unsharded_column(attr_name, "local", owner)
|
|
91
98
|
end
|
|
92
99
|
end
|
|
93
100
|
|
|
@@ -99,7 +106,13 @@ module Switchman
|
|
|
99
106
|
if reflection.options[:polymorphic]
|
|
100
107
|
# a polymorphic association has to be discovered at runtime. This code ends up being something like
|
|
101
108
|
# context_type.&.constantize&.connection_class_for_self
|
|
102
|
-
|
|
109
|
+
<<~RUBY
|
|
110
|
+
begin
|
|
111
|
+
read_attribute(:#{reflection.foreign_type})&.constantize&.connection_class_for_self
|
|
112
|
+
rescue NameError
|
|
113
|
+
::ActiveRecord::Base
|
|
114
|
+
end
|
|
115
|
+
RUBY
|
|
103
116
|
else
|
|
104
117
|
# otherwise we can just return a symbol for the statically known type of the association
|
|
105
118
|
"::#{reflection.klass.connection_class_for_self.name}"
|
|
@@ -113,9 +126,14 @@ module Switchman
|
|
|
113
126
|
if sharded_column?(attr_name)
|
|
114
127
|
reflection = reflection_for_integer_attribute(attr_name)
|
|
115
128
|
class_name = connection_class_for_self_code_for_reflection(reflection)
|
|
116
|
-
safe_class_name = class_name.unpack1(
|
|
117
|
-
define_cached_method(owner,
|
|
118
|
-
|
|
129
|
+
safe_class_name = class_name.unpack1("h*")
|
|
130
|
+
define_cached_method(owner,
|
|
131
|
+
attr_name,
|
|
132
|
+
as: "sharded_#{safe_class_name}_#{attr_name}",
|
|
133
|
+
namespace: :switchman) do |batch|
|
|
134
|
+
batch << build_sharded_getter("sharded_#{safe_class_name}_#{attr_name}",
|
|
135
|
+
"original_#{attr_name}",
|
|
136
|
+
class_name)
|
|
119
137
|
end
|
|
120
138
|
else
|
|
121
139
|
define_cached_method(owner, attr_name, as: "plain_#{attr_name}", namespace: :switchman) do |batch|
|
|
@@ -136,7 +154,7 @@ module Switchman
|
|
|
136
154
|
|
|
137
155
|
abs_raw_value = raw_value.abs
|
|
138
156
|
current_shard = ::Switchman::Shard.current(#{attr_connection_class})
|
|
139
|
-
same_shard =
|
|
157
|
+
same_shard = loaded_from_shard == current_shard
|
|
140
158
|
return raw_value if same_shard && abs_raw_value < ::Switchman::Shard::IDS_PER_SHARD
|
|
141
159
|
|
|
142
160
|
value_shard_id = abs_raw_value / ::Switchman::Shard::IDS_PER_SHARD
|
|
@@ -144,9 +162,9 @@ module Switchman
|
|
|
144
162
|
# of a local id
|
|
145
163
|
return raw_value % ::Switchman::Shard::IDS_PER_SHARD if value_shard_id == current_shard.id
|
|
146
164
|
return raw_value if !same_shard && abs_raw_value > ::Switchman::Shard::IDS_PER_SHARD
|
|
147
|
-
return
|
|
165
|
+
return loaded_from_shard.global_id_for(raw_value) if !same_shard && abs_raw_value < ::Switchman::Shard::IDS_PER_SHARD
|
|
148
166
|
|
|
149
|
-
::Switchman::Shard.relative_id_for(raw_value,
|
|
167
|
+
::Switchman::Shard.relative_id_for(raw_value, loaded_from_shard, current_shard)
|
|
150
168
|
end
|
|
151
169
|
RUBY
|
|
152
170
|
end
|
|
@@ -155,8 +173,11 @@ module Switchman
|
|
|
155
173
|
if sharded_column?(attr_name)
|
|
156
174
|
reflection = reflection_for_integer_attribute(attr_name)
|
|
157
175
|
class_name = connection_class_for_self_code_for_reflection(reflection)
|
|
158
|
-
safe_class_name = class_name.unpack1(
|
|
159
|
-
define_cached_method(owner,
|
|
176
|
+
safe_class_name = class_name.unpack1("h*")
|
|
177
|
+
define_cached_method(owner,
|
|
178
|
+
"#{attr_name}=",
|
|
179
|
+
as: "sharded_#{safe_class_name}_#{attr_name}=",
|
|
180
|
+
namespace: :switchman) do |batch|
|
|
160
181
|
batch << build_sharded_setter("sharded_#{safe_class_name}_#{attr_name}", attr_name, class_name)
|
|
161
182
|
end
|
|
162
183
|
else
|
|
@@ -173,14 +194,17 @@ module Switchman
|
|
|
173
194
|
def build_sharded_setter(attr_name, attr_field, attr_connection_class)
|
|
174
195
|
<<-RUBY
|
|
175
196
|
def #{attr_name}=(new_value)
|
|
176
|
-
self.original_#{attr_field} = ::Switchman::Shard.relative_id_for(new_value, ::Switchman::Shard.current(#{attr_connection_class}),
|
|
197
|
+
self.original_#{attr_field} = ::Switchman::Shard.relative_id_for(new_value, ::Switchman::Shard.current(#{attr_connection_class}), loaded_from_shard)
|
|
177
198
|
end
|
|
178
199
|
RUBY
|
|
179
200
|
end
|
|
180
201
|
|
|
181
202
|
def define_method_original_attribute(attr_name, owner:)
|
|
182
203
|
if sharded_column?(attr_name)
|
|
183
|
-
define_cached_method(owner,
|
|
204
|
+
define_cached_method(owner,
|
|
205
|
+
"original_#{attr_name}",
|
|
206
|
+
as: "sharded_original_#{attr_name}",
|
|
207
|
+
namespace: :switchman) do |batch|
|
|
184
208
|
batch << <<-RUBY
|
|
185
209
|
def sharded_original_#{attr_name}
|
|
186
210
|
_read_attribute("#{attr_name}") { |n| missing_attribute(n, caller) }
|
|
@@ -188,14 +212,17 @@ module Switchman
|
|
|
188
212
|
RUBY
|
|
189
213
|
end
|
|
190
214
|
else
|
|
191
|
-
define_method_unsharded_column(attr_name,
|
|
215
|
+
define_method_unsharded_column(attr_name, "global", owner)
|
|
192
216
|
end
|
|
193
217
|
end
|
|
194
218
|
|
|
195
219
|
def define_method_original_attribute=(attr_name, owner:)
|
|
196
220
|
return unless sharded_column?(attr_name)
|
|
197
221
|
|
|
198
|
-
define_cached_method(owner,
|
|
222
|
+
define_cached_method(owner,
|
|
223
|
+
"original_#{attr_name}=",
|
|
224
|
+
as: "sharded_original_#{attr_name}=",
|
|
225
|
+
namespace: :switchman) do |batch|
|
|
199
226
|
batch << <<-RUBY
|
|
200
227
|
def sharded_original_#{attr_name}=(new_value)
|
|
201
228
|
_write_attribute('#{attr_name}', new_value)
|
|
@@ -205,9 +232,12 @@ module Switchman
|
|
|
205
232
|
end
|
|
206
233
|
|
|
207
234
|
def define_method_unsharded_column(attr_name, prefix, owner)
|
|
208
|
-
return if columns_hash["#{prefix}_#{attr_name}"] || attr_name ==
|
|
235
|
+
return if columns_hash["#{prefix}_#{attr_name}"] || attr_name == "id"
|
|
209
236
|
|
|
210
|
-
define_cached_method(owner,
|
|
237
|
+
define_cached_method(owner,
|
|
238
|
+
"#{prefix}_#{attr_name}",
|
|
239
|
+
as: "unsharded_#{prefix}_#{attr_name}",
|
|
240
|
+
namespace: :switchman) do |batch|
|
|
211
241
|
batch << <<-RUBY
|
|
212
242
|
def unsharded_#{prefix}_#{attr_name}
|
|
213
243
|
raise NoMethodError, "undefined method `#{prefix}_#{attr_name}'; are you missing an association?"
|
|
@@ -219,8 +249,8 @@ module Switchman
|
|
|
219
249
|
|
|
220
250
|
def self.prepended(klass)
|
|
221
251
|
klass.singleton_class.prepend(ClassMethods)
|
|
222
|
-
klass.attribute_method_prefix
|
|
223
|
-
klass.attribute_method_affix prefix:
|
|
252
|
+
klass.attribute_method_prefix "global_", "local_", "original_"
|
|
253
|
+
klass.attribute_method_affix prefix: "original_", suffix: "="
|
|
224
254
|
end
|
|
225
255
|
|
|
226
256
|
# these are called if the specific methods haven't been defined yet
|
|
@@ -228,7 +258,11 @@ module Switchman
|
|
|
228
258
|
return super unless self.class.sharded_column?(attr_name)
|
|
229
259
|
|
|
230
260
|
reflection = self.class.send(:reflection_for_integer_attribute, attr_name)
|
|
231
|
-
::Switchman::Shard.relative_id_for(
|
|
261
|
+
::Switchman::Shard.relative_id_for(
|
|
262
|
+
super,
|
|
263
|
+
shard,
|
|
264
|
+
::Switchman::Shard.current(connection_class_for_self_for_reflection(reflection))
|
|
265
|
+
)
|
|
232
266
|
end
|
|
233
267
|
|
|
234
268
|
def attribute=(attr_name, new_value)
|
|
@@ -238,7 +272,11 @@ module Switchman
|
|
|
238
272
|
end
|
|
239
273
|
|
|
240
274
|
reflection = self.class.send(:reflection_for_integer_attribute, attr_name)
|
|
241
|
-
super(
|
|
275
|
+
super(attr_name, ::Switchman::Shard.relative_id_for(
|
|
276
|
+
new_value,
|
|
277
|
+
::Switchman::Shard.current(connection_class_for_self_for_reflection(reflection)),
|
|
278
|
+
shard
|
|
279
|
+
))
|
|
242
280
|
end
|
|
243
281
|
|
|
244
282
|
def global_attribute(attr_name)
|
|
@@ -251,7 +289,7 @@ module Switchman
|
|
|
251
289
|
|
|
252
290
|
def local_attribute(attr_name)
|
|
253
291
|
if self.class.sharded_column?(attr_name)
|
|
254
|
-
::Switchman::Shard.local_id_for(attribute(attr_name)
|
|
292
|
+
::Switchman::Shard.local_id_for(attribute(attr_name)).first
|
|
255
293
|
else
|
|
256
294
|
attribute(attr_name)
|
|
257
295
|
end
|