activerecord 7.1.1 → 7.1.5

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.

Potentially problematic release.


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

Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +496 -0
  3. data/README.rdoc +1 -0
  4. data/lib/active_record/associations/association.rb +2 -1
  5. data/lib/active_record/associations/belongs_to_association.rb +4 -4
  6. data/lib/active_record/associations/collection_association.rb +4 -4
  7. data/lib/active_record/associations/has_many_association.rb +2 -2
  8. data/lib/active_record/associations/has_one_association.rb +2 -2
  9. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  10. data/lib/active_record/associations/join_dependency.rb +10 -12
  11. data/lib/active_record/associations/preloader/association.rb +4 -1
  12. data/lib/active_record/associations.rb +21 -15
  13. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  14. data/lib/active_record/attribute_methods/dirty.rb +15 -11
  15. data/lib/active_record/attribute_methods/read.rb +3 -3
  16. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
  17. data/lib/active_record/attribute_methods/write.rb +3 -3
  18. data/lib/active_record/attribute_methods.rb +47 -7
  19. data/lib/active_record/autosave_association.rb +5 -2
  20. data/lib/active_record/callbacks.rb +2 -2
  21. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +19 -9
  22. data/lib/active_record/connection_adapters/abstract/database_statements.rb +5 -3
  23. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +8 -4
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +27 -19
  26. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +37 -13
  27. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +2 -1
  28. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +3 -0
  29. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -10
  30. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +4 -1
  31. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  32. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  33. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -6
  34. data/lib/active_record/connection_adapters/postgresql_adapter.rb +32 -33
  35. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -3
  36. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -2
  37. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +1 -0
  38. data/lib/active_record/connection_adapters/trilogy_adapter.rb +25 -21
  39. data/lib/active_record/connection_handling.rb +1 -1
  40. data/lib/active_record/core.rb +49 -10
  41. data/lib/active_record/counter_cache.rb +7 -3
  42. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  43. data/lib/active_record/database_configurations/hash_config.rb +6 -2
  44. data/lib/active_record/delegated_type.rb +7 -7
  45. data/lib/active_record/destroy_association_async_job.rb +1 -1
  46. data/lib/active_record/encryption/encryptable_record.rb +7 -1
  47. data/lib/active_record/encryption/encrypted_attribute_type.rb +6 -2
  48. data/lib/active_record/encryption/extended_deterministic_queries.rb +0 -15
  49. data/lib/active_record/encryption/scheme.rb +8 -4
  50. data/lib/active_record/encryption.rb +2 -0
  51. data/lib/active_record/enum.rb +6 -9
  52. data/lib/active_record/errors.rb +5 -4
  53. data/lib/active_record/fixtures.rb +16 -0
  54. data/lib/active_record/future_result.rb +10 -0
  55. data/lib/active_record/gem_version.rb +1 -1
  56. data/lib/active_record/insert_all.rb +3 -3
  57. data/lib/active_record/internal_metadata.rb +1 -1
  58. data/lib/active_record/locking/optimistic.rb +1 -1
  59. data/lib/active_record/marshalling.rb +4 -1
  60. data/lib/active_record/message_pack.rb +1 -1
  61. data/lib/active_record/middleware/database_selector.rb +1 -1
  62. data/lib/active_record/migration/compatibility.rb +14 -0
  63. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  64. data/lib/active_record/migration.rb +9 -5
  65. data/lib/active_record/model_schema.rb +12 -7
  66. data/lib/active_record/nested_attributes.rb +16 -5
  67. data/lib/active_record/normalization.rb +8 -0
  68. data/lib/active_record/persistence.rb +6 -5
  69. data/lib/active_record/promise.rb +1 -1
  70. data/lib/active_record/query_cache.rb +1 -1
  71. data/lib/active_record/query_logs_formatter.rb +1 -1
  72. data/lib/active_record/railtie.rb +14 -14
  73. data/lib/active_record/railties/controller_runtime.rb +2 -1
  74. data/lib/active_record/railties/databases.rake +7 -7
  75. data/lib/active_record/reflection.rb +21 -3
  76. data/lib/active_record/relation/calculations.rb +28 -1
  77. data/lib/active_record/relation/delegation.rb +1 -1
  78. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  79. data/lib/active_record/relation/query_methods.rb +21 -7
  80. data/lib/active_record/relation.rb +30 -5
  81. data/lib/active_record/result.rb +1 -1
  82. data/lib/active_record/runtime_registry.rb +15 -1
  83. data/lib/active_record/schema_migration.rb +1 -1
  84. data/lib/active_record/secure_token.rb +1 -1
  85. data/lib/active_record/tasks/database_tasks.rb +35 -13
  86. data/lib/active_record/test_fixtures.rb +1 -0
  87. data/lib/active_record/timestamp.rb +3 -1
  88. data/lib/active_record.rb +2 -2
  89. data/lib/arel/nodes/homogeneous_in.rb +1 -1
  90. data/lib/arel/tree_manager.rb +5 -1
  91. data/lib/arel/visitors/to_sql.rb +2 -1
  92. metadata +14 -13
