activerecord 7.1.1 → 7.1.3

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +177 -0
  3. data/README.rdoc +1 -0
  4. data/lib/active_record/associations/association.rb +2 -1
  5. data/lib/active_record/associations/preloader/association.rb +4 -1
  6. data/lib/active_record/associations.rb +15 -15
  7. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  8. data/lib/active_record/attribute_methods/dirty.rb +13 -9
  9. data/lib/active_record/attribute_methods.rb +1 -1
  10. data/lib/active_record/callbacks.rb +2 -2
  11. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +11 -8
  12. data/lib/active_record/connection_adapters/abstract/database_statements.rb +4 -2
  13. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
  14. data/lib/active_record/connection_adapters/abstract_adapter.rb +13 -4
  15. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +3 -0
  16. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +4 -1
  17. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  18. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -5
  19. data/lib/active_record/connection_adapters/postgresql_adapter.rb +32 -33
  20. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -3
  21. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +1 -0
  22. data/lib/active_record/connection_adapters/trilogy_adapter.rb +9 -1
  23. data/lib/active_record/connection_handling.rb +1 -1
  24. data/lib/active_record/core.rb +41 -7
  25. data/lib/active_record/delegated_type.rb +1 -1
  26. data/lib/active_record/encryption/encryptable_record.rb +7 -1
  27. data/lib/active_record/encryption/encrypted_attribute_type.rb +4 -0
  28. data/lib/active_record/encryption/extended_deterministic_queries.rb +0 -15
  29. data/lib/active_record/enum.rb +6 -9
  30. data/lib/active_record/errors.rb +5 -4
  31. data/lib/active_record/fixtures.rb +16 -0
  32. data/lib/active_record/future_result.rb +1 -0
  33. data/lib/active_record/gem_version.rb +1 -1
  34. data/lib/active_record/insert_all.rb +3 -3
  35. data/lib/active_record/internal_metadata.rb +1 -1
  36. data/lib/active_record/middleware/database_selector.rb +1 -1
  37. data/lib/active_record/migration/compatibility.rb +8 -0
  38. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  39. data/lib/active_record/migration.rb +9 -5
  40. data/lib/active_record/model_schema.rb +5 -5
  41. data/lib/active_record/nested_attributes.rb +3 -3
  42. data/lib/active_record/normalization.rb +8 -0
  43. data/lib/active_record/persistence.rb +4 -3
  44. data/lib/active_record/promise.rb +1 -1
  45. data/lib/active_record/railties/controller_runtime.rb +2 -1
  46. data/lib/active_record/railties/databases.rake +5 -5
  47. data/lib/active_record/reflection.rb +13 -1
  48. data/lib/active_record/relation/calculations.rb +28 -1
  49. data/lib/active_record/relation/delegation.rb +1 -1
  50. data/lib/active_record/relation.rb +18 -3
  51. data/lib/active_record/runtime_registry.rb +15 -1
  52. data/lib/active_record/schema_migration.rb +1 -1
  53. data/lib/active_record/secure_token.rb +1 -1
  54. data/lib/active_record/tasks/database_tasks.rb +5 -5
  55. data/lib/arel/nodes/homogeneous_in.rb +1 -1
  56. metadata +10 -9
@@ -108,10 +108,11 @@ module ActiveRecord
108
108
  # but significantly increases the risk of data loss if the database
109
109
  # crashes. As a result, this should not be used in production
110
110
  # environments. If you would like all created tables to be unlogged in
111
- # the test environment you can add the following line to your test.rb
112
- # file:
111
+ # the test environment you can add the following to your test.rb file:
113
112
  #
114
- # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
113
+ # ActiveSupport.on_load(:active_record_postgresqladapter) do
114
+ # self.create_unlogged_tables = true
115
+ # end
115
116
  class_attribute :create_unlogged_tables, default: false
116
117
 
117
118
  ##
@@ -277,6 +278,10 @@ module ActiveRecord
277
278
  database_version >= 12_00_00 # >= 12.0
278
279
  end
279
280
 
