activerecord 6.1.0 → 6.1.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +305 -17
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/aggregations.rb +4 -4
  5. data/lib/active_record/association_relation.rb +10 -0
  6. data/lib/active_record/associations/association.rb +13 -7
  7. data/lib/active_record/associations/association_scope.rb +7 -5
  8. data/lib/active_record/associations/belongs_to_association.rb +8 -5
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -2
  11. data/lib/active_record/associations/builder/belongs_to.rb +2 -2
  12. data/lib/active_record/associations/has_many_association.rb +1 -1
  13. data/lib/active_record/associations/join_dependency/join_association.rb +8 -7
  14. data/lib/active_record/associations/join_dependency.rb +1 -1
  15. data/lib/active_record/associations.rb +6 -2
  16. data/lib/active_record/attributes.rb +1 -1
  17. data/lib/active_record/coders/yaml_column.rb +11 -1
  18. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +4 -4
  19. data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
  20. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  21. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +1 -1
  22. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -0
  23. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +7 -1
  24. data/lib/active_record/connection_adapters/abstract/transaction.rb +14 -3
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +10 -11
  26. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +6 -2
  27. data/lib/active_record/connection_adapters/mysql/quoting.rb +17 -2
  28. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +4 -1
  29. data/lib/active_record/connection_adapters/pool_config.rb +13 -3
  30. data/lib/active_record/connection_adapters/pool_manager.rb +5 -1
  31. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
  32. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  33. data/lib/active_record/connection_adapters/postgresql_adapter.rb +2 -8
  34. data/lib/active_record/connection_adapters/schema_cache.rb +9 -1
  35. data/lib/active_record/connection_adapters/sql_type_metadata.rb +0 -2
  36. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
  37. data/lib/active_record/connection_adapters.rb +2 -0
  38. data/lib/active_record/connection_handling.rb +20 -12
  39. data/lib/active_record/core.rb +58 -29
  40. data/lib/active_record/database_configurations/url_config.rb +1 -1
  41. data/lib/active_record/enum.rb +52 -34
  42. data/lib/active_record/fixtures.rb +5 -2
  43. data/lib/active_record/gem_version.rb +1 -1
  44. data/lib/active_record/insert_all.rb +5 -1
  45. data/lib/active_record/locking/optimistic.rb +14 -4
  46. data/lib/active_record/log_subscriber.rb +3 -2
  47. data/lib/active_record/migration/compatibility.rb +2 -1
  48. data/lib/active_record/migration.rb +1 -1
  49. data/lib/active_record/model_schema.rb +4 -4
  50. data/lib/active_record/railties/console_sandbox.rb +2 -4
  51. data/lib/active_record/railties/databases.rake +16 -9
  52. data/lib/active_record/reflection.rb +1 -1
  53. data/lib/active_record/relation/calculations.rb +6 -2
  54. data/lib/active_record/relation/finder_methods.rb +1 -1
  55. data/lib/active_record/relation/predicate_builder/association_query_value.rb +3 -3
  56. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +9 -5
  57. data/lib/active_record/relation/predicate_builder.rb +5 -6
  58. data/lib/active_record/relation/query_methods.rb +9 -6
  59. data/lib/active_record/relation/where_clause.rb +17 -14
  60. data/lib/active_record/relation.rb +10 -17
  61. data/lib/active_record/scoping/default.rb +1 -3
  62. data/lib/active_record/signed_id.rb +1 -1
  63. data/lib/active_record/statement_cache.rb +2 -2
  64. data/lib/active_record/table_metadata.rb +6 -3
  65. data/lib/active_record/tasks/database_tasks.rb +1 -0
  66. data/lib/active_record/test_fixtures.rb +42 -1
  67. data/lib/active_record/transactions.rb +4 -2
  68. data/lib/active_record/validations/numericality.rb +1 -1
  69. data/lib/arel/collectors/bind.rb +2 -2
  70. data/lib/arel/collectors/composite.rb +3 -3
  71. data/lib/arel/collectors/sql_string.rb +1 -1
  72. data/lib/arel/collectors/substitute_binds.rb +1 -1
  73. data/lib/arel/nodes/homogeneous_in.rb +4 -0
  74. data/lib/arel/predications.rb +2 -2
  75. data/lib/arel/visitors/to_sql.rb +1 -1
  76. metadata +10 -10
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  def load(yaml)
24
24
  return object_class.new if object_class != Object && yaml.nil?