@@ -17,11 +17,12 @@ module ActiveRecord
17
17
  def eql?(other)
18
18
  association_key_name == other.association_key_name &&
19
19
  scope.table_name == other.scope.table_name &&
20
+ scope.connection_specification_name == other.scope.connection_specification_name &&
20
21
  scope.values_for_queries == other.scope.values_for_queries
21
22
  end
22
23
 
23
24
  def hash
24
- [association_key_name, scope.table_name, scope.values_for_queries].hash
25
+ [association_key_name, scope.table_name, scope.connection_specification_name, scope.values_for_queries].hash
25
26
  end
26
27
 
27
28
  def records_for(loaders)
@@ -38,6 +39,8 @@ module ActiveRecord
38
39
  end
39
40
 
40
41
  def load_records_for_keys(keys, &block)
42
+ return [] if keys.empty?
43
+
41
44
  if association_key_name.is_a?(Array)
42
45
  query_constraints = Hash.new { |hsh, key| hsh[key] = Set.new }
43
46
 
@@ -1050,7 +1050,7 @@ module ActiveRecord
1050
1050
  # query per addressable type.
1051
1051
  # For example, if all the addressables are either of class Person or Company, then a total
1052
1052
  # of 3 queries will be executed. The list of addressable types to load is determined on
1053
- # the back of the addresses loaded. This is not supported if Active Record has to fallback
1053
+ # the back of the addresses loaded. This is not supported if Active Record has to fall back
1054
1054
  # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError.
1055
1055
  # The reason is that the parent model's type is a column value so its corresponding table
1056
1056
  # name cannot be put in the +FROM+/+JOIN+ clauses of that query.
@@ -1339,7 +1339,7 @@ module ActiveRecord
1339
1339
  # Returns a Relation of all of the associated objects, forcing a database read.
1340
1340
  # An empty Relation is returned if none are found.
1341
1341
  #
1342
- # === Example
1342
+ # ==== Example
1343
1343
  #
1344
1344
  # class Firm < ActiveRecord::Base
1345
1345
  # has_many :clients
@@ -1369,7 +1369,7 @@ module ActiveRecord
1369
1369
  #
1370
1370
  # The declaration can also include an +options+ hash to specialize the behavior of the association.
1371
1371
  #
1372
- # === Scopes
1372
+ # ==== Scopes
1373
1373
  #
1374
1374
  # You can pass a second argument +scope+ as a callable (i.e. proc or
1375
1375
  # lambda) to retrieve a specific set of records or customize the generated
@@ -1380,7 +1380,7 @@ module ActiveRecord
1380
1380
  # has_many :employees, -> { joins(:address) }
1381
1381
  # has_many :posts, ->(blog) { where("max_post_length > ?", blog.max_post_length) }
1382
1382
  #
1383
- # === Extensions
1383
+ # ==== Extensions
1384
1384
  #
1385
1385
  # The +extension+ argument allows you to pass a block into a has_many
