active_record_shards 3.15.1 → 3.18.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,171 @@
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 columns_with_force_replica(*args, &block)
41
+ on_cx_switch_block(:replica, construct_ro_scope: false, force: true) do
42
+ columns_without_force_replica(*args, &block)
43
+ end
44
+ end
45
+ alias_method :columns_with_force_slave, :columns_with_force_replica
46
+
47
+ def table_exists_with_force_replica?(*args, &block)
48
+ on_cx_switch_block(:replica, construct_ro_scope: false, force: true) do
49
+ table_exists_without_force_replica?(*args, &block)
50
+ end
51
+ end
52
+ alias_method :table_exists_with_force_slave?, :table_exists_with_force_replica?
53
+
54
+ def transaction_with_replica_off(*args, &block)
55
+ if on_replica_by_default?
56
+ begin
57
+ old_val = Thread.current[:_active_record_shards_replica_off]
58
+ Thread.current[:_active_record_shards_replica_off] = true
59
+ transaction_without_replica_off(*args, &block)
60
+ ensure
61
+ Thread.current[:_active_record_shards_replica_off] = old_val
62
+ end
63
+ else
64
+ transaction_without_replica_off(*args, &block)
65
+ end
66
+ end
67
+ alias_method :transaction_with_slave_off, :transaction_with_replica_off
68
+
69
+ module InstanceMethods
70
+ # fix ActiveRecord to do the right thing, and use our aliased quote_value
71
+ def quote_value(*args, &block)
72
+ self.class.quote_value(*args, &block)
73
+ end
74
+ end
75
+
76
+ CLASS_REPLICA_METHODS = [
77
+ :calculate,
78
+ :count_by_sql,
79
+ :exists?,
80
+ :find_by_sql,
81
+ :find_every,
82
+ :find_one,
83
+ :find_some
84
+ ].freeze
85
+
86
+ CLASS_FORCE_REPLICA_METHODS = [
87
+ :columns,
88
+ :replace_bind_variable,
89
+ :replace_bind_variables,
90
+ :sanitize_sql_array,
91
+ :sanitize_sql_hash_for_assignment,
92
+ :table_exists?
93
+ ].freeze
94
+
95
+ CLASS_SLAVE_METHODS = CLASS_REPLICA_METHODS
96
+ CLASS_FORCE_SLAVE_METHODS = CLASS_FORCE_REPLICA_METHODS
97
+
98
+ def self.extended(base)
99
+ CLASS_REPLICA_METHODS.each { |m| ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, m) }
100
+ CLASS_FORCE_SLAVE_METHODS.each { |m| ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, m, force_on_replica: true) }
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
+ if ActiveRecord::Associations.const_defined?(:HasAndBelongsToManyAssociation)
111
+ ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, ActiveRecord::Associations::HasAndBelongsToManyAssociation, :construct_sql)
112
+ ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, ActiveRecord::Associations::HasAndBelongsToManyAssociation, :construct_find_options!)
113
+ end
114
+ end
115
+
116
+ def on_replica_unless_tx
117
+ if on_replica_by_default? && !Thread.current[:_active_record_shards_replica_off]
118
+ on_replica { yield }
119
+ else
120
+ yield
121
+ end
122
+ end
123
+ alias_method :on_slave_unless_tx, :on_replica_unless_tx
124
+
125
+ def force_on_replica(&block)
126
+ on_cx_switch_block(:replica, construct_ro_scope: false, force: true, &block)
127
+ end
128
+
129
+ module ActiveRelationPatches
130
+ def self.included(base)
131
+ [:calculate, :exists?, :pluck, :load].each do |m|
132
+ ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, m)
133
+ end
134
+
135
+ if ActiveRecord::VERSION::MAJOR == 4
136
+ # `where` and `having` clauses call `create_binds`, which will use the primary connection
137
+ ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, :create_binds, force_on_replica: true)
138
+ end
139
+
140
+ ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, :to_sql, force_on_replica: true)
141
+ end
142
+
143
+ def on_replica_unless_tx
144
+ @klass.on_replica_unless_tx { yield }
145
+ end
146
+ end
147
+
148
+ # in rails 4.1+, they create a join class that's used to pull in records for HABTM.
149
+ # this simplifies the hell out of our existence, because all we have to do is inerit on-replica-by-default
150
+ # down from the parent now.
151
+ module Rails41HasAndBelongsToManyBuilderExtension
152
+ def self.included(base)
153
+ base.class_eval do
154
+ alias_method :through_model_without_inherit_default_replica_from_lhs, :through_model
155
+ alias_method :through_model, :through_model_with_inherit_default_replica_from_lhs
156
+ end
157
+ end
158
+
159
+ def through_model_with_inherit_default_replica_from_lhs
160
+ model = through_model_without_inherit_default_replica_from_lhs
161
+ def model.on_replica_by_default?
162
+ left_reflection.klass.on_replica_by_default?
163
+ end
164
+
165
+ # also transfer the sharded-ness of the left table to the join model
166
+ model.not_sharded unless model.left_reflection.klass.is_sharded?
167
+ model
168
+ end
169
+ end
170
+ end
171
+ end
@@ -1,129 +1,5 @@
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
- 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
- end
60
-
61
- CLASS_SLAVE_METHODS = [:find_by_sql, :count_by_sql, :calculate, :find_one, :find_some, :find_every, :exists?].freeze
62
-
63
- def self.extended(base)
64
- CLASS_SLAVE_METHODS.each { |m| ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(true, base, m) }
65
-
66
- base.class_eval do
67
- include InstanceMethods
1
+ ActiveRecordShards::Deprecation.warn('`DefaultSlavePatches` is deprecated, please use `DefaultReplicaPatches`.')
68
2
 
