active_record_shards 4.0.0.beta8 → 5.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +144 -57
- data/lib/active_record_shards/association_collection_connection_selection.rb +16 -14
- data/lib/active_record_shards/configuration_parser.rb +6 -5
- data/lib/active_record_shards/{connection_switcher_5_1.rb → connection_switcher-5-1.rb} +11 -1
- data/lib/active_record_shards/connection_switcher-6-0.rb +30 -0
- data/lib/active_record_shards/connection_switcher.rb +130 -46
- data/lib/active_record_shards/default_replica_patches.rb +237 -0
- data/lib/active_record_shards/default_shard.rb +27 -0
- data/lib/active_record_shards/migration.rb +124 -0
- data/lib/active_record_shards/model.rb +36 -21
- data/lib/active_record_shards/schema_dumper_extension.rb +42 -0
- data/lib/active_record_shards/shard_selection.rb +47 -7
- data/lib/active_record_shards/shard_support.rb +7 -7
- data/lib/active_record_shards/sql_comments.rb +11 -4
- data/lib/active_record_shards/tasks.rb +53 -29
- data/lib/active_record_shards.rb +60 -12
- metadata +65 -52
- data/lib/active_record_shards/base_config.rb +0 -26
- data/lib/active_record_shards/connection_resolver.rb +0 -31
- data/lib/active_record_shards/connection_switcher_5_0.rb +0 -19
- data/lib/active_record_shards/default_slave_patches.rb +0 -136
- data/lib/active_record_shards/no_shard_selection.rb +0 -18
- data/lib/active_record_shards/sharded_model.rb +0 -31
- data/lib/active_record_shards/slave_db.rb +0 -105
@@ -1,136 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
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
|
-
force_cx_switch_slave_block do
|
30
|
-
columns_without_force_slave(*args, &block)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def table_exists_with_force_slave?(*args, &block)
|
35
|
-
force_cx_switch_slave_block 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
|
136
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
module ActiveRecordShards
|
3
|
-
class NoShardSelection
|
4
|
-
class NoShardSelected < RuntimeError; end
|
5
|
-
|
6
|
-
def shard
|
7
|
-
raise NoShardSelected, "Missing shard information on connection"
|
8
|
-
end
|
9
|
-
|
10
|
-
def connection_config
|
11
|
-
raise NoShardSelected, "No shard selected, can't connect"
|
12
|
-
end
|
13
|
-
|
14
|
-
def on_shard?
|
15
|
-
false
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
module ActiveRecordShards
|
2
|
-
module Ext
|
3
|
-
module ShardedModel
|
4
|
-
def self.extended(base)
|
5
|
-
base.extend(ActiveRecordShards::ConnectionSwitcher)
|
6
|
-
base.include(InstanceMethods)
|
7
|
-
base.after_initialize :initialize_shard
|
8
|
-
end
|
9
|
-
|
10
|
-
def is_sharded? # rubocop:disable Naming/PredicateName
|
11
|
-
true
|
12
|
-
end
|
13
|
-
|
14
|
-
module InstanceMethods
|
15
|
-
def initialize_shard
|
16
|
-
@from_shard = self.class.current_shard_selection.shard
|
17
|
-
end
|
18
|
-
|
19
|
-
def from_shard
|
20
|
-
@from_shard
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
class ShardedModel < ActiveRecord::Base
|
27
|
-
self.abstract_class = true
|
28
|
-
|
29
|
-
extend ActiveRecordShards::Ext::ShardedModel
|
30
|
-
end
|
31
|
-
end
|
@@ -1,105 +0,0 @@
|
|
1
|
-
module ActiveRecordShards
|
2
|
-
module SlaveDb
|
3
|
-
def on_slave_if(condition, &block)
|
4
|
-
condition ? on_slave(&block) : yield
|
5
|
-
end
|
6
|
-
|
7
|
-
def on_slave_unless(condition, &block)
|
8
|
-
on_slave_if(!condition, &block)
|
9
|
-
end
|
10
|
-
|
11
|
-
def on_master_if(condition, &block)
|
12
|
-
condition ? on_master(&block) : yield
|
13
|
-
end
|
14
|
-
|
15
|
-
def on_master_unless(condition, &block)
|
16
|
-
on_master_if(!condition, &block)
|
17
|
-
end
|
18
|
-
|
19
|
-
def on_master_or_slave(which, &block)
|
20
|
-
if block_given?
|
21
|
-
on_cx_switch_block(which, &block)
|
22
|
-
else
|
23
|
-
MasterSlaveProxy.new(self, which)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def on_slave?
|
28
|
-
current_slave_selection
|
29
|
-
end
|
30
|
-
|
31
|
-
# Executes queries using the slave database. Fails over to master if no slave is found.
|
32
|
-
# if you want to execute a block of code on the slave you can go:
|
33
|
-
# Account.on_slave do
|
34
|
-
# Account.first
|
35
|
-
# end
|
36
|
-
# the first account will be found on the slave DB
|
37
|
-
#
|
38
|
-
# For one-liners you can simply do
|
39
|
-
# Account.on_slave.first
|
40
|
-
def on_slave(&block)
|
41
|
-
on_master_or_slave(:slave, &block)
|
42
|
-
end
|
43
|
-
|
44
|
-
def on_master(&block)
|
45
|
-
on_master_or_slave(:master, &block)
|
46
|
-
end
|
47
|
-
|
48
|
-
def force_cx_switch_slave_block
|
49
|
-
old_options = current_slave_selection
|
50
|
-
switch_slave_connection(slave: true)
|
51
|
-
yield
|
52
|
-
ensure
|
53
|
-
switch_slave_connection(slave: old_options)
|
54
|
-
end
|
55
|
-
|
56
|
-
def on_cx_switch_block(which, &block)
|
57
|
-
@disallow_slave ||= 0
|
58
|
-
@disallow_slave += 1 if which == :master
|
59
|
-
|
60
|
-
switch_to_slave = @disallow_slave.zero?
|
61
|
-
old_options = current_slave_selection
|
62
|
-
|
63
|
-
switch_slave_connection(slave: switch_to_slave)
|
64
|
-
|
65
|
-
# we avoid_readonly_scope to prevent some stack overflow problems, like when
|
66
|
-
# .columns calls .with_scope which calls .columns and onward, endlessly.
|
67
|
-
if self == ActiveRecord::Base || !switch_to_slave
|
68
|
-
yield
|
69
|
-
else
|
70
|
-
readonly.scoping(&block)
|
71
|
-
end
|
72
|
-
ensure
|
73
|
-
@disallow_slave -= 1 if which == :master
|
74
|
-
switch_slave_connection(slave: old_options)
|
75
|
-
end
|
76
|
-
|
77
|
-
def current_slave_selection=(on_slave)
|
78
|
-
Thread.current[:slave_selection] = on_slave
|
79
|
-
end
|
80
|
-
|
81
|
-
def current_slave_selection
|
82
|
-
!!Thread.current[:slave_selection]
|
83
|
-
end
|
84
|
-
|
85
|
-
def connection_config
|
86
|
-
super.merge(slave: current_slave_selection)
|
87
|
-
end
|
88
|
-
|
89
|
-
def switch_slave_connection(options)
|
90
|
-
self.current_slave_selection = options[:slave]
|
91
|
-
ensure_shard_connection
|
92
|
-
end
|
93
|
-
|
94
|
-
class MasterSlaveProxy
|
95
|
-
def initialize(target, which)
|
96
|
-
@target = target
|
97
|
-
@which = which
|
98
|
-
end
|
99
|
-
|
100
|
-
def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissing
|
101
|
-
@target.on_master_or_slave(@which) { @target.send(method, *args, &block) }
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|