1386
1386
  # association. This is useful for adding new finders, creators, and other
@@ -1394,7 +1394,7 @@ module ActiveRecord
1394
1394
  # end
1395
1395
  # end
1396
1396
  #
1397
- # === Options
1397
+ # ==== Options
1398
1398
  # [+:class_name+]
1399
1399
  # Specify the class name of the association. Use it only if that name can't be inferred
1400
1400
  # from the association name. So <tt>has_many :products</tt> will by default be linked
@@ -1506,6 +1506,11 @@ module ActiveRecord
1506
1506
  # Serves as a composite foreign key. Defines the list of columns to be used to query the associated object.
1507
1507
  # This is an optional option. By default Rails will attempt to derive the value automatically.
1508
1508
  # When the value is set the Array size must match associated model's primary key or +query_constraints+ size.
1509
+ # [+:index_errors+]
1510
+ # Enables differentiation of multiple validation errors from the association records, by including
1511
+ # an index in the error attribute name, e.g. +roles[2].level+.
1512
+ # The index is based on association order, i.e. database order, with yet to be
1513
+ # persisted new records placed at the end.
1509
1514
  #
1510
1515
  # Option examples:
1511
1516
  # has_many :comments, -> { order("posted_on") }
@@ -1519,6 +1524,7 @@ module ActiveRecord
1519
1524
  # has_many :subscribers, through: :subscriptions, disable_joins: true
1520
1525
  # has_many :comments, strict_loading: true
1521
1526
  # has_many :comments, query_constraints: [:blog_id, :post_id]
1527
+ # has_many :comments, index_errors: true
1522
1528
  def has_many(name, scope = nil, **options, &extension)
1523
1529
  reflection = Builder::HasMany.build(self, name, scope, options, &extension)
1524
1530
  Reflection.add_reflection self, name, reflection
@@ -1556,7 +1562,7 @@ module ActiveRecord
1556
1562
  # [<tt>reset_association</tt>]
1557
1563
  # Unloads the associated object. The next access will query it from the database.
1558
1564
  #
1559
- # === Example
1565
+ # ==== Example
1560
1566
  #
1561
1567
  # class Account < ActiveRecord::Base
1562
1568
  # has_one :beneficiary
@@ -1575,7 +1581,7 @@ module ActiveRecord
1575
1581
  # account.reload_beneficiary
1576
1582
  # account.reset_beneficiary
1577
1583
  #
1578
- # === Scopes
1584
+ # ==== Scopes
1579
1585
  #
1580
1586
  # You can pass a second argument +scope+ as a callable (i.e. proc or
1581
1587
  # lambda) to retrieve a specific record or customize the generated query
@@ -1586,7 +1592,7 @@ module ActiveRecord
1586
1592
  # has_one :employer, -> { joins(:company) }
1587
1593
  # has_one :latest_post, ->(blog) { where("created_at > ?", blog.enabled_at) }
1588
1594
  #
1589
- # === Options
1595
+ # ==== Options
1590
1596
  #
1591
1597
  # The declaration can also include an +options+ hash to specialize the behavior of the association.
1592
1598
  #
@@ -1745,7 +1751,7 @@ module ActiveRecord
1745
1751
  # [<tt>association_previously_changed?</tt>]
1746
1752
  # Returns true if the previous save updated the association to reference a new associate object.
1747
1753
  #
1748
- # === Example
1754
+ # ==== Example
1749
1755
  #
1750
1756
  # class Post < ActiveRecord::Base
1751
1757
  # belongs_to :author
@@ -1766,7 +1772,7 @@ module ActiveRecord
1766
1772
  # post.author_changed?
1767
1773
  # post.author_previously_changed?
1768
1774
  #
1769
- # === Scopes
1775
+ # ==== Scopes
1770
1776
  #
1771
1777
  # You can pass a second argument +scope+ as a callable (i.e. proc or
1772
1778
  # lambda) to retrieve a specific record or customize the generated query
@@ -1777,7 +1783,7 @@ module ActiveRecord
1777
1783
  # belongs_to :user, -> { joins(:friends) }