281
+ def supports_identity_columns? # :nodoc:
282
+ database_version >= 10_00_00 # >= 10.0
283
+ end
284
+
280
285
  def supports_nulls_not_distinct?
281
286
  database_version >= 15_00_00 # >= 15.0
282
287
  end
@@ -535,7 +540,7 @@ module ActiveRecord
535
540
  END
536
541
  $$;
537
542
  SQL
538
- internal_exec_query(query)
543
+ internal_exec_query(query).tap { reload_type_map }
539
544
  end
540
545
 
541
546
  # Drops an enum type.
@@ -551,7 +556,7 @@ module ActiveRecord
551
556
  query = <<~SQL
552
557
  DROP TYPE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(name)};
553
558
  SQL
554
- internal_exec_query(query)
559
+ internal_exec_query(query).tap { reload_type_map }
555
560
  end
556
561
 
557
562
  # Rename an existing enum type to something else.
@@ -596,14 +601,6 @@ module ActiveRecord
596
601
  @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
597
602
  end
598
603
 
599
- # Returns the maximum length of a table name.
600
- def table_name_length
601
- # PostgreSQL automatically creates an index for PRIMARY KEY with name consisting of
602
- # truncated table name and "_pkey" suffix fitting into max_identifier_length number of characters.
603
- # We allow smaller table names to be able to correctly rename this index when renaming the table.
604
- max_identifier_length - "_pkey".length
605
- end
606
-
607
604
  # Set the authorized user for this session
608
605
  def session_auth=(user)
609
606
  clear_cache!
@@ -894,7 +891,9 @@ module ActiveRecord
894
891
  type_casted_binds = type_casted_binds(binds)
895
892
  log(sql, name, binds, type_casted_binds, async: async) do
896
893
  with_raw_connection do |conn|
897
- conn.exec_params(sql, type_casted_binds)
894
+ result = conn.exec_params(sql, type_casted_binds)
895
+ verified!
896
+ result
898
897
  end
899
898
  end
900
899
  end
@@ -904,12 +903,14 @@ module ActiveRecord
904
903
 
905
904
  update_typemap_for_default_timezone
906
905
 
907
- stmt_key = prepare_statement(sql, binds)
908
- type_casted_binds = type_casted_binds(binds)
909
-
910
906
  with_raw_connection do |conn|
907
+ stmt_key = prepare_statement(sql, binds, conn)
908
+ type_casted_binds = type_casted_binds(binds)
909
+
911
910
  log(sql, name, binds, type_casted_binds, stmt_key, async: async) do
912
- conn.exec_prepared(stmt_key, type_casted_binds)
911
+ result = conn.exec_prepared(stmt_key, type_casted_binds)
912
+ verified!
913
+ result
913
914
  end
914
915
  end
915
916
  rescue ActiveRecord::StatementInvalid => e
@@ -957,22 +958,20 @@ module ActiveRecord
957
958
 
958
959
  # Prepare the statement if it hasn't been prepared, return
959
960
  # the statement key.
960
- def prepare_statement(sql, binds)
961
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
962
- sql_key = sql_key(sql)
963
- unless @statements.key? sql_key
964
- nextkey = @statements.next_key
965
- begin
966
- conn.prepare nextkey, sql
967
- rescue => e
968
- raise translate_exception_class(e, sql, binds)
969
- end
970
- # Clear the queue
971
- conn.get_last_result
972
- @statements[sql_key] = nextkey
961
+ def prepare_statement(sql, binds, conn)
962
+ sql_key = sql_key(sql)
963
+ unless @statements.key? sql_key
964
+ nextkey = @statements.next_key
965
+ begin
966
+ conn.prepare nextkey, sql
967
+ rescue => e
968
+ raise translate_exception_class(e, sql, binds)
973
969
  end
974
- @statements[sql_key]
970
+ # Clear the queue
971
+ conn.get_last_result
972
+ @statements[sql_key] = nextkey
975
973
  end
974
+ @statements[sql_key]
976
975
  end
977
976
 
978
977
  # Connects to a PostgreSQL server and sets up the adapter depending on the
@@ -1076,7 +1075,7 @@ module ActiveRecord
1076
1075
  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
1077
1076
  pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
