activerecord 7.1.5 → 7.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (183) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -2440
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +9 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +14 -7
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +6 -4
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +1 -1
  17. data/lib/active_record/associations/join_dependency/join_association.rb +29 -28
  18. data/lib/active_record/associations/join_dependency.rb +5 -5
  19. data/lib/active_record/associations/nested_error.rb +47 -0
  20. data/lib/active_record/associations/preloader/association.rb +2 -1
  21. data/lib/active_record/associations/preloader/branch.rb +7 -1
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  23. data/lib/active_record/associations/singular_association.rb +6 -0
  24. data/lib/active_record/associations/through_association.rb +1 -1
  25. data/lib/active_record/associations.rb +33 -16
  26. data/lib/active_record/attribute_assignment.rb +1 -11
  27. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +4 -16
  31. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -10
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +60 -71
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +13 -32
  37. data/lib/active_record/base.rb +2 -3
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +248 -65
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -74
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +14 -5
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -1
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  59. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  60. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -13
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
  63. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  64. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  67. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +107 -75
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  74. data/lib/active_record/connection_adapters.rb +121 -0
  75. data/lib/active_record/connection_handling.rb +56 -41
  76. data/lib/active_record/core.rb +53 -37
  77. data/lib/active_record/counter_cache.rb +18 -9
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  79. data/lib/active_record/database_configurations/database_config.rb +15 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  81. data/lib/active_record/database_configurations/url_config.rb +20 -1
  82. data/lib/active_record/database_configurations.rb +1 -1
  83. data/lib/active_record/delegated_type.rb +24 -0
  84. data/lib/active_record/dynamic_matchers.rb +2 -2
  85. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  86. data/lib/active_record/encryption/encrypted_attribute_type.rb +22 -2
  87. data/lib/active_record/encryption/encryptor.rb +17 -2
  88. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  89. data/lib/active_record/encryption/message_serializer.rb +4 -0
  90. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  91. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption.rb +0 -2
  93. data/lib/active_record/enum.rb +10 -1
  94. data/lib/active_record/errors.rb +16 -11
  95. data/lib/active_record/explain.rb +13 -24
  96. data/lib/active_record/fixtures.rb +37 -31
  97. data/lib/active_record/future_result.rb +8 -4
  98. data/lib/active_record/gem_version.rb +3 -3
  99. data/lib/active_record/inheritance.rb +4 -2
  100. data/lib/active_record/insert_all.rb +18 -15
  101. data/lib/active_record/integration.rb +4 -1
  102. data/lib/active_record/internal_metadata.rb +48 -34
  103. data/lib/active_record/locking/optimistic.rb +7 -6
  104. data/lib/active_record/log_subscriber.rb +0 -21
  105. data/lib/active_record/marshalling.rb +1 -4
  106. data/lib/active_record/message_pack.rb +1 -1
  107. data/lib/active_record/migration/command_recorder.rb +2 -3
  108. data/lib/active_record/migration/compatibility.rb +5 -3
  109. data/lib/active_record/migration/default_strategy.rb +4 -5
  110. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  111. data/lib/active_record/migration.rb +85 -76
  112. data/lib/active_record/model_schema.rb +28 -68
  113. data/lib/active_record/nested_attributes.rb +13 -16
  114. data/lib/active_record/normalization.rb +3 -7
  115. data/lib/active_record/persistence.rb +30 -352
  116. data/lib/active_record/query_cache.rb +18 -6
  117. data/lib/active_record/query_logs.rb +15 -0
  118. data/lib/active_record/querying.rb +21 -9
  119. data/lib/active_record/railtie.rb +50 -62
  120. data/lib/active_record/railties/controller_runtime.rb +13 -4
  121. data/lib/active_record/railties/databases.rake +41 -44
  122. data/lib/active_record/reflection.rb +90 -35
  123. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  124. data/lib/active_record/relation/batches.rb +3 -3
  125. data/lib/active_record/relation/calculations.rb +94 -61
  126. data/lib/active_record/relation/delegation.rb +8 -11
  127. data/lib/active_record/relation/finder_methods.rb +16 -2
  128. data/lib/active_record/relation/merger.rb +4 -6
  129. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  130. data/lib/active_record/relation/predicate_builder.rb +3 -3
  131. data/lib/active_record/relation/query_methods.rb +196 -57
  132. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  133. data/lib/active_record/relation/spawn_methods.rb +2 -18
  134. data/lib/active_record/relation/where_clause.rb +7 -19
  135. data/lib/active_record/relation.rb +496 -72
  136. data/lib/active_record/result.rb +31 -44
  137. data/lib/active_record/runtime_registry.rb +39 -0
  138. data/lib/active_record/sanitization.rb +24 -19
  139. data/lib/active_record/schema.rb +8 -6
  140. data/lib/active_record/schema_dumper.rb +19 -9
  141. data/lib/active_record/schema_migration.rb +30 -14
  142. data/lib/active_record/signed_id.rb +11 -1
  143. data/lib/active_record/statement_cache.rb +7 -7
  144. data/lib/active_record/table_metadata.rb +1 -10
  145. data/lib/active_record/tasks/database_tasks.rb +76 -70
  146. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  147. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  148. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  149. data/lib/active_record/test_fixtures.rb +81 -91
  150. data/lib/active_record/testing/query_assertions.rb +121 -0
  151. data/lib/active_record/timestamp.rb +1 -1
  152. data/lib/active_record/token_for.rb +22 -12
  153. data/lib/active_record/touch_later.rb +1 -1
  154. data/lib/active_record/transaction.rb +68 -0
  155. data/lib/active_record/transactions.rb +43 -14
  156. data/lib/active_record/translation.rb +0 -2
  157. data/lib/active_record/type/serialized.rb +1 -3
  158. data/lib/active_record/type_caster/connection.rb +4 -4
  159. data/lib/active_record/validations/associated.rb +9 -3
  160. data/lib/active_record/validations/uniqueness.rb +14 -10
  161. data/lib/active_record/validations.rb +4 -1
  162. data/lib/active_record.rb +149 -40
  163. data/lib/arel/alias_predication.rb +1 -1
  164. data/lib/arel/collectors/bind.rb +2 -0
  165. data/lib/arel/collectors/composite.rb +7 -0
  166. data/lib/arel/collectors/sql_string.rb +1 -1
  167. data/lib/arel/collectors/substitute_binds.rb +1 -1
  168. data/lib/arel/nodes/binary.rb +0 -6
  169. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  170. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  171. data/lib/arel/nodes/node.rb +4 -3
  172. data/lib/arel/nodes/sql_literal.rb +7 -0
  173. data/lib/arel/nodes.rb +2 -2
  174. data/lib/arel/predications.rb +1 -1
  175. data/lib/arel/select_manager.rb +1 -1
  176. data/lib/arel/tree_manager.rb +3 -2
  177. data/lib/arel/update_manager.rb +2 -1
  178. data/lib/arel/visitors/dot.rb +1 -0
  179. data/lib/arel/visitors/mysql.rb +9 -4
  180. data/lib/arel/visitors/postgresql.rb +1 -12
  181. data/lib/arel/visitors/to_sql.rb +29 -16
  182. data/lib/arel.rb +7 -3
  183. metadata +20 -15