1778
1784
  # belongs_to :level, ->(game) { where("game_level > ?", game.current_level) }
1779
1785
  #
1780
- # === Options
1786
+ # ==== Options
1781
1787
  #
1782
1788
  # The declaration can also include an +options+ hash to specialize the behavior of the association.
1783
1789
  #
@@ -1964,7 +1970,7 @@ module ActiveRecord
1964
1970
  # Returns a Relation of all of the associated objects, forcing a database read.
1965
1971
  # An empty Relation is returned if none are found.
1966
1972
  #
1967
- # === Example
1973
+ # ==== Example
1968
1974
  #
1969
1975
  # class Developer < ActiveRecord::Base
1970
1976
  # has_and_belongs_to_many :projects
@@ -1993,7 +1999,7 @@ module ActiveRecord
1993
1999
  #
1994
2000
  # The declaration may include an +options+ hash to specialize the behavior of the association.
1995
2001
  #
1996
- # === Scopes
2002
+ # ==== Scopes
1997
2003
  #
1998
2004
  # You can pass a second argument +scope+ as a callable (i.e. proc or
1999
2005
  # lambda) to retrieve a specific set of records or customize the generated
@@ -2005,7 +2011,7 @@ module ActiveRecord
2005
2011
  # where("default_category = ?", post.default_category)
2006
2012
  # }
2007
2013
  #
2008
- # === Extensions
2014
+ # ==== Extensions
2009
2015
  #
2010
2016
  # The +extension+ argument allows you to pass a block into a
2011
2017
  # has_and_belongs_to_many association. This is useful for adding new
@@ -2020,7 +2026,7 @@ module ActiveRecord
2020
2026
  # end
2021
2027
  # end
2022
2028
  #
2023
- # === Options
2029
+ # ==== Options
2024
2030
  #
2025
2031
  # [+:class_name+]
2026
2032
  # Specify the class name of the association. Use it only if that name can't be inferred
@@ -56,7 +56,7 @@ module ActiveRecord
56
56
  # serialization.
57
57
  #
58
58
  # class Book < ActiveRecord::Base
59
- # enum status: { draft: 1, published: 2 }
59
+ # enum :status, { draft: 1, published: 2 }
60
60
  # end
61
61
  #
62
62
  # book = Book.new(status: "published")
@@ -14,7 +14,7 @@ module ActiveRecord
14
14
  # class Person < ActiveRecord::Base
15
15
  # end
16
16
  #
17
- # person = Person.create(name: "Alisson")
17
+ # person = Person.create(name: "Allison")
18
18
  # person.changed? # => false
19
19
  #
20
20
  # Change the name:
@@ -31,7 +31,7 @@ module ActiveRecord
31
31
  # person.name_in_database # => "Alice"
32
32
  # person.saved_change_to_name? # => true
33
33
  # person.saved_change_to_name # => ["Allison", "Alice"]
34
- # person.name_before_last_change # => "Allison"
34
+ # person.name_before_last_save # => "Allison"
35
35
  #
36
36
  # Similar to ActiveModel::Dirty, methods can be invoked as
37
37
  # +saved_change_to_name?+ or by passing an argument to the generic method
@@ -76,11 +76,13 @@ module ActiveRecord
76
76
  #
77
77
  # ==== Options
78
78
  #
79
- # +from+ When passed, this method will return false unless the original
80
- # value is equal to the given option
79
+ # [+from+]
80
+ # When specified, this method will return false unless the original
81
+ # value is equal to the given value.
81
82
  #
82
- # +to+ When passed, this method will return false unless the value was
83
- # changed to the given value
83
+ # [+to+]
84
+ # When specified, this method will return false unless the value will be
85
+ # changed to the given value.
84
86
  def saved_change_to_attribute?(attr_name, **options)
85
87
  mutations_before_last_save.changed?(attr_name.to_s, **options)
86
88
  end
@@ -126,11 +128,13 @@ module ActiveRecord
126
128
  #
127
129
  # ==== Options
