switchman 2.0.13 → 3.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 +10 -2
- data/app/models/switchman/shard.rb +234 -271
- data/app/models/switchman/unsharded_record.rb +7 -0
- data/db/migrate/20130328212039_create_switchman_shards.rb +1 -1
- data/db/migrate/20130328224244_create_default_shard.rb +5 -5
- data/db/migrate/20161206323434_add_back_default_string_limits_switchman.rb +1 -0
- data/db/migrate/20180828183945_add_default_shard_index.rb +2 -2
- data/db/migrate/20180828192111_add_timestamps_to_shards.rb +7 -5
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +5 -3
- data/lib/switchman.rb +3 -5
- data/lib/switchman/action_controller/caching.rb +2 -2
- data/lib/switchman/active_record/abstract_adapter.rb +1 -0
- data/lib/switchman/active_record/association.rb +78 -89
- data/lib/switchman/active_record/attribute_methods.rb +58 -52
- data/lib/switchman/active_record/base.rb +58 -59
- data/lib/switchman/active_record/calculations.rb +74 -67
- data/lib/switchman/active_record/connection_pool.rb +14 -41
- data/lib/switchman/active_record/database_configurations.rb +34 -0
- data/lib/switchman/active_record/database_configurations/database_config.rb +13 -0
- data/lib/switchman/active_record/finder_methods.rb +11 -16
- data/lib/switchman/active_record/log_subscriber.rb +4 -8
- data/lib/switchman/active_record/migration.rb +6 -47
- data/lib/switchman/active_record/model_schema.rb +1 -1
- data/lib/switchman/active_record/persistence.rb +4 -6
- data/lib/switchman/active_record/postgresql_adapter.rb +124 -168
- data/lib/switchman/active_record/predicate_builder.rb +2 -2
- data/lib/switchman/active_record/query_cache.rb +18 -19
- data/lib/switchman/active_record/query_methods.rb +172 -197
- data/lib/switchman/active_record/reflection.rb +6 -10
- data/lib/switchman/active_record/relation.rb +30 -78
- data/lib/switchman/active_record/spawn_methods.rb +27 -29
- data/lib/switchman/active_record/statement_cache.rb +18 -35
- data/lib/switchman/active_record/tasks/database_tasks.rb +16 -0
- data/lib/switchman/active_support/cache.rb +3 -5
- data/lib/switchman/arel.rb +13 -8
- data/lib/switchman/database_server.rb +121 -142
- data/lib/switchman/default_shard.rb +52 -16
- data/lib/switchman/engine.rb +61 -58
- data/lib/switchman/environment.rb +4 -8
- data/lib/switchman/errors.rb +1 -0
- data/lib/switchman/guard_rail.rb +6 -19
- data/lib/switchman/guard_rail/relation.rb +5 -7
- data/lib/switchman/r_spec_helper.rb +29 -37
- data/lib/switchman/rails.rb +14 -12
- data/lib/switchman/schema_cache.rb +1 -9
- data/lib/switchman/sharded_instrumenter.rb +1 -1
- data/lib/switchman/standard_error.rb +15 -3
- data/lib/switchman/test_helper.rb +7 -11
- data/lib/switchman/version.rb +1 -1
- data/lib/tasks/switchman.rake +54 -69
- metadata +87 -45
- data/lib/switchman/active_record/batches.rb +0 -11
- data/lib/switchman/active_record/connection_handler.rb +0 -172
- data/lib/switchman/active_record/where_clause_factory.rb +0 -36
- data/lib/switchman/connection_pool_proxy.rb +0 -173
@@ -5,7 +5,7 @@ class CreateSwitchmanShards < ActiveRecord::Migration[4.2]
|
|
5
5
|
create_table :switchman_shards do |t|
|
6
6
|
t.string :name
|
7
7
|
t.string :database_server_id
|
8
|
-
t.boolean :default, :
|
8
|
+
t.boolean :default, default: false, null: false
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
class CreateDefaultShard < ActiveRecord::Migration[4.2]
|
4
4
|
def up
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
return if Switchman::Shard.default.is_a?(Switchman::Shard)
|
6
|
+
|
7
|
+
Switchman::Shard.reset_column_information
|
8
|
+
Switchman::Shard.create!(default: true)
|
9
|
+
Switchman::Shard.default(reload: true)
|
10
10
|
end
|
11
11
|
end
|
@@ -6,10 +6,10 @@ class AddDefaultShardIndex < ActiveRecord::Migration[4.2]
|
|
6
6
|
change_column_default :switchman_shards, :default, false
|
7
7
|
change_column_null :switchman_shards, :default, false
|
8
8
|
options = if connection.adapter_name == 'PostgreSQL'
|
9
|
-
{ unique: true, where: "
|
9
|
+
{ unique: true, where: '"default"' }
|
10
10
|
else
|
11
11
|
{}
|
12
12
|
end
|
13
|
-
add_index :switchman_shards, :default,
|
13
|
+
add_index :switchman_shards, :default, options
|
14
14
|
end
|
15
15
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class AddTimestampsToShards < ActiveRecord::Migration[4.2]
|
4
|
+
disable_ddl_transaction!
|
5
|
+
|
4
6
|
def change
|
5
7
|
add_timestamps :switchman_shards, null: true
|
6
8
|
now = Time.now.utc
|
@@ -8,10 +10,10 @@ class AddTimestampsToShards < ActiveRecord::Migration[4.2]
|
|
8
10
|
change_column_null :switchman_shards, :updated_at, false
|
9
11
|
change_column_null :switchman_shards, :created_at, false
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
return unless Switchman::Shard.current.default?
|
14
|
+
|
15
|
+
Switchman::Shard.connection.schema_cache.clear!
|
16
|
+
Switchman::Shard.reset_column_information
|
17
|
+
Switchman::Shard.columns
|
16
18
|
end
|
17
19
|
end
|
@@ -2,8 +2,10 @@
|
|
2
2
|
|
3
3
|
class AddUniqueNameIndexes < ActiveRecord::Migration[4.2]
|
4
4
|
def change
|
5
|
-
add_index :switchman_shards, [
|
6
|
-
add_index :switchman_shards, :database_server_id, unique: true, where:
|
7
|
-
|
5
|
+
add_index :switchman_shards, %i[database_server_id name], unique: true
|
6
|
+
add_index :switchman_shards, :database_server_id, unique: true, where: 'name IS NULL',
|
7
|
+
name: 'index_switchman_shards_unique_primary_shard'
|
8
|
+
add_index :switchman_shards, '(true)', unique: true, where: 'database_server_id IS NULL AND name IS NULL',
|
9
|
+
name: 'index_switchman_shards_unique_primary_db_and_shard'
|
8
10
|
end
|
9
11
|
end
|
data/lib/switchman.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require 'guard_rail'
|
4
|
+
require 'switchman/open4'
|
5
|
+
require 'switchman/engine'
|
6
6
|
|
7
7
|
module Switchman
|
8
8
|
def self.config
|
@@ -17,6 +17,4 @@ module Switchman
|
|
17
17
|
def self.cache=(cache)
|
18
18
|
@cache = cache
|
19
19
|
end
|
20
|
-
|
21
|
-
class OrderOnMultiShardQuery < RuntimeError; end
|
22
20
|
end
|
@@ -13,7 +13,7 @@ module Switchman
|
|
13
13
|
# disallow assigning to ActionController::Base.cache_store or
|
14
14
|
# ActionController::Base#cache_store for the same reasons we disallow
|
15
15
|
# assigning to Rails.cache
|
16
|
-
def cache_store=(
|
16
|
+
def cache_store=(_cache)
|
17
17
|
raise NoMethodError
|
18
18
|
end
|
19
19
|
end
|
@@ -21,7 +21,7 @@ module Switchman
|
|
21
21
|
include ConfigMethods
|
22
22
|
|
23
23
|
def self.included(base)
|
24
|
-
base.
|
24
|
+
base.singleton_class.prepend(ConfigMethods)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
@@ -8,27 +8,17 @@ module Switchman
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def build_record(*args)
|
11
|
-
|
11
|
+
shard.activate { super }
|
12
12
|
end
|
13
13
|
|
14
14
|
def load_target
|
15
|
-
|
15
|
+
shard.activate { super }
|
16
16
|
end
|
17
17
|
|
18
18
|
def scope
|
19
|
-
shard_value = @reflection.options[:multishard] ? @owner :
|
19
|
+
shard_value = @reflection.options[:multishard] ? @owner : shard
|
20
20
|
@owner.shard.activate { super.shard(shard_value, :association) }
|
21
21
|
end
|
22
|
-
|
23
|
-
def creation_attributes
|
24
|
-
attributes = super
|
25
|
-
|
26
|
-
# translate keys
|
27
|
-
if reflection.macro.in?([:has_one, :has_many]) && !options[:through]
|
28
|
-
attributes[reflection.foreign_key] = Shard.relative_id_for(owner[reflection.active_record_primary_key], owner.shard, self.shard)
|
29
|
-
end
|
30
|
-
attributes
|
31
|
-
end
|
32
22
|
end
|
33
23
|
|
34
24
|
module CollectionAssociation
|
@@ -36,15 +26,19 @@ module Switchman
|
|
36
26
|
shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards) ? owner.associated_shards : [shard]
|
37
27
|
# activate both the owner and the target's shard category, so that Reflection#join_id_for,
|
38
28
|
# when called for the owner, will be returned relative to shard the query will execute on
|
39
|
-
Shard.with_each_shard(shards, [klass.
|
29
|
+
Shard.with_each_shard(shards, [klass.connection_classes, owner.class.connection_classes].uniq) do
|
40
30
|
super
|
41
31
|
end
|
42
32
|
end
|
33
|
+
|
34
|
+
def _create_record(*)
|
35
|
+
shard.activate { super }
|
36
|
+
end
|
43
37
|
end
|
44
38
|
|
45
39
|
module BelongsToAssociation
|
46
|
-
def replace_keys(record)
|
47
|
-
if record
|
40
|
+
def replace_keys(record, force: false)
|
41
|
+
if record&.class&.sharded_column?(reflection.association_primary_key(record.class))
|
48
42
|
foreign_id = record[reflection.association_primary_key(record.class)]
|
49
43
|
owner[reflection.foreign_key] = Shard.relative_id_for(foreign_id, record.shard, owner.shard)
|
50
44
|
else
|
@@ -54,7 +48,7 @@ module Switchman
|
|
54
48
|
|
55
49
|
def shard
|
56
50
|
if @owner.class.sharded_column?(@reflection.foreign_key) &&
|
57
|
-
|
51
|
+
(foreign_id = @owner[@reflection.foreign_key])
|
58
52
|
Shard.shard_for(foreign_id, @owner.shard)
|
59
53
|
else
|
60
54
|
super
|
@@ -62,9 +56,22 @@ module Switchman
|
|
62
56
|
end
|
63
57
|
end
|
64
58
|
|
65
|
-
module
|
66
|
-
|
59
|
+
module ForeignAssociation
|
60
|
+
# significant change:
|
61
|
+
# * transpose the key to the correct shard
|
62
|
+
def set_owner_attributes(record) # rubocop:disable Naming/AccessorMethodName
|
63
|
+
return if options[:through]
|
64
|
+
|
65
|
+
key = owner._read_attribute(reflection.join_foreign_key)
|
66
|
+
key = Shard.relative_id_for(key, owner.shard, shard)
|
67
|
+
record._write_attribute(reflection.join_primary_key, key)
|
68
|
+
|
69
|
+
record._write_attribute(reflection.type, owner.class.polymorphic_name) if reflection.type
|
67
70
|
end
|
71
|
+
end
|
72
|
+
|
73
|
+
module Extension
|
74
|
+
def self.build(_model, _reflection); end
|
68
75
|
|
69
76
|
def self.valid_options
|
70
77
|
[:multishard]
|
@@ -75,45 +82,35 @@ module Switchman
|
|
75
82
|
|
76
83
|
module Preloader
|
77
84
|
module Association
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
85
|
+
# Copypasta from Activerecord but with added global_id_for goodness.
|
86
|
+
def records_for(ids)
|
87
|
+
scope.where(association_key_name => ids).load do |record|
|
88
|
+
global_key = if record.class.connection_classes == UnshardedRecord
|
89
|
+
convert_key(record[association_key_name])
|
90
|
+
else
|
91
|
+
Shard.global_id_for(record[association_key_name], record.shard)
|
92
|
+
end
|
93
|
+
owner = owners_by_key[convert_key(global_key)].first
|
94
|
+
association = owner.association(reflection.name)
|
95
|
+
association.set_inverse_instance(record)
|
83
96
|
end
|
84
97
|
end
|
85
98
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
Shard.global_id_for(record[association_key_name], record.shard)
|
94
|
-
end
|
95
|
-
owner = owners_by_key[global_key.to_s].first
|
96
|
-
association = owner.association(reflection.name)
|
97
|
-
association.set_inverse_instance(record)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def records_by_owner
|
102
|
-
associated_records_by_owner
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def associated_records_by_owner(preloader = nil)
|
107
|
-
return @associated_records_by_owner if defined?(@associated_records_by_owner)
|
108
|
-
owners_map = owners_by_key
|
99
|
+
# significant changes:
|
100
|
+
# * partition_by_shard the records_for call
|
101
|
+
# * re-globalize the fetched owner id before looking up in the map
|
102
|
+
def load_records
|
103
|
+
# owners can be duplicated when a relation has a collection association join
|
104
|
+
# #compare_by_identity makes such owners different hash keys
|
105
|
+
@records_by_owner = {}.compare_by_identity
|
109
106
|
|
110
|
-
if
|
111
|
-
|
107
|
+
if owner_keys.empty?
|
108
|
+
raw_records = []
|
112
109
|
else
|
113
110
|
# determine the shard to search for each owner
|
114
111
|
if reflection.macro == :belongs_to
|
115
112
|
# for belongs_to, it's the shard of the foreign_key
|
116
|
-
partition_proc =
|
113
|
+
partition_proc = lambda do |owner|
|
117
114
|
if owner.class.sharded_column?(owner_key_name)
|
118
115
|
Shard.shard_for(owner[owner_key_name], owner.shard)
|
119
116
|
else
|
@@ -123,64 +120,56 @@ module Switchman
|
|
123
120
|
elsif !reflection.options[:multishard]
|
124
121
|
# for non-multishard associations, it's *just* the owner's shard
|
125
122
|
partition_proc = ->(owner) { owner.shard }
|
126
|
-
else
|
127
|
-
# for multishard associations, it's the owner object itself
|
128
|
-
# (all associated shards)
|
129
|
-
|
130
|
-
# this is the default behavior of partition_by_shard, so just let it be nil
|
131
|
-
# to avoid the proc call
|
132
|
-
# partition_proc = ->(owner) { owner }
|
133
123
|
end
|
134
124
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
key = owner[owner_key_name]
|
142
|
-
if key && owner.class.sharded_column?(owner_key_name)
|
143
|
-
key = Shard.relative_id_for(key, owner.shard, Shard.current(owner.class.shard_category))
|
144
|
-
end
|
145
|
-
key && key.to_s
|
125
|
+
raw_records = Shard.partition_by_shard(owners, partition_proc) do |partitioned_owners|
|
126
|
+
relative_owner_keys = partitioned_owners.map do |owner|
|
127
|
+
key = owner[owner_key_name]
|
128
|
+
if key && owner.class.sharded_column?(owner_key_name)
|
129
|
+
key = Shard.relative_id_for(key, owner.shard,
|
130
|
+
Shard.current(klass.connection_classes))
|
146
131
|
end
|
147
|
-
|
148
|
-
relative_owner_keys.uniq!
|
149
|
-
records_for(relative_owner_keys)
|
132
|
+
convert_key(key)
|
150
133
|
end
|
134
|
+
relative_owner_keys.compact!
|
135
|
+
relative_owner_keys.uniq!
|
136
|
+
records_for(relative_owner_keys)
|
151
137
|
end
|
152
|
-
records.flatten!
|
153
138
|
end
|
154
139
|
|
155
|
-
|
156
|
-
|
157
|
-
# by that class (and its subclasses).
|
158
|
-
@preloaded_records = records
|
140
|
+
@preloaded_records = raw_records.select do |record|
|
141
|
+
assignments = false
|
159
142
|
|
160
|
-
# Each record may have multiple owners, and vice-versa
|
161
|
-
@associated_records_by_owner = owners.each_with_object({}) do |owner,h|
|
162
|
-
h[owner] = []
|
163
|
-
end
|
164
|
-
records.each do |record|
|
165
143
|
owner_key = record[association_key_name]
|
166
|
-
|
144
|
+
if owner_key && record.class.sharded_column?(association_key_name)
|
145
|
+
owner_key = Shard.global_id_for(owner_key,
|
146
|
+
record.shard)
|
147
|
+
end
|
167
148
|
|
168
|
-
|
169
|
-
owner
|
170
|
-
|
149
|
+
owners_by_key[convert_key(owner_key)].each do |owner|
|
150
|
+
entries = (@records_by_owner[owner] ||= [])
|
151
|
+
|
152
|
+
if reflection.collection? || entries.empty?
|
153
|
+
entries << record
|
154
|
+
assignments = true
|
155
|
+
end
|
171
156
|
end
|
157
|
+
|
158
|
+
assignments
|
172
159
|
end
|
173
|
-
@associated_records_by_owner
|
174
160
|
end
|
175
161
|
|
162
|
+
# significant change: globalize keys on sharded columns
|
176
163
|
def owners_by_key
|
177
|
-
@owners_by_key ||= owners.
|
164
|
+
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
|
178
165
|
key = owner[owner_key_name]
|
179
166
|
key = Shard.global_id_for(key, owner.shard) if key && owner.class.sharded_column?(owner_key_name)
|
180
|
-
key
|
167
|
+
key = convert_key(key)
|
168
|
+
(result[key] ||= []) << owner if key
|
181
169
|
end
|
182
170
|
end
|
183
171
|
|
172
|
+
# significant change: don't cache scope (since it could be for different shards)
|
184
173
|
def scope
|
185
174
|
build_scope
|
186
175
|
end
|
@@ -210,7 +199,7 @@ module Switchman
|
|
210
199
|
# this seems counter-intuitive, but the autosave code will assign to attribute bypassing switchman,
|
211
200
|
# after reading the id attribute _without_ bypassing switchman. So we need Shard.current for the
|
212
201
|
# category of the associated record to match Shard.current for the category of self
|
213
|
-
shard.activate(
|
202
|
+
shard.activate(connection_classes_for_reflection(reflection)) { super }
|
214
203
|
end
|
215
204
|
end
|
216
205
|
end
|
@@ -4,22 +4,23 @@ module Switchman
|
|
4
4
|
module ActiveRecord
|
5
5
|
module AttributeMethods
|
6
6
|
module ClassMethods
|
7
|
-
|
8
7
|
def sharded_primary_key?
|
9
|
-
self
|
8
|
+
!(self <= UnshardedRecord) && integral_id?
|
10
9
|
end
|
11
10
|
|
12
11
|
def sharded_foreign_key?(column_name)
|
13
12
|
reflection = reflection_for_integer_attribute(column_name.to_s)
|
14
13
|
return false unless reflection
|
14
|
+
|
15
15
|
reflection.options[:polymorphic] || reflection.klass.sharded_primary_key?
|
16
16
|
end
|
17
17
|
|
18
18
|
def sharded_column?(column_name)
|
19
19
|
column_name = column_name.to_s
|
20
20
|
@sharded_column_values ||= {}
|
21
|
-
unless @sharded_column_values.
|
22
|
-
@sharded_column_values[column_name] =
|
21
|
+
unless @sharded_column_values.key?(column_name)
|
22
|
+
@sharded_column_values[column_name] =
|
23
|
+
(column_name == primary_key && sharded_primary_key?) || sharded_foreign_key?(column_name)
|
23
24
|
end
|
24
25
|
@sharded_column_values[column_name]
|
25
26
|
end
|
@@ -29,109 +30,114 @@ module Switchman
|
|
29
30
|
def reflection_for_integer_attribute(attr_name)
|
30
31
|
attr_name = attr_name.to_s
|
31
32
|
columns_hash[attr_name] && columns_hash[attr_name].type == :integer &&
|
32
|
-
|
33
|
+
reflections.find { |_, r| r.belongs_to? && r.foreign_key.to_s == attr_name }&.last
|
33
34
|
rescue ::ActiveRecord::StatementInvalid
|
34
35
|
# this is for when models are referenced in initializers before migrations have been run
|
35
|
-
raise if connection.open_transactions
|
36
|
+
raise if connection.open_transactions.positive?
|
36
37
|
end
|
37
38
|
|
38
|
-
def define_method_global_attribute(attr_name)
|
39
|
+
def define_method_global_attribute(attr_name, owner:)
|
39
40
|
if sharded_column?(attr_name)
|
40
|
-
|
41
|
-
def
|
42
|
-
Shard.global_id_for(original_#{attr_name}, shard)
|
41
|
+
owner << <<-RUBY
|
42
|
+
def global_#{attr_name}
|
43
|
+
::Switchman::Shard.global_id_for(original_#{attr_name}, shard)
|
43
44
|
end
|
44
|
-
alias_method 'global_#{attr_name}', :__temp__
|
45
|
-
undef_method :__temp__
|
46
45
|
RUBY
|
47
46
|
else
|
48
|
-
define_method_unsharded_column(attr_name, 'global')
|
47
|
+
define_method_unsharded_column(attr_name, 'global', owner)
|
49
48
|
end
|
50
49
|
end
|
51
50
|
|
52
|
-
def define_method_local_attribute(attr_name)
|
51
|
+
def define_method_local_attribute(attr_name, owner:)
|
53
52
|
if sharded_column?(attr_name)
|
54
|
-
|
55
|
-
def
|
56
|
-
Shard.local_id_for(original_#{attr_name}).first
|
53
|
+
owner << <<-RUBY
|
54
|
+
def local_#{attr_name}
|
55
|
+
::Switchman::Shard.local_id_for(original_#{attr_name}).first
|
57
56
|
end
|
58
|
-
alias_method 'local_#{attr_name}', :__temp__
|
59
|
-
undef_method :__temp__
|
60
57
|
RUBY
|
61
58
|
else
|
62
|
-
define_method_unsharded_column(attr_name, 'local')
|
59
|
+
define_method_unsharded_column(attr_name, 'local', owner)
|
63
60
|
end
|
64
61
|
end
|
65
62
|
|
66
|
-
# see also Base#
|
63
|
+
# see also Base#connection_classes_for_reflection
|
67
64
|
# the difference being this will output static strings for the common cases, making them
|
68
65
|
# more performant
|
69
|
-
def
|
66
|
+
def connection_classes_code_for_reflection(reflection)
|
70
67
|
if reflection
|
71
68
|
if reflection.options[:polymorphic]
|
72
69
|
# a polymorphic association has to be discovered at runtime. This code ends up being something like
|
73
|
-
# context_type.&.constantize&.
|
74
|
-
"read_attribute(:#{reflection.foreign_type})&.constantize&.
|
70
|
+
# context_type.&.constantize&.connection_classes
|
71
|
+
"read_attribute(:#{reflection.foreign_type})&.constantize&.connection_classes"
|
75
72
|
else
|
76
73
|
# otherwise we can just return a symbol for the statically known type of the association
|
77
|
-
reflection.klass.
|
74
|
+
"::#{reflection.klass.connection_classes.name}"
|
78
75
|
end
|
79
76
|
else
|
80
|
-
|
77
|
+
"::#{connection_classes.name}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# just a dummy class with the proper interface that calls module_eval immediately
|
82
|
+
class CodeGenerator
|
83
|
+
def initialize(mod, line)
|
84
|
+
@module = mod
|
85
|
+
@line = line
|
86
|
+
end
|
87
|
+
|
88
|
+
def <<(string)
|
89
|
+
@module.module_eval(string, __FILE__, @line)
|
81
90
|
end
|
82
91
|
end
|
83
92
|
|
84
|
-
def define_method_original_attribute(attr_name)
|
93
|
+
def define_method_original_attribute(attr_name, owner:)
|
85
94
|
if sharded_column?(attr_name)
|
86
95
|
reflection = reflection_for_integer_attribute(attr_name)
|
87
|
-
if attr_name ==
|
88
|
-
return if
|
89
|
-
|
90
|
-
|
91
|
-
owner = generated_attribute_methods
|
96
|
+
if attr_name == 'id'
|
97
|
+
return if method_defined?(:original_id)
|
98
|
+
|
99
|
+
owner = CodeGenerator.new(self, __LINE__ + 4)
|
92
100
|
end
|
93
|
-
|
94
|
-
|
101
|
+
|
102
|
+
owner << <<-RUBY
|
103
|
+
# rename the original method to original_*
|
95
104
|
alias_method 'original_#{attr_name}', '#{attr_name}'
|
96
105
|
# and replace with one that transposes the id
|
97
|
-
def
|
98
|
-
Shard.relative_id_for(original_#{attr_name}, shard, Shard.current(#{
|
106
|
+
def #{attr_name}
|
107
|
+
::Switchman::Shard.relative_id_for(original_#{attr_name}, shard, ::Switchman::Shard.current(#{connection_classes_code_for_reflection(reflection)}))
|
99
108
|
end
|
100
|
-
alias_method '#{attr_name}', :__temp__
|
101
|
-
undef_method :__temp__
|
102
109
|
|
103
110
|
alias_method 'original_#{attr_name}=', '#{attr_name}='
|
104
|
-
def
|
105
|
-
self.original_#{attr_name} = Shard.relative_id_for(new_value, Shard.current(#{
|
111
|
+
def #{attr_name}=(new_value)
|
112
|
+
self.original_#{attr_name} = ::Switchman::Shard.relative_id_for(new_value, ::Switchman::Shard.current(#{connection_classes_code_for_reflection(reflection)}), shard)
|
106
113
|
end
|
107
|
-
alias_method '#{attr_name}=', :__temp__
|
108
|
-
undef_method :__temp__
|
109
114
|
RUBY
|
110
115
|
else
|
111
|
-
define_method_unsharded_column(attr_name, 'global')
|
116
|
+
define_method_unsharded_column(attr_name, 'global', owner)
|
112
117
|
end
|
113
118
|
end
|
114
119
|
|
115
|
-
def define_method_unsharded_column(attr_name, prefix)
|
120
|
+
def define_method_unsharded_column(attr_name, prefix, owner)
|
116
121
|
return if columns_hash["#{prefix}_#{attr_name}"]
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
undef_method :__temp__
|
122
|
+
|
123
|
+
owner << <<-RUBY
|
124
|
+
def #{prefix}_#{attr_name}
|
125
|
+
raise NoMethodError, "undefined method `#{prefix}_#{attr_name}'; are you missing an association?"
|
126
|
+
end
|
123
127
|
RUBY
|
124
128
|
end
|
125
129
|
end
|
126
130
|
|
127
131
|
def self.included(klass)
|
128
|
-
klass.
|
129
|
-
klass.attribute_method_prefix
|
132
|
+
klass.singleton_class.include(ClassMethods)
|
133
|
+
klass.attribute_method_prefix 'global_', 'local_', 'original_'
|
130
134
|
end
|
131
135
|
|
132
136
|
# ensure that we're using the sharded attribute method
|
133
137
|
# and not the silly one in AR::AttributeMethods::PrimaryKey
|
134
138
|
def id
|
139
|
+
return super if is_a?(Shard)
|
140
|
+
|
135
141
|
self.class.define_attribute_methods
|
136
142
|
super
|
137
143
|
end
|