activerecord 6.1.7 → 7.2.2

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 (333) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +616 -1290
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +17 -14
  7. data/lib/active_record/association_relation.rb +2 -12
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +60 -21
  10. data/lib/active_record/associations/association_scope.rb +17 -12
  11. data/lib/active_record/associations/belongs_to_association.rb +37 -11
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
  13. data/lib/active_record/associations/builder/association.rb +11 -5
  14. data/lib/active_record/associations/builder/belongs_to.rb +41 -14
  15. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +4 -4
  19. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  20. data/lib/active_record/associations/collection_association.rb +46 -36
  21. data/lib/active_record/associations/collection_proxy.rb +44 -16
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/errors.rb +265 -0
  24. data/lib/active_record/associations/foreign_association.rb +10 -3
  25. data/lib/active_record/associations/has_many_association.rb +29 -19
  26. data/lib/active_record/associations/has_many_through_association.rb +19 -8
  27. data/lib/active_record/associations/has_one_association.rb +20 -10
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  30. data/lib/active_record/associations/join_dependency.rb +28 -20
  31. data/lib/active_record/associations/nested_error.rb +47 -0
  32. data/lib/active_record/associations/preloader/association.rb +212 -53
  33. data/lib/active_record/associations/preloader/batch.rb +48 -0
  34. data/lib/active_record/associations/preloader/branch.rb +153 -0
  35. data/lib/active_record/associations/preloader/through_association.rb +50 -16
  36. data/lib/active_record/associations/preloader.rb +50 -121
  37. data/lib/active_record/associations/singular_association.rb +15 -3
  38. data/lib/active_record/associations/through_association.rb +25 -14
  39. data/lib/active_record/associations.rb +429 -522
  40. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  41. data/lib/active_record/attribute_assignment.rb +1 -5
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  43. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  44. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  45. data/lib/active_record/attribute_methods/primary_key.rb +47 -27
  46. data/lib/active_record/attribute_methods/query.rb +31 -19
  47. data/lib/active_record/attribute_methods/read.rb +14 -11
  48. data/lib/active_record/attribute_methods/serialization.rb +174 -37
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +15 -9
  50. data/lib/active_record/attribute_methods/write.rb +12 -15
  51. data/lib/active_record/attribute_methods.rb +164 -52
  52. data/lib/active_record/attributes.rb +57 -54
  53. data/lib/active_record/autosave_association.rb +74 -57
  54. data/lib/active_record/base.rb +27 -5
  55. data/lib/active_record/callbacks.rb +19 -35
  56. data/lib/active_record/coders/column_serializer.rb +61 -0
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +70 -46
  59. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +325 -604
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +230 -64
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +378 -143
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +348 -165
  75. data/lib/active_record/connection_adapters/column.rb +13 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -130
  78. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
  79. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  81. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  82. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  83. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +107 -68
  85. data/lib/active_record/connection_adapters/pool_config.rb +26 -16
  86. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  87. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  88. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
  89. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  94. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  97. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  100. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  102. data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
  103. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  104. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  105. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +173 -3
  106. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  107. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +403 -77
  108. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  109. data/lib/active_record/connection_adapters/postgresql_adapter.rb +520 -253
  110. data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
  111. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  112. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
  113. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
  114. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  115. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +66 -22
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
  119. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  120. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  121. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  122. data/lib/active_record/connection_adapters.rb +130 -6
  123. data/lib/active_record/connection_handling.rb +132 -146
  124. data/lib/active_record/core.rb +310 -253
  125. data/lib/active_record/counter_cache.rb +68 -34
  126. data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -4
  127. data/lib/active_record/database_configurations/database_config.rb +34 -10
  128. data/lib/active_record/database_configurations/hash_config.rb +107 -31
  129. data/lib/active_record/database_configurations/url_config.rb +38 -13
  130. data/lib/active_record/database_configurations.rb +96 -60
  131. data/lib/active_record/delegated_type.rb +90 -20
  132. data/lib/active_record/deprecator.rb +7 -0
  133. data/lib/active_record/destroy_association_async_job.rb +4 -2
  134. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  135. data/lib/active_record/dynamic_matchers.rb +3 -3
  136. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +68 -0
  140. data/lib/active_record/encryption/configurable.rb +60 -0
  141. data/lib/active_record/encryption/context.rb +42 -0
  142. data/lib/active_record/encryption/contexts.rb +76 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  147. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  148. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  149. data/lib/active_record/encryption/encryptor.rb +170 -0
  150. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  151. data/lib/active_record/encryption/errors.rb +15 -0
  152. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  153. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  154. data/lib/active_record/encryption/key.rb +28 -0
  155. data/lib/active_record/encryption/key_generator.rb +53 -0
  156. data/lib/active_record/encryption/key_provider.rb +46 -0
  157. data/lib/active_record/encryption/message.rb +33 -0
  158. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  159. data/lib/active_record/encryption/message_serializer.rb +96 -0
  160. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  161. data/lib/active_record/encryption/properties.rb +76 -0
  162. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  163. data/lib/active_record/encryption/scheme.rb +100 -0
  164. data/lib/active_record/encryption.rb +58 -0
  165. data/lib/active_record/enum.rb +170 -62
  166. data/lib/active_record/errors.rb +210 -27
  167. data/lib/active_record/explain.rb +21 -12
  168. data/lib/active_record/explain_registry.rb +11 -6
  169. data/lib/active_record/explain_subscriber.rb +1 -1
  170. data/lib/active_record/fixture_set/file.rb +15 -1
  171. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  172. data/lib/active_record/fixture_set/render_context.rb +2 -0
  173. data/lib/active_record/fixture_set/table_row.rb +70 -14
  174. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  175. data/lib/active_record/fixtures.rb +179 -112
  176. data/lib/active_record/future_result.rb +178 -0
  177. data/lib/active_record/gem_version.rb +4 -4
  178. data/lib/active_record/inheritance.rb +85 -31
  179. data/lib/active_record/insert_all.rb +148 -32
  180. data/lib/active_record/integration.rb +14 -10
  181. data/lib/active_record/internal_metadata.rb +123 -23
  182. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  183. data/lib/active_record/locking/optimistic.rb +43 -27
  184. data/lib/active_record/locking/pessimistic.rb +15 -6
  185. data/lib/active_record/log_subscriber.rb +41 -29
  186. data/lib/active_record/marshalling.rb +59 -0
  187. data/lib/active_record/message_pack.rb +124 -0
  188. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  189. data/lib/active_record/middleware/database_selector.rb +23 -13
  190. data/lib/active_record/middleware/shard_selector.rb +62 -0
  191. data/lib/active_record/migration/command_recorder.rb +113 -16
  192. data/lib/active_record/migration/compatibility.rb +235 -46
  193. data/lib/active_record/migration/default_strategy.rb +22 -0
  194. data/lib/active_record/migration/execution_strategy.rb +19 -0
  195. data/lib/active_record/migration/join_table.rb +1 -1
  196. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  197. data/lib/active_record/migration.rb +374 -177
  198. data/lib/active_record/model_schema.rb +145 -158
  199. data/lib/active_record/nested_attributes.rb +61 -23
  200. data/lib/active_record/no_touching.rb +3 -3
  201. data/lib/active_record/normalization.rb +163 -0
  202. data/lib/active_record/persistence.rb +282 -283
  203. data/lib/active_record/promise.rb +84 -0
  204. data/lib/active_record/query_cache.rb +18 -25
  205. data/lib/active_record/query_logs.rb +189 -0
  206. data/lib/active_record/query_logs_formatter.rb +41 -0
  207. data/lib/active_record/querying.rb +44 -9
  208. data/lib/active_record/railtie.rb +229 -71
  209. data/lib/active_record/railties/controller_runtime.rb +25 -11
  210. data/lib/active_record/railties/databases.rake +189 -256
  211. data/lib/active_record/railties/job_runtime.rb +23 -0
  212. data/lib/active_record/readonly_attributes.rb +41 -3
  213. data/lib/active_record/reflection.rb +332 -103
  214. data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
  215. data/lib/active_record/relation/batches.rb +200 -65
  216. data/lib/active_record/relation/calculations.rb +301 -112
  217. data/lib/active_record/relation/delegation.rb +33 -22
  218. data/lib/active_record/relation/finder_methods.rb +123 -52
  219. data/lib/active_record/relation/merger.rb +26 -19
  220. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  221. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  222. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  223. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  224. data/lib/active_record/relation/predicate_builder.rb +29 -22
  225. data/lib/active_record/relation/query_attribute.rb +30 -12
  226. data/lib/active_record/relation/query_methods.rb +870 -163
  227. data/lib/active_record/relation/record_fetch_warning.rb +10 -9
  228. data/lib/active_record/relation/spawn_methods.rb +7 -6
  229. data/lib/active_record/relation/where_clause.rb +15 -36
  230. data/lib/active_record/relation.rb +736 -145
  231. data/lib/active_record/result.rb +67 -54
  232. data/lib/active_record/runtime_registry.rb +71 -13
  233. data/lib/active_record/sanitization.rb +84 -34
  234. data/lib/active_record/schema.rb +39 -23
  235. data/lib/active_record/schema_dumper.rb +90 -31
  236. data/lib/active_record/schema_migration.rb +74 -23
  237. data/lib/active_record/scoping/default.rb +72 -15
  238. data/lib/active_record/scoping/named.rb +6 -13
  239. data/lib/active_record/scoping.rb +65 -34
  240. data/lib/active_record/secure_password.rb +60 -0
  241. data/lib/active_record/secure_token.rb +21 -3
  242. data/lib/active_record/serialization.rb +6 -1
  243. data/lib/active_record/signed_id.rb +30 -9
  244. data/lib/active_record/statement_cache.rb +7 -7
  245. data/lib/active_record/store.rb +10 -10
  246. data/lib/active_record/suppressor.rb +13 -15
  247. data/lib/active_record/table_metadata.rb +7 -3
  248. data/lib/active_record/tasks/database_tasks.rb +288 -149
  249. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  250. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  251. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  252. data/lib/active_record/test_databases.rb +1 -1
  253. data/lib/active_record/test_fixtures.rb +173 -155
  254. data/lib/active_record/testing/query_assertions.rb +121 -0
  255. data/lib/active_record/timestamp.rb +32 -19
  256. data/lib/active_record/token_for.rb +123 -0
  257. data/lib/active_record/touch_later.rb +12 -7
  258. data/lib/active_record/transaction.rb +132 -0
  259. data/lib/active_record/transactions.rb +118 -41
  260. data/lib/active_record/translation.rb +3 -5
  261. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  262. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  263. data/lib/active_record/type/internal/timezone.rb +7 -2
  264. data/lib/active_record/type/serialized.rb +9 -7
  265. data/lib/active_record/type/time.rb +4 -0
  266. data/lib/active_record/type/type_map.rb +17 -20
  267. data/lib/active_record/type.rb +1 -2
  268. data/lib/active_record/type_caster/connection.rb +4 -4
  269. data/lib/active_record/validations/absence.rb +1 -1
  270. data/lib/active_record/validations/associated.rb +13 -7
  271. data/lib/active_record/validations/numericality.rb +5 -4
  272. data/lib/active_record/validations/presence.rb +5 -28
  273. data/lib/active_record/validations/uniqueness.rb +65 -15
  274. data/lib/active_record/validations.rb +12 -5
  275. data/lib/active_record/version.rb +1 -1
  276. data/lib/active_record.rb +444 -32
  277. data/lib/arel/alias_predication.rb +1 -1
  278. data/lib/arel/attributes/attribute.rb +0 -8
  279. data/lib/arel/collectors/bind.rb +2 -0
  280. data/lib/arel/collectors/composite.rb +7 -0
  281. data/lib/arel/collectors/sql_string.rb +1 -1
  282. data/lib/arel/collectors/substitute_binds.rb +1 -1
  283. data/lib/arel/crud.rb +28 -22
  284. data/lib/arel/delete_manager.rb +18 -4
  285. data/lib/arel/errors.rb +10 -0
  286. data/lib/arel/factory_methods.rb +4 -0
  287. data/lib/arel/filter_predications.rb +9 -0
  288. data/lib/arel/insert_manager.rb +2 -3
  289. data/lib/arel/nodes/binary.rb +6 -7
  290. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  291. data/lib/arel/nodes/casted.rb +1 -1
  292. data/lib/arel/nodes/cte.rb +36 -0
  293. data/lib/arel/nodes/delete_statement.rb +12 -13
  294. data/lib/arel/nodes/filter.rb +10 -0
  295. data/lib/arel/nodes/fragments.rb +35 -0
  296. data/lib/arel/nodes/function.rb +1 -0
  297. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  298. data/lib/arel/nodes/insert_statement.rb +2 -2
  299. data/lib/arel/nodes/leading_join.rb +8 -0
  300. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  301. data/lib/arel/nodes/node.rb +115 -5
  302. data/lib/arel/nodes/select_core.rb +2 -2
  303. data/lib/arel/nodes/select_statement.rb +2 -2
  304. data/lib/arel/nodes/sql_literal.rb +13 -0
  305. data/lib/arel/nodes/table_alias.rb +4 -0
  306. data/lib/arel/nodes/update_statement.rb +8 -3
  307. data/lib/arel/nodes.rb +7 -2
  308. data/lib/arel/predications.rb +14 -4
  309. data/lib/arel/select_manager.rb +11 -5
  310. data/lib/arel/table.rb +9 -6
  311. data/lib/arel/tree_manager.rb +8 -15
  312. data/lib/arel/update_manager.rb +20 -5
  313. data/lib/arel/visitors/dot.rb +81 -90
  314. data/lib/arel/visitors/mysql.rb +23 -5
  315. data/lib/arel/visitors/postgresql.rb +1 -22
  316. data/lib/arel/visitors/sqlite.rb +25 -0
  317. data/lib/arel/visitors/to_sql.rb +170 -36
  318. data/lib/arel/visitors/visitor.rb +2 -2
  319. data/lib/arel.rb +23 -4
  320. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  321. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  322. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  323. data/lib/rails/generators/active_record/migration.rb +3 -1
  324. data/lib/rails/generators/active_record/model/USAGE +113 -0
  325. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  326. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  327. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  328. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  329. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  330. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  331. metadata +103 -17
  332. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  333. data/lib/active_record/null_relation.rb +0 -67
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  # = Active Record Has One Association
6
- class HasOneAssociation < SingularAssociation #:nodoc:
6
+ class HasOneAssociation < SingularAssociation # :nodoc:
7
7
  include ForeignAssociation