25
25
  return yaml unless yaml.is_a?(String) && yaml.start_with?("---")
26
- obj = YAML.load(yaml)
26
+ obj = yaml_load(yaml)
27
27
 
28
28
  assert_valid_value(obj, action: "load")
29
29
  obj ||= object_class.new if object_class != Object
@@ -44,6 +44,16 @@ module ActiveRecord
44
44
  rescue ArgumentError
45
45
  raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
46
46
  end
47
+
48
+ if YAML.respond_to?(:unsafe_load)
49
+ def yaml_load(payload)
50
+ YAML.unsafe_load(payload)
51
+ end
52
+ else
53
+ def yaml_load(payload)
54
+ YAML.load(payload)
55
+ end
56
+ end
47
57
  end
48
58
  end
49
59
  end
@@ -24,7 +24,7 @@ module ActiveRecord
24
24
 
25
25
  attr_accessor :schema_cache
26
26
 
27
- def owner_name
27
+ def connection_klass
28
28
  nil
29
29
  end
30
30
  end
@@ -360,7 +360,7 @@ module ActiveRecord
360
360
  include ConnectionAdapters::AbstractPool
361
361
 
362
362
  attr_accessor :automatic_reconnect, :checkout_timeout
363
- attr_reader :db_config, :size, :reaper, :pool_config, :owner_name
363
+ attr_reader :db_config, :size, :reaper, :pool_config, :connection_klass
364
364
 
365
365
  delegate :schema_cache, :schema_cache=, to: :pool_config
366
366
 
@@ -375,7 +375,7 @@ module ActiveRecord
375
375
 
376
376
  @pool_config = pool_config
377
377
  @db_config = pool_config.db_config
378
- @owner_name = pool_config.connection_specification_name
378
+ @connection_klass = pool_config.connection_klass
379
379
 
380
380
  @checkout_timeout = db_config.checkout_timeout
381
381
  @idle_timeout = db_config.idle_timeout
@@ -1040,7 +1040,7 @@ module ActiveRecord
1040
1040
  end
1041
1041
  alias :connection_pools :connection_pool_list
1042
1042
 
1043
- def establish_connection(config, owner_name: Base.name, role: ActiveRecord::Base.current_role, shard: Base.current_shard)
1043
+ def establish_connection(config, owner_name: Base, role: ActiveRecord::Base.current_role, shard: Base.current_shard)
1044
1044
  owner_name = config.to_s if config.is_a?(Symbol)
1045
1045
 
1046
1046
  pool_config = resolve_pool_config(config, owner_name)
@@ -395,7 +395,7 @@ module ActiveRecord
395
395
 
396
396
  # Inserts the given fixture into the table. Overridden in adapters that require
397
397
  # something beyond a simple insert (e.g. Oracle).
398
- # Most of adapters should implement `insert_fixtures_set` that leverages bulk SQL insert.
398
+ # Most of adapters should implement +insert_fixtures_set+ that leverages bulk SQL insert.
399
399
  # We keep this method to provide fallback
400
400
  # for databases like sqlite that do not support bulk inserts.
401
401
  def insert_fixture(fixture, table_name)
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  module QueryCache
8
8
  class << self
9
9
  def included(base) #:nodoc:
10
- dirties_query_cache base, :insert, :update, :delete, :truncate, :truncate_tables,
10
+ dirties_query_cache base, :create, :insert, :update, :delete, :truncate, :truncate_tables,
11
11
  :rollback_to_savepoint, :rollback_db_transaction, :exec_insert_all
