active_record_shards 3.11.2 → 3.19.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 +5 -5
- data/README.md +120 -57
- data/lib/active_record_shards/association_collection_connection_selection.rb +23 -16
- data/lib/active_record_shards/configuration_parser.rb +19 -5
- data/lib/active_record_shards/connection_handler.rb +3 -8
- data/lib/active_record_shards/connection_pool.rb +1 -0
- data/lib/active_record_shards/connection_specification.rb +7 -16
- data/lib/active_record_shards/connection_switcher-4-2.rb +56 -0
- data/lib/active_record_shards/connection_switcher-5-0.rb +1 -1
- data/lib/active_record_shards/connection_switcher-5-1.rb +1 -1
- data/lib/active_record_shards/connection_switcher.rb +74 -57
- data/lib/active_record_shards/default_replica_patches.rb +278 -0
- data/lib/active_record_shards/default_slave_patches.rb +3 -148
- data/lib/active_record_shards/deprecation.rb +12 -0
- data/lib/active_record_shards/migration.rb +35 -31
- data/lib/active_record_shards/model.rb +17 -12
- data/lib/active_record_shards/patches-4-2.rb +1 -5
- data/lib/active_record_shards/schema_dumper_extension.rb +6 -5
- data/lib/active_record_shards/shard_selection.rb +28 -26
- data/lib/active_record_shards/shard_support.rb +1 -0
- data/lib/active_record_shards/sql_comments.rb +11 -4
- data/lib/active_record_shards/tasks.rb +40 -35
- data/lib/active_record_shards.rb +111 -7
- metadata +61 -58
- data/lib/active_record_shards/connection_switcher-4-0.rb +0 -68
- data/lib/active_record_shards/patches-3-2.rb +0 -11
- data/lib/active_record_shards/patches-5-0.rb +0 -15
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'active_record_shards/shard_support'
|
3
4
|
|
4
5
|
module ActiveRecordShards
|
5
6
|
module ConnectionSwitcher
|
6
|
-
SHARD_NAMES_CONFIG_KEY = 'shard_names'
|
7
|
+
SHARD_NAMES_CONFIG_KEY = 'shard_names'
|
7
8
|
|
8
9
|
def self.extended(base)
|
9
10
|
if ActiveRecord::VERSION::MAJOR >= 5
|
@@ -23,6 +24,10 @@ module ActiveRecordShards
|
|
23
24
|
switch_connection(shard: new_default_shard)
|
24
25
|
end
|
25
26
|
|
27
|
+
def on_primary_db(&block)
|
28
|
+
on_shard(nil, &block)
|
29
|
+
end
|
30
|
+
|
26
31
|
def on_shard(shard)
|
27
32
|
old_options = current_shard_selection.options
|
28
33
|
switch_connection(shard: shard) if supports_sharding?
|
@@ -31,9 +36,9 @@ module ActiveRecordShards
|
|
31
36
|
switch_connection(old_options)
|
32
37
|
end
|
33
38
|
|
34
|
-
def on_first_shard
|
39
|
+
def on_first_shard(&block)
|
35
40
|
shard_name = shard_names.first
|
36
|
-
on_shard(shard_name)
|
41
|
+
on_shard(shard_name, &block)
|
37
42
|
end
|
38
43
|
|
39
44
|
def shards
|
@@ -54,70 +59,79 @@ module ActiveRecordShards
|
|
54
59
|
switch_connection(old_options)
|
55
60
|
end
|
56
61
|
|
57
|
-
def
|
58
|
-
condition ?
|
62
|
+
def on_replica_if(condition, &block)
|
63
|
+
condition ? on_replica(&block) : yield
|
59
64
|
end
|
65
|
+
alias_method :on_slave_if, :on_replica_if
|
60
66
|
|
61
|
-
def
|
62
|
-
|
67
|
+
def on_replica_unless(condition, &block)
|
68
|
+
on_replica_if(!condition, &block)
|
63
69
|
end
|
70
|
+
alias_method :on_slave_unless, :on_replica_unless
|
64
71
|
|
65
|
-
def
|
66
|
-
condition ?
|
72
|
+
def on_primary_if(condition, &block)
|
73
|
+
condition ? on_primary(&block) : yield
|
67
74
|
end
|
75
|
+
alias_method :on_master_if, :on_primary_if
|
68
76
|
|
69
|
-
def
|
70
|
-
|
77
|
+
def on_primary_unless(condition, &block)
|
78
|
+
on_primary_if(!condition, &block)
|
71
79
|
end
|
80
|
+
alias_method :on_master_unless, :on_primary_unless
|
72
81
|
|
73
|
-
def
|
82
|
+
def on_primary_or_replica(which, &block)
|
74
83
|
if block_given?
|
75
84
|
on_cx_switch_block(which, &block)
|
76
85
|
else
|
77
|
-
|
86
|
+
PrimaryReplicaProxy.new(self, which)
|
78
87
|
end
|
79
88
|
end
|
89
|
+
alias_method :on_master_or_slave, :on_primary_or_replica
|
80
90
|
|
81
|
-
# Executes queries using the
|
82
|
-
# if you want to execute a block of code on the
|
83
|
-
# Account.
|
91
|
+
# Executes queries using the replica database. Fails over to primary if no replica is found.
|
92
|
+
# if you want to execute a block of code on the replica you can go:
|
93
|
+
# Account.on_replica do
|
84
94
|
# Account.first
|
85
95
|
# end
|
86
|
-
# the first account will be found on the
|
96
|
+
# the first account will be found on the replica DB
|
87
97
|
#
|
88
98
|
# For one-liners you can simply do
|
89
|
-
# Account.
|
90
|
-
def
|
91
|
-
|
99
|
+
# Account.on_replica.first
|
100
|
+
def on_replica(&block)
|
101
|
+
on_primary_or_replica(:replica, &block)
|
92
102
|
end
|
103
|
+
alias_method :on_slave, :on_replica
|
93
104
|
|
94
|
-
def
|
95
|
-
|
105
|
+
def on_primary(&block)
|
106
|
+
on_primary_or_replica(:primary, &block)
|
96
107
|
end
|
108
|
+
alias_method :on_master, :on_primary
|
97
109
|
|
98
110
|
# just to ease the transition from replica to active_record_shards
|
99
|
-
alias_method :with_slave, :
|
100
|
-
alias_method :with_slave_if, :
|
101
|
-
alias_method :with_slave_unless, :
|
111
|
+
alias_method :with_slave, :on_replica
|
112
|
+
alias_method :with_slave_if, :on_replica_if
|
113
|
+
alias_method :with_slave_unless, :on_replica_unless
|
102
114
|
|
103
115
|
def on_cx_switch_block(which, force: false, construct_ro_scope: nil, &block)
|
104
|
-
@
|
105
|
-
@
|
116
|
+
@disallow_replica ||= 0
|
117
|
+
@disallow_replica += 1 if [:primary, :master].include?(which)
|
118
|
+
|
119
|
+
ActiveRecordShards::Deprecation.warn('the `:master` option should be replaced with `:primary`!') if which == :master
|
106
120
|
|
107
|
-
|
121
|
+
switch_to_replica = force || @disallow_replica.zero?
|
108
122
|
old_options = current_shard_selection.options
|
109
123
|
|
110
|
-
switch_connection(
|
124
|
+
switch_connection(replica: switch_to_replica)
|
111
125
|
|
112
126
|
# we avoid_readonly_scope to prevent some stack overflow problems, like when
|
113
127
|
# .columns calls .with_scope which calls .columns and onward, endlessly.
|
114
|
-
if self == ActiveRecord::Base || !
|
128
|
+
if self == ActiveRecord::Base || !switch_to_replica || construct_ro_scope == false
|
115
129
|
yield
|
116
130
|
else
|
117
131
|
readonly.scoping(&block)
|
118
132
|
end
|
119
133
|
ensure
|
120
|
-
@
|
134
|
+
@disallow_replica -= 1 if [:primary, :master].include?(which)
|
121
135
|
switch_connection(old_options) if old_options
|
122
136
|
end
|
123
137
|
|
@@ -125,9 +139,10 @@ module ActiveRecordShards
|
|
125
139
|
shard_names.any?
|
126
140
|
end
|
127
141
|
|
128
|
-
def
|
129
|
-
current_shard_selection.
|
142
|
+
def on_replica?
|
143
|
+
current_shard_selection.on_replica?
|
130
144
|
end
|
145
|
+
alias_method :on_slave?, :on_replica?
|
131
146
|
|
132
147
|
def current_shard_selection
|
133
148
|
Thread.current[:shard_selection] ||= ShardSelection.new
|
@@ -139,8 +154,12 @@ module ActiveRecordShards
|
|
139
154
|
|
140
155
|
def shard_names
|
141
156
|
unless config = configurations[shard_env]
|
142
|
-
raise "Did not find #{shard_env} in configurations, did you forget to add it to your database
|
157
|
+
raise "Did not find #{shard_env} in configurations, did you forget to add it to your database config? (configurations: #{configurations.keys.inspect})"
|
143
158
|
end
|
159
|
+
unless config.fetch(SHARD_NAMES_CONFIG_KEY, []).all? { |shard_name| shard_name.is_a?(Integer) }
|
160
|
+
raise "All shard names must be integers: #{config[SHARD_NAMES_CONFIG_KEY].inspect}."
|
161
|
+
end
|
162
|
+
|
144
163
|
config[SHARD_NAMES_CONFIG_KEY] || []
|
145
164
|
end
|
146
165
|
|
@@ -148,11 +167,15 @@ module ActiveRecordShards
|
|
148
167
|
|
149
168
|
def switch_connection(options)
|
150
169
|
if options.any?
|
151
|
-
if options.key?(:
|
152
|
-
current_shard_selection.
|
170
|
+
if options.key?(:replica)
|
171
|
+
current_shard_selection.on_replica = options[:replica]
|
153
172
|
end
|
154
173
|
|
155
174
|
if options.key?(:shard)
|
175
|
+
unless configurations[shard_env]
|
176
|
+
raise "Did not find #{shard_env} in configurations, did you forget to add it to your database config? (configurations: #{configurations.keys.inspect})"
|
177
|
+
end
|
178
|
+
|
156
179
|
current_shard_selection.shard = options[:shard]
|
157
180
|
end
|
158
181
|
|
@@ -164,21 +187,13 @@ module ActiveRecordShards
|
|
164
187
|
ActiveRecordShards.rails_env
|
165
188
|
end
|
166
189
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
end
|
175
|
-
else
|
176
|
-
def with_default_shard
|
177
|
-
if is_sharded? && current_shard_id.nil? && table_name != ActiveRecord::Migrator.schema_migrations_table_name
|
178
|
-
on_first_shard { yield }
|
179
|
-
else
|
180
|
-
yield
|
181
|
-
end
|
190
|
+
# Make these few schema related methods available before having switched to
|
191
|
+
# a shard.
|
192
|
+
def with_default_shard(&block)
|
193
|
+
if is_sharded? && current_shard_id.nil? && table_name != ActiveRecord::SchemaMigration.table_name
|
194
|
+
on_first_shard(&block)
|
195
|
+
else
|
196
|
+
yield
|
182
197
|
end
|
183
198
|
end
|
184
199
|
|
@@ -196,25 +211,27 @@ module ActiveRecordShards
|
|
196
211
|
with_default_shard { table_exists_without_default_shard? }
|
197
212
|
end
|
198
213
|
|
199
|
-
class
|
214
|
+
class PrimaryReplicaProxy
|
200
215
|
def initialize(target, which)
|
201
216
|
@target = target
|
202
217
|
@which = which
|
203
218
|
end
|
204
219
|
|
205
|
-
def method_missing(method, *args, &block) # rubocop:disable Style/
|
206
|
-
@target.
|
220
|
+
def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
|
221
|
+
@target.on_primary_or_replica(@which) { @target.send(method, *args, &block) }
|
207
222
|
end
|
208
223
|
end
|
224
|
+
|
225
|
+
MasterSlaveProxy = PrimaryReplicaProxy
|
209
226
|
end
|
210
227
|
end
|
211
228
|
|
212
229
|
case "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
|
213
|
-
when '
|
214
|
-
require 'active_record_shards/connection_switcher-4-
|
230
|
+
when '4.2'
|
231
|
+
require 'active_record_shards/connection_switcher-4-2'
|
215
232
|
when '5.0'
|
216
233
|
require 'active_record_shards/connection_switcher-5-0'
|
217
|
-
when '5.1'
|
234
|
+
when '5.1', '5.2', '6.0'
|
218
235
|
require 'active_record_shards/connection_switcher-5-1'
|
219
236
|
else
|
220
237
|
raise "ActiveRecordShards is not compatible with #{ActiveRecord::VERSION::STRING}"
|
@@ -0,0 +1,278 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordShards
|
4
|
+
module DefaultReplicaPatches
|
5
|
+
def self.wrap_method_in_on_replica(class_method, base, method, force_on_replica: false)
|
6
|
+
base_methods =
|
7
|
+
if class_method
|
8
|
+
base.methods + base.private_methods
|
9
|
+
else
|
10
|
+
base.instance_methods + base.private_instance_methods
|
11
|
+
end
|
12
|
+
|
13
|
+
return unless base_methods.include?(method)
|
14
|
+
|
15
|
+
_, method, punctuation = method.to_s.match(/^(.*?)([\?\!]?)$/).to_a
|
16
|
+
# _ALWAYS_ on replica, or only for on `on_replica_by_default = true` models?
|
17
|
+
wrapper = force_on_replica ? 'force_on_replica' : 'on_replica_unless_tx'
|
18
|
+
base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
19
|
+
#{class_method ? 'class << self' : ''}
|
20
|
+
def #{method}_with_default_replica#{punctuation}(*args, &block)
|
21
|
+
#{wrapper} do
|
22
|
+
#{method}_without_default_replica#{punctuation}(*args, &block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
alias_method :#{method}_without_default_replica#{punctuation}, :#{method}#{punctuation}
|
27
|
+
alias_method :#{method}#{punctuation}, :#{method}_with_default_replica#{punctuation}
|
28
|
+
#{class_method ? 'end' : ''}
|
29
|
+
RUBY
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.wrap_method_in_on_slave(*args)
|
33
|
+
ActiveRecordShards::Deprecation.deprecation_warning(
|
34
|
+
:'self.wrap_method_in_on_slave',
|
35
|
+
:'self.wrap_method_in_on_replica'
|
36
|
+
)
|
37
|
+
wrap_method_in_on_replica(*args)
|
38
|
+
end
|
39
|
+
|
40
|
+
def transaction_with_replica_off(*args, &block)
|
41
|
+
if on_replica_by_default?
|
42
|
+
begin
|
43
|
+
old_val = Thread.current[:_active_record_shards_in_tx]
|
44
|
+
Thread.current[:_active_record_shards_in_tx] = true
|
45
|
+
transaction_without_replica_off(*args, &block)
|
46
|
+
ensure
|
47
|
+
Thread.current[:_active_record_shards_in_tx] = old_val
|
48
|
+
end
|
49
|
+
else
|
50
|
+
transaction_without_replica_off(*args, &block)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
alias_method :transaction_with_slave_off, :transaction_with_replica_off
|
54
|
+
|
55
|
+
module InstanceMethods
|
56
|
+
# fix ActiveRecord to do the right thing, and use our aliased quote_value
|
57
|
+
def quote_value(*args, &block)
|
58
|
+
self.class.quote_value(*args, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
def on_replica_unless_tx
|
62
|
+
self.class.on_replica_unless_tx { yield }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
CLASS_REPLICA_METHODS = [
|
67
|
+
:calculate,
|
68
|
+
:count_by_sql,
|
69
|
+
:exists?,
|
70
|
+
:find,
|
71
|
+
:find_by,
|
72
|
+
:find_by_sql,
|
73
|
+
:find_every,
|
74
|
+
:find_one,
|
75
|
+
:find_some,
|
76
|
+
:get_primary_key
|
77
|
+
].freeze
|
78
|
+
|
79
|
+
CLASS_FORCE_REPLICA_METHODS = [
|
80
|
+
:replace_bind_variable,
|
81
|
+
:replace_bind_variables,
|
82
|
+
:sanitize_sql_array,
|
83
|
+
:sanitize_sql_hash_for_assignment,
|
84
|
+
:table_exists?
|
85
|
+
].freeze
|
86
|
+
|
87
|
+
CLASS_SLAVE_METHODS = CLASS_REPLICA_METHODS
|
88
|
+
CLASS_FORCE_SLAVE_METHODS = CLASS_FORCE_REPLICA_METHODS
|
89
|
+
|
90
|
+
def self.extended(base)
|
91
|
+
CLASS_REPLICA_METHODS.each { |m| ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, m) }
|
92
|
+
CLASS_FORCE_REPLICA_METHODS.each { |m| ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, m, force_on_replica: true) }
|
93
|
+
|
94
|
+
if ActiveRecord::VERSION::MAJOR >= 5
|
95
|
+
ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, :load_schema!, force_on_replica: true)
|
96
|
+
else
|
97
|
+
ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, :columns, force_on_replica: true)
|
98
|
+
end
|
99
|
+
|
100
|
+
ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, :reload)
|
101
|
+
|
102
|
+
base.class_eval do
|
103
|
+
include InstanceMethods
|
104
|
+
|
105
|
+
class << self
|
106
|
+
alias_method :transaction_without_replica_off, :transaction
|
107
|
+
alias_method :transaction, :transaction_with_replica_off
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def on_replica_unless_tx(&block)
|
113
|
+
return yield if Thread.current[:_active_record_shards_in_migration]
|
114
|
+
return yield if Thread.current[:_active_record_shards_in_tx]
|
115
|
+
|
116
|
+
if on_replica_by_default?
|
117
|
+
on_replica(&block)
|
118
|
+
else
|
119
|
+
yield
|
120
|
+
end
|
121
|
+
end
|
122
|
+
alias_method :on_slave_unless_tx, :on_replica_unless_tx
|
123
|
+
|
124
|
+
def force_on_replica(&block)
|
125
|
+
return yield if Thread.current[:_active_record_shards_in_migration]
|
126
|
+
|
127
|
+
on_cx_switch_block(:replica, construct_ro_scope: false, force: true, &block)
|
128
|
+
end
|
129
|
+
|
130
|
+
module ActiveRelationPatches
|
131
|
+
def self.included(base)
|
132
|
+
[:calculate, :exists?, :pluck, :load].each do |m|
|
133
|
+
ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, m)
|
134
|
+
end
|
135
|
+
|
136
|
+
if ActiveRecord::VERSION::MAJOR == 4
|
137
|
+
# `where` and `having` clauses call `create_binds`, which will use the primary connection
|
138
|
+
ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, :create_binds, force_on_replica: true)
|
139
|
+
end
|
140
|
+
|
141
|
+
ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, :to_sql, force_on_replica: true)
|
142
|
+
end
|
143
|
+
|
144
|
+
def on_replica_unless_tx
|
145
|
+
@klass.on_replica_unless_tx { yield }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
module Rails52RelationPatches
|
150
|
+
def connection
|
151
|
+
return super if Thread.current[:_active_record_shards_in_migration]
|
152
|
+
return super if Thread.current[:_active_record_shards_in_tx]
|
153
|
+
|
154
|
+
if @klass.on_replica_by_default?
|
155
|
+
@klass.on_replica.connection
|
156
|
+
else
|
157
|
+
super
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# in rails 4.1+, they create a join class that's used to pull in records for HABTM.
|
163
|
+
# this simplifies the hell out of our existence, because all we have to do is inerit on-replica-by-default
|
164
|
+
# down from the parent now.
|
165
|
+
module Rails41HasAndBelongsToManyBuilderExtension
|
166
|
+
def self.included(base)
|
167
|
+
base.class_eval do
|
168
|
+
alias_method :through_model_without_inherit_default_replica_from_lhs, :through_model
|
169
|
+
alias_method :through_model, :through_model_with_inherit_default_replica_from_lhs
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def through_model_with_inherit_default_replica_from_lhs
|
174
|
+
model = through_model_without_inherit_default_replica_from_lhs
|
175
|
+
def model.on_replica_by_default?
|
176
|
+
left_reflection.klass.on_replica_by_default?
|
177
|
+
end
|
178
|
+
|
179
|
+
# also transfer the sharded-ness of the left table to the join model
|
180
|
+
model.not_sharded unless model.left_reflection.klass.is_sharded?
|
181
|
+
model
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
module AssociationsAssociationAssociationScopePatch
|
186
|
+
def association_scope
|
187
|
+
if klass
|
188
|
+
on_replica_unless_tx { super }
|
189
|
+
else
|
190
|
+
super
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def on_replica_unless_tx
|
195
|
+
klass.on_replica_unless_tx { yield }
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
module AssociationsAssociationFindTargetPatch
|
200
|
+
def find_target
|
201
|
+
if klass
|
202
|
+
on_replica_unless_tx { super }
|
203
|
+
else
|
204
|
+
super
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def on_replica_unless_tx
|
209
|
+
klass.on_replica_unless_tx { yield }
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
module AssociationsAssociationGetRecordsPatch
|
214
|
+
def get_records # rubocop:disable Naming/AccessorMethodName
|
215
|
+
if klass
|
216
|
+
on_replica_unless_tx { super }
|
217
|
+
else
|
218
|
+
super
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def on_replica_unless_tx
|
223
|
+
klass.on_replica_unless_tx { yield }
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
module AssociationsPreloaderAssociationAssociatedRecordsByOwnerPatch
|
228
|
+
def associated_records_by_owner(preloader)
|
229
|
+
if klass
|
230
|
+
on_replica_unless_tx { super }
|
231
|
+
else
|
232
|
+
super
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def on_replica_unless_tx
|
237
|
+
klass.on_replica_unless_tx { yield }
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
module AssociationsPreloaderAssociationLoadRecordsPatch
|
242
|
+
def load_records
|
243
|
+
if klass
|
244
|
+
on_replica_unless_tx { super }
|
245
|
+
else
|
246
|
+
super
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def on_replica_unless_tx
|
251
|
+
klass.on_replica_unless_tx { yield }
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
module TypeCasterConnectionConnectionPatch
|
256
|
+
def connection
|
257
|
+
return super if Thread.current[:_active_record_shards_in_migration]
|
258
|
+
return super if Thread.current[:_active_record_shards_in_tx]
|
259
|
+
|
260
|
+
if @klass.on_replica_by_default?
|
261
|
+
@klass.on_replica.connection
|
262
|
+
else
|
263
|
+
super
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
module SchemaDefinePatch
|
269
|
+
def define(info, &block)
|
270
|
+
old_val = Thread.current[:_active_record_shards_in_migration]
|
271
|
+
Thread.current[:_active_record_shards_in_migration] = true
|
272
|
+
super
|
273
|
+
ensure
|
274
|
+
Thread.current[:_active_record_shards_in_migration] = old_val
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -1,150 +1,5 @@
|
|
1
|
-
|
2
|
-
module ActiveRecordShards
|
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
|
1
|
+
ActiveRecordShards::Deprecation.warn('`DefaultSlavePatches` is deprecated, please use `DefaultReplicaPatches`.')
|
75
2
|
|
76
|
-
|
77
|
-
|
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
|
-
module HasAndBelongsToManyPreloaderPatches
|
114
|
-
def self.included(base)
|
115
|
-
ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, base, :records_for) rescue nil # rubocop:disable Style/RescueModifier
|
116
|
-
end
|
117
|
-
|
118
|
-
def on_slave_unless_tx
|
119
|
-
klass.on_slave_unless_tx { yield }
|
120
|
-
end
|
121
|
-
|
122
|
-
def exists_with_default_slave?(*args, &block)
|
123
|
-
on_slave_unless_tx { exists_without_default_slave?(*args, &block) }
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
# in rails 4.1+, they create a join class that's used to pull in records for HABTM.
|
128
|
-
# this simplifies the hell out of our existence, because all we have to do is inerit on-slave-by-default
|
129
|
-
# down from the parent now.
|
130
|
-
module Rails41HasAndBelongsToManyBuilderExtension
|
131
|
-
def self.included(base)
|
132
|
-
base.class_eval do
|
133
|
-
alias_method :through_model_without_inherit_default_slave_from_lhs, :through_model
|
134
|
-
alias_method :through_model, :through_model_with_inherit_default_slave_from_lhs
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
def through_model_with_inherit_default_slave_from_lhs
|
139
|
-
model = through_model_without_inherit_default_slave_from_lhs
|
140
|
-
def model.on_slave_by_default?
|
141
|
-
left_reflection.klass.on_slave_by_default?
|
142
|
-
end
|
143
|
-
|
144
|
-
# also transfer the sharded-ness of the left table to the join model
|
145
|
-
model.not_sharded unless model.left_reflection.klass.is_sharded?
|
146
|
-
model
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
3
|
+
module ActiveRecordShards
|
4
|
+
DefaultSlavePatches = DefaultReplicaPatches
|
150
5
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ActiveRecordShards
|
2
|
+
class Deprecation < ActiveSupport::Deprecation
|
3
|
+
# This allows us to define separate deprecation behavior for ActiveRecordShards, but defaults to
|
4
|
+
# the same behavior globally configured with ActiveSupport.
|
5
|
+
#
|
6
|
+
# For example, this allows us to silence our own deprecation warnings in test while still being
|
7
|
+
# able to fail tests for upstream deprecation warnings.
|
8
|
+
def behavior
|
9
|
+
@behavior ||= ActiveSupport::Deprecation.behavior
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|