data/README.rdoc CHANGED
@@ -34,7 +34,7 @@ A short rundown of some of the major features:
34
34
  This would also define the following accessors: <tt>Product#name</tt> and
35
35
  <tt>Product#name=(new_name)</tt>.
36
36
 
37
- {Learn more}[link:classes/ActiveRecord/Base.html]
37
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Base.html]
38
38
 
39
39
  * Associations between objects defined by simple class methods.
40
40
 
@@ -44,7 +44,7 @@ A short rundown of some of the major features:
44
44
  belongs_to :conglomerate
45
45
  end
46
46
 
47
- {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html]
47
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html]
48
48
 
49
49
 
50
50
  * Aggregations of value objects.
@@ -56,7 +56,7 @@ A short rundown of some of the major features:
56
56
  mapping: [%w(address_street street), %w(address_city city)]
57
57
  end
58
58
 
59
- {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html]
59
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html]
60
60
 
61
61
 
62
62
  * Validation rules that can differ for new or existing objects.
@@ -68,7 +68,7 @@ A short rundown of some of the major features:
68
68
  validates :password, :email_address, confirmation: true, on: :create
69
69
  end
70
70
 
71
- {Learn more}[link:classes/ActiveRecord/Validations.html]
71
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Validations.html]
72
72
 