128
130
  #
129
- # +from+ When passed, this method will return false unless the original
130
- # value is equal to the given option
131
+ # [+from+]
132
+ # When specified, this method will return false unless the original
133
+ # value is equal to the given value.
131
134
  #
132
- # +to+ When passed, this method will return false unless the value will be
133
- # changed to the given value
135
+ # [+to+]
136
+ # When specified, this method will return false unless the value will be
137
+ # changed to the given value.
134
138
  def will_save_change_to_attribute?(attr_name, **options)
135
139
  mutations_from_database.changed?(attr_name.to_s, **options)
136
140
  end
@@ -247,7 +251,7 @@ module ActiveRecord
247
251
  changed_attribute_names_to_save
248
252
  else
249
253
  attribute_names.reject do |attr_name|
250
- if column_for_attribute(attr_name).default_function
254
+ if column_for_attribute(attr_name).auto_populated?
251
255
  !attribute_changed?(attr_name)
252
256
  end
253
257
  end
@@ -8,11 +8,11 @@ module ActiveRecord
8
8
 
9
9
  module ClassMethods # :nodoc:
10
10
  private
11
- def define_method_attribute(name, owner:)
11
+ def define_method_attribute(canonical_name, owner:, as: canonical_name)
12
12
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
13
- owner, name
13
+ owner, canonical_name
14
14
  ) do |temp_method_name, attr_name_expr|
15
- owner.define_cached_method(name, as: temp_method_name, namespace: :active_record) do |batch|
15
+ owner.define_cached_method(temp_method_name, as: as, namespace: :active_record) do |batch|
16
16
  batch <<
17
17
  "def #{temp_method_name}" <<
18
18
  " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
@@ -32,6 +32,10 @@ module ActiveRecord
32
32
  end
33
33
  end
34
34
 
35
+ def ==(other)
36
+ other.is_a?(self.class) && __getobj__ == other.__getobj__
37
+ end
38
+
35
39
  private
36
40
  def convert_time_to_time_zone(value)
37
41
  return if value.nil?
@@ -12,11 +12,11 @@ module ActiveRecord
12
12
 
13
13
  module ClassMethods # :nodoc:
14
14
  private
15
- def define_method_attribute=(name, owner:)
15
+ def define_method_attribute=(canonical_name, owner:, as: canonical_name)
16
16
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
17
- owner, name, writer: true,
17
+ owner, canonical_name, writer: true,
18
18
  ) do |temp_method_name, attr_name_expr|
19
- owner.define_cached_method("#{name}=", as: temp_method_name, namespace: :active_record) do |batch|
19
+ owner.define_cached_method(temp_method_name, as: "#{as}=", namespace: :active_record) do |batch|
20
20
  batch <<
21
21
  "def #{temp_method_name}(value)" <<
22
22
  " _write_attribute(#{attr_name_expr}, value)" <<
@@ -34,7 +34,7 @@ module ActiveRecord
34
34
  Base.private_instance_methods -
35
35
  Base.superclass.instance_methods -
36
36
  Base.superclass.private_instance_methods +
37
- %i[__id__ dup freeze frozen? hash object_id class clone]
37
+ %i[__id__ dup freeze frozen? hash class clone]
38
38
  ).map { |m| -m.to_s }.to_set.freeze
39
39
  end
40
40
  end
@@ -66,7 +66,7 @@ module ActiveRecord
66
66
 
67
67
  def generate_alias_attributes # :nodoc:
68
68
  superclass.generate_alias_attributes unless superclass == Base
69
- return if @alias_attributes_mass_generated
69
+ return false if @alias_attributes_mass_generated
70
70
 
71
71
  generated_attribute_methods.synchronize do
72
72
  return if @alias_attributes_mass_generated
@@ -80,12 +80,19 @@ module ActiveRecord
80
80
 
81
81
  @alias_attributes_mass_generated = true
82
82
  end
83
+ true
83
84
  end
84
85
 