1078
1077
  c.collname, col_description(a.attrelid, a.attnum) AS comment,
1079
- a.attidentity AS identity,
1078
+ #{supports_identity_columns? ? 'attidentity' : quote('')} AS identity,
1080
1079
  #{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated
1081
1080
  FROM pg_attribute a
1082
1081
  LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
@@ -50,6 +50,7 @@ module ActiveRecord
50
50
  stmt.bind_params(type_casted_binds)
51
51
  records = stmt.to_a
52
52
  end
53
+ verified!
53
54
 
54
55
  build_result(columns: cols, rows: records)
55
56
  end
@@ -76,7 +77,9 @@ module ActiveRecord
76
77
  def begin_db_transaction # :nodoc:
77
78
  log("begin transaction", "TRANSACTION") do
78
79
  with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
79
- conn.transaction
80
+ result = conn.transaction
81
+ verified!
82
+ result
80
83
  end
81
84
  end
82
85
  end
@@ -112,7 +115,9 @@ module ActiveRecord
112
115
  def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: false)
113
116
  log(sql, name, async: async) do
114
117
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
115
- conn.execute(sql)
118
+ result = conn.execute(sql)
119
+ verified!
120
+ result
116
121
  end
117
122
  end
118
123
  end
@@ -133,7 +138,9 @@ module ActiveRecord
133
138
 
134
139
  log(sql, name) do
135
140
  with_raw_connection do |conn|
136
- conn.execute_batch2(sql)
141
+ result = conn.execute_batch2(sql)
142
+ verified!
143
+ result
137
144
  end
138
145
  end
139
146
  end
@@ -46,6 +46,7 @@ module ActiveRecord
46
46
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
47
47
  sync_timezone_changes(conn)
48
48
  result = conn.query(sql)
49
+ verified!
49
50
  handle_warnings(sql)
50
51
  result
51
52
  end
@@ -57,7 +57,7 @@ module ActiveRecord
57
57
  def new_client(config)
58
58
  config[:ssl_mode] = parse_ssl_mode(config[:ssl_mode]) if config[:ssl_mode]
59
59
  ::Trilogy.new(config)
60
- rescue ::Trilogy::ConnectionError, ::Trilogy::ProtocolError => error
60
+ rescue ::Trilogy::Error => error
61
61
  raise translate_connect_error(config, error)
62
62
  end
63
63
 
@@ -99,6 +99,14 @@ module ActiveRecord
99
99
  end
100
100
  end
101
101
 
102
+ def initialize(...)
103
+ super
104
+
105
+ # Trilogy ignore `socket` if `host is set. We want the opposite to allow
106
+ # configuring UNIX domain sockets via `DATABASE_URL`.
107
+ @config.delete(:host) if @config[:socket]
108
+ end
109
+
102
110
  TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
103
111
 
104
112
  def supports_json?
@@ -256,7 +256,7 @@ module ActiveRecord
256
256
 
257
257
  attr_writer :connection_specification_name
258
258
 
259
- # Return the connection specification name from the current class or its parent.
259
+ # Returns the connection specification name from the current class or its parent.
260
260
  def connection_specification_name
261
261
  if !defined?(@connection_specification_name) || @connection_specification_name.nil?
262
262
  return self == Base ? Base.name : superclass.connection_specification_name
@@ -15,9 +15,10 @@ module ActiveRecord
15
15
  ##
16
16
  # :singleton-method:
17
17
  #
18
- # Accepts a logger conforming to the interface of Log4r which is then
19
- # passed on to any new database connections made and which can be
20
- # retrieved on both a class and instance level by calling +logger+.
18
+ # Accepts a logger conforming to the interface of Log4r or the default
19
+ # Ruby +Logger+ class, which is then passed on to any new database
20
+ # connections made. You can retrieve this logger by calling +logger+ on
21
+ # either an Active Record model class or an Active Record model instance.
21
22
  class_attribute :logger, instance_writer: false
22
23
 
23
24
  class_attribute :_destroy_association_async_job, instance_accessor: false, default: "ActiveRecord::DestroyAssociationAsyncJob"
