activerecord 7.1.3.3 → 7.2.0.beta1

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 (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +507 -2128
  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 +18 -11
  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 +4 -2
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +3 -3
  17. data/lib/active_record/associations/has_one_association.rb +2 -2
  18. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  19. data/lib/active_record/associations/join_dependency.rb +5 -7
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +34 -11
  27. data/lib/active_record/attribute_assignment.rb +1 -11
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  30. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  31. data/lib/active_record/attribute_methods/read.rb +1 -13
  32. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  33. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  34. data/lib/active_record/attribute_methods.rb +87 -58
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +14 -30
  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 -58
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +160 -75
  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 +22 -9
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -61
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
  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 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
  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 +6 -0
  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 +16 -12
  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 +109 -77
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
  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 +59 -38
  77. data/lib/active_record/counter_cache.rb +23 -10
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  79. data/lib/active_record/database_configurations/database_config.rb +15 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +44 -36
  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 +30 -6
  84. data/lib/active_record/destroy_association_async_job.rb +1 -1
  85. data/lib/active_record/dynamic_matchers.rb +2 -2
  86. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  87. data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
  88. data/lib/active_record/encryption/encryptor.rb +17 -2
  89. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  90. data/lib/active_record/encryption/message_serializer.rb +4 -0
  91. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  93. data/lib/active_record/encryption/scheme.rb +8 -4
  94. data/lib/active_record/enum.rb +11 -2
  95. data/lib/active_record/errors.rb +16 -11
  96. data/lib/active_record/explain.rb +13 -24
  97. data/lib/active_record/fixtures.rb +37 -31
  98. data/lib/active_record/future_result.rb +17 -4
  99. data/lib/active_record/gem_version.rb +3 -3
  100. data/lib/active_record/inheritance.rb +4 -2
  101. data/lib/active_record/insert_all.rb +18 -15
  102. data/lib/active_record/integration.rb +4 -1
  103. data/lib/active_record/internal_metadata.rb +48 -34
  104. data/lib/active_record/locking/optimistic.rb +8 -7
  105. data/lib/active_record/log_subscriber.rb +0 -21
  106. data/lib/active_record/marshalling.rb +1 -1
  107. data/lib/active_record/message_pack.rb +2 -2
  108. data/lib/active_record/migration/command_recorder.rb +2 -3
  109. data/lib/active_record/migration/compatibility.rb +11 -3
  110. data/lib/active_record/migration/default_strategy.rb +4 -5
  111. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  112. data/lib/active_record/migration.rb +85 -76
  113. data/lib/active_record/model_schema.rb +34 -69
  114. data/lib/active_record/nested_attributes.rb +11 -3
  115. data/lib/active_record/normalization.rb +3 -7
  116. data/lib/active_record/persistence.rb +32 -354
  117. data/lib/active_record/query_cache.rb +18 -6
  118. data/lib/active_record/query_logs.rb +15 -0
  119. data/lib/active_record/query_logs_formatter.rb +1 -1
  120. data/lib/active_record/querying.rb +21 -9
  121. data/lib/active_record/railtie.rb +52 -64
  122. data/lib/active_record/railties/controller_runtime.rb +13 -4
  123. data/lib/active_record/railties/databases.rake +41 -44
  124. data/lib/active_record/reflection.rb +98 -37
  125. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  126. data/lib/active_record/relation/batches.rb +3 -3
  127. data/lib/active_record/relation/calculations.rb +94 -61
  128. data/lib/active_record/relation/delegation.rb +8 -11
  129. data/lib/active_record/relation/finder_methods.rb +16 -2
  130. data/lib/active_record/relation/merger.rb +4 -6
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  132. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  133. data/lib/active_record/relation/predicate_builder.rb +3 -3
  134. data/lib/active_record/relation/query_methods.rb +196 -43
  135. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  136. data/lib/active_record/relation/spawn_methods.rb +2 -18
  137. data/lib/active_record/relation/where_clause.rb +7 -19
  138. data/lib/active_record/relation.rb +500 -66
  139. data/lib/active_record/result.rb +32 -45
  140. data/lib/active_record/runtime_registry.rb +39 -0
  141. data/lib/active_record/sanitization.rb +24 -19
  142. data/lib/active_record/schema.rb +8 -6
  143. data/lib/active_record/schema_dumper.rb +19 -9
  144. data/lib/active_record/schema_migration.rb +30 -14
  145. data/lib/active_record/signed_id.rb +11 -1
  146. data/lib/active_record/statement_cache.rb +7 -7
  147. data/lib/active_record/table_metadata.rb +1 -10
  148. data/lib/active_record/tasks/database_tasks.rb +70 -42
  149. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  152. data/lib/active_record/test_fixtures.rb +82 -91
  153. data/lib/active_record/testing/query_assertions.rb +121 -0
  154. data/lib/active_record/timestamp.rb +4 -2
  155. data/lib/active_record/token_for.rb +22 -12
  156. data/lib/active_record/touch_later.rb +1 -1
  157. data/lib/active_record/transaction.rb +68 -0
  158. data/lib/active_record/transactions.rb +43 -14
  159. data/lib/active_record/translation.rb +0 -2
  160. data/lib/active_record/type/serialized.rb +1 -3
  161. data/lib/active_record/type_caster/connection.rb +4 -4
  162. data/lib/active_record/validations/associated.rb +9 -3
  163. data/lib/active_record/validations/uniqueness.rb +14 -10
  164. data/lib/active_record/validations.rb +4 -1
  165. data/lib/active_record.rb +149 -40
  166. data/lib/arel/alias_predication.rb +1 -1
  167. data/lib/arel/collectors/bind.rb +2 -0
  168. data/lib/arel/collectors/composite.rb +7 -0
  169. data/lib/arel/collectors/sql_string.rb +1 -1
  170. data/lib/arel/collectors/substitute_binds.rb +1 -1
  171. data/lib/arel/nodes/binary.rb +0 -6
  172. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  173. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  174. data/lib/arel/nodes/node.rb +4 -3
  175. data/lib/arel/nodes/sql_literal.rb +7 -0
  176. data/lib/arel/nodes.rb +2 -2
  177. data/lib/arel/predications.rb +1 -1
  178. data/lib/arel/select_manager.rb +1 -1
  179. data/lib/arel/tree_manager.rb +8 -3
  180. data/lib/arel/update_manager.rb +2 -1
  181. data/lib/arel/visitors/dot.rb +1 -0
  182. data/lib/arel/visitors/mysql.rb +9 -4
  183. data/lib/arel/visitors/postgresql.rb +1 -12
  184. data/lib/arel/visitors/to_sql.rb +31 -17
  185. data/lib/arel.rb +7 -3
  186. metadata +16 -11
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
@@ -12,11 +12,11 @@ module ActiveRecord
12
12
  raise ActiveRecord::Rollback unless target.destroy
13
13
  when :destroy_async
14
14
  if reflection.foreign_key.is_a?(Array)
15
- primary_key_column = reflection.active_record_primary_key.map(&:to_sym)
16
- id = reflection.foreign_key.map { |col| owner.public_send(col.to_sym) }
15
+ primary_key_column = reflection.active_record_primary_key
16
+ id = reflection.foreign_key.map { |col| owner.public_send(col) }
17
17
  else
18
- primary_key_column = reflection.active_record_primary_key.to_sym
19
- id = owner.public_send(reflection.foreign_key.to_sym)
18
+ primary_key_column = reflection.active_record_primary_key
19
+ id = owner.public_send(reflection.foreign_key)
20
20
  end
21
21
 
22
22
  enqueue_destroy_association(
@@ -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!
@@ -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?
@@ -303,7 +305,7 @@ module ActiveRecord
303
305
 
304
306
  def find_from_target?
305
307
  loaded? ||
306
- owner.strict_loading? ||
308
+ (owner.strict_loading? && owner.strict_loading_all?) ||
307
309
  reflection.strict_loading? ||
308
310
  owner.new_record? ||
309
311
  target.any? { |record| record.new_record? || record.changed? }
@@ -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
 
@@ -35,10 +35,10 @@ module ActiveRecord
35
35
  unless target.empty?
36
36
  association_class = target.first.class
37
37
  if association_class.query_constraints_list
38
- primary_key_column = association_class.query_constraints_list.map(&:to_sym)
38
+ primary_key_column = association_class.query_constraints_list
39
39
  ids = target.collect { |assoc| primary_key_column.map { |col| assoc.public_send(col) } }
40
40
  else
41
- primary_key_column = association_class.primary_key.to_sym
41
+ primary_key_column = association_class.primary_key
42
42
  ids = target.collect { |assoc| assoc.public_send(primary_key_column) }
43
43
  end
44
44
 
@@ -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)
@@ -34,10 +34,10 @@ module ActiveRecord
34
34
  throw(:abort) unless target.destroyed?
35
35
  when :destroy_async
36
36
  if target.class.query_constraints_list
37
- primary_key_column = target.class.query_constraints_list.map(&:to_sym)
37
+ primary_key_column = target.class.query_constraints_list
38
38
  id = primary_key_column.map { |col| target.public_send(col) }
39
39
  else
40
- primary_key_column = target.class.primary_key.to_sym
40
+ primary_key_column = target.class.primary_key
41
41
  id = target.public_send(primary_key_column)
42
42
  end
43
43
 
@@ -37,39 +37,41 @@ module ActiveRecord
37
37
  chain << [reflection, table]
38
38
  end
39
39
 
40
- # The chain starts with the target table, but we want to end with it here (makes
41
- # more sense in this context), so we reverse
42
- chain.reverse_each do |reflection, table|
43
- 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
44
45
 
45
- scope = reflection.join_scope(table, foreign_table, foreign_klass)
46
+ scope = reflection.join_scope(table, foreign_table, foreign_klass)
46
47
 
47
- unless scope.references_values.empty?
48
- associations = scope.eager_load_values | scope.includes_values
48
+ unless scope.references_values.empty?
49
+ associations = scope.eager_load_values | scope.includes_values
49
50
 
50
- unless associations.empty?
51
- 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
52
54
  end
53
- end
54
55
 
55
- arel = scope.arel(alias_tracker.aliases)
56
- nodes = arel.constraints.first
56
+ arel = scope.arel(alias_tracker.aliases)
57
+ nodes = arel.constraints.first
57
58
 
58
- if nodes.is_a?(Arel::Nodes::And)
59
- others = nodes.children.extract! do |node|
60
- !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
61
63
  end
62
- end
63
64
 
64
- joins << join_type.new(table, Arel::Nodes::On.new(nodes))
65
+ joins << join_type.new(table, Arel::Nodes::On.new(nodes))
65
66
 
66
- if others && !others.empty?
67
- joins.concat arel.join_sources
68
- append_constraints(joins.last, others)
69
- end
67
+ if others && !others.empty?
68
+ joins.concat arel.join_sources
69
+ append_constraints(connection, joins.last, others)
70
+ end
70
71
 
71
- # The current table in this iteration becomes the foreign table in the next
72
- 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
73
75
  end
74
76
 
75
77
  joins
@@ -88,10 +90,10 @@ module ActiveRecord
88
90
  end
89
91
 
90
92
  private
91
- def append_constraints(join, constraints)
93
+ def append_constraints(connection, join, constraints)
92
94
  if join.is_a?(Arel::Nodes::StringJoin)
93
95
  join_string = Arel::Nodes::And.new(constraints.unshift join.left)
94
- join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
96
+ join.left = Arel.sql(connection.visitor.compile(join_string))
95
97
  else
96
98
  right = join.right
97
99
  right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
@@ -254,10 +254,10 @@ module ActiveRecord
254
254
 
255
255
  if node.primary_key
256
256
  keys = Array(node.primary_key).map { |column| aliases.column_alias(node, column) }
257
- ids = keys.map { |key| row[key] }
257
+ id = keys.map { |key| row[key] }
258
258
  else
259
259
  keys = Array(node.reflection.join_primary_key).map { |column| aliases.column_alias(node, column.to_s) }
260
- ids = keys.map { nil } # Avoid id-based model caching.
260
+ id = keys.map { nil } # Avoid id-based model caching.
261
261
  end
262
262
 
263
263
  if keys.any? { |key| row[key].nil? }
@@ -266,11 +266,9 @@ module ActiveRecord
266
266
  next
267
267
  end
268
268
 
269
- ids.each do |id|
270
- unless model = seen[ar_parent][node][id]
271
- model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
272
- seen[ar_parent][node][id] = model if id
273
- end
269
+ unless model = seen[ar_parent][node][id]
270
+ model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
271
+ seen[ar_parent][node][id] = model if id
274
272
  end
275
273
 
276
274
  construct(model, node, row, seen, model_cache, strict_loading_value)
@@ -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)