12
12
 
13
13
  base.set_callback :checkout, :after, :configure_query_cache!
@@ -94,8 +94,8 @@ module ActiveRecord
94
94
  sql = ["CREATE"]
95
95
  sql << "UNIQUE" if index.unique
96
96
  sql << "INDEX"
97
- sql << "IF NOT EXISTS" if o.if_not_exists
98
97
  sql << o.algorithm if o.algorithm
98
+ sql << "IF NOT EXISTS" if o.if_not_exists
99
99
  sql << index.type if index.type
100
100
  sql << "#{quote_column_name(index.name)} ON #{quote_table_name(index.table)}"
101
101
  sql << "USING #{index.using}" if supports_index_using? && index.using
@@ -71,6 +71,10 @@ module ActiveRecord
71
71
  end
72
72
  CODE
73
73
  end
74
+
75
+ def aliased_types(name, fallback)
76
+ "timestamp" == name ? :datetime : fallback
77
+ end
74
78
  end
75
79
 
76
80
  AddColumnDefinition = Struct.new(:column) # :nodoc:
@@ -1387,8 +1387,14 @@ module ActiveRecord
1387
1387
 
1388
1388
  checks = []
1389
1389
 
1390
+ if !options.key?(:name) && column_name.is_a?(String) && /\W/.match?(column_name)
1391
+ options[:name] = index_name(table_name, column_name)
1392
+ column_names = []
1393
+ else
1394
+ column_names = index_column_names(column_name || options[:column])
1395
+ end
1396
+
1390
1397
  checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1391
- column_names = index_column_names(column_name || options[:column])
1392
1398
 
1393
1399
  if column_names.present?
1394
1400
  checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
@@ -33,6 +33,10 @@ module ActiveRecord
33
33
  @state == :fully_rolledback
34
34
  end
35
35
 
36
+ def invalidated?
37
+ @state == :invalidated
38
+ end
39
+
36
40
  def fully_completed?
37
41
  completed?
38
42
  end
@@ -51,6 +55,11 @@ module ActiveRecord
51
55
  @state = :fully_rolledback
52
56
  end
53
57
 
58
+ def invalidate!
59
+ @children&.each { |c| c.invalidate! }
60
+ @state = :invalidated
61
+ end
62
+
54
63
  def commit!
55
64
  @state = :committed
56
65
  end
@@ -299,7 +308,7 @@ module ActiveRecord
299
308
  def rollback_transaction(transaction = nil)
300
309
  @connection.lock.synchronize do
301
310
  transaction ||= @stack.pop
302
- transaction.rollback
311
+ transaction.rollback unless transaction.state.invalidated?
303
312
  transaction.rollback_records
304
313
  end
305
314
  end
@@ -312,15 +321,17 @@ module ActiveRecord
312
321
  ret
313
322
  rescue Exception => error
314
323
  if transaction
324
+ transaction.state.invalidate! if error.is_a? ActiveRecord::TransactionRollbackError
315
325
  rollback_transaction
316
326
  after_failure_actions(transaction, error)
317
327
  end
328
+
318
329
  raise
319
330
  ensure
320
331
  if transaction
321
332
  if error
322
- # @connection still holds an open transaction, so we must not
323
- # put it back in the pool for reuse
333
+ # @connection still holds an open or invalid transaction, so we must not
334
+ # put it back in the pool for reuse.
324
335
  @connection.throw_away! unless transaction.state.rolledback?
325
336
  else
326
337
  if Thread.current.status == "aborting"
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "set"
4
- require "active_record/connection_adapters/schema_cache"
5
4
  require "active_record/connection_adapters/sql_type_metadata"
6
5
  require "active_record/connection_adapters/abstract/schema_dumper"
7
6
  require "active_record/connection_adapters/abstract/schema_creation"