8
8
 
9
9
  def handle_dependency
@@ -33,8 +33,13 @@ module ActiveRecord
33
33
  target.destroy
34
34
  throw(:abort) unless target.destroyed?
35
35
  when :destroy_async
36
- primary_key_column = target.class.primary_key.to_sym
37
- id = target.public_send(primary_key_column)
36
+ if target.class.query_constraints_list
37
+ primary_key_column = target.class.query_constraints_list
38
+ id = primary_key_column.map { |col| target.public_send(col) }
39
+ else
40
+ primary_key_column = target.class.primary_key
41
+ id = target.public_send(primary_key_column)
42
+ end
38
43
 
39
44
  enqueue_destroy_association(
40
45
  owner_model_name: owner.class.to_s,
@@ -70,7 +75,7 @@ module ActiveRecord
70
75
  if save && !record.save
71
76
  nullify_owner_attributes(record)
72
77
  set_owner_attributes(target) if target
73
- raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
78
+ raise RecordNotSaved.new("Failed to save the new associated #{reflection.name}.", record)
74
79
  end
75
80
  end
76
81
  end
@@ -102,19 +107,24 @@ module ActiveRecord
102
107
 
103
108
  if target.persisted? && owner.persisted? && !target.save
104
109
  set_owner_attributes(target)
105
- raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " \
106
- "The record failed to save after its foreign key was set to nil."
110
+ raise RecordNotSaved.new(
111
+ "Failed to remove the existing associated #{reflection.name}. " \
112
+ "The record failed to save after its foreign key was set to nil.",
113
+ target
114
+ )
107
115
  end
108
116
  end
109
117
  end
110
118
 
111
119
  def nullify_owner_attributes(record)
112
- record[reflection.foreign_key] = nil
120
+ Array(reflection.foreign_key).each do |foreign_key_column|
121
+ record[foreign_key_column] = nil unless foreign_key_column.in?(Array(record.class.primary_key))
122
+ end
113
123
  end
114
124
 
115
- def transaction_if(value)
125
+ def transaction_if(value, &block)
116
126
  if value
117
- reflection.klass.transaction { yield }
127
+ reflection.klass.transaction(&block)
118
128
  else
119
129
  yield
120
130
  end
@@ -122,7 +132,7 @@ module ActiveRecord
122
132
 
123
133
  def _create_record(attributes, raise_error = false, &block)
124
134
  unless owner.persisted?
125
- raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
135
+ raise ActiveRecord::RecordNotSaved.new("You cannot call create unless the parent is saved", owner)
126
136
  end
127
137
 
128
138
  super
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  # = Active Record Has One Through Association
6
- class HasOneThroughAssociation < HasOneAssociation #:nodoc:
6
+ class HasOneThroughAssociation < HasOneAssociation # :nodoc:
7
7
  include ThroughAssociation
8
8
 
9
9
  private
@@ -25,8 +25,9 @@ module ActiveRecord
25
25
  joins = []
26
26
  chain = []
27
27
 
28
- reflection.chain.each do |reflection|
29
- table, terminated = yield reflection
28
+ reflection_chain = reflection.chain
29
+ reflection_chain.each_with_index do |reflection, index|
30
+ table, terminated = yield reflection, reflection_chain[index..]
30
31
  @table ||= table
31
32
 
32
33
  if terminated
@@ -37,39 +38,41 @@ module ActiveRecord
37
38
  chain << [reflection, table]
38
39
  end
39
40
 
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
41
+ base_klass.with_connection do |connection|
42
+ # The chain starts with the target table, but we want to end with it here (makes
43
+ # more sense in this context), so we reverse
44
+ chain.reverse_each do |reflection, table|
45
+ klass = reflection.klass
44
46
 
45
- scope = reflection.join_scope(table, foreign_table, foreign_klass)
47
+ scope = reflection.join_scope(table, foreign_table, foreign_klass)
46
48
 
47
- unless scope.references_values.empty?
48
- associations = scope.eager_load_values | scope.includes_values
49
+ unless scope.references_values.empty?
50
+ associations = scope.eager_load_values | scope.includes_values
49
51
 
50
- unless associations.empty?
51
- scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
52
+ unless associations.empty?
53
+ scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
54
+ end
52
55
  end
53
- end
54
56
 
55
- arel = scope.arel(alias_tracker.aliases)
56
- nodes = arel.constraints.first
57
+ arel = scope.arel(alias_tracker.aliases)
58
+ nodes = arel.constraints.first
57
59
 
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 }
60
+ if nodes.is_a?(Arel::Nodes::And)
61
+ others = nodes.children.extract! do |node|
62
+ !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
63
+ end
61
64
  end