@@ -271,10 +272,25 @@ module ActiveRecord
271
272
  elsif reflection.belongs_to? && !reflection.polymorphic?
272
273
  key = reflection.join_foreign_key
273
274
  pkey = reflection.join_primary_key
274
- value = value.public_send(pkey) if value.respond_to?(pkey)
275
+
276
+ if pkey.is_a?(Array)
277
+ if pkey.all? { |attribute| value.respond_to?(attribute) }
278
+ value = pkey.map do |attribute|
279
+ if attribute == "id"
280
+ value.id_value
281
+ else
282
+ value.public_send(attribute)
283
+ end
284
+ end
285
+ composite_primary_key = true
286
+ end
287
+ else
288
+ value = value.public_send(pkey) if value.respond_to?(pkey)
289
+ end
275
290
  end
276
291
 
277
- if !columns_hash.key?(key) || StatementCache.unsupported_value?(value)
292
+ if !composite_primary_key &&
293
+ (!columns_hash.key?(key) || StatementCache.unsupported_value?(value))
278
294
  return super
279
295
  end
280
296
 
@@ -401,12 +417,18 @@ module ActiveRecord
401
417
 
402
418
  def cached_find_by(keys, values)
403
419
  statement = cached_find_by_statement(keys) { |params|
404
- wheres = keys.index_with { params.bind }
420
+ wheres = keys.index_with do |key|
421
+ if key.is_a?(Array)
422
+ [key.map { params.bind }]
423
+ else
424
+ params.bind
425
+ end
426
+ end
405
427
  where(wheres).limit(1)
406
428
  }
407
429
 
408
430
  begin
409
- statement.execute(values, connection).first
431
+ statement.execute(values.flatten, connection).first
410
432
  rescue TypeError
411
433
  raise ActiveRecord::StatementInvalid
412
434
  end
@@ -544,6 +566,10 @@ module ActiveRecord
544
566
  # Returns a hash of the given methods with their names as keys and returned
545
567
  # values as values.
546
568
  #
569
+ # topic = Topic.new(title: "Budget", author_name: "Jason")
570
+ # topic.slice(:title, :author_name)
571
+ # => { "title" => "Budget", "author_name" => "Jason" }
572
+ #
547
573
  #--
548
574
  # Implemented by ActiveModel::Access#slice.
549
575
 
@@ -554,6 +580,10 @@ module ActiveRecord
554
580
  #
555
581
  # Returns an array of the values returned by the given methods.
556
582
  #
583
+ # topic = Topic.new(title: "Budget", author_name: "Jason")
584
+ # topic.values_at(:title, :author_name)
585
+ # => ["Budget", "Jason"]
586
+ #
557
587
  #--
558
588
  # Implemented by ActiveModel::Access#values_at.
559
589
 
@@ -672,6 +702,10 @@ module ActiveRecord
672
702
  end
673
703
 
674
704
  # Marks this record as read only.
705
+ #
706
+ # customer = Customer.first
707
+ # customer.readonly!
708
+ # customer.save # Raises an ActiveRecord::ReadOnlyRecord
675
709
  def readonly!
676
710
  @readonly = true
677
711
  end
@@ -138,7 +138,7 @@ module ActiveRecord
138
138
  #
139
139
  # Now you can list a bunch of entries, call <tt>Entry#title</tt>, and polymorphism will provide you with the answer.
140
140
  #
141
- # == Nested Attributes
141
+ # == Nested \Attributes
142
142
  #
143
143
  # Enabling nested attributes on a delegated_type association allows you to
144
144
  # create the entry and message in one go:
@@ -144,7 +144,13 @@ module ActiveRecord
144
144
 
145
145
  # Returns whether a given attribute is encrypted or not.
146
146
  def encrypted_attribute?(attribute_name)
147
- ActiveRecord::Encryption.encryptor.encrypted? read_attribute_before_type_cast(attribute_name)
147
+ name = attribute_name.to_s
148
+ name = self.class.attribute_aliases[name] || name
149
+
150
+ return false unless self.class.encrypted_attributes&.include? name.to_sym
151
+
152
+ type = type_for_attribute(name)
153
+ type.encrypted? read_attribute_before_type_cast(name)
148
154
  end