73
73
 
74
74
  * Callbacks available for the entire life cycle (instantiation, saving, destroying, validating, etc.).
@@ -78,7 +78,7 @@ A short rundown of some of the major features:
78
78
  # the `invalidate_payment_plan` method gets called just before Person#destroy
79
79
  end
80
80
 
81
- {Learn more}[link:classes/ActiveRecord/Callbacks.html]
81
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html]
82
82
 
83
83
 
84
84
  * Inheritance hierarchies.
@@ -88,7 +88,7 @@ A short rundown of some of the major features:
88
88
  class Client < Company; end
89
89
  class PriorityClient < Client; end
90
90
 
91
- {Learn more}[link:classes/ActiveRecord/Base.html]
91
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Base.html]
92
92
 
93
93
 
94
94
  * Transactions.
@@ -99,7 +99,7 @@ A short rundown of some of the major features:
99
99
  mary.deposit(100)
100
100
  end
101
101
 
102
- {Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html]
102
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html]
103
103
 
104
104
 
105
105
  * Reflections on columns, associations, and aggregations.
@@ -108,7 +108,7 @@ A short rundown of some of the major features:
108
108
  reflection.klass # => Client (class)
109
109
  Firm.columns # Returns an array of column descriptors for the firms table
110
110
 
111
- {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html]
111
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Reflection/ClassMethods.html]
112
112
 
113
113
 
114
114
  * Database abstraction through simple adapters.
@@ -125,13 +125,13 @@ A short rundown of some of the major features:
125
125
  database: 'activerecord'
126
126
  )
127
127
 
128
- {Learn more}[link:classes/ActiveRecord/Base.html] and read about the built-in support for
129
- MySQL[link:classes/ActiveRecord/ConnectionAdapters/Mysql2Adapter.html],
130
- PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], and
131
- SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html].
128
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Base.html] and read about the built-in support for
129
+ MySQL[https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Mysql2Adapter.html],
130
+ PostgreSQL[https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], and
131
+ SQLite3[https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html].
132
132
 
133
133
 