62
- end
63
65
 
64
- joins << join_type.new(table, Arel::Nodes::On.new(nodes))
66
+ joins << join_type.new(table, Arel::Nodes::On.new(nodes))
65
67
 
66
- if others && !others.empty?
67
- joins.concat arel.join_sources
68
- append_constraints(joins.last, others)
69
- end
68
+ if others && !others.empty?
69
+ joins.concat arel.join_sources
70
+ append_constraints(connection, joins.last, others)
71
+ end
70
72
 
71
- # The current table in this iteration becomes the foreign table in the next
72
- foreign_table, foreign_klass = table, klass
73
+ # The current table in this iteration becomes the foreign table in the next
74
+ foreign_table, foreign_klass = table, klass
75
+ end
73
76
  end
74
77
 
75
78
  joins
@@ -88,10 +91,10 @@ module ActiveRecord
88
91
  end
89
92
 
90
93
  private
91
- def append_constraints(join, constraints)
94
+ def append_constraints(connection, join, constraints)
92
95
  if join.is_a?(Arel::Nodes::StringJoin)
93
96
  join_string = Arel::Nodes::And.new(constraints.unshift join.left)
94
- join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
97
+ join.left = Arel.sql(connection.visitor.compile(join_string))
95
98
  else
96
99
  right = join.right