149
155
 
150
156
  # Returns the ciphertext for +attribute_name+.
@@ -44,6 +44,10 @@ module ActiveRecord
44
44
  end
45
45
  end
46
46
 
47
+ def encrypted?(value)
48
+ with_context { encryptor.encrypted? value }
49
+ end
50
+
47
51
  def changed_in_place?(raw_old_value, new_value)
48
52
  old_value = raw_old_value.nil? ? nil : deserialize(raw_old_value)
49
53
  old_value != new_value
@@ -28,7 +28,6 @@ module ActiveRecord
28
28
  ActiveRecord::Relation.prepend(RelationQueries)
29
29
  ActiveRecord::Base.include(CoreQueries)
30
30
  ActiveRecord::Encryption::EncryptedAttributeType.prepend(ExtendedEncryptableType)
31
- Arel::Nodes::HomogeneousIn.prepend(InWithAdditionalValues)
32
31
  end
33
32
 
34
33
  # When modifying this file run performance tests in
@@ -153,20 +152,6 @@ module ActiveRecord
153
152
  end
154
153
  end
155
154
  end
156
-
157
- module InWithAdditionalValues
158
- def proc_for_binds
159
- -> value { ActiveModel::Attribute.with_cast_value(attribute.name, value, encryption_aware_type_caster) }
160
- end
161
-
162
- def encryption_aware_type_caster
163
- if attribute.type_caster.is_a?(ActiveRecord::Encryption::EncryptedAttributeType)
164
- attribute.type_caster.cast_type
165
- else
166
- attribute.type_caster
167
- end
168
- end
169
- end
170
155
  end
171
156
  end
172
157
  end
@@ -167,15 +167,6 @@ module ActiveRecord
167
167
  base.class_attribute(:defined_enums, instance_writer: false, default: {})
168
168
  end
169
169
 
170
- def load_schema! # :nodoc:
171
- attributes_to_define_after_schema_loads.each do |name, (cast_type, _default)|
172
- unless columns_hash.key?(name)
173
- cast_type = cast_type[type_for_attribute(name)] if Proc === cast_type
174
- raise "Unknown enum attribute '#{name}' for #{self.name}" if Enum::EnumType === cast_type
175
- end
176
- end
177
- end
178
-
179
170
  class EnumType < Type::Value # :nodoc:
180
171
  delegate :type, to: :subtype
181
172
 
@@ -255,6 +246,12 @@ module ActiveRecord
255
246
  detect_enum_conflict!(name, "#{name}=")
256
247
 
257
248
  attribute(name, **options) do |subtype|
249
+ if subtype == ActiveModel::Type.default_value
250
+ raise "Undeclared attribute type for enum '#{name}'. Enums must be" \
251
+ " backed by a database column or declared with an explicit type" \
252
+ " via `attribute`."
253
+ end
254
+
258
255
  subtype = subtype.subtype if EnumType === subtype
259
256
  EnumType.new(name, enum_values, subtype, raise_on_invalid_values: !validate)
260
257
  end
@@ -318,14 +318,15 @@ module ActiveRecord
318
318
  class << self
319
319
  def db_error(db_name)
320
320
  NoDatabaseError.new(<<~MSG)
321
- We could not find your database: #{db_name}. Available database configurations can be found in config/database.yml file.
321
+ We could not find your database: #{db_name}. Available database configurations can be found in config/database.yml.
322
322
 
323
323
  To resolve this error:
324
324
 
325
- - Did you create the database for this app, or delete it? You may need to create your database.
326
- - Has the database name changed? Check your database.yml config has the correct database name.
325
+ - Did you not create the database, or did you delete it? To create the database, run:
327
326
 
328
- To create your database, run:\n\n bin/rails db:create
327
+ bin/rails db:create
328
+
329
+ - Has the database name changed? Verify that config/database.yml contains the correct database name.
329
330
  MSG
330
331
  end
331
332
  end
@@ -268,6 +268,8 @@ module ActiveRecord
268
268
  # name: Reginald the Pirate
269
269
  # monkey_id: 1