@@ -111,22 +110,21 @@ module ActiveRecord
111
110
  @config.fetch(:use_metadata_table, true)
112
111
  end
113
112
 
114
- # Determines whether writes are currently being prevents.
113
+ # Determines whether writes are currently being prevented.
115
114
  #
116
115
  # Returns true if the connection is a replica.
117
116
  #
118
117
  # If the application is using legacy handling, returns
119
- # true if `connection_handler.prevent_writes` is set.
118
+ # true if +connection_handler.prevent_writes+ is set.
120
119
  #
121
120
  # If the application is using the new connection handling
122
- # will return true based on `current_preventing_writes`.
121
+ # will return true based on +current_preventing_writes+.
123
122
  def preventing_writes?
124
123
  return true if replica?
125
124
  return ActiveRecord::Base.connection_handler.prevent_writes if ActiveRecord::Base.legacy_connection_handling
126
- return false if owner_name.nil?
125
+ return false if connection_klass.nil?
127
126
 
128
- klass = self.owner_name.safe_constantize
129
- klass&.current_preventing_writes
127
+ connection_klass.current_preventing_writes
130
128
  end
131
129
 
132
130
  def migrations_paths # :nodoc:
@@ -155,9 +153,10 @@ module ActiveRecord
155
153
  end
156
154
  end
157
155
 
158
- def prepared_statements
156
+ def prepared_statements?
159
157
  @prepared_statements && !prepared_statements_disabled_cache.include?(object_id)
160
158
  end
159
+ alias :prepared_statements :prepared_statements?
161
160
 
162
161
  def prepared_statements_disabled_cache # :nodoc:
163
162
  Thread.current[:ar_prepared_statements_disabled_cache] ||= Set.new
@@ -202,8 +201,8 @@ module ActiveRecord
202
201
  @owner = Thread.current
203
202
  end
204
203
 
205
- def owner_name # :nodoc:
206
- @pool.owner_name
204
+ def connection_klass # :nodoc:
205
+ @pool.connection_klass
207
206
  end
208
207
 
209
208
  def schema_cache
@@ -251,7 +250,7 @@ module ActiveRecord
251
250
  end
252
251
 
253
252
  def unprepared_statement
254
- cache = prepared_statements_disabled_cache.add(object_id) if @prepared_statements
253
+ cache = prepared_statements_disabled_cache.add?(object_id) if @prepared_statements
255
254
  yield
256
255
  ensure
257
256
  cache&.delete(object_id)
@@ -23,8 +23,12 @@ module ActiveRecord
23
23
  @name_to_pool_config[shard]
24
24
  end
25
25
 
26
- def set_pool_config(_, shard, pool_config)
27
- @name_to_pool_config[shard] = pool_config
26
+ def set_pool_config(role, shard, pool_config)
27
+ if pool_config
28
+ @name_to_pool_config[shard] = pool_config
29
+ else
30
+ raise ArgumentError, "The `pool_config` for the :#{role} role and :#{shard} shard was `nil`. Please check your configuration. If you want your writing role to be something other than `:writing` set `config.active_record.writing_role` in your application configuration. The same setting should be applied for the `reading_role` if applicable."
31
+ end
28
32
  end
29
33
  end
30
34
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/time_with_zone"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  module MySQL
@@ -69,10 +71,23 @@ module ActiveRecord
69
71
  private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
70
72
 
71
73
  private
74
+ # Override +_type_cast+ we pass to mysql2 Date and Time objects instead
75
+ # of Strings since mysql2 is able to handle those classes more efficiently.
72
76
  def _type_cast(value)
73
77
  case value
74
- when Date, Time then value
75
- else super
78
+ when ActiveSupport::TimeWithZone
79
+ # We need to check explicitly for ActiveSupport::TimeWithZone because
80
+ # we need to transform it to Time objects but we don't want to
81
+ # transform Time objects to themselves.
82
+ if ActiveRecord::Base.default_timezone == :utc
83
+ value.getutc
84
+ else
85
+ value.getlocal
86
+ end
87
+ when Date, Time
88
+ value
89
+ else
90
+ super
76
91
  end