69
- class << self
70
- alias_method :columns_without_force_slave, :columns
71
- alias_method :columns, :columns_with_force_slave
72
-
73
- alias_method :table_exists_without_force_slave?, :table_exists?
74
- alias_method :table_exists?, :table_exists_with_force_slave?
75
-
76
- alias_method :transaction_without_slave_off, :transaction
77
- alias_method :transaction, :transaction_with_slave_off
78
- end
79
- end
80
- if ActiveRecord::Associations.const_defined?(:HasAndBelongsToManyAssociation)
81
- ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, ActiveRecord::Associations::HasAndBelongsToManyAssociation, :construct_sql)
82
- ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, ActiveRecord::Associations::HasAndBelongsToManyAssociation, :construct_find_options!)
83
- end
84
- end
85
-
86
- def on_slave_unless_tx
87
- if on_slave_by_default? && !Thread.current[:_active_record_shards_slave_off]
88
- on_slave { yield }
89
- else
90
- yield
91
- end
92
- end
93
-
94
- module ActiveRelationPatches
95
- def self.included(base)
96
- [:calculate, :exists?, :pluck, :load].each do |m|
97
- ActiveRecordShards::DefaultSlavePatches.wrap_method_in_on_slave(false, base, m)
98
- end
99
- end
100
-
101
- def on_slave_unless_tx
102
- @klass.on_slave_unless_tx { yield }
103
- end
104
- end
105
-
106
- # in rails 4.1+, they create a join class that's used to pull in records for HABTM.
107
- # this simplifies the hell out of our existence, because all we have to do is inerit on-slave-by-default
108
- # down from the parent now.
109
- module Rails41HasAndBelongsToManyBuilderExtension
110
- def self.included(base)
111
- base.class_eval do
112
- alias_method :through_model_without_inherit_default_slave_from_lhs, :through_model
113
- alias_method :through_model, :through_model_with_inherit_default_slave_from_lhs
114
- end
115
- end
116
-
117
- def through_model_with_inherit_default_slave_from_lhs
118
- model = through_model_without_inherit_default_slave_from_lhs
119
- def model.on_slave_by_default?
120
- left_reflection.klass.on_slave_by_default?
121
- end
122
-
123
- # also transfer the sharded-ness of the left table to the join model
124
- model.not_sharded unless model.left_reflection.klass.is_sharded?
125
- model
126
- end
127
- end
128
- end
3
+ module ActiveRecordShards
4
+ DefaultSlavePatches = DefaultReplicaPatches
129
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
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ActiveRecord
3
4
  class Migrator
