active_record_shards 3.19.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24b77105aa8df10b1e79313f01b54fe87dba293938c9d41dc103f41cac64ec84
4
- data.tar.gz: ec1da959a6dc2adbb6f287f73a3346c68fd49db30f7922dbc9adca7368532af2
3
+ metadata.gz: 07a2ae4e9b397abcc455655266c9d66c76af160e56ea963b401a130589f3f48c
4
+ data.tar.gz: 45993e917fb0585637c72f89d6bee77f805e0192efb6ca3f476e86929d9192ab
5
5
  SHA512:
6
- metadata.gz: 5ccae049b9c4a450f04cc2ae6b0cfca9eba75ac72d5f795c0c49ae5fc19e37fad1341d14f3ba34f75c684696131586a663fb306bcd35c2dad9fc625ad36955f3
7
- data.tar.gz: 0b46d1cafa4fecae447a01106bfc4de592b7271fc17fef4c254a44a80cc22b22f5155e460d6aba036baad1a229f6f0b6935c3d108c935c5fd5cec9e6271eab83
6
+ metadata.gz: 16309006259fadb5b3851ea470d8859618c2df4d10555e5ecb50c257d538ac3f36c69490a33547bc7e53e6a2962d5985623176021b5f0866f9a35973138000cc
7
+ data.tar.gz: d23f2ae22180be953ba4a8bbb23000f73901ecf7950cae95858d371eec24bf01f2e7d6ee0c7225e8bbe2f76490261baae4059193718ae3cb17ffd78ab338f852
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  ActiveRecord Shards is an extension for ActiveRecord that provides support for sharded database and replicas. Basically it is just a nice way to
6
6
  switch between database connections. We've made the implementation very small, and have tried not to reinvent any wheels already present in ActiveRecord.
7
7
 
8
- ActiveRecord Shards has been used and tested on Rails 4.2, 5.x and 6.0, and has in some form or another been used in production on large Rails apps for several years.
8
+ ActiveRecord Shards has been used and tested on Rails 5.x and 6.0, and has in some form or another been used in production on large Rails apps for several years.
9
9
 