77
92
  end
78
93
  end
@@ -79,7 +79,10 @@ module ActiveRecord
79
79
  " WHERE table_schema = #{scope[:schema]}" \
80
80
  " AND table_name = #{scope[:name]}" \
81
81
  " AND column_name = #{column_name}"
82
- @connection.query_value(sql, "SCHEMA").inspect
82
+ # Calling .inspect leads into issues with the query result
83
+ # which already returns escaped quotes.
84
+ # We remove the escape sequence from the result in order to deal with double escaping issues.
85
+ @connection.query_value(sql, "SCHEMA").gsub("\\'", "'").inspect
83
86
  end
84
87
  end
85
88
  end
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  class PoolConfig # :nodoc:
6
6
  include Mutex_m
7
7
 
8
- attr_reader :db_config, :connection_specification_name
8
+ attr_reader :db_config, :connection_klass
9
9
  attr_accessor :schema_cache
10
10
 
11
11
  INSTANCES = ObjectSpace::WeakMap.new
@@ -17,14 +17,24 @@ module ActiveRecord
17
17
  end
18
18
  end
19
19
 
20
- def initialize(connection_specification_name, db_config)
20
+ def initialize(connection_klass, db_config)
21
21
  super()
22
- @connection_specification_name = connection_specification_name
22
+ @connection_klass = connection_klass
23
23
  @db_config = db_config
24
24
  @pool = nil
25
25
  INSTANCES[self] = self
26
26
  end
27
27
 
28
+ def connection_specification_name
29
+ if connection_klass.is_a?(String)
30
+ connection_klass
31
+ elsif connection_klass.primary_class?
32
+ "ActiveRecord::Base"
33
+ else
34
+ connection_klass.name
35
+ end
36
+ end
37
+
28
38
  def disconnect!
29
39
  ActiveSupport::ForkTracker.check!
30
40
 
@@ -36,7 +36,11 @@ module ActiveRecord
36
36
  end
37
37
 
38
38
  def set_pool_config(role, shard, pool_config)
39
- @name_to_role_mapping[role][shard] = pool_config
39
+ if pool_config
40
+ @name_to_role_mapping[role][shard] = pool_config
41
+ else
42
+ raise ArgumentError, "The `pool_config` for the :#{role} role and :#{shard} shard was `nil`. Please check your configuration. If you want your writing role to be something other than `:writing` set `config.active_record.writing_role` in your application configuration. The same setting should be applied for the `reading_role` if applicable."
43
+ end
40
44
  end
41
45
  end
42
46
  end
@@ -57,7 +57,7 @@ module ActiveRecord
57
57
  ftype = result.ftype i
58
58
  fmod = result.fmod i
59
59
  case type = get_oid_type(ftype, fmod, fname)
60
- when Type::Integer, Type::Float, Type::Decimal, Type::String, Type::DateTime, Type::Boolean
60
+ when Type::Integer, Type::Float, OID::Decimal, Type::String, Type::DateTime, Type::Boolean
61
61
  # skip if a column has already been type casted by pg decoders
62
62
  else types[fname] = type
63
63
  end
@@ -26,9 +26,9 @@ module ActiveRecord
26
26
 
27
27
  value = value.sub(/^\((.+)\)$/, '-\1') # (4)
28
28
  case value
29
- when /^-?\D*[\d,]+\.\d{2}$/ # (1)
29
+ when /^-?\D*+[\d,]+\.\d{2}$/ # (1)
30
30
  value.gsub!(/[^-\d.]/, "")
31
- when /^-?\D*[\d.]+,\d{2}$/ # (2)
31
+ when /^-?\D*+[\d.]+,\d{2}$/ # (2)
32
32
  value.gsub!(/[^-\d,]/, "").sub!(/,/, ".")
33
33
  end
34
34
 