4
5
  def self.shards_migration_context
5
- if ActiveRecord::VERSION::STRING >= '5.2.0'
6
- ActiveRecord::MigrationContext.new(['db/migrate'])
6
+ if ActiveRecord::VERSION::MAJOR >= 6
7
+ ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths, ActiveRecord::SchemaMigration)
8
+ elsif ActiveRecord::VERSION::STRING >= '5.2.0'
9
+ ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths)
7
10
  else
8
11
  self
9
12
  end
@@ -6,6 +6,7 @@ module ActiveRecordShards
6
6
  if self != ActiveRecord::Base && self != base_class
7
7
  raise "You should only call not_sharded on direct descendants of ActiveRecord::Base"
8
8
  end
9
+
9
10
  self.sharded = false
10
11
  end
11
12
 
@@ -23,34 +24,38 @@ module ActiveRecordShards
23
24
  end
24
25
  end
25
26
 
26
- def on_slave_by_default?
27
+ def on_replica_by_default?
27
28
  if self == ActiveRecord::Base
28
29
  false
29
30
  else
30
31
  base = base_class
31
- if base.instance_variable_defined?(:@on_slave_by_default)
32
- base.instance_variable_get(:@on_slave_by_default)
32
+ if base.instance_variable_defined?(:@on_replica_by_default)
33
+ base.instance_variable_get(:@on_replica_by_default)
33
34
  end
34
35
  end
35
36
  end
37
+ alias_method :on_slave_by_default?, :on_replica_by_default?
36
38
 
37
- def on_slave_by_default=(value)
39
+ def on_replica_by_default=(value)
38
40
  if self == ActiveRecord::Base
39
- raise ArgumentError, "Cannot set on_slave_by_default on ActiveRecord::Base"
41
+ raise ArgumentError, "Cannot set on_replica_by_default on ActiveRecord::Base"
40
42
  else
41
- base_class.instance_variable_set(:@on_slave_by_default, value)
43
+ base_class.instance_variable_set(:@on_replica_by_default, value)
42
44
  end
43
45
  end
46
+ alias_method :on_slave_by_default=, :on_replica_by_default=
44
47
 
45
48
  module InstanceMethods
46
- def initialize_shard_and_slave
47
- @from_slave = !!self.class.current_shard_selection.options[:slave]
49
+ def initialize_shard_and_replica
50
+ @from_replica = !!self.class.current_shard_selection.options[:replica]
48
51
  @from_shard = self.class.current_shard_selection.options[:shard]
49
52
  end
53
+ alias_method :initialize_shard_and_slave, :initialize_shard_and_replica
50
54
 
51
- def from_slave?
52
- @from_slave
55
+ def from_replica?
56
+ @from_replica
53
57
  end
58
+ alias_method :from_slave?, :from_replica?
54
59
 
55
60
  def from_shard
56
61
  @from_shard
@@ -59,7 +64,7 @@ module ActiveRecordShards
59
64
 
60
65
  def self.extended(base)
61
66
  base.send(:include, InstanceMethods)
62
- base.after_initialize :initialize_shard_and_slave
67
+ base.after_initialize :initialize_shard_and_replica
63
68
  end
64
69
 
65
70
  private
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'active_record_shards/connection_pool'
3
4
  require 'active_record_shards/connection_handler'
4
5
  require 'active_record_shards/connection_specification'
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ActiveRecordShards
3
4
  module SchemaDumperExtension
4
5
  def dump(stream)
@@ -23,15 +24,15 @@ module ActiveRecordShards
23
24
  def shard_header(stream)
24
25
  define_params = @version ? "version: #{@version}" : ""
25
26
 
26
- stream.puts <<HEADER
27
+ stream.puts <<~HEADER
27
28
 
28
29
 