270
270
  #
271
+ # <code></code>
272
+ #
271
273
  # ### in monkeys.yml
272
274
  #
273
275
  # george:
@@ -285,6 +287,8 @@ module ActiveRecord
285
287
  # name: Reginald the Pirate
286
288
  # monkey: george
287
289
  #
290
+ # <code></code>
291
+ #
288
292
  # ### in monkeys.yml
289
293
  #
290
294
  # george:
@@ -306,6 +310,8 @@ module ActiveRecord
306
310
  #
307
311
  # belongs_to :eater, polymorphic: true
308
312
  #
313
+ # <code></code>
314
+ #
309
315
  # ### in fruits.yml
310
316
  #
311
317
  # apple:
@@ -331,6 +337,8 @@ module ActiveRecord
331
337
  # id: 1
332
338
  # name: George the Monkey
333
339
  #
340
+ # <code></code>
341
+ #
334
342
  # ### in fruits.yml
335
343
  #
336
344
  # apple:
@@ -345,6 +353,8 @@ module ActiveRecord
345
353
  # id: 3
346
354
  # name: grape
347
355
  #
356
+ # <code></code>
357
+ #
348
358
  # ### in fruits_monkeys.yml
349
359
  #
350
360
  # apple_george:
@@ -368,6 +378,8 @@ module ActiveRecord
368
378
  # name: George the Monkey
369
379
  # fruits: apple, orange, grape
370
380
  #
381
+ # <code></code>
382
+ #
371
383
  # ### in fruits.yml
372
384
  #
373
385
  # apple:
@@ -467,6 +479,8 @@ module ActiveRecord
467
479
  # belongs_to :author
468
480
  # end
469
481
  #
482
+ # <code></code>
483
+ #
470
484
  # # books.yml
471
485
  # alices_adventure_in_wonderland:
472
486
  # author_id: <%= ActiveRecord::FixtureSet.identify(:lewis_carroll) %>
@@ -482,6 +496,8 @@ module ActiveRecord
482
496
  # belongs_to :book, query_constraints: [:author_id, :book_id]
483
497
  # end
484
498
  #
499
+ # <code></code>
500
+ #
485
501
  # # book_orders.yml
486
502
  # alices_adventure_in_wonderland_in_books:
487
503
  # author: lewis_carroll
@@ -48,6 +48,7 @@ module ActiveRecord
48
48
  Canceled = Class.new(ActiveRecordError)
49
49
 
50
50
  delegate :empty?, :to_a, to: :result
51
+ delegate_missing_to :result
51
52
 
52
53
  attr_reader :lock_wait
53
54
 
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 7
11
11
  MINOR = 1
12
- TINY = 1
12
+ TINY = 3
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -23,8 +23,6 @@ module ActiveRecord
23
23
  @keys = @inserts.first.keys
24
24
  end
25
25
 
26
- configure_on_duplicate_update_logic
27
-
28
26
  if model.scope_attributes?
29
27
  @scope_attributes = model.scope_attributes
30
28
  @keys |= @scope_attributes.keys
@@ -35,8 +33,8 @@ module ActiveRecord
35
33
  @returning = false if @returning == []
36
34
 
37
35
  @unique_by = find_unique_index_for(@unique_by)
38
- @on_duplicate = :skip if @on_duplicate == :update && updatable_columns.empty?
39
36
 
37
+ configure_on_duplicate_update_logic
40
38
  ensure_valid_options_for_connection!
41
39
  end
42
40
 
@@ -135,6 +133,8 @@ module ActiveRecord
135
133
  elsif custom_update_sql_provided?
136
134
  @update_sql = on_duplicate
137
135
  @on_duplicate = :update
136
+ elsif @on_duplicate == :update && updatable_columns.empty?
137
+ @on_duplicate = :skip
138
138
  end
139
139
  end
140
140
 
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  # This is enabled by default. To disable this functionality set
11
11
  # `use_metadata_table` to false in your database configuration.
12
12
  class InternalMetadata # :nodoc:
13
- class NullInternalMetadata
13
+ class NullInternalMetadata # :nodoc:
14
14
  end