97
100
  right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
@@ -3,8 +3,12 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  class JoinDependency # :nodoc:
6
- autoload :JoinBase, "active_record/associations/join_dependency/join_base"
7
- autoload :JoinAssociation, "active_record/associations/join_dependency/join_association"
6
+ extend ActiveSupport::Autoload
7
+
8
+ eager_autoload do
9
+ autoload :JoinBase
10
+ autoload :JoinAssociation
11
+ end
8
12
 
9
13
  class Aliases # :nodoc:
10
14
  def initialize(tables)
@@ -57,7 +61,7 @@ module ActiveRecord
57
61
  when Hash
58
62
  associations.each do |k, v|
59
63
  cache = hash[k] ||= {}
60
- walk_tree v, cache
64
+ walk_tree v, cache if v
61
65
  end
62
66
  else
63
67
  raise ConfigurationError, associations.inspect
@@ -186,12 +190,12 @@ module ActiveRecord
186
190
  def make_constraints(parent, child, join_type)
187
191
  foreign_table = parent.table
188
192
  foreign_klass = parent.base_klass
189
- child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection|
190
- table, terminated = @joined_tables[reflection]
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]
191
195
  root = reflection == child.reflection
192
196
 
193
197
  if table && (!root || !terminated)
194
- @joined_tables[reflection] = [table, root] if root
198
+ @joined_tables[remaining_reflection_chain] = [table, root] if root
195
199
  next table, true