134
- * Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[https://ruby-doc.org/stdlib/libdoc/logger/rdoc/].
134
+ * Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[https://docs.ruby-lang.org/en/master/Logger.html].
135
135
 
136
136
  ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
137
137
  ActiveRecord::Base.logger = Log4r::Logger.new('Application Log')
@@ -139,7 +139,7 @@ A short rundown of some of the major features:
139
139
 
140
140
  * Database agnostic schema management with Migrations.
141
141
 
142
- class AddSystemSettings < ActiveRecord::Migration[7.1]
142
+ class AddSystemSettings < ActiveRecord::Migration[7.2]
143
143
  def up
144
144
  create_table :system_settings do |t|
145
145
  t.string :name
@@ -157,7 +157,7 @@ A short rundown of some of the major features:
157
157
  end
158
158
  end
159
159
 
160
- {Learn more}[link:classes/ActiveRecord/Migration.html]
160
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveRecord/Migration.html]
161
161
 
162
162
 
163
163
  == Philosophy
@@ -176,10 +176,10 @@ Benchmark.ips(TIME) do |x|
176
176
  end
177
177
 
178
178
  x.report "Model.log" do
179
- Exhibit.connection.send(:log, "hello", "world") { }
179
+ Exhibit.lease_connection.send(:log, "hello", "world") { }
180
180
  end
181
181
 
182
182
  x.report "AR.execute(query)" do
183
- ActiveRecord::Base.connection.execute("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}")
183
+ ActiveRecord::Base.lease_connection.execute("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}")
184
184
  end
185
185
  end
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
  raise ArgumentError, "Bulk insert or upsert is currently not supported for has_many through association"
23
23
  end
24
24
 
25
- scoping { klass.#{method}(attributes, **kwargs) }
25
+ super
26
26
  end
27
27
  RUBY
28
28
  end
@@ -6,21 +6,23 @@ module ActiveRecord
6
6
  module Associations
7
7
  # Keeps track of table aliases for ActiveRecord::Associations::JoinDependency
8
8
  class AliasTracker # :nodoc:
9
- def self.create(connection, initial_table, joins, aliases = nil)
10
- if joins.empty?
11
- aliases ||= Hash.new(0)
12
- elsif aliases
13
- default_proc = aliases.default_proc || proc { 0 }
14
- aliases.default_proc = proc { |h, k|
15
- h[k] = initial_count_for(connection, k, joins) + default_proc.call(h, k)
16
- }
17
- else
18
- aliases = Hash.new { |h, k|
19
- h[k] = initial_count_for(connection, k, joins)
20
- }
9
+ def self.create(pool, initial_table, joins, aliases = nil)
10
+ pool.with_connection do |connection|
11
+ if joins.empty?
12
+ aliases ||= Hash.new(0)
13
+ elsif aliases
14
+ default_proc = aliases.default_proc || proc { 0 }
15
+ aliases.default_proc = proc { |h, k|
16
+ h[k] = initial_count_for(connection, k, joins) + default_proc.call(h, k)
17
+ }
18
+ else
19
+ aliases = Hash.new { |h, k|
20
+ h[k] = initial_count_for(connection, k, joins)
21
+ }
22
+ end
23
+ aliases[initial_table] = 1
24
+ new(connection.table_alias_length, aliases)
21
25
  end
22
- aliases[initial_table] = 1
23
- new(connection, aliases)
24
26
  end
25
27
 
26
28
  def self.initial_count_for(connection, name, table_joins)
@@ -46,9 +48,9 @@ module ActiveRecord
46
48
  end
47
49
 
48
50
  # table_joins is an array of arel joins which might conflict with the aliases we assign here
49
- def initialize(connection, aliases)
50
- @aliases = aliases
51
- @connection = connection
51
+ def initialize(table_alias_length, aliases)
52
+ @aliases = aliases
53
+ @table_alias_length = table_alias_length
52
54
  end
53
55
 
54
56
  def aliased_table_for(arel_table, table_name = nil)
@@ -60,7 +62,7 @@ module ActiveRecord
60
62
  arel_table = arel_table.alias(table_name) if arel_table.name != table_name
61
63
  else
62
64
  # Otherwise, we need to use an alias
63
- aliased_name = @connection.table_alias_for(yield)
65
+ aliased_name = table_alias_for(yield)
64
66
 
65
67
  # Update the count
66
68
  count = aliases[aliased_name] += 1
@@ -76,8 +78,12 @@ module ActiveRecord
76
78
  attr_reader :aliases
77
79
 
78
80
  private
81
+ def table_alias_for(table_name)
82
+ table_name[0...@table_alias_length].tr(".", "_")
83
+ end
84
+
79
85
  def truncate(name)
80
- name.slice(0, @connection.table_alias_length - 2)
86
+ name.slice(0, @table_alias_length - 2)
81
87
  end
82
88
  end
83
89
  end
@@ -53,7 +53,6 @@ module ActiveRecord
53
53
  # Resets the \loaded flag to +false+ and sets the \target to +nil+.
54
54
  def reset
55
55
  @loaded = false
56
- @target = nil
57
56
  @stale_state = nil
58
57
  end
59
58
 
@@ -64,7 +63,7 @@ module ActiveRecord
64
63
  # Reloads the \target and returns +self+ on success.
65
64
  # The QueryCache is cleared if +force+ is true.
66
65
  def reload(force = false)
67
- klass.connection.clear_query_cache if force && klass
66
+ klass.connection_pool.clear_query_cache if force && klass
68
67
  reset
69
68
  reset_scope
70
69
  load_target
@@ -232,12 +231,14 @@ module ActiveRecord
232
231
  end
233
232
 
234
233
  binds = AssociationScope.get_bind_values(owner, reflection.chain)
235
- sc.execute(binds, klass.connection) do |record|
236
- set_inverse_instance(record)
237
- if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many
238
- record.strict_loading!
239
- else
240
- record.strict_loading!(false, mode: owner.strict_loading_mode)
234
+ klass.with_connection do |c|
235
+ sc.execute(binds, c) do |record|
236
+ set_inverse_instance(record)
237
+ if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many
238
+ record.strict_loading!
239
+ else
240
+ record.strict_loading!(false, mode: owner.strict_loading_mode)
241
+ end
241
242
  end
242
243
  end
243
244
  end
@@ -124,12 +124,20 @@ module ActiveRecord
124
124
  end
125
125
 
126
126
  def replace_keys(record, force: false)
127
- target_key_values = record ? Array(primary_key(record.class)).map { |key| record._read_attribute(key) } : []
128
- reflection_fk = Array(reflection.foreign_key)
127
+ reflection_fk = reflection.foreign_key
128
+ if reflection_fk.is_a?(Array)
129
+ target_key_values = record ? Array(primary_key(record.class)).map { |key| record._read_attribute(key) } : []
130
+
131
+ if force || reflection_fk.map { |fk| owner._read_attribute(fk) } != target_key_values
132
+ reflection_fk.each_with_index do |key, index|
133
+ owner[key] = target_key_values[index]
134
+ end
135
+ end
136
+ else
137
+ target_key_value = record ? record._read_attribute(primary_key(record.class)) : nil
129
138
 
130
- if force || reflection_fk.map { |fk| owner._read_attribute(fk) } != target_key_values
131
- reflection_fk.zip(target_key_values).each do |key, value|
132
- owner[key] = value
139
+ if force || owner._read_attribute(reflection_fk) != target_key_value
140
+ owner[reflection_fk] = target_key_value
133
141
  end
134
142
  end
135
143
  end
@@ -148,8 +156,7 @@ module ActiveRecord
148
156
  end
149
157
 
150
158
  def stale_state
151
- result = owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
152
- result && result.to_s
159
+ owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
153
160
  end
154
161
  end
155
162
  end
@@ -41,8 +41,9 @@ module ActiveRecord
41
41
  end
42
42
 
43
43
  def stale_state
44
- foreign_key = super
45
- foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
44
+ if foreign_key = super
45
+ [foreign_key, owner[reflection.foreign_type]]
46
+ end
46
47
  end
47
48
  end
48
49
  end
@@ -38,6 +38,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
38
38
 
39
39
  klass = reflection.class_name.safe_constantize
40
40
  klass._counter_cache_columns |= [cache_column] if klass && klass.respond_to?(:_counter_cache_columns)
41
+ model.counter_cached_association_names |= [reflection.name]
41
42
  end
42
43
 
43
44
  def self.touch_record(o, changes, foreign_key, name, touch) # :nodoc:
@@ -42,8 +42,8 @@ module ActiveRecord::Associations::Builder # :nodoc:
42
42
  self.right_reflection = _reflect_on_association(rhs_name)
43
43
  end
44
44
 
45
- def self.retrieve_connection
46
- left_model.retrieve_connection
45
+ def self.connection_pool
46
+ left_model.connection_pool
47
47
  end
48
48
  }
49
49
 
@@ -7,11 +7,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
7
7
  end
8
8
 
9
9
  def self.valid_options(options)
10
- valid = super + [:counter_cache, :join_table, :index_errors]
11
- valid += [:as, :foreign_type] if options[:as]
12
- valid += [:through, :source, :source_type] if options[:through]
10
+ valid = super + [:counter_cache, :join_table, :index_errors, :as, :through]
11
+ valid += [:foreign_type] if options[:as]
12
+ valid += [:source, :source_type, :disable_joins] if options[:through]
13
13
  valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
14
- valid += [:disable_joins] if options[:disable_joins] && options[:through]
15
14
  valid
16
15
  end
17
16
 
@@ -7,11 +7,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
7
7
  end
8
8
 
9
9
  def self.valid_options(options)
10
- valid = super
11
- valid += [:as, :foreign_type] if options[:as]
10
+ valid = super + [:as, :through]
11
+ valid += [:foreign_type] if options[:as]
12
12
  valid += [:ensuring_owner_was] if options[:dependent] == :destroy_async
13
- valid += [:through, :source, :source_type] if options[:through]
14
- valid += [:disable_joins] if options[:disable_joins] && options[:through]
13
+ valid += [:source, :source_type, :disable_joins] if options[:through]
15
14
  valid
16
15
  end
17
16
 
@@ -28,6 +28,8 @@ module ActiveRecord
28
28
  # If you need to work on all current children, new and existing records,
29
29
  # +load_target+ and the +loaded+ flag are your friends.
30
30
  class CollectionAssociation < Association # :nodoc:
31
+ attr_accessor :nested_attributes_target
32
+
31
33
  # Implements the reader method, e.g. foo.items for Foo.has_many :items
32
34
  def reader
33
35
  ensure_klass_exists!
@@ -48,11 +50,11 @@ module ActiveRecord
48
50
  # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
49
51
  def ids_reader
50
52
  if loaded?
51
- target.pluck(*reflection.association_primary_key)
53
+ target.pluck(reflection.association_primary_key)
52
54
  elsif !target.empty?
53
- load_target.pluck(*reflection.association_primary_key)
55
+ load_target.pluck(reflection.association_primary_key)
54
56
  else
55
- @association_ids ||= scope.pluck(*reflection.association_primary_key)
57
+ @association_ids ||= scope.pluck(reflection.association_primary_key)
56
58
  end
57
59
  end
58
60
 
@@ -228,7 +230,7 @@ module ActiveRecord
228
230
  # loaded and you are going to fetch the records anyway it is better to
229
231
  # check <tt>collection.length.zero?</tt>.
230
232
  def empty?
231
- if loaded? || @association_ids || reflection.has_cached_counter?
233
+ if loaded? || @association_ids || reflection.has_active_cached_counter?
232
234
  size.zero?
233
235
  else
234
236
  target.empty? && !scope.exists?
@@ -928,7 +928,20 @@ module ActiveRecord
928
928
  !!@association.include?(record)
929
929
  end
930
930
 
931
- def proxy_association # :nodoc:
931
+ # Returns the association object for the collection.
932
+ #
933
+ # class Person < ActiveRecord::Base
934
+ # has_many :pets
935
+ # end
936
+ #
937
+ # person.pets.proxy_association
938
+ # # => #<ActiveRecord::Associations::HasManyAssociation owner="#<Person:0x00>">
939
+ #
940
+ # Returns the same object as <tt>person.association(:pets)</tt>,
941
+ # allowing you to make calls like <tt>person.pets.proxy_association.owner</tt>.
942
+ #
943
+ # See Associations::ClassMethods@Association+extensions for more.
944
+ def proxy_association
932
945
  @association
933
946
  end
934
947
 
@@ -78,7 +78,7 @@ module ActiveRecord
78
78
  # If the collection is empty the target is set to an empty array and
79
79
  # the loaded flag is set to true as well.
80
80
  def count_records
81
- count = if reflection.has_cached_counter?
81
+ count = if reflection.has_active_cached_counter?
82
82
  owner.read_attribute(reflection.counter_cache_column).to_i
83
83
  else
84
84
  scope.count(:all)
@@ -25,9 +25,8 @@ module ActiveRecord
25
25
  joins = []
26
26
  chain = []
27
27
 
28
- reflection_chain = reflection.chain
29
- reflection_chain.each_with_index do |reflection, index|
30
- table, terminated = yield reflection, reflection_chain[index..]
28
+ reflection.chain.each do |reflection|
29
+ table, terminated = yield reflection
31
30
  @table ||= table
32
31
 
33
32
  if terminated
@@ -38,39 +37,41 @@ module ActiveRecord
38
37
  chain << [reflection, table]
39
38
  end
40
39
 
41
- # The chain starts with the target table, but we want to end with it here (makes
42
- # more sense in this context), so we reverse
43
- chain.reverse_each do |reflection, table|
44
- klass = reflection.klass
40
+ base_klass.with_connection do |connection|
41
+ # The chain starts with the target table, but we want to end with it here (makes
42
+ # more sense in this context), so we reverse
43
+ chain.reverse_each do |reflection, table|
44
+ klass = reflection.klass
45
45
 
46
- scope = reflection.join_scope(table, foreign_table, foreign_klass)
46
+ scope = reflection.join_scope(table, foreign_table, foreign_klass)
47
47
 
48
- unless scope.references_values.empty?
49
- associations = scope.eager_load_values | scope.includes_values
48
+ unless scope.references_values.empty?
49
+ associations = scope.eager_load_values | scope.includes_values
50
50
 
51
- unless associations.empty?
52
- scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
51
+ unless associations.empty?
52
+ scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
53
+ end
53
54
  end
54
- end
55
55
 
56
- arel = scope.arel(alias_tracker.aliases)
57
- nodes = arel.constraints.first
56
+ arel = scope.arel(alias_tracker.aliases)
57
+ nodes = arel.constraints.first
58
58
 
59
- if nodes.is_a?(Arel::Nodes::And)
60
- others = nodes.children.extract! do |node|
61
- !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
59
+ if nodes.is_a?(Arel::Nodes::And)
60
+ others = nodes.children.extract! do |node|
61
+ !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
62
+ end
62
63
  end
63
- end
64
64
 
65
- joins << join_type.new(table, Arel::Nodes::On.new(nodes))
65
+ joins << join_type.new(table, Arel::Nodes::On.new(nodes))
66
66
 
67
- if others && !others.empty?
68
- joins.concat arel.join_sources
69
- append_constraints(joins.last, others)
70
- end
67
+ if others && !others.empty?
68
+ joins.concat arel.join_sources
69
+ append_constraints(connection, joins.last, others)
70
+ end
71
71
 
72
- # The current table in this iteration becomes the foreign table in the next
73
- foreign_table, foreign_klass = table, klass
72
+ # The current table in this iteration becomes the foreign table in the next
73
+ foreign_table, foreign_klass = table, klass
74
+ end
74
75
  end
75
76
 
76
77
  joins
@@ -89,10 +90,10 @@ module ActiveRecord
89
90
  end
90
91
 
91
92
  private
92
- def append_constraints(join, constraints)
93
+ def append_constraints(connection, join, constraints)
93
94
  if join.is_a?(Arel::Nodes::StringJoin)
94
95
  join_string = Arel::Nodes::And.new(constraints.unshift join.left)
95
- join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
96
+ join.left = Arel.sql(connection.visitor.compile(join_string))
96
97
  else
97
98
  right = join.right
98
99
  right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
@@ -61,7 +61,7 @@ module ActiveRecord
61
61
  when Hash
62
62
  associations.each do |k, v|
63
63
  cache = hash[k] ||= {}
64
- walk_tree v, cache if v
64
+ walk_tree v, cache
65
65
  end
66
66
  else
67
67
  raise ConfigurationError, associations.inspect
@@ -190,12 +190,12 @@ module ActiveRecord
190
190
  def make_constraints(parent, child, join_type)
191
191
  foreign_table = parent.table
192
192
  foreign_klass = parent.base_klass
193
- child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection, remaining_reflection_chain|
194
- table, terminated = @joined_tables[remaining_reflection_chain]
193
+ child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection|
194
+ table, terminated = @joined_tables[reflection]
195
195
  root = reflection == child.reflection
196
196
 
197
197
  if table && (!root || !terminated)
198
- @joined_tables[remaining_reflection_chain] = [table, root] if root
198
+ @joined_tables[reflection] = [table, root] if root
199
199
  next table, true
200
200
  end
201
201
 
@@ -206,7 +206,7 @@ module ActiveRecord
206
206
  root ? name : "#{name}_join"
207
207
  end
208
208
 
209
- @joined_tables[remaining_reflection_chain] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
209
+ @joined_tables[reflection] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
210
210
  table
211
211
  end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
212
212
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Validation error class to wrap association records' errors,
4
+ # with index_errors support.
5
+ module ActiveRecord
6
+ module Associations
7
+ class NestedError < ::ActiveModel::NestedError
8
+ def initialize(association, inner_error)
9
+ @base = association.owner
10
+ @association = association
11
+ @inner_error = inner_error
12
+ super(@base, inner_error, { attribute: compute_attribute(inner_error) })
13
+ end
14
+
15
+ private
16
+ attr_reader :association
17
+
18
+ def compute_attribute(inner_error)
19
+ association_name = association.reflection.name
20
+
21
+ if index_errors_setting && index
22
+ "#{association_name}[#{index}].#{inner_error.attribute}".to_sym
23
+ else
24
+ "#{association_name}.#{inner_error.attribute}".to_sym
25
+ end
26
+ end
27
+
28
+ def index_errors_setting
29
+ @index_errors_setting ||=
30
+ association.options.fetch(:index_errors, ActiveRecord.index_nested_attribute_errors)
31
+ end
32
+
33
+ def index
34
+ @index ||= ordered_records&.find_index(inner_error.base)
35
+ end
36
+
37
+ def ordered_records
38
+ case index_errors_setting
39
+ when true # default is association order
40
+ association.target
41
+ when :nested_attributes_order
42
+ association.nested_attributes_target
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -248,7 +248,8 @@ module ActiveRecord
248
248
  association = owner.association(reflection.name)
249
249
 
250
250
  if reflection.collection?
251
- association.target = records
251
+ not_persisted_records = association.target.reject(&:persisted?)
252
+ association.target = records + not_persisted_records
252
253
  else
253
254
  association.target = records.first
254
255
  end
@@ -9,7 +9,13 @@ module ActiveRecord
9
9
  attr_writer :preloaded_records
10
10
 
11
11
  def initialize(association:, children:, parent:, associate_by_default:, scope:)
12
- @association = association
12
+ @association = if association
13
+ begin
14
+ @association = association.to_sym
15
+ rescue NoMethodError
16
+ raise ArgumentError, "Association names must be Symbol or String, got: #{association.class.name}"
17
+ end
18
+ end
13
19
  @parent = parent
14
20
  @scope = scope
15
21
  @associate_by_default = associate_by_default
@@ -9,9 +9,7 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  def records_by_owner
12
- return @records_by_owner if defined?(@records_by_owner)
13
-
14
- @records_by_owner = owners.each_with_object({}) do |owner, result|
12
+ @records_by_owner ||= owners.each_with_object({}) do |owner, result|
15
13
  if loaded?(owner)
16
14
  result[owner] = target_for(owner)
17
15
  next
@@ -14,6 +14,12 @@ module ActiveRecord
14
14
  target
15
15
  end
16
16
 
17
+ # Resets the \loaded flag to +false+ and sets the \target to +nil+.
18
+ def reset
19
+ super
20
+ @target = nil
21
+ end
22
+
17
23
  # Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
18
24
  def writer(record)
19
25
  replace(record)
@@ -82,7 +82,7 @@ module ActiveRecord
82
82
  def stale_state
83
83
  if through_reflection.belongs_to?
84
84
  Array(through_reflection.foreign_key).filter_map do |foreign_key_column|
85
- owner[foreign_key_column] && owner[foreign_key_column].to_s
85
+ owner[foreign_key_column]
86
86
  end.presence
87
87
  end
88
88
  end