15
15
 
16
16
  attr_reader :connection, :arel_table
@@ -24,7 +24,7 @@ module ActiveRecord
24
24
  # To use the DatabaseSelector in your application with default settings,
25
25
  # run the provided generator.
26
26
  #
27
- # bin/rails g active_record:multi_db
27
+ # $ bin/rails g active_record:multi_db
28
28
  #
29
29
  # This will create a file named +config/initializers/multi_db.rb+ with the
30
30
  # following contents:
@@ -61,8 +61,10 @@ module ActiveRecord
61
61
  column_name.is_a?(String) && /\W/.match?(column_name)
62
62
  end
63
63
  end
64
+
64
65
  module TableDefinition
65
66
  include LegacyIndexName
67
+
66
68
  def column(name, type, **options)
67
69
  options[:_skip_validate_options] = true
68
70
  super
@@ -95,6 +97,12 @@ module ActiveRecord
95
97
  super
96
98
  end
97
99
 
100
+ def add_reference(table_name, ref_name, **options)
101
+ options[:_skip_validate_options] = true
102
+ super
103
+ end
104
+ alias :add_belongs_to :add_reference
105
+
98
106
  def create_table(table_name, **options)
99
107
  options[:_uses_legacy_table_name] = true
100
108
  options[:_skip_validate_options] = true
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PendingMigrationConnection # :nodoc:
5
+ def self.establish_temporary_connection(db_config, &block)
6
+ pool = ActiveRecord::Base.connection_handler.establish_connection(db_config, owner_name: self)
7
+
8
+ yield pool.connection
9
+ ensure
10
+ ActiveRecord::Base.connection_handler.remove_connection_pool(self.name)
11
+ end
12
+
13
+ def self.primary_class?
14
+ false
15
+ end
16
+
17
+ def self.current_preventing_writes
18
+ false
19
+ end
20
+ end
21
+ end
@@ -7,6 +7,7 @@ require "active_support/core_ext/array/access"
7
7
  require "active_support/core_ext/enumerable"
8
8
  require "active_support/core_ext/module/attribute_accessors"
9
9
  require "active_support/actionable_error"
10
+ require "active_record/migration/pending_migration_connection"
10
11
 
11
12
  module ActiveRecord
12
13
  class MigrationError < ActiveRecordError # :nodoc:
@@ -370,7 +371,8 @@ module ActiveRecord
370
371
  # The \Rails package has several tools to help create and apply migrations.
371
372
  #
372
373
  # To generate a new migration, you can use
373
- # bin/rails generate migration MyNewMigration
374
+ #
375
+ # $ bin/rails generate migration MyNewMigration
374
376
  #
375
377
  # where MyNewMigration is the name of your migration. The generator will
376
378
  # create an empty migration file <tt>timestamp_my_new_migration.rb</tt>
@@ -379,7 +381,7 @@ module ActiveRecord
379
381
  #
380
382
  # There is a special syntactic shortcut to generate migrations that add fields to a table.
381
383
  #
382
- # bin/rails generate migration add_fieldname_to_tablename fieldname:string
384
+ # $ bin/rails generate migration add_fieldname_to_tablename fieldname:string
383
385
  #
384
386
  # This will generate the file <tt>timestamp_add_fieldname_to_tablename.rb</tt>, which will look like this:
385
387
  # class AddFieldnameToTablename < ActiveRecord::Migration[7.1]
@@ -768,9 +770,11 @@ module ActiveRecord
768
770
  def pending_migrations
769
771
  pending_migrations = []
770
772
 
771
- ActiveRecord::Tasks::DatabaseTasks.with_temporary_connection_for_each(env: env) do |connection|
772
- if pending = connection.migration_context.open.pending_migrations
773
- pending_migrations << pending
773
+ ActiveRecord::Base.configurations.configs_for(env_name: env).each do |db_config|
774
+ ActiveRecord::PendingMigrationConnection.establish_temporary_connection(db_config) do |conn|
775
+ if pending = conn.migration_context.open.pending_migrations
776
+ pending_migrations << pending
777
+ end
774
778
  end
775
779
  end
776
780