85
- def alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
86
+ def generate_alias_attribute_methods(code_generator, new_name, old_name) # :nodoc:
87
+ attribute_method_patterns.each do |pattern|
88
+ alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
89
+ end
90
+ attribute_method_patterns_cache.clear
91
+ end
92
+
93
+ def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) # :nodoc:
86
94
  method_name = pattern.method_name(new_name).to_s
87
95
  target_name = pattern.method_name(old_name).to_s
88
- parameters = pattern.parameters
89
96
  old_name = old_name.to_s
90
97
 
91
98
  method_defined = method_defined?(target_name) || private_method_defined?(target_name)
@@ -115,9 +122,7 @@ module ActiveRecord
115
122
  )
116
123
  super
117
124
  else
118
- define_proxy_call(code_generator, method_name, pattern.proxy_target, parameters, old_name,
119
- namespace: :proxy_alias_attribute
120
- )
125
+ define_attribute_method_pattern(pattern, old_name, owner: code_generator, as: new_name, override: true)
121
126
  end
122
127
  end
123
128
 
@@ -133,6 +138,11 @@ module ActiveRecord
133
138
  super(attribute_names)
134
139
  @attribute_methods_generated = true
135
140
  end
141
+ true
142
+ end
143
+
144
+ def attribute_methods_generated? # :nodoc:
145
+ @attribute_methods_generated && @alias_attributes_mass_generated
136
146
  end
137
147
 
138
148
  def undefine_attribute_methods # :nodoc:
@@ -459,6 +469,36 @@ module ActiveRecord
459
469
  end
460
470
 
461
471
  private
472
+ def respond_to_missing?(name, include_private = false)
473
+ if self.class.define_attribute_methods
474
+ # Some methods weren't defined yet.
475
+ return true if self.class.method_defined?(name)
476
+ return true if include_private && self.class.private_method_defined?(name)
477
+ end
478
+
479
+ super
480
+ end
481
+
482
+ def method_missing(name, ...)
483
+ unless self.class.attribute_methods_generated?
484
+ if self.class.method_defined?(name)
485
+ # The method is explicitly defined in the model, but calls a generated
486
+ # method with super. So we must resume the call chain at the right setp.
487
+ last_method = method(name)
488
+ last_method = last_method.super_method while last_method.super_method
489
+ self.class.define_attribute_methods
490
+ if last_method.super_method
491
+ return last_method.super_method.call(...)
492
+ end
493
+ elsif self.class.define_attribute_methods | self.class.generate_alias_attributes
494
+ # Some attribute methods weren't generated yet, we retry the call
495
+ return public_send(name, ...)
496
+ end
497
+ end
498
+
499
+ super
500
+ end
501
+
462
502
  def attribute_method?(attr_name)
463
503
  # We check defined? because Syck calls respond_to? before actually calling initialize.
464
504
  defined?(@attributes) && @attributes.key?(attr_name)
@@ -441,7 +441,9 @@ module ActiveRecord
441
441
  # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
442
442
  def save_has_one_association(reflection)
443
443
  association = association_instance_get(reflection.name)
444
- record = association && association.load_target
444
+ return unless association && association.loaded?
445
+
446
+ record = association.load_target
445
447
 
446
448
  if record && !record.destroyed?
447
449
  autosave = reflection.options[:autosave]
@@ -458,7 +460,8 @@ module ActiveRecord
458
460
  primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
459
461
 
460
462
  primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
461
- record[foreign_key] = _read_attribute(primary_key)
463
+ association_id = _read_attribute(primary_key)
464
+ record[foreign_key] = association_id unless record[foreign_key] == association_id
462
465
  end
463
466
  association.set_inverse_instance(record)
464
467
  end
@@ -241,8 +241,8 @@ module ActiveRecord
241
241
  #
242
242
  # config.active_record.run_after_transaction_callbacks_in_order_defined = false
243
243
  #
244
- # If +true+ (the default from \Rails 7.1), callbacks are executed in the order they
245
- # are defined, just like the example above. If +false+, the order is reversed, so
244
+ # When set to +true+ (the default from \Rails 7.1), callbacks are executed in the order they
245
+ # are defined, just like the example above. When set to +false+, the order is reversed, so
246
246
  # +do_something_else+ is executed before +log_children+.