10
10
  - [Installation](#installation)
11
11
  - [Configuration](#configuration)
@@ -148,12 +148,16 @@ ActiveRecord::Base.on_replica do
148
148
  end
149
149
  ```
150
150
 
151
- This will perform the query on the replica, and mark the returned instances as read only. There is also a shortcut for this:
151
+ This will perform the query on the replica, and mark the returned instances as read-only. There is also a shortcut for this:
152
152
 
153
153
  ```ruby
154
154
  Account.on_replica.find_by_big_expensive_query
155
155
  ```
156
156
 
157
+ If you do not want instances returned from replicas to be marked as read-only, this can be disabled globally:
158
+
159
+ `ActiveRecordShards.disable_replica_readonly_records = true`
160
+
157
161
  ## Debugging
158
162
 
159
163
  Show if a query went to primary or replica in the logs:
@@ -5,32 +5,26 @@ module ActiveRecordShards
5
5
  def on_replica_if(condition)
6
6
  condition ? on_replica : self
7
7
  end
8
- alias_method :on_slave_if, :on_replica_if
9
8
 
10
9
  def on_replica_unless(condition)
11
10
  on_replica_if(!condition)
12
11
  end
13
- alias_method :on_slave_unless, :on_replica_unless
14
12
 
15
13
  def on_primary_if(condition)
16
14
  condition ? on_primary : self
17
15
  end
18
- alias_method :on_master_if, :on_primary_if
19
16
 
20
17
  def on_primary_unless(condition)
21
18
  on_primary_if(!condition)
22
19
  end
23
- alias_method :on_master_unless, :on_primary_unless
24
20
 
25
21
  def on_replica
26
22
  PrimaryReplicaProxy.new(self, :replica)
27
23
  end
28
- alias_method :on_slave, :on_replica
29
24
 
30
25
  def on_primary
31
26
  PrimaryReplicaProxy.new(self, :primary)
32
27
  end
33
- alias_method :on_master, :on_primary
34
28
 
35
29
  class PrimaryReplicaProxy
36
30
  def initialize(association_collection, which)
@@ -42,8 +36,7 @@ module ActiveRecordShards
42
36
  reflection = @association_collection.proxy_association.reflection
43
37
  reflection.klass.on_cx_switch_block(@which) { @association_collection.send(method, *args, &block) }
44
38
  end
39
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
45
40
  end
46
-
47
- MasterSlaveProxy = PrimaryReplicaProxy
48
41
  end
49
42
  end
@@ -28,14 +28,6 @@ module ActiveRecordShards
28
28
  expand_child!(env_config, replica_conf)
29
29
  conf["#{env_name}_replica"] = replica_conf
30
30
  end
31
-
32
- # rubocop:disable Style/Next
33
- if legacy_replica_conf = env_config.delete('slave')
34
- ActiveRecordShards::Deprecation.warn('`slave` configuration keys should be replaced with `replica` keys!')
35
- expand_child!(env_config, legacy_replica_conf)
36
- conf["#{env_name}_replica"] = legacy_replica_conf
37
- end
38
- # rubocop:enable Style/Next
39
31
  end
40
32
 
41
33
  conf
@@ -43,7 +35,7 @@ module ActiveRecordShards
43
35
 
44
36
  def expand_child!(parent, child)
45
37
  parent.each do |key, value|
46
- unless ['slave', 'replica', 'shards'].include?(key) || value.is_a?(Hash)
38
+ unless ['replica', 'shards'].include?(key) || value.is_a?(Hash)
47
39
  child[key] ||= value
48
40
  end
49
41
  end
@@ -4,7 +4,7 @@ module ActiveRecordShards
4
4
  name = current_shard_selection.resolve_connection_name(sharded: is_sharded?, configurations: configurations)
5
5
 
6
6
  unless configurations[name] || name == "primary"
7
- raise ActiveRecord::AdapterNotSpecified, "No database defined by #{name} in your database config. (configurations: #{configurations.keys.inspect})"
7
+ raise ActiveRecord::AdapterNotSpecified, "No database defined by #{name} in your database config. (configurations: #{configurations.to_h.keys.inspect})"
8
8
  end
9
9
 
10
10
  name
@@ -3,8 +3,13 @@ module ActiveRecordShards
3
3
  def connection_specification_name
4
4
  name = current_shard_selection.resolve_connection_name(sharded: is_sharded?, configurations: configurations)
5
5
 
6
- unless configurations[name] || name == "primary"
7
- raise ActiveRecord::AdapterNotSpecified, "No database defined by #{name} in your database config. (configurations: #{configurations.keys.inspect})"
6
+ @_ars_connection_specification_names ||= {}
7
+ unless @_ars_connection_specification_names.include?(name)
8
+ unless configurations[name] || name == "primary"
9
+ raise ActiveRecord::AdapterNotSpecified, "No database defined by #{name} in your database config. (configurations: #{configurations.to_h.keys.inspect})"
10
+ end
11
+
12
+ @_ars_connection_specification_names[name] = true
8
13
  end
9
14
 
10
15
  name
@@ -19,11 +24,7 @@ module ActiveRecordShards
19
24
  spec_name = connection_specification_name
20
25
 
21
26
  pool = connection_handler.retrieve_connection_pool(spec_name)
22
- if pool.nil?
23
- resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new(configurations)
24
- spec = resolver.spec(spec_name.to_sym, spec_name)
25
- connection_handler.establish_connection(spec)
26
- end
27
+ connection_handler.establish_connection(spec_name.to_sym) if pool.nil?
27
28
  end
28
29
  end
29
30
  end
@@ -7,13 +7,8 @@ module ActiveRecordShards
7
7
  SHARD_NAMES_CONFIG_KEY = 'shard_names'
8
8
 
9
9
  def self.extended(base)
10
- if ActiveRecord::VERSION::MAJOR >= 5
11
- base.singleton_class.send(:alias_method, :load_schema_without_default_shard!, :load_schema!)
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
10
+ base.singleton_class.send(:alias_method, :load_schema_without_default_shard!, :load_schema!)
11
+ base.singleton_class.send(:alias_method, :load_schema!, :load_schema_with_default_shard!)
17
12
 
18
13
  base.singleton_class.send(:alias_method, :table_exists_without_default_shard?, :table_exists?)
19
14
  base.singleton_class.send(:alias_method, :table_exists?, :table_exists_with_default_shard?)
@@ -62,22 +57,18 @@ module ActiveRecordShards
62
57
  def on_replica_if(condition, &block)
63
58
  condition ? on_replica(&block) : yield
64
59
  end
65
- alias_method :on_slave_if, :on_replica_if
66
60
 
67
61
  def on_replica_unless(condition, &block)
68
62
  on_replica_if(!condition, &block)
69
63
  end
70
- alias_method :on_slave_unless, :on_replica_unless
71
64
 
72
65
  def on_primary_if(condition, &block)
73
66
  condition ? on_primary(&block) : yield
74
67
  end
75
- alias_method :on_master_if, :on_primary_if
76
68
 
77
69
  def on_primary_unless(condition, &block)
78
70
  on_primary_if(!condition, &block)
79
71
  end
80
- alias_method :on_master_unless, :on_primary_unless
81
72
 
82
73
  def on_primary_or_replica(which, &block)
83
74
  if block_given?
@@ -86,7 +77,6 @@ module ActiveRecordShards
86
77
  PrimaryReplicaProxy.new(self, which)
87
78
  end
88
79
  end
89
- alias_method :on_master_or_slave, :on_primary_or_replica
90
80
 
91
81
  # Executes queries using the replica database. Fails over to primary if no replica is found.
92
82
  # if you want to execute a block of code on the replica you can go:
@@ -100,23 +90,14 @@ module ActiveRecordShards
100
90
  def on_replica(&block)
101
91
  on_primary_or_replica(:replica, &block)
102
92
  end
103
- alias_method :on_slave, :on_replica
104
93
 
105
94
  def on_primary(&block)
106
95
  on_primary_or_replica(:primary, &block)
107
96
  end
108
- alias_method :on_master, :on_primary
109
-
110
- # just to ease the transition from replica to active_record_shards
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
114
97
 
115
98
  def on_cx_switch_block(which, force: false, construct_ro_scope: nil, &block)
116
99
  @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
100
+ @disallow_replica += 1 if which == :primary
120
101
 
121
102
  switch_to_replica = force || @disallow_replica.zero?
122
103
  old_options = current_shard_selection.options
@@ -125,13 +106,13 @@ module ActiveRecordShards
125
106
 
126
107
  # we avoid_readonly_scope to prevent some stack overflow problems, like when
127
108
  # .columns calls .with_scope which calls .columns and onward, endlessly.
128
- if self == ActiveRecord::Base || !switch_to_replica || construct_ro_scope == false
109
+ if self == ActiveRecord::Base || !switch_to_replica || construct_ro_scope == false || ActiveRecordShards.disable_replica_readonly_records == true
129
110
  yield
130
111
  else
131
112
  readonly.scoping(&block)
132
113
  end
133
114
  ensure
134
- @disallow_replica -= 1 if [:primary, :master].include?(which)
115
+ @disallow_replica -= 1 if which == :primary
135
116
  switch_connection(old_options) if old_options
136
117
  end
137
118
 
@@ -142,7 +123,6 @@ module ActiveRecordShards
142
123
  def on_replica?
143
124
  current_shard_selection.on_replica?
144
125
  end
145
- alias_method :on_slave?, :on_replica?
146
126
 
147
127
  def current_shard_selection
148
128
  Thread.current[:shard_selection] ||= ShardSelection.new
@@ -153,18 +133,27 @@ module ActiveRecordShards
153
133
  end
154
134
 
155
135
  def shard_names
156
- unless config = configurations[shard_env]
157
- raise "Did not find #{shard_env} in configurations, did you forget to add it to your database config? (configurations: #{configurations.keys.inspect})"
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}."
136
+ unless config_for_env.fetch(SHARD_NAMES_CONFIG_KEY, []).all? { |shard_name| shard_name.is_a?(Integer) }
137
+ raise "All shard names must be integers: #{config_for_env[SHARD_NAMES_CONFIG_KEY].inspect}."
161
138
  end
162
139
 
163
- config[SHARD_NAMES_CONFIG_KEY] || []
140
+ config_for_env[SHARD_NAMES_CONFIG_KEY] || []
164
141
  end
165
142
 
166
143
  private
167
144
 
145
+ def config_for_env
146
+ @_ars_config_for_env ||= {}
147
+ @_ars_config_for_env[shard_env] ||= begin
148
+ unless config = configurations[shard_env]
149
+ raise "Did not find #{shard_env} in configurations, did you forget to add it to your database config? (configurations: #{configurations.to_h.keys.inspect})"
150
+ end
151
+
152
+ config
153
+ end
154
+ end
155
+ alias_method :check_config_for_env, :config_for_env
156
+
168
157
  def switch_connection(options)
169
158
  if options.any?
170
159
  if options.key?(:replica)
@@ -172,9 +161,7 @@ module ActiveRecordShards
172
161
  end
173
162
 
174
163
  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
164
+ check_config_for_env
178
165
 
179
166
  current_shard_selection.shard = options[:shard]
180
167
  end
@@ -184,7 +171,7 @@ module ActiveRecordShards
184
171
  end
185
172
 
186
173
  def shard_env
187
- ActiveRecordShards.rails_env
174
+ ActiveRecordShards.app_env
188
175
  end
189
176
 
190
177
  # Make these few schema related methods available before having switched to
@@ -197,14 +184,8 @@ module ActiveRecordShards
197
184
  end
198
185
  end
199
186
 
200
- if ActiveRecord::VERSION::MAJOR >= 5
201
- def load_schema_with_default_shard!
202
- with_default_shard { load_schema_without_default_shard! }
203
- end
204
- else
205
- def columns_with_default_shard
206
- with_default_shard { columns_without_default_shard }
207
- end
187
+ def load_schema_with_default_shard!
188
+ with_default_shard { load_schema_without_default_shard! }
208
189
  end
209
190
 
210
191
  def table_exists_with_default_shard?
@@ -220,19 +201,16 @@ module ActiveRecordShards
220
201
  def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
221
202
  @target.on_primary_or_replica(@which) { @target.send(method, *args, &block) }
222
203
  end
204
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
223
205
  end
224
-
225
- MasterSlaveProxy = PrimaryReplicaProxy
226
206
  end
227
207
  end
228
208
 
229
209
  case "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
230
- when '4.2'
231
- require 'active_record_shards/connection_switcher-4-2'
232
- when '5.0'
233
- require 'active_record_shards/connection_switcher-5-0'
234
- when '5.1', '5.2', '6.0'
210
+ when '5.1', '5.2'
235
211
  require 'active_record_shards/connection_switcher-5-1'
212
+ when '6.0'
213
+ require 'active_record_shards/connection_switcher-6-0'
236
214
  else
237
215
  raise "ActiveRecordShards is not compatible with #{ActiveRecord::VERSION::STRING}"
238
216
  end
@@ -22,21 +22,13 @@ module ActiveRecordShards
22
22
  #{method}_without_default_replica#{punctuation}(*args, &block)
23
23
  end
24
24
  end
25
-
25
+ ruby2_keywords(:#{method}_with_default_replica#{punctuation}) if respond_to?(:ruby2_keywords, true)
26
26
  alias_method :#{method}_without_default_replica#{punctuation}, :#{method}#{punctuation}
27
27
  alias_method :#{method}#{punctuation}, :#{method}_with_default_replica#{punctuation}
28
28
  #{class_method ? 'end' : ''}
29
29
  RUBY
30
30
  end
31
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
32
  def transaction_with_replica_off(*args, &block)
41
33
  if on_replica_by_default?
42
34
  begin
@@ -50,14 +42,9 @@ module ActiveRecordShards
50
42
  transaction_without_replica_off(*args, &block)
51
43
  end
52
44
  end
53
- alias_method :transaction_with_slave_off, :transaction_with_replica_off
45
+ ruby2_keywords(:transaction_with_replica_off) if respond_to?(:ruby2_keywords, true)
54
46
 
55
47
  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
48
  def on_replica_unless_tx
62
49
  self.class.on_replica_unless_tx { yield }
63
50
  end
@@ -84,19 +71,11 @@ module ActiveRecordShards
84
71
  :table_exists?
85
72
  ].freeze
86
73
 
87
- CLASS_SLAVE_METHODS = CLASS_REPLICA_METHODS
88
- CLASS_FORCE_SLAVE_METHODS = CLASS_FORCE_REPLICA_METHODS
89
-
90
74
  def self.extended(base)
91
75
  CLASS_REPLICA_METHODS.each { |m| ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, m) }
92
76
  CLASS_FORCE_REPLICA_METHODS.each { |m| ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, m, force_on_replica: true) }
93
77
 
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
-
78
+ ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(true, base, :load_schema!, force_on_replica: true)
100
79
  ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, :reload)
101
80
 
102
81
  base.class_eval do
@@ -119,7 +98,6 @@ module ActiveRecordShards
119
98
  yield
120
99
  end
121
100
  end
122
- alias_method :on_slave_unless_tx, :on_replica_unless_tx
123
101
 
124
102
  def force_on_replica(&block)
125
103
  return yield if Thread.current[:_active_record_shards_in_migration]
@@ -133,11 +111,6 @@ module ActiveRecordShards
133
111
  ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, m)
134
112
  end
135
113
 
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
114
  ActiveRecordShards::DefaultReplicaPatches.wrap_method_in_on_replica(false, base, :to_sql, force_on_replica: true)
142
115
  end
143
116
 
@@ -210,20 +183,6 @@ module ActiveRecordShards
210
183
  end
211
184
  end
212
185
 
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
186
  module AssociationsPreloaderAssociationAssociatedRecordsByOwnerPatch
228
187
  def associated_records_by_owner(preloader)
229
188
  if klass
@@ -19,11 +19,10 @@ module ActiveRecord
19
19
  # manually on the sharded DBs.
20
20
  ActiveRecord::Base.on_all_shards do
21
21
  ActiveRecord::SchemaMigration.create_table
22
- if ActiveRecord::VERSION::MAJOR >= 5
23
- ActiveRecord::InternalMetadata.create_table
24
- end
22
+ ActiveRecord::InternalMetadata.create_table
25
23
  end
26
24
  end
25
+ ruby2_keywords(:initialize_with_sharding) if respond_to?(:ruby2_keywords, true)
27
26
  alias_method :initialize_without_sharding, :initialize
28
27
  alias_method :initialize, :initialize_with_sharding
29
28
 
@@ -34,7 +34,6 @@ module ActiveRecordShards
34
34
  end
35
35
  end
36
36
  end
37
- alias_method :on_slave_by_default?, :on_replica_by_default?
38
37
 
39
38
  def on_replica_by_default=(value)
40
39
  if self == ActiveRecord::Base
@@ -43,19 +42,16 @@ module ActiveRecordShards
43
42
  base_class.instance_variable_set(:@on_replica_by_default, value)
44
43
  end
45
44
  end
46
- alias_method :on_slave_by_default=, :on_replica_by_default=
47
45
 
48
46
  module InstanceMethods
49
47
  def initialize_shard_and_replica
50
48
  @from_replica = !!self.class.current_shard_selection.options[:replica]
51
49
  @from_shard = self.class.current_shard_selection.options[:shard]
52
50
  end
53
- alias_method :initialize_shard_and_slave, :initialize_shard_and_replica
54
51
 
55
52
  def from_replica?
56
53
  @from_replica
57
54
  end
58
- alias_method :from_slave?, :from_replica?
59
55
 
60
56
  def from_shard
61
57
  @from_shard
@@ -10,64 +10,33 @@ module ActiveRecordShards
10
10
  @shard = nil
11
11
  end
12
12
 
13
- if ActiveRecord::VERSION::MAJOR < 5
14
-
15
- def shard(klass = nil)
16
- if (@shard || self.class.default_shard) && (klass.nil? || klass.is_sharded?)
17
- if @shard == NO_SHARD
18
- nil
19
- else
20
- @shard || self.class.default_shard
21
- end
22
- end
13
+ def shard
14
+ if @shard.nil? || @shard == NO_SHARD
15
+ nil
16
+ else
17
+ @shard || self.class.default_shard
23
18
  end
19
+ end
24
20
 
25
- def shard_name(klass = nil, try_replica = true)
26
- the_shard = shard(klass)
27
-
28
- @shard_names ||= {}
29
- @shard_names[ActiveRecordShards.rails_env] ||= {}
30
- @shard_names[ActiveRecordShards.rails_env][the_shard] ||= {}
31
- @shard_names[ActiveRecordShards.rails_env][the_shard][try_replica] ||= {}
32
- @shard_names[ActiveRecordShards.rails_env][the_shard][try_replica][@on_replica] ||= begin
33
- s = ActiveRecordShards.rails_env.dup
34
- s << "_shard_#{the_shard}" if the_shard
35
- s << "_replica" if @on_replica && try_replica
36
- s
37
- end
38
- end
39
-
40
- else
41
-
42
- def shard
43
- if @shard.nil? || @shard == NO_SHARD
44
- nil
21
+ PRIMARY = "primary"
22
+ def resolve_connection_name(sharded:, configurations:)
23
+ resolved_shard = sharded ? shard : nil
24
+ env = ActiveRecordShards.app_env
25
+
26
+ @connection_names ||= {}
27
+ @connection_names[env] ||= {}
28
+ @connection_names[env][resolved_shard] ||= {}
29
+ @connection_names[env][resolved_shard][@on_replica] ||= begin
30
+ name = env.dup
31
+ name << "_shard_#{resolved_shard}" if resolved_shard
32
+ if @on_replica && configurations["#{name}_replica"]
33
+ "#{name}_replica"
45
34
  else
46
- @shard || self.class.default_shard
47
- end
48
- end
49
-
50
- PRIMARY = "primary"
51
- def resolve_connection_name(sharded:, configurations:)
52
- resolved_shard = sharded ? shard : nil
53
- env = ActiveRecordShards.rails_env
54
-
55
- @connection_names ||= {}
56
- @connection_names[env] ||= {}
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
35
+ # ActiveRecord always names its default connection pool 'primary'
36
+ # while everything else is named by the configuration name
37
+ resolved_shard ? name : PRIMARY
68
38
  end
69
39
  end
70
-
71
40
  end
72
41
 
73
42
  def shard=(new_shard)
@@ -77,12 +46,10 @@ module ActiveRecordShards
77
46
  def on_replica?
78
47
  @on_replica
79
48
  end
80
- alias_method :on_slave?, :on_replica?
81
49
 
82
50
  def on_replica=(new_replica)
83
51
  @on_replica = (new_replica == true)
84
52
  end
85
- alias_method :on_slave=, :on_replica=
86
53
 
87
54
  def options
88
55
  { shard: @shard, replica: @on_replica }
@@ -23,15 +23,14 @@ module ActiveRecordShards
23
23
 
24
24
  exception = nil
25
25
  enum.each do
26
- begin
27
- record = @scope.find(*find_args)
28
- return record if record
29
- rescue ActiveRecord::RecordNotFound => e
30
- exception = e
31
- end
26
+ record = @scope.find(*find_args)
27
+ return record if record
28
+ rescue ActiveRecord::RecordNotFound => e
29
+ exception = e
32
30
  end
33
31
  raise exception
34
32
  end
33
+ ruby2_keywords(:find) if respond_to?(:ruby2_keywords, true)
35
34
 
36
35
  def count
37
36
  enum.inject(0) { |accum, _shard| @scope.clone.count + accum }
@@ -10,7 +10,7 @@ namespace :db do
10
10
  desc 'Drops the database for the current RAILS_ENV including shards'
11
11
  task drop: :load_config do
12
12
  ActiveRecord::Base.configurations.to_h.each do |key, conf|
13
- next if !key.start_with?(ActiveRecordShards.rails_env) || key.end_with?("_replica", "_slave")
13
+ next if !key.start_with?(ActiveRecordShards.app_env) || key.end_with?("_replica")
14
14
 
15
15
  begin
16
16
  ActiveRecordShards::Tasks.root_connection(conf).drop_database(conf['database'])
@@ -31,7 +31,7 @@ namespace :db do
31
31
  desc "Create the database defined in config/database.yml for the current RAILS_ENV including shards"
32
32
  task create: :load_config do
33
33
  ActiveRecord::Base.configurations.to_h.each do |key, conf|
34
- next if !key.start_with?(ActiveRecordShards.rails_env) || key.end_with?("_replica", "_slave")
34
+ next if !key.start_with?(ActiveRecordShards.app_env) || key.end_with?("_replica")
35
35
 
36
36
  begin
37
37
  # MysqlAdapter takes charset instead of encoding in Rails 4.2 or greater
@@ -48,7 +48,7 @@ namespace :db do
48
48
  end
49
49
  end
50
50
  end
51
- ActiveRecord::Base.establish_connection(ActiveRecordShards.rails_env.to_sym)
51
+ ActiveRecord::Base.establish_connection(ActiveRecordShards.app_env.to_sym)
52
52
  end
53
53
 
54
54
  desc "Raises an error if there are pending migrations"
@@ -78,14 +78,12 @@ namespace :db do
78
78
  namespace :test do
79
79
  desc 'Purges the test databases by dropping and creating'
80
80
  task purge: :load_config do |t|
81
- begin
82
- saved_env = Rails.env
83
- Rails.env = 'test'
84
- Rake.application.lookup('db:drop', t.scope).execute
85
- Rake.application.lookup('db:create', t.scope).execute
86
- ensure
87
- Rails.env = saved_env
88
- end
81
+ saved_env = Rails.env
82
+ Rails.env = 'test'
83
+ Rake.application.lookup('db:drop', t.scope).execute
84
+ Rake.application.lookup('db:create', t.scope).execute
85
+ ensure
86
+ Rails.env = saved_env
89
87
  end
90
88
  end
91
89
  end
@@ -12,10 +12,16 @@ require 'active_record_shards/default_replica_patches'
12
12
  require 'active_record_shards/schema_dumper_extension'
13
13
 
14
14
  module ActiveRecordShards
15
- def self.rails_env
15
+ class << self
16
+ attr_accessor :disable_replica_readonly_records
17
+ end
18
+
19
+ def self.app_env
16
20
  env = Rails.env if defined?(Rails.env)
17
21
  env ||= RAILS_ENV if Object.const_defined?(:RAILS_ENV)
18
22
  env ||= ENV['RAILS_ENV']
23
+ env ||= APP_ENV if Object.const_defined?(:APP_ENV)
24
+ env ||= ENV['APP_ENV']
19
25
  env || 'development'
20
26
  end
21
27
  end
@@ -30,32 +36,6 @@ ActiveRecord::Associations::Builder::HasAndBelongsToMany.include(ActiveRecordSha
30
36
  ActiveRecord::SchemaDumper.prepend(ActiveRecordShards::SchemaDumperExtension)
31
37
 
32
38
  case "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
33
- when '4.2'
34
- require 'active_record_shards/patches-4-2'
35
-
36
- # https://github.com/rails/rails/blob/v4.2.11.3/activerecord/lib/active_record/associations/association.rb#L97
37
- ActiveRecord::Associations::Association.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationAssociationScopePatch)
38
-
39
- # https://github.com/rails/rails/blob/v4.2.11.3/activerecord/lib/active_record/associations/singular_association.rb#L44-L53
40
- ActiveRecord::Associations::SingularAssociation.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationGetRecordsPatch)
41
-
42
- # https://github.com/rails/rails/blob/v4.2.11.3/activerecord/lib/active_record/associations/collection_association.rb#L447-L456
43
- ActiveRecord::Associations::CollectionAssociation.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationGetRecordsPatch)
44
-
45
- # https://github.com/rails/rails/blob/v4.2.11.3/activerecord/lib/active_record/associations/preloader/association.rb#L89
46
- ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsPreloaderAssociationAssociatedRecordsByOwnerPatch)
47
- when '5.0'
48
- # https://github.com/rails/rails/blob/v5.0.7/activerecord/lib/active_record/associations/association.rb#L97
49
- ActiveRecord::Associations::Association.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationAssociationScopePatch)
50
-
51
- # https://github.com/rails/rails/blob/v5.0.7/activerecord/lib/active_record/associations/singular_association.rb#L56-L65
52
- ActiveRecord::Associations::SingularAssociation.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationGetRecordsPatch)
53
-
54
- # https://github.com/rails/rails/blob/v5.0.7/activerecord/lib/active_record/associations/collection_association.rb#L442-L451
55
- ActiveRecord::Associations::CollectionAssociation.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationGetRecordsPatch)
56
-
57
- # https://github.com/rails/rails/blob/v5.0.7/activerecord/lib/active_record/associations/preloader/association.rb#L120
58
- ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsPreloaderAssociationLoadRecordsPatch)
59
39
  when '5.1'
60
40
  # https://github.com/rails/rails/blob/v5.1.7/activerecord/lib/active_record/associations/association.rb#L97
61
41
  ActiveRecord::Associations::Association.prepend(ActiveRecordShards::DefaultReplicaPatches::AssociationsAssociationAssociationScopePatch)
@@ -97,45 +77,3 @@ when '6.0'
97
77
  else
98
78
  raise "ActiveRecordShards is not compatible with #{ActiveRecord::VERSION::STRING}"
99
79
  end
100
-
101
- require 'active_record_shards/deprecation'
102
-
103
- ActiveRecordShards::Deprecation.deprecate_methods(
104
- ActiveRecordShards::AssociationCollectionConnectionSelection,
105
- on_slave_if: :on_replica_if,
106
- on_slave_unless: :on_replica_unless,
107
- on_slave: :on_replica,
108
- on_master: :on_primary,
109
- on_master_if: :on_primary_if,
110
- on_master_unless: :on_primary_unless
111
- )
112
-
113
- ActiveRecordShards::Deprecation.deprecate_methods(
114
- ActiveRecordShards::ConnectionSwitcher,
115
- on_slave_if: :on_replica_if,
116
- on_slave_unless: :on_replica_unless,
117
- on_master_or_slave: :on_primary_or_replica,
118
- on_slave: :on_replica,
119
- on_master: :on_primary,
120
- on_master_if: :on_primary_if,
121
- on_master_unless: :on_primary_unless,
122
- on_slave?: :on_replica?
123
- )
124
-
125
- ActiveRecordShards::Deprecation.deprecate_methods(
126
- ActiveRecordShards::DefaultReplicaPatches,
127
- transaction_with_slave_off: :transaction_with_replica_off,
128
- on_slave_unless_tx: :on_replica_unless_tx
129
- )
130
-
131
- ActiveRecordShards::Deprecation.deprecate_methods(
132
- ActiveRecordShards::Model,
133
- on_slave_by_default?: :on_replica_by_default?,
134
- :on_slave_by_default= => :on_replica_by_default=
135
- )
136
-
137
- ActiveRecordShards::Deprecation.deprecate_methods(
138
- ActiveRecordShards::ShardSelection,
139
- on_slave?: :on_replica?,
140
- :on_slave= => :on_replica=
141
- )
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_shards
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.19.0
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Quorning
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2021-07-09 00:00:00.000000000 Z
16
+ date: 2022-07-06 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: activerecord
@@ -21,7 +21,7 @@ dependencies:
21
21
  requirements:
22
22
  - - ">="
23
23
  - !ruby/object:Gem::Version
24
- version: '4.2'
24
+ version: '5.1'
25
25
  - - "<"
26
26
  - !ruby/object:Gem::Version
27
27
  version: '6.1'
@@ -31,7 +31,7 @@ dependencies:
31
31
  requirements:
32
32
  - - ">="
33
33
  - !ruby/object:Gem::Version
34
- version: '4.2'
34
+ version: '5.1'
35
35
  - - "<"
36
36
  - !ruby/object:Gem::Version
37
37
  version: '6.1'
@@ -41,7 +41,7 @@ dependencies:
41
41
  requirements:
42
42
  - - ">="
43
43
  - !ruby/object:Gem::Version
44
- version: '4.2'
44
+ version: '5.1'
45
45
  - - "<"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '6.1'
@@ -51,7 +51,7 @@ dependencies:
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '4.2'
54
+ version: '5.1'
55
55
  - - "<"
56
56
  - !ruby/object:Gem::Version
57
57
  version: '6.1'
@@ -195,19 +195,12 @@ files:
195
195
  - lib/active_record_shards.rb
196
196
  - lib/active_record_shards/association_collection_connection_selection.rb
197
197
  - lib/active_record_shards/configuration_parser.rb
198
- - lib/active_record_shards/connection_handler.rb
199
- - lib/active_record_shards/connection_pool.rb
200
- - lib/active_record_shards/connection_specification.rb
201
- - lib/active_record_shards/connection_switcher-4-2.rb
202
- - lib/active_record_shards/connection_switcher-5-0.rb
203
198
  - lib/active_record_shards/connection_switcher-5-1.rb
199
+ - lib/active_record_shards/connection_switcher-6-0.rb
204
200
  - lib/active_record_shards/connection_switcher.rb
205
201
  - lib/active_record_shards/default_replica_patches.rb
206
- - lib/active_record_shards/default_slave_patches.rb
207
- - lib/active_record_shards/deprecation.rb
208
202
  - lib/active_record_shards/migration.rb
209
203
  - lib/active_record_shards/model.rb
210
- - lib/active_record_shards/patches-4-2.rb
211
204
  - lib/active_record_shards/schema_dumper_extension.rb
212
205
  - lib/active_record_shards/shard_selection.rb
213
206
  - lib/active_record_shards/shard_support.rb
@@ -225,14 +218,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
225
218
  requirements:
226
219
  - - ">="
227
220
  - !ruby/object:Gem::Version
228
- version: '2.3'
221
+ version: '2.6'
229
222
  required_rubygems_version: !ruby/object:Gem::Requirement
230
223
  requirements:
231
224
  - - ">="
232
225
  - !ruby/object:Gem::Version
233
226
  version: '0'
234
227
  requirements: []
235
- rubygems_version: 3.2.16
228
+ rubygems_version: 3.1.6
236
229
  signing_key:
237
230
  specification_version: 4
238
231
  summary: Simple database switching for ActiveRecord.
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- ActiveRecord::ConnectionAdapters::ConnectionHandler.class_eval do
4
- remove_method :retrieve_connection_pool
5
- def retrieve_connection_pool(klass)
6
- class_to_pool[klass.connection_pool_name] ||= pool_for(klass)
7
- end
8
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveRecordShards
4
- ConnectionPoolNameDecorator = Struct.new(:name)
5
-
6
- # It overrides given connection handler methods (they differ depend on
7
- # Rails version).
8
- #
9
- # It takes the first argument, ActiveRecord::Base object or
10
- # String (connection_pool_name), converts it in Struct object and
11
- # passes to the original method.
12
- #
13
- # Example:
14
- # methods_to_override = [:establish_connection, :remove_connection]
15
- # ActiveRecordShards.override_connection_handler_methods(methods_to_override)
16
- #
17
- def self.override_connection_handler_methods(method_names)
18
- method_names.each do |method_name|
19
- ActiveRecord::ConnectionAdapters::ConnectionHandler.class_eval do
20
- define_method("#{method_name}_with_connection_pool_name") do |*args|
21
- unless args[0].is_a? ConnectionPoolNameDecorator
22
- name = if args[0].is_a? String
23
- args[0]
24
- else
25
- args[0].connection_pool_name
26
- end
27
- args[0] = ConnectionPoolNameDecorator.new(name)
28
- end
29
- send("#{method_name}_without_connection_pool_name", *args)
30
- end
31
- alias_method :"#{method_name}_without_connection_pool_name", method_name
32
- alias_method method_name, :"#{method_name}_with_connection_pool_name"
33
- end
34
- end
35
- end
36
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class << ActiveRecord::Base
4
- remove_method :establish_connection if ActiveRecord::VERSION::MAJOR >= 5
5
- def establish_connection(spec = ENV["DATABASE_URL"])
6
- spec ||= ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
7
- spec = spec.to_sym if spec.is_a?(String)
8
- resolver = ActiveRecordShards::ConnectionSpecification::Resolver.new configurations
9
- spec = resolver.spec(spec)
10
-
11
- unless respond_to?(spec.adapter_method)
12
- raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
13
- end
14
-
15
- remove_connection
16
- specification_cache[connection_pool_name] = spec
17
- connection_handler.establish_connection self, spec
18
- end
19
- end
@@ -1,56 +0,0 @@
1
- module ActiveRecordShards
2
- module ConnectionSwitcher
3
- # Name of the connection pool. Used by ConnectionHandler to retrieve the current connection pool.
4
- def connection_pool_name # :nodoc:
5
- name = current_shard_selection.shard_name(self)
6
-
7
- # e.g. if "production_replica" is not defined in `Configuration`, fall back to "production"
8
- if configurations[name].nil? && on_replica?
9
- current_shard_selection.shard_name(self, false)
10
- else
11
- name
12
- end
13
- end
14
-
15
- private
16
-
17
- def ensure_shard_connection
18
- establish_shard_connection unless connected_to_shard?
19
- end
20
-
21
- def establish_shard_connection
22
- name = connection_pool_name
23
- spec = configurations[name]
24
-
25
- if spec.nil?
26
- raise ActiveRecord::AdapterNotSpecified, "No database defined by #{name} in your database config. (configurations: #{configurations.keys.inspect})"
27
- end
28
-
29
- specification_cache[name] ||= begin
30
- resolver = ActiveRecordShards::ConnectionSpecification::Resolver.new configurations
31
- resolver.spec(spec)
32
- end
33
-
34
- connection_handler.establish_connection(self, specification_cache[name])
35
- end
36
-
37
- def specification_cache
38
- @@specification_cache ||= {}
39
- end
40
-
41
- # Helper method to clear global state when testing.
42
- def clear_specification_cache
43
- @@specification_cache = {}
44
- end
45
-
46
- def connection_pool_key
47
- specification_cache[connection_pool_name]
48
- end
49
-
50
- def connected_to_shard?
51
- specs_to_pools = Hash[connection_handler.connection_pool_list.map { |pool| [pool.spec, pool] }]
52
-
53
- specs_to_pools.key?(connection_pool_key)
54
- end
55
- end
56
- end
@@ -1,5 +0,0 @@
1
- ActiveRecordShards::Deprecation.warn('`DefaultSlavePatches` is deprecated, please use `DefaultReplicaPatches`.')
2
-
3
- module ActiveRecordShards
4
- DefaultSlavePatches = DefaultReplicaPatches
5
- end
@@ -1,12 +0,0 @@
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 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_record_shards/connection_pool'
4
- require 'active_record_shards/connection_handler'
5
- require 'active_record_shards/connection_specification'
6
-
7
- ActiveRecordShards::ConnectionSpecification = ActiveRecord::ConnectionAdapters::ConnectionSpecification
8
- methods_to_override = [:establish_connection, :remove_connection, :pool_for, :pool_from_any_process_for]
9
- ActiveRecordShards.override_connection_handler_methods(methods_to_override)