29
- # This section generated by active_record_shards
30
+ # This section generated by active_record_shards
30
31
 
31
- ActiveRecord::Base.on_all_shards do
32
- ActiveRecord::Schema.define(#{define_params}) do
32
+ ActiveRecord::Base.on_all_shards do
33
+ ActiveRecord::Schema.define(#{define_params}) do
33
34
 
34
- HEADER
35
+ HEADER
35
36
  end
36
37
 
37
38
  def shard_trailer(stream)
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ActiveRecordShards
3
4
  class ShardSelection
4
5
  NO_SHARD = :_no_shard
5
6
  cattr_accessor :default_shard
6
7
 
7
8
  def initialize
8
- @on_slave = false
9
+ @on_replica = false
9
10
  @shard = nil
10
11
  end
11
12
 
@@ -21,17 +22,17 @@ module ActiveRecordShards
21
22
  end
22
23
  end
23
24
 
24
- def shard_name(klass = nil, try_slave = true)
25
+ def shard_name(klass = nil, try_replica = true)
25
26
  the_shard = shard(klass)
26
27
 
27
28
  @shard_names ||= {}
28
29
  @shard_names[ActiveRecordShards.rails_env] ||= {}
29
30
  @shard_names[ActiveRecordShards.rails_env][the_shard] ||= {}
30
- @shard_names[ActiveRecordShards.rails_env][the_shard][try_slave] ||= {}
31
- @shard_names[ActiveRecordShards.rails_env][the_shard][try_slave][@on_slave] ||= begin
31
+ @shard_names[ActiveRecordShards.rails_env][the_shard][try_replica] ||= {}
32
+ @shard_names[ActiveRecordShards.rails_env][the_shard][try_replica][@on_replica] ||= begin
32
33
  s = ActiveRecordShards.rails_env.dup
33
34
  s << "_shard_#{the_shard}" if the_shard
34
- s << "_slave" if @on_slave && try_slave
35
+ s << "_replica" if @on_replica && try_replica
35
36
  s
36
37
  end
37
38
  end
@@ -46,7 +47,7 @@ module ActiveRecordShards
46
47
  end
47
48
  end
48
49
 
49
- PRIMARY = "primary".freeze
50
+ PRIMARY = "primary"
50
51
  def resolve_connection_name(sharded:, configurations:)
51
52
  resolved_shard = sharded ? shard : nil
52
53
  env = ActiveRecordShards.rails_env
@@ -54,11 +55,11 @@ module ActiveRecordShards
54
55
  @connection_names ||= {}
55
56
  @connection_names[env] ||= {}
56
57
  @connection_names[env][resolved_shard] ||= {}
57
- @connection_names[env][resolved_shard][@on_slave] ||= begin
58
+ @connection_names[env][resolved_shard][@on_replica] ||= begin
58
59
  name = env.dup
59
60
  name << "_shard_#{resolved_shard}" if resolved_shard
60
- if @on_slave && configurations["#{name}_slave"]
61
- "#{name}_slave"
61
+ if @on_replica && configurations["#{name}_replica"]
62
+ "#{name}_replica"
62
63
  else
63
64
  # ActiveRecord always names its default connection pool 'primary'
64
65
  # while everything else is named by the configuration name
@@ -73,16 +74,18 @@ module ActiveRecordShards
73
74
  @shard = (new_shard || NO_SHARD)
74
75
  end
75
76
 
76
- def on_slave?
77
- @on_slave
77
+ def on_replica?
78
+ @on_replica
78
79
  end
80
+ alias_method :on_slave?, :on_replica?
79
81
 
80
- def on_slave=(new_slave)
81
- @on_slave = (new_slave == true)
82
+ def on_replica=(new_replica)
83
+ @on_replica = (new_replica == true)
82
84
  end
85
+ alias_method :on_slave=, :on_replica=
83
86
 
84
87
  def options
85
- { shard: @shard, slave: @on_slave }
88
+ { shard: @shard, replica: @on_replica }
86
89
  end
87
90
  end
88
91
  end