247
247
  #
248
248
  # == \Transactions
@@ -58,12 +58,10 @@ module ActiveRecord
58
58
  # Connections can be obtained and used from a connection pool in several
59
59
  # ways:
60
60
  #
61
- # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection]
62
- # as with Active Record 2.1 and
63
- # earlier (pre-connection-pooling). Eventually, when you're done with
64
- # the connection(s) and wish it to be returned to the pool, you call
61
+ # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection].
62
+ # When you're done with the connection(s) and wish it to be returned to the pool, you call
65
63
  # {ActiveRecord::Base.connection_handler.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
66
- # This will be the default behavior for Active Record when used in conjunction with
64
+ # This is the default behavior for Active Record when used in conjunction with
67
65
  # Action Pack's request handling cycle.
68
66
  # 2. Manually check out a connection from the pool with
69
67
  # {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
@@ -465,8 +463,7 @@ module ActiveRecord
465
463
  @available.num_waiting
466
464
  end
467
465
 
468
- # Return connection pool's usage statistic
469
- # Example:
466
+ # Returns the connection pool's usage statistic.
470
467
  #
471
468
  # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
472
469
  def stat
@@ -658,7 +655,13 @@ module ActiveRecord
658
655
  conn
659
656
  else
660
657
  reap
661
- @available.poll(checkout_timeout)
658
+ # Retry after reaping, which may return an available connection,
659
+ # remove an inactive connection, or both
660
+ if conn = @available.poll || try_to_checkout_new_connection
661
+ conn
662
+ else
663
+ @available.poll(checkout_timeout)
664
+ end
662
665
  end
663
666
  end
664
667
 
@@ -672,7 +675,14 @@ module ActiveRecord
672
675
  def new_connection
673
676
  connection = Base.public_send(db_config.adapter_method, db_config.configuration_hash)
674
677
  connection.pool = self
675
- connection.check_version
678
+
679
+ begin
680
+ connection.check_version
681
+ rescue
682
+ connection.disconnect!
683
+ raise
684
+ end
685
+
676
686
  connection
677
687
  rescue ConnectionNotEstablished => ex
678
688
  raise ex.set_pool(self)
@@ -189,8 +189,10 @@ module ActiveRecord
189
189
  def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [], returning: nil)
190
190
  sql, binds = to_sql_and_binds(arel, binds)
191
191
  value = exec_insert(sql, name, binds, pk, sequence_name, returning: returning)
192
- return id_value if id_value
193
- returning.nil? ? last_inserted_id(value) : returning_column_values(value)
192
+
193
+ return returning_column_values(value) unless returning.nil?
194
+
195
+ id_value || last_inserted_id(value)
194
196
  end
195
197
  alias create insert
196
198
 
@@ -627,7 +629,7 @@ module ActiveRecord
627
629
 
628
630
  result = internal_exec_query(sql, name, binds, prepare: prepare)
629
631
  if async
630
- FutureResult::Complete.new(result)
632
+ FutureResult.wrap(result)
631
633
  else
632
634
  result
633
635
  end
@@ -106,7 +106,8 @@ module ActiveRecord
106
106
  sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
107
107
 
108
108
  if async
109
- lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
109
+ result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
110
+ FutureResult.wrap(result)
110
111
  else
111
112
  cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async) }
112
113
  end
@@ -963,7 +963,11 @@ module ActiveRecord
963
963
  def index_name(table_name, options) # :nodoc:
964
964
  if Hash === options
965
965
  if options[:column]
966
- generate_index_name(table_name, options[:column])
966
+ if options[:_uses_legacy_index_name]
967
+ "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
968
+ else
969
+ generate_index_name(table_name, options[:column])
970
+ end
967
971
  elsif options[:name]
968
972
  options[:name]
969
973
  else
@@ -1636,11 +1640,11 @@ module ActiveRecord
1636
1640
  end