196
200
  end
197
201
 
@@ -202,7 +206,7 @@ module ActiveRecord
202
206
  root ? name : "#{name}_join"
203
207
  end
204
208
 
205
- @joined_tables[reflection] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
209
+ @joined_tables[remaining_reflection_chain] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
206
210
  table
207
211
  end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
208
212
  end
@@ -248,35 +252,39 @@ module ActiveRecord
248
252
  next
249
253
  end
250
254
 
251
- key = aliases.column_alias(node, node.primary_key)
252
- id = row[key]
253
- if id.nil?
255
+ if node.primary_key
256
+ keys = Array(node.primary_key).map { |column| aliases.column_alias(node, column) }
257
+ id = keys.map { |key| row[key] }
258
+ else
259
+ keys = Array(node.reflection.join_primary_key).map { |column| aliases.column_alias(node, column.to_s) }
260
+ id = keys.map { nil } # Avoid id-based model caching.
261
+ end
262
+
263
+ if keys.any? { |key| row[key].nil? }
254
264
  nil_association = ar_parent.association(node.reflection.name)
255
265
  nil_association.loaded!
256
266
  next
257
267
  end
258
268
 
259
- model = seen[ar_parent][node][id]
260
-
261
- if model
262
- construct(model, node, row, seen, model_cache, strict_loading_value)
263
- else
269
+ unless model = seen[ar_parent][node][id]
264
270
  model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
