active_record_shards 3.15.1 → 3.18.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.
@@ -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