@@ -227,11 +227,7 @@ module ActiveRecord
227
227
  end
228
228
 
229
229
  def next_key
230
- "a#{@counter + 1}"
231
- end
232
-
233
- def []=(sql, key)
234
- super.tap { @counter += 1 }
230
+ "a#{@counter += 1}"
235
231
  end
236
232
 
237
233
  private
@@ -649,9 +645,7 @@ module ActiveRecord
649
645
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
650
646
  end
651
647
 
652
- if without_prepared_statement?(binds)
653
- result = exec_no_cache(sql, name, [])
654
- elsif !prepare
648
+ if !prepare || without_prepared_statement?(binds)
655
649
  result = exec_no_cache(sql, name, binds)
656
650
  else
657
651
  result = exec_cache(sql, name, binds)
@@ -9,7 +9,15 @@ module ActiveRecord
9
9
  return unless File.file?(filename)
10
10
 
11
11
  read(filename) do |file|
12
- filename.include?(".dump") ? Marshal.load(file) : YAML.load(file)
12
+ if filename.include?(".dump")
13
+ Marshal.load(file)
14
+ else
15
+ if YAML.respond_to?(:unsafe_load)
16
+ YAML.unsafe_load(file)
17
+ else
18
+ YAML.load(file)
19
+ end
20
+ end
13
21
  end
14
22
  end
15
23
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record/connection_adapters/deduplicable"
4
-
5
3
  module ActiveRecord
6
4
  # :stopdoc:
7
5
  module ConnectionAdapters
@@ -271,7 +271,7 @@ module ActiveRecord
271
271
  def change_column(table_name, column_name, type, **options) #:nodoc:
272
272
  alter_table(table_name) do |definition|
273
273
  definition[column_name].instance_eval do
274
- self.type = type
274
+ self.type = aliased_types(type.to_s, type)
275
275
  self.options.merge!(options)
276
276
  end
277
277
  end
@@ -12,6 +12,8 @@ module ActiveRecord
12
12
  autoload :PoolConfig
13
13
  autoload :PoolManager
14
14
  autoload :LegacyPoolManager
15
+ autoload :SchemaCache
16
+ autoload :Deduplicable
15
17
 
16
18
  autoload_at "active_record/connection_adapters/abstract/schema_definitions" do
17
19
  autoload :IndexDefinition
@@ -91,6 +91,7 @@ module ActiveRecord
91
91
  db_config, owner_name = resolve_config_for_connection(database_key)
92
92
  handler = lookup_connection_handler(role.to_sym)
93
93
 
94
+ self.connection_class = true
94
95
  connections << handler.establish_connection(db_config, owner_name: owner_name, role: role)
95
96
  end
96
97
 
@@ -99,6 +100,7 @@ module ActiveRecord
99
100
  db_config, owner_name = resolve_config_for_connection(database_key)
100
101
  handler = lookup_connection_handler(role.to_sym)
101
102
 
103
+ self.connection_class = true
102
104
  connections << handler.establish_connection(db_config, owner_name: owner_name, role: role, shard: shard.to_sym)
103
105
  end
104
106
  end
@@ -112,7 +114,7 @@ module ActiveRecord
112
114
  #
113
115
  # If only a role is passed, Active Record will look up the connection
114
116
  # based on the requested role. If a non-established role is requested
115
- # an `ActiveRecord::ConnectionNotEstablished` error will be raised:
117
+ # an +ActiveRecord::ConnectionNotEstablished+ error will be raised:
116
118
  #
117
119
  # ActiveRecord::Base.connected_to(role: :writing) do
118
120
  # Dog.create! # creates dog using dog writing connection
@@ -123,7 +125,7 @@ module ActiveRecord
123
125
  # end
124
126
  #
125
127
  # When swapping to a shard, the role must be passed as well. If a non-existent
126
- # shard is passed, an `ActiveRecord::ConnectionNotEstablished` error will be
128
+ # shard is passed, an +ActiveRecord::ConnectionNotEstablished+ error will be
127
129
  # raised.