265
-
266
- seen[ar_parent][node][id] = model
267
- construct(model, node, row, seen, model_cache, strict_loading_value)
271
+ seen[ar_parent][node][id] = model if id
268
272
  end
273
+
274
+ construct(model, node, row, seen, model_cache, strict_loading_value)
269
275
  end
270
276
  end
271
277
 
272
278
  def construct_model(record, node, row, model_cache, id, strict_loading_value)
273
279
  other = record.association(node.reflection.name)
274
280
 
275
- model = model_cache[node][id] ||=
276
- node.instantiate(row, aliases.column_aliases(node)) do |m|
281
+ unless model = model_cache[node][id]
282
+ model = node.instantiate(row, aliases.column_aliases(node)) do |m|
277
283
  m.strict_loading! if strict_loading_value
278
284
  other.set_inverse_instance(m)
279
285
  end
286
+ model_cache[node][id] = model if id
287
+ end
280
288
 
281
289
  if node.reflection.collection?
282
290
  other.target.push(model)
@@ -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 association.collection? && 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
@@ -1,19 +1,141 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :enddoc:
4
+
3
5
  module ActiveRecord
4
6
  module Associations
5
7
  class Preloader
6
- class Association #:nodoc:
7
- def initialize(klass, owners, reflection, preload_scope, associate_by_default = true)
8
+ class Association # :nodoc:
9
+ class LoaderQuery
10
+ attr_reader :scope, :association_key_name
11
+
12
+ def initialize(scope, association_key_name)
13
+ @scope = scope
14
+ @association_key_name = association_key_name
15
+ end
16
+
17
+ def eql?(other)
18
+ association_key_name == other.association_key_name &&
19
+ scope.table_name == other.scope.table_name &&
20
+ scope.connection_specification_name == other.scope.connection_specification_name &&
21
+ scope.values_for_queries == other.scope.values_for_queries
22
+ end
23
+
24
+ def hash
25
+ [association_key_name, scope.table_name, scope.connection_specification_name, scope.values_for_queries].hash
26
+ end
27
+
28
+ def records_for(loaders)
29
+ LoaderRecords.new(loaders, self).records
30
+ end
31
+
32
+ def load_records_in_batch(loaders)
33
+ raw_records = records_for(loaders)
34
+
35
+ loaders.each do |loader|
36
+ loader.load_records(raw_records)
37
+ loader.run
38
+ end
39
+ end
40
+
41
+ def load_records_for_keys(keys, &block)
42
+ return [] if keys.empty?
43
+
44
+ if association_key_name.is_a?(Array)
45
+ query_constraints = Hash.new { |hsh, key| hsh[key] = Set.new }
46
+
47
+ keys.each_with_object(query_constraints) do |values_set, constraints|
48
+ association_key_name.zip(values_set).each do |key_name, value|
49
+ constraints[key_name] << value
50
+ end
51
+ end
52
+
53
+ scope.where(query_constraints)
54
+ else
55
+ scope.where(association_key_name => keys)
56
+ end.load(&block)
57
+ end
58
+ end
59
+
60
+ class LoaderRecords
61
+ def initialize(loaders, loader_query)
62
+ @loader_query = loader_query
63
+ @loaders = loaders
64
+ @keys_to_load = Set.new
65
+ @already_loaded_records_by_key = {}
66
+
67
+ populate_keys_to_load_and_already_loaded_records
68
+ end
69
+
70
+ def records
71
+ load_records + already_loaded_records
72
+ end
73
+
74
+ private
75
+ attr_reader :loader_query, :loaders, :keys_to_load, :already_loaded_records_by_key
76
+
77
+ def populate_keys_to_load_and_already_loaded_records
78
+ loaders.each do |loader|
79
+ loader.owners_by_key.each do |key, owners|
80
+ if loaded_owner = owners.find { |owner| loader.loaded?(owner) }
81
+ already_loaded_records_by_key[key] = loader.target_for(loaded_owner)
82
+ else
83
+ keys_to_load << key
84
+ end
85
+ end
86
+ end
87
+
88
+ @keys_to_load.subtract(already_loaded_records_by_key.keys)
89
+ end
90
+
91
+ def load_records
92
+ loader_query.load_records_for_keys(keys_to_load) do |record|
93
+ loaders.each { |l| l.set_inverse(record) }
94
+ end
95
+ end
96
+
97
+ def already_loaded_records
98
+ already_loaded_records_by_key.values.flatten
99
+ end
100
+ end
101
+
102
+ attr_reader :klass
103
+
104
+ def initialize(klass, owners, reflection, preload_scope, reflection_scope, associate_by_default)
8
105
  @klass = klass