1637
1641
  end
1638
1642
 
1639
- def rename_table_indexes(table_name, new_name)
1643
+ def rename_table_indexes(table_name, new_name, **options)
1640
1644
  indexes(new_name).each do |index|
1641
- generated_index_name = index_name(table_name, column: index.columns)
1645
+ generated_index_name = index_name(table_name, column: index.columns, **options)
1642
1646
  if generated_index_name == index.name
1643
- rename_index new_name, generated_index_name, index_name(new_name, column: index.columns)
1647
+ rename_index new_name, generated_index_name, index_name(new_name, column: index.columns, **options)
1644
1648
  end
1645
1649
  end
1646
1650
  end
@@ -711,13 +711,14 @@ module ActiveRecord
711
711
  end
712
712
  end
713
713
 
714
-
715
714
  # Disconnects from the database if already connected. Otherwise, this
716
715
  # method does nothing.
717
716
  def disconnect!
718
- clear_cache!(new_connection: true)
719
- reset_transaction
720
- @raw_connection_dirty = false
717
+ @lock.synchronize do
718
+ clear_cache!(new_connection: true)
719
+ reset_transaction
720
+ @raw_connection_dirty = false
721
+ end
721
722
  end
722
723
 
723
724
  # Immediately forget this connection ever existed. Unlike disconnect!,
@@ -773,19 +774,17 @@ module ActiveRecord
773
774
  # is no longer active, then this method will reconnect to the database.
774
775
  def verify!
775
776
  unless active?
776
- if @unconfigured_connection
777
- @lock.synchronize do
778
- if @unconfigured_connection
779
- @raw_connection = @unconfigured_connection
780
- @unconfigured_connection = nil
781
- configure_connection
782
- @verified = true
783
- return
784
- end
777
+ @lock.synchronize do
778
+ if @unconfigured_connection
779
+ @raw_connection = @unconfigured_connection
780
+ @unconfigured_connection = nil
781
+ configure_connection
782
+ @verified = true
783
+ return
785
784
  end
786
- end
787
785
 
788
- reconnect!(restore_transactions: true)
786
+ reconnect!(restore_transactions: true)
787
+ end
789
788
  end
790
789
 
791
790
  @verified = true
@@ -970,7 +969,11 @@ module ActiveRecord
970
969
  # If +allow_retry+ is true, a connection-related exception will
971
970
  # cause an automatic reconnect and re-run of the block, up to
972
971
  # the connection's configured +connection_retries+ setting
973
- # and the configured +retry_deadline+ limit.
972
+ # and the configured +retry_deadline+ limit. (Note that when
973
+ # +allow_retry+ is true, it's possible to return without having marked
974
+ # the connection as verified. If the block is guaranteed to exercise the
975
+ # connection, consider calling `verified!` to avoid needless
976
+ # verification queries in subsequent calls.)
974
977
  #
975
978
  # If +materialize_transactions+ is false, the block will be run without
976
979
  # ensuring virtual transactions have been materialized in the DB
@@ -1021,9 +1024,7 @@ module ActiveRecord
1021
1024
  end
1022
1025
 
1023
1026
  begin
1024
- result = yield @raw_connection
1025
- @verified = true
1026
- result
1027
+ yield @raw_connection
1027
1028
  rescue => original_exception
1028
1029
  translated_exception = translate_exception_class(original_exception, nil, nil)
1029
1030
  invalidate_transaction(translated_exception)
@@ -1058,6 +1059,13 @@ module ActiveRecord
1058
1059
  end
1059
1060
  end
1060
1061
 
1062
+ # Mark the connection as verified. Call this inside a
1063
+ # `with_raw_connection` block only when the block is guaranteed to
1064
+ # exercise the raw connection.
1065
+ def verified!
1066
+ @verified = true
1067
+ end
1068
+
1061
1069
  def retryable_connection_error?(exception)
1062
1070
  exception.is_a?(ConnectionNotEstablished) || exception.is_a?(ConnectionFailed)
1063
1071
  end