active_record_shards 3.22.0 → 4.0.0.beta1
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 +5 -5
- data/README.md +57 -126
- data/lib/active_record_shards/association_collection_connection_selection.rb +14 -23
- data/lib/active_record_shards/configuration_parser.rb +6 -30
- data/lib/active_record_shards/connection_switcher-5-0.rb +0 -10
- data/lib/active_record_shards/connection_switcher-5-1.rb +0 -10
- data/lib/active_record_shards/connection_switcher.rb +71 -115
- data/lib/active_record_shards/default_slave_patches.rb +134 -3
- data/lib/active_record_shards/model.rb +16 -35
- data/lib/active_record_shards/shard_selection.rb +27 -66
- data/lib/active_record_shards/shard_support.rb +0 -1
- data/lib/active_record_shards/sharded_model.rb +15 -0
- data/lib/active_record_shards/sql_comments.rb +4 -11
- data/lib/active_record_shards/tasks.rb +22 -41
- data/lib/active_record_shards.rb +6 -126
- metadata +64 -74
- data/lib/active_record_shards/connection_handler.rb +0 -8
- data/lib/active_record_shards/connection_pool.rb +0 -36
- data/lib/active_record_shards/connection_specification.rb +0 -19
- data/lib/active_record_shards/connection_switcher-4-2.rb +0 -56
- data/lib/active_record_shards/connection_switcher-6-0.rb +0 -30
- data/lib/active_record_shards/default_replica_patches.rb +0 -278
- data/lib/active_record_shards/deprecation.rb +0 -12
- data/lib/active_record_shards/migration.rb +0 -125
- data/lib/active_record_shards/patches-4-2.rb +0 -9
- data/lib/active_record_shards/schema_dumper_extension.rb +0 -42
@@ -1,34 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
2
|
require 'active_record_shards/shard_support'
|
4
3
|
|
5
4
|
module ActiveRecordShards
|
6
5
|
module ConnectionSwitcher
|
7
|
-
SHARD_NAMES_CONFIG_KEY = 'shard_names'
|
6
|
+
SHARD_NAMES_CONFIG_KEY = 'shard_names'.freeze
|
8
7
|
|
9
8
|
def self.extended(base)
|
10
|
-
|
11
|
-
|
12
|
-
base.singleton_class.send(:alias_method, :load_schema!, :load_schema_with_default_shard!)
|
13
|
-
else
|
14
|
-
base.singleton_class.send(:alias_method, :columns_without_default_shard, :columns)
|
15
|
-
base.singleton_class.send(:alias_method, :columns, :columns_with_default_shard)
|
16
|
-
end
|
9
|
+
base.singleton_class.send(:alias_method, :load_schema_without_default_shard!, :load_schema!)
|
10
|
+
base.singleton_class.send(:alias_method, :load_schema!, :load_schema_with_default_shard!)
|
17
11
|
|
18
12
|
base.singleton_class.send(:alias_method, :table_exists_without_default_shard?, :table_exists?)
|
19
13
|
base.singleton_class.send(:alias_method, :table_exists?, :table_exists_with_default_shard?)
|
20
|
-
|
21
|
-
base.singleton_class.send(:alias_method, :reset_primary_key_without_default_shard, :reset_primary_key)
|
22
|
-
base.singleton_class.send(:alias_method, :reset_primary_key, :reset_primary_key_with_default_shard)
|
23
|
-
end
|
24
|
-
|
25
|
-
def default_shard=(new_default_shard)
|
26
|
-
ActiveRecordShards::ShardSelection.default_shard = new_default_shard
|
27
|
-
switch_connection(shard: new_default_shard)
|
28
|
-
end
|
29
|
-
|
30
|
-
def on_primary_db(&block)
|
31
|
-
on_shard(nil, &block)
|
32
14
|
end
|
33
15
|
|
34
16
|
def on_shard(shard)
|
@@ -39,9 +21,9 @@ module ActiveRecordShards
|
|
39
21
|
switch_connection(old_options)
|
40
22
|
end
|
41
23
|
|
42
|
-
def on_first_shard
|
24
|
+
def on_first_shard
|
43
25
|
shard_name = shard_names.first
|
44
|
-
on_shard(shard_name
|
26
|
+
on_shard(shard_name) { yield }
|
45
27
|
end
|
46
28
|
|
47
29
|
def shards
|
@@ -62,93 +44,88 @@ module ActiveRecordShards
|
|
62
44
|
switch_connection(old_options)
|
63
45
|
end
|
64
46
|
|
65
|
-
def
|
66
|
-
condition ?
|
47
|
+
def on_slave_if(condition, &block)
|
48
|
+
condition ? on_slave(&block) : yield
|
67
49
|
end
|
68
|
-
alias_method :on_slave_if, :on_replica_if
|
69
50
|
|
70
|
-
def
|
71
|
-
|
51
|
+
def on_slave_unless(condition, &block)
|
52
|
+
on_slave_if(!condition, &block)
|
72
53
|
end
|
73
|
-
alias_method :on_slave_unless, :on_replica_unless
|
74
54
|
|
75
|
-
def
|
76
|
-
condition ?
|
55
|
+
def on_master_if(condition, &block)
|
56
|
+
condition ? on_master(&block) : yield
|
77
57
|
end
|
78
|
-
alias_method :on_master_if, :on_primary_if
|
79
58
|
|
80
|
-
def
|
81
|
-
|
59
|
+
def on_master_unless(condition, &block)
|
60
|
+
on_master_if(!condition, &block)
|
82
61
|
end
|
83
|
-
alias_method :on_master_unless, :on_primary_unless
|
84
62
|
|
85
|
-
def
|
63
|
+
def on_master_or_slave(which, &block)
|
86
64
|
if block_given?
|
87
65
|
on_cx_switch_block(which, &block)
|
88
66
|
else
|
89
|
-
|
67
|
+
MasterSlaveProxy.new(self, which)
|
90
68
|
end
|
91
69
|
end
|
92
|
-
alias_method :on_master_or_slave, :on_primary_or_replica
|
93
70
|
|
94
|
-
# Executes queries using the
|
95
|
-
# if you want to execute a block of code on the
|
96
|
-
# Account.
|
71
|
+
# Executes queries using the slave database. Fails over to master if no slave is found.
|
72
|
+
# if you want to execute a block of code on the slave you can go:
|
73
|
+
# Account.on_slave do
|
97
74
|
# Account.first
|
98
75
|
# end
|
99
|
-
# the first account will be found on the
|
76
|
+
# the first account will be found on the slave DB
|
100
77
|
#
|
101
78
|
# For one-liners you can simply do
|
102
|
-
# Account.
|
103
|
-
def
|
104
|
-
|
79
|
+
# Account.on_slave.first
|
80
|
+
def on_slave(&block)
|
81
|
+
on_master_or_slave(:slave, &block)
|
105
82
|
end
|
106
|
-
alias_method :on_slave, :on_replica
|
107
83
|
|
108
|
-
def
|
109
|
-
|
84
|
+
def on_master(&block)
|
85
|
+
on_master_or_slave(:master, &block)
|
110
86
|
end
|
111
|
-
alias_method :on_master, :on_primary
|
112
|
-
|
113
|
-
# just to ease the transition from replica to active_record_shards
|
114
|
-
alias_method :with_slave, :on_replica
|
115
|
-
alias_method :with_slave_if, :on_replica_if
|
116
|
-
alias_method :with_slave_unless, :on_replica_unless
|
117
87
|
|
118
88
|
def on_cx_switch_block(which, force: false, construct_ro_scope: nil, &block)
|
119
|
-
@
|
120
|
-
@
|
89
|
+
@disallow_slave ||= 0
|
90
|
+
@disallow_slave += 1 if which == :master
|
121
91
|
|
122
|
-
|
123
|
-
|
124
|
-
switch_to_replica = force || @disallow_replica.zero?
|
92
|
+
switch_to_slave = force || @disallow_slave.zero?
|
125
93
|
old_options = current_shard_selection.options
|
126
94
|
|
127
|
-
switch_connection(
|
95
|
+
switch_connection(slave: switch_to_slave)
|
128
96
|
|
129
97
|
# we avoid_readonly_scope to prevent some stack overflow problems, like when
|
130
98
|
# .columns calls .with_scope which calls .columns and onward, endlessly.
|
131
|
-
if self == ActiveRecord::Base || !
|
99
|
+
if self == ActiveRecord::Base || !switch_to_slave || construct_ro_scope == false
|
132
100
|
yield
|
133
101
|
else
|
134
102
|
readonly.scoping(&block)
|
135
103
|
end
|
136
104
|
ensure
|
137
|
-
@
|
105
|
+
@disallow_slave -= 1 if which == :master
|
138
106
|
switch_connection(old_options) if old_options
|
139
107
|
end
|
140
108
|
|
109
|
+
def connection_specification_name
|
110
|
+
name = current_shard_selection.resolve_connection_name(sharded: is_sharded?, configurations: configurations)
|
111
|
+
|
112
|
+
unless configurations[name] || name == "primary"
|
113
|
+
raise ActiveRecord::AdapterNotSpecified, "No database defined by #{name} in your database config. (configurations: #{configurations.inspect})"
|
114
|
+
end
|
115
|
+
|
116
|
+
name
|
117
|
+
end
|
118
|
+
|
141
119
|
def supports_sharding?
|
142
120
|
shard_names.any?
|
143
121
|
end
|
144
122
|
|
145
|
-
def
|
146
|
-
current_shard_selection.
|
123
|
+
def on_slave?
|
124
|
+
current_shard_selection.on_slave?
|
147
125
|
end
|
148
|
-
alias_method :on_slave?, :on_replica?
|
149
126
|
|
150
127
|
def current_shard_selection
|
151
|
-
Thread.current[:shard_selection] ||= ShardSelection.new
|
128
|
+
Thread.current[:shard_selection] ||= ShardSelection.new(shard_names.first)
|
152
129
|
end
|
153
130
|
|
154
131
|
def current_shard_id
|
@@ -156,99 +133,78 @@ module ActiveRecordShards
|
|
156
133
|
end
|
157
134
|
|
158
135
|
def shard_names
|
159
|
-
unless
|
160
|
-
raise "
|
136
|
+
unless config = configurations[shard_env]
|
137
|
+
raise "Did not find #{shard_env} in configurations, did you forget to add it to your database config? (configurations: #{configurations.inspect})"
|
161
138
|
end
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
139
|
+
unless config[SHARD_NAMES_CONFIG_KEY]
|
140
|
+
raise "No shards configured for #{shard_env}"
|
141
|
+
end
|
142
|
+
unless config[SHARD_NAMES_CONFIG_KEY].all? { |shard_name| shard_name.is_a?(Integer) }
|
143
|
+
raise "All shard names must be integers: #{config[SHARD_NAMES_CONFIG_KEY].inspect}."
|
144
|
+
end
|
145
|
+
config[SHARD_NAMES_CONFIG_KEY]
|
168
146
|
end
|
169
147
|
|
170
148
|
private
|
171
149
|
|
172
|
-
def config_for_env
|
173
|
-
@_ars_config_for_env ||= {}
|
174
|
-
@_ars_config_for_env[shard_env] ||= begin
|
175
|
-
unless config = configurations[shard_env]
|
176
|
-
raise "Did not find #{shard_env} in configurations, did you forget to add it to your database config? (configurations: #{configurations.to_h.keys.inspect})"
|
177
|
-
end
|
178
|
-
|
179
|
-
config
|
180
|
-
end
|
181
|
-
end
|
182
|
-
alias_method :check_config_for_env, :config_for_env
|
183
|
-
|
184
150
|
def switch_connection(options)
|
185
151
|
if options.any?
|
186
|
-
if options.key?(:replica)
|
187
|
-
current_shard_selection.on_replica = options[:replica]
|
188
|
-
end
|
189
|
-
|
190
152
|
if options.key?(:shard)
|
191
|
-
|
192
|
-
|
153
|
+
unless config = configurations[shard_env]
|
154
|
+
raise "Did not find #{shard_env} in configurations, did you forget to add it to your database config? (configurations: #{configurations.inspect})"
|
155
|
+
end
|
156
|
+
unless config['shard_names'].include?(options[:shard])
|
157
|
+
raise "Did not find shard #{options[:shard]} in configurations"
|
158
|
+
end
|
193
159
|
current_shard_selection.shard = options[:shard]
|
194
160
|
end
|
195
161
|
|
162
|
+
if options.key?(:slave)
|
163
|
+
current_shard_selection.on_slave = options[:slave]
|
164
|
+
end
|
165
|
+
|
196
166
|
ensure_shard_connection
|
197
167
|
end
|
198
168
|
end
|
199
169
|
|
200
170
|
def shard_env
|
201
|
-
ActiveRecordShards.
|
171
|
+
ActiveRecordShards.rails_env
|
202
172
|
end
|
203
173
|
|
204
|
-
|
205
|
-
# a shard.
|
206
|
-
def with_default_shard(&block)
|
174
|
+
def with_default_shard
|
207
175
|
if is_sharded? && current_shard_id.nil? && table_name != ActiveRecord::SchemaMigration.table_name
|
208
|
-
on_first_shard
|
176
|
+
on_first_shard { yield }
|
209
177
|
else
|
210
178
|
yield
|
211
179
|
end
|
212
180
|
end
|
213
181
|
|
214
|
-
|
215
|
-
|
216
|
-
with_default_shard { load_schema_without_default_shard! }
|
217
|
-
end
|
218
|
-
else
|
219
|
-
def columns_with_default_shard
|
220
|
-
with_default_shard { columns_without_default_shard }
|
221
|
-
end
|
182
|
+
def load_schema_with_default_shard!
|
183
|
+
with_default_shard { load_schema_without_default_shard! }
|
222
184
|
end
|
223
185
|
|
224
186
|
def table_exists_with_default_shard?
|
225
187
|
with_default_shard { table_exists_without_default_shard? }
|
226
188
|
end
|
227
189
|
|
228
|
-
class
|
190
|
+
class MasterSlaveProxy
|
229
191
|
def initialize(target, which)
|
230
192
|
@target = target
|
231
193
|
@which = which
|
232
194
|
end
|
233
195
|
|
234
|
-
def method_missing(method, *args, &block) # rubocop:disable Style/
|
235
|
-
@target.
|
196
|
+
def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissing
|
197
|
+
@target.on_master_or_slave(@which) { @target.send(method, *args, &block) }
|
236
198
|
end
|
237
199
|
end
|
238
|
-
|
239
|
-
MasterSlaveProxy = PrimaryReplicaProxy
|
240
200
|
end
|
241
201
|
end
|
242
202
|
|
243
203
|
case "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
|
244
|
-
when '4.2'
|
245
|
-
require 'active_record_shards/connection_switcher-4-2'
|
246
204
|
when '5.0'
|
247
205
|
require 'active_record_shards/connection_switcher-5-0'
|
248
|
-
when '5.1'
|
206
|
+
when '5.1'
|
249
207
|
require 'active_record_shards/connection_switcher-5-1'
|
250
|
-
when '6.0'
|
251
|
-
require 'active_record_shards/connection_switcher-6-0'
|
252
208
|
else
|
253
209
|
raise "ActiveRecordShards is not compatible with #{ActiveRecord::VERSION::STRING}"
|
254
210
|
end
|
@@ -1,5 +1,136 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
3
2
|
module ActiveRecordShards
|
4
|
-
DefaultSlavePatches
|
3
|
+
module DefaultSlavePatches
|
4
|
+
def self.wrap_method_in_on_slave(class_method, base, method)
|
5
|
+
base_methods =
|
6
|
+
if class_method
|
7
|
+
base.methods + base.private_methods
|
8
|
+
else
|
9
|
+
base.instance_methods + base.private_instance_methods
|
10
|
+
end
|
11
|
+
|
12
|
+
return unless base_methods.include?(method)
|
13
|
+
_, method, punctuation = method.to_s.match(/^(.*?)([\?\!]?)$/).to_a
|
14
|
+
base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
15
|
+
#{class_method ? 'class << self' : ''}
|
16
|
+
def #{method}_with_default_slave#{punctuation}(*args, &block)
|
17
|
+
on_slave_unless_tx do
|
18
|
+
#{method}_without_default_slave#{punctuation}(*args, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
alias_method :#{method}_without_default_slave#{punctuation}, :#{method}#{punctuation}
|
23
|
+
alias_method :#{method}#{punctuation}, :#{method}_with_default_slave#{punctuation}
|
24
|
+
#{class_method ? 'end' : ''}
|
25
|
+
RUBY
|
26
|
+
end
|
27
|
+
|
28
|
+
def columns_with_force_slave(*args, &block)
|
29
|
+
on_cx_switch_block(:slave, construct_ro_scope: false, force: true) do
|
30
|
+
columns_without_force_slave(*args, &block)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def table_exists_with_force_slave?(*args, &block)
|
35
|
+
on_cx_switch_block(:slave, construct_ro_scope: false, force: true) do
|
36
|
+
table_exists_without_force_slave?(*args, &block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def transaction_with_slave_off(*args, &block)
|
41
|
+
if on_slave_by_default?
|
42
|
+
begin
|
43
|
+
old_val = Thread.current[:_active_record_shards_slave_off]
|
44
|
+
Thread.current[:_active_record_shards_slave_off] = true
|
45
|
+
transaction_without_slave_off(*args, &block)
|
46
|
+
ensure
|
47
|
+
Thread.current[:_active_record_shards_slave_off] = old_val
|
48
|
+
end
|
49
|
+
else
|
50
|
+
transaction_without_slave_off(*args, &block)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module InstanceMethods
|
55
|
+
# fix ActiveRecord to do the right thing, and use our aliased quote_value
|
56
|
+
def quote_value(*args, &block)
|
57
|
+
self.class.quote_value(*args, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def reload_with_slave_off(*args, &block)
|
61
|
+
self.class.on_master { reload_without_slave_off(*args, &block) }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
CLASS_SLAVE_METHODS = [:find_by_sql, :count_by_sql, :calculate, :find_one, :find_some, :find_every, :exists?].freeze
|
66
|
+
|
67
|
+
def self.extended(base)
|
68
|
+
CLASS_SLAVE_METHODS.each { |m| ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(true, base, m) }
|
69
|
+
|
70
|
+
base.class_eval do
|
71
|
+
include InstanceMethods
|
72
|
+
|
73
|
+
alias_method :reload_without_slave_off, :reload
|
74
|
+
alias_method :reload, :reload_with_slave_off
|
75
|
+
|
76
|
+
class << self
|
77
|
+
alias_method :columns_without_force_slave, :columns
|
78
|
+
alias_method :columns, :columns_with_force_slave
|
79
|
+
|
80
|
+
alias_method :table_exists_without_force_slave?, :table_exists?
|
81
|
+
alias_method :table_exists?, :table_exists_with_force_slave?
|
82
|
+
|
83
|
+
alias_method :transaction_without_slave_off, :transaction
|
84
|
+
alias_method :transaction, :transaction_with_slave_off
|
85
|
+
end
|
86
|
+
end
|
87
|
+
if ActiveRecord::Associations.const_defined?(:HasAndBelongsToManyAssociation)
|
88
|
+
ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, ActiveRecord::Associations::HasAndBelongsToManyAssociation, :construct_sql)
|
89
|
+
ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, ActiveRecord::Associations::HasAndBelongsToManyAssociation, :construct_find_options!)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def on_slave_unless_tx
|
94
|
+
if on_slave_by_default? && !Thread.current[:_active_record_shards_slave_off]
|
95
|
+
on_slave { yield }
|
96
|
+
else
|
97
|
+
yield
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
module ActiveRelationPatches
|
102
|
+
def self.included(base)
|
103
|
+
[:calculate, :exists?, :pluck, :find_with_associations].each do |m|
|
104
|
+
ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, base, m)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def on_slave_unless_tx
|
109
|
+
@klass.on_slave_unless_tx { yield }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# in rails 4.1+, they create a join class that's used to pull in records for HABTM.
|
114
|
+
# this simplifies the hell out of our existence, because all we have to do is inerit on-slave-by-default
|
115
|
+
# down from the parent now.
|
116
|
+
module HasAndBelongsToManyBuilderExtension
|
117
|
+
def self.included(base)
|
118
|
+
base.class_eval do
|
119
|
+
alias_method :through_model_without_inherit_default_slave_from_lhs, :through_model
|
120
|
+
alias_method :through_model, :through_model_with_inherit_default_slave_from_lhs
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def through_model_with_inherit_default_slave_from_lhs
|
125
|
+
model = through_model_without_inherit_default_slave_from_lhs
|
126
|
+
def model.on_slave_by_default?
|
127
|
+
left_reflection.klass.on_slave_by_default?
|
128
|
+
end
|
129
|
+
|
130
|
+
model.extend(ActiveRecordShards::Ext::ShardedModel) if model.left_reflection.klass.is_sharded?
|
131
|
+
|
132
|
+
model
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
5
136
|
end
|
@@ -3,59 +3,44 @@
|
|
3
3
|
module ActiveRecordShards
|
4
4
|
module Model
|
5
5
|
def not_sharded
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
self.sharded = false
|
6
|
+
ActiveSupport::Deprecation.warn("Calling not_sharded is deprecated. "\
|
7
|
+
"Please ensure to still read from the "\
|
8
|
+
"account db slave after removing the "\
|
9
|
+
"call.")
|
11
10
|
end
|
12
11
|
|
13
12
|
def is_sharded? # rubocop:disable Naming/PredicateName
|
14
|
-
|
15
|
-
sharded != false && supports_sharding?
|
16
|
-
elsif self == base_class
|
17
|
-
if sharded.nil?
|
18
|
-
ActiveRecord::Base.is_sharded?
|
19
|
-
else
|
20
|
-
sharded != false
|
21
|
-
end
|
22
|
-
else
|
23
|
-
base_class.is_sharded?
|
24
|
-
end
|
13
|
+
false
|
25
14
|
end
|
26
15
|
|
27
|
-
def
|
16
|
+
def on_slave_by_default?
|
28
17
|
if self == ActiveRecord::Base
|
29
18
|
false
|
30
19
|
else
|
31
20
|
base = base_class
|
32
|
-
if base.instance_variable_defined?(:@
|
33
|
-
base.instance_variable_get(:@
|
21
|
+
if base.instance_variable_defined?(:@on_slave_by_default)
|
22
|
+
base.instance_variable_get(:@on_slave_by_default)
|
34
23
|
end
|
35
24
|
end
|
36
25
|
end
|
37
|
-
alias_method :on_slave_by_default?, :on_replica_by_default?
|
38
26
|
|
39
|
-
def
|
27
|
+
def on_slave_by_default=(value)
|
40
28
|
if self == ActiveRecord::Base
|
41
|
-
raise ArgumentError, "Cannot set
|
29
|
+
raise ArgumentError, "Cannot set on_slave_by_default on ActiveRecord::Base"
|
42
30
|
else
|
43
|
-
base_class.instance_variable_set(:@
|
31
|
+
base_class.instance_variable_set(:@on_slave_by_default, value)
|
44
32
|
end
|
45
33
|
end
|
46
|
-
alias_method :on_slave_by_default=, :on_replica_by_default=
|
47
34
|
|
48
35
|
module InstanceMethods
|
49
|
-
def
|
50
|
-
@
|
36
|
+
def initialize_shard_and_slave
|
37
|
+
@from_slave = !!self.class.current_shard_selection.options[:slave]
|
51
38
|
@from_shard = self.class.current_shard_selection.options[:shard]
|
52
39
|
end
|
53
|
-
alias_method :initialize_shard_and_slave, :initialize_shard_and_replica
|
54
40
|
|
55
|
-
def
|
56
|
-
@
|
41
|
+
def from_slave?
|
42
|
+
@from_slave
|
57
43
|
end
|
58
|
-
alias_method :from_slave?, :from_replica?
|
59
44
|
|
60
45
|
def from_shard
|
61
46
|
@from_shard
|
@@ -64,11 +49,7 @@ module ActiveRecordShards
|
|
64
49
|
|
65
50
|
def self.extended(base)
|
66
51
|
base.send(:include, InstanceMethods)
|
67
|
-
base.after_initialize :
|
52
|
+
base.after_initialize :initialize_shard_and_slave
|
68
53
|
end
|
69
|
-
|
70
|
-
private
|
71
|
-
|
72
|
-
attr_accessor :sharded
|
73
54
|
end
|
74
55
|
end
|
@@ -1,91 +1,52 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
2
|
module ActiveRecordShards
|
4
3
|
class ShardSelection
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
def initialize
|
9
|
-
@on_replica = false
|
10
|
-
@shard = nil
|
4
|
+
def initialize(shard)
|
5
|
+
@on_slave = false
|
6
|
+
self.shard = shard
|
11
7
|
end
|
12
8
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
if @shard == NO_SHARD
|
18
|
-
nil
|
19
|
-
else
|
20
|
-
@shard || self.class.default_shard
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def shard_name(klass = nil, try_replica = true)
|
26
|
-
the_shard = shard(klass)
|
27
|
-
|
28
|
-
@shard_names ||= {}
|
29
|
-
@shard_names[ActiveRecordShards.app_env] ||= {}
|
30
|
-
@shard_names[ActiveRecordShards.app_env][the_shard] ||= {}
|
31
|
-
@shard_names[ActiveRecordShards.app_env][the_shard][try_replica] ||= {}
|
32
|
-
@shard_names[ActiveRecordShards.app_env][the_shard][try_replica][@on_replica] ||= begin
|
33
|
-
s = ActiveRecordShards.app_env.dup
|
34
|
-
s << "_shard_#{the_shard}" if the_shard
|
35
|
-
s << "_replica" if @on_replica && try_replica
|
36
|
-
s
|
37
|
-
end
|
38
|
-
end
|
9
|
+
def shard
|
10
|
+
raise "Missing shard information on connection" unless @shard
|
11
|
+
@shard
|
12
|
+
end
|
39
13
|
|
40
|
-
|
14
|
+
PRIMARY = "primary".freeze
|
15
|
+
def resolve_connection_name(sharded:, configurations:)
|
16
|
+
resolved_shard = sharded ? shard : nil
|
41
17
|
|
42
|
-
|
43
|
-
|
44
|
-
nil
|
45
|
-
else
|
46
|
-
@shard || self.class.default_shard
|
47
|
-
end
|
18
|
+
if !resolved_shard && !@on_slave
|
19
|
+
return PRIMARY
|
48
20
|
end
|
49
21
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
22
|
+
@shard_names ||= {}
|
23
|
+
@shard_names[ActiveRecordShards.rails_env] ||= {}
|
24
|
+
@shard_names[ActiveRecordShards.rails_env][resolved_shard] ||= {}
|
25
|
+
@shard_names[ActiveRecordShards.rails_env][resolved_shard][@on_slave] ||= begin
|
26
|
+
s = ActiveRecordShards.rails_env.dup
|
27
|
+
s << "_shard_#{resolved_shard}" if resolved_shard
|
54
28
|
|
55
|
-
@
|
56
|
-
|
57
|
-
@connection_names[env][resolved_shard] ||= {}
|
58
|
-
@connection_names[env][resolved_shard][@on_replica] ||= begin
|
59
|
-
name = env.dup
|
60
|
-
name << "_shard_#{resolved_shard}" if resolved_shard
|
61
|
-
if @on_replica && configurations["#{name}_replica"]
|
62
|
-
"#{name}_replica"
|
63
|
-
else
|
64
|
-
# ActiveRecord always names its default connection pool 'primary'
|
65
|
-
# while everything else is named by the configuration name
|
66
|
-
resolved_shard ? name : PRIMARY
|
67
|
-
end
|
29
|
+
if @on_slave && configurations["#{s}_slave"] # fall back to master connection
|
30
|
+
s << "_slave"
|
68
31
|
end
|
32
|
+
s
|
69
33
|
end
|
70
|
-
|
71
34
|
end
|
72
35
|
|
73
36
|
def shard=(new_shard)
|
74
|
-
@shard = (new_shard
|
37
|
+
@shard = Integer(new_shard)
|
75
38
|
end
|
76
39
|
|
77
|
-
def
|
78
|
-
@
|
40
|
+
def on_slave?
|
41
|
+
@on_slave
|
79
42
|
end
|
80
|
-
alias_method :on_slave?, :on_replica?
|
81
43
|
|
82
|
-
def
|
83
|
-
@
|
44
|
+
def on_slave=(new_slave)
|
45
|
+
@on_slave = (new_slave == true)
|
84
46
|
end
|
85
|
-
alias_method :on_slave=, :on_replica=
|
86
47
|
|
87
48
|
def options
|
88
|
-
{ shard: @shard,
|
49
|
+
{ shard: @shard, slave: @on_slave }
|
89
50
|
end
|
90
51
|
end
|
91
52
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ActiveRecordShards
|
2
|
+
module Ext
|
3
|
+
module ShardedModel
|
4
|
+
def is_sharded? # rubocop:disable Naming/PredicateName
|
5
|
+
true
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class ShardedModel < ActiveRecord::Base
|
11
|
+
self.abstract_class = true
|
12
|
+
|
13
|
+
extend ActiveRecordShards::Ext::ShardedModel
|
14
|
+
end
|
15
|
+
end
|