9
106
  @owners = owners.uniq(&:__id__)
10
107
  @reflection = reflection
11
108
  @preload_scope = preload_scope
109
+ @reflection_scope = reflection_scope
12
110
  @associate = associate_by_default || !preload_scope || preload_scope.empty_scope?
13
111
  @model = owners.first && owners.first.class
112
+ @run = false
113
+ end
114
+
115
+ def table_name
116
+ @klass.table_name
117
+ end
118
+
119
+ def future_classes
120
+ if run?
121
+ []
122
+ else
123
+ [@klass]
124
+ end
125
+ end
126
+
127
+ def runnable_loaders
128
+ [self]
129
+ end
130
+
131
+ def run?
132
+ @run
14
133
  end
15
134
 
16
135
  def run
136
+ return self if run?
137
+ @run = true
138
+
17
139
  records = records_by_owner
18
140
 
19
141
  owners.each do |owner|
@@ -35,35 +157,85 @@ module ActiveRecord
35
157
  @preloaded_records
36
158
  end
37
159
 
38
- private
39
- attr_reader :owners, :reflection, :preload_scope, :model, :klass
160
+ # The name of the key on the associated records
161
+ def association_key_name
162
+ reflection.join_primary_key(klass)
163
+ end
40
164
 
41
- def load_records
42
- # owners can be duplicated when a relation has a collection association join
43
- # #compare_by_identity makes such owners different hash keys
44
- @records_by_owner = {}.compare_by_identity
45
- raw_records = owner_keys.empty? ? [] : records_for(owner_keys)
165
+ def loader_query
166
+ LoaderQuery.new(scope, association_key_name)
167
+ end
46
168
 
47
- @preloaded_records = raw_records.select do |record|
48
- assignments = false
169
+ def owners_by_key
170
+ @owners_by_key ||= owners.each_with_object({}) do |owner, result|
171
+ key = derive_key(owner, owner_key_name)
172
+ (result[key] ||= []) << owner if key
173
+ end
174
+ end
49
175
 
50
- owners_by_key[convert_key(record[association_key_name])].each do |owner|
51
- entries = (@records_by_owner[owner] ||= [])
176
+ def loaded?(owner)
177
+ owner.association(reflection.name).loaded?
178
+ end
52
179
 
53
- if reflection.collection? || entries.empty?
54
- entries << record
55
- assignments = true
56
- end
57
- end
180
+ def target_for(owner)
181
+ Array.wrap(owner.association(reflection.name).target)
182
+ end
58
183
 