128
130
  #
129
131
  # When a shard and role is passed, Active Record will first lookup the role,
@@ -143,6 +145,10 @@ module ActiveRecord
143
145
  if self != Base && !abstract_class
144
146
  raise NotImplementedError, "calling `connected_to` is only allowed on ActiveRecord::Base or abstract classes."
145
147
  end
148
+
149
+ if name != connection_specification_name && !primary_class?
150
+ raise NotImplementedError, "calling `connected_to` is only allowed on the abstract class that established the connection."
151
+ end
146
152
  end
147
153
 
148
154
  if database && (role || shard)
@@ -172,20 +178,22 @@ module ActiveRecord
172
178
  end
173
179
  end
174
180
 
175
- # Connects a role and/or shard to the provided connection names. Optionally `prevent_writes`
176
- # can be passed to block writes on a connection. `reading` will automatically set
177
- # `prevent_writes` to true.
181
+ # Connects a role and/or shard to the provided connection names. Optionally +prevent_writes+
182
+ # can be passed to block writes on a connection. +reading+ will automatically set
183
+ # +prevent_writes+ to true.
178
184
  #
179
- # `connected_to_many` is an alternative to deeply nested `connected_to` blocks.
185
+ # +connected_to_many+ is an alternative to deeply nested +connected_to+ blocks.
180
186
  #
181
187
  # Usage:
182
188
  #
183
- # ActiveRecord::Base.connected_to(AnimalsRecord, MealsRecord], role: :reading) do
189
+ # ActiveRecord::Base.connected_to_many(AnimalsRecord, MealsRecord, role: :reading) do
184
190
  # Dog.first # Read from animals replica
185
191
  # Dinner.first # Read from meals replica
186
192
  # Person.first # Read from primary writer
187
193
  # end
188
- def connected_to_many(classes, role:, shard: nil, prevent_writes: false)
194
+ def connected_to_many(*classes, role:, shard: nil, prevent_writes: false)
195
+ classes = classes.flatten
196
+
189
197
  if legacy_connection_handling
190
198
  raise NotImplementedError, "connected_to_many is not available with legacy connection handling"
191
199
  end
@@ -208,7 +216,7 @@ module ActiveRecord
208
216
  # being used. For example, when booting a console in readonly mode.
209
217
  #
210
218
  # It is not recommended to use this method in a request since it
211
- # does not yield to a block like `connected_to`.
219
+ # does not yield to a block like +connected_to+.
212
220
  def connecting_to(role: default_role, shard: default_shard, prevent_writes: false)
213
221
  if legacy_connection_handling
214
222
  raise NotImplementedError, "`connecting_to` is not available with `legacy_connection_handling`."
@@ -222,13 +230,13 @@ module ActiveRecord
222
230
  # Prevent writing to the database regardless of role.
223
231
  #
224
232
  # In some cases you may want to prevent writes to the database
225
- # even if you are on a database that can write. `while_preventing_writes`
233
+ # even if you are on a database that can write. +while_preventing_writes+
226
234
  # will prevent writes to the database for the duration of the block.
227
235
  #
228
236
  # This method does not provide the same protection as a readonly
229
237
  # user and is meant to be a safeguard against accidental writes.
230
238
  #
231
- # See `READ_QUERY` for the queries that are blocked by this
239
+ # See +READ_QUERY+ for the queries that are blocked by this
232
240
  # method.
233
241
  def while_preventing_writes(enabled = true, &block)
234
242
  if legacy_connection_handling
@@ -357,7 +365,7 @@ module ActiveRecord
357
365
  self.connection_specification_name = owner_name
358
366
 
359
367
  db_config = Base.configurations.resolve(config_or_env)
360
- [db_config, owner_name]
368
+ [db_config, self]
361
369
  end
362
370
 
363
371
  def with_handler(handler_key, &blk)