59
- assignments
184
+ def scope
185
+ @scope ||= build_scope
186
+ end
187
+
188
+ def set_inverse(record)
189
+ if owners = owners_by_key[derive_key(record, association_key_name)]
190
+ # Processing only the first owner
191
+ # because the record is modified but not an owner
192
+ association = owners.first.association(reflection.name)
193
+ association.set_inverse_instance(record)
194
+ end
195
+ end
196
+
197
+ def load_records(raw_records = nil)
198
+ # owners can be duplicated when a relation has a collection association join
199
+ # #compare_by_identity makes such owners different hash keys
200
+ @records_by_owner = {}.compare_by_identity
201
+ raw_records ||= loader_query.records_for([self])
202
+ @preloaded_records = raw_records.select do |record|
203
+ assignments = false
204
+
205
+ owners_by_key[derive_key(record, association_key_name)]&.each do |owner|
206
+ entries = (@records_by_owner[owner] ||= [])
207
+
208
+ if reflection.collection? || entries.empty?
209
+ entries << record
210
+ assignments = true
211
+ end
60
212
  end
213
+
214
+ assignments
61
215
  end
216
+ end
217
+
218
+ def associate_records_from_unscoped(unscoped_records)
219
+ return if unscoped_records.nil? || unscoped_records.empty?
220
+ return if !reflection_scope.empty_scope?
221
+ return if preload_scope && !preload_scope.empty_scope?
222
+ return if reflection.collection?
223
+
224
+ unscoped_records.select { |r| r[association_key_name].present? }.each do |record|
225
+ owners = owners_by_key[derive_key(record, association_key_name)]
226
+ owners&.each_with_index do |owner, i|
227
+ association = owner.association(reflection.name)
228
+ association.target = record
62
229
 
63
- # The name of the key on the associated records
64
- def association_key_name
65
- reflection.join_primary_key(klass)
230
+ if i == 0 # Set inverse on first owner
231
+ association.set_inverse_instance(record)
232
+ end
233
+ end
66
234
  end
235
+ end
236
+
237
+ private
238
+ attr_reader :owners, :reflection, :preload_scope, :model
67
239
 
68
240
  # The name of the key on the model which declares the association
69
241
  def owner_key_name
@@ -71,25 +243,18 @@ module ActiveRecord
71
243
  end
72
244
 
73
245
  def associate_records_to_owner(owner, records)
246
+ return if loaded?(owner)
247
+
74
248
  association = owner.association(reflection.name)
249
+
75
250
  if reflection.collection?
76
- association.target = records
251
+ not_persisted_records = association.target.reject(&:persisted?)
252
+ association.target = records + not_persisted_records
77
253
  else
78
254
  association.target = records.first
79
255
  end
80
256
  end
81
257
 
82
- def owner_keys
83
- @owner_keys ||= owners_by_key.keys
84
- end
85
-
86
- def owners_by_key
87
- @owners_by_key ||= owners.each_with_object({}) do |owner, result|
88
- key = convert_key(owner[owner_key_name])
89
- (result[key] ||= []) << owner if key
90
- end
91
- end
92
-
93
258
  def key_conversion_required?
94
259
  unless defined?(@key_conversion_required)
95
260
  @key_conversion_required = (association_key_type != owner_key_type)
@@ -98,6 +263,14 @@ module ActiveRecord
98
263
  @key_conversion_required
99
264
  end
100
265
 
266
+ def derive_key(owner, key)
267
+ if key.is_a?(Array)
268
+ key.map { |k| convert_key(owner._read_attribute(k)) }
269
+ else
270
+ convert_key(owner._read_attribute(key))
271
+ end
272
+ end
273
+
101
274
  def convert_key(key)
102
275
  if key_conversion_required?
103
276
  key.to_s
@@ -114,20 +287,6 @@ module ActiveRecord
114
287
  @model.type_for_attribute(owner_key_name).type
115
288
  end
116
289
 
117
- def records_for(ids)
118
- scope.where(association_key_name => ids).load do |record|
119
- # Processing only the first owner
120
- # because the record is modified but not an owner
121
- owner = owners_by_key[convert_key(record[association_key_name])].first
122
- association = owner.association(reflection.name)
123
- association.set_inverse_instance(record)
124
- end
125
- end
126
-
127
- def scope
128
- @scope ||= build_scope
129
- end
130
-
131
290
  def reflection_scope
132
291
  @reflection_scope ||= reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(klass.unscoped, &:merge!)
133
292
  end
@@ -145,11 +304,11 @@ module ActiveRecord
145
304
  scope.merge!(preload_scope)
146
305
  end
147
306
 
148
- if preload_scope && preload_scope.strict_loading_value
149
- scope.strict_loading
150
- else
151
- scope
152
- end
307
+ cascade_strict_loading(scope)
308
+ end
309
+
310
+ def cascade_strict_loading(scope)
311
+ preload_scope&.strict_loading_value ? scope.strict_loading : scope
153
312
  end
154
313
  end
155
314
  end