activerecord 6.0.0 → 7.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (376) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +996 -594
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +34 -34
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +22 -20
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +41 -30
  9. data/lib/active_record/associations/association.rb +106 -41
  10. data/lib/active_record/associations/association_scope.rb +30 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +69 -14
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +20 -6
  13. data/lib/active_record/associations/builder/association.rb +39 -6
  14. data/lib/active_record/associations/builder/belongs_to.rb +47 -17
  15. data/lib/active_record/associations/builder/collection_association.rb +14 -6
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -10
  17. data/lib/active_record/associations/builder/has_many.rb +7 -3
  18. data/lib/active_record/associations/builder/has_one.rb +13 -16
  19. data/lib/active_record/associations/builder/singular_association.rb +7 -3
  20. data/lib/active_record/associations/collection_association.rb +90 -53
  21. data/lib/active_record/associations/collection_proxy.rb +54 -19
  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 +21 -1
  25. data/lib/active_record/associations/has_many_association.rb +41 -10
  26. data/lib/active_record/associations/has_many_through_association.rb +29 -12
  27. data/lib/active_record/associations/has_one_association.rb +33 -9
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +41 -17
  30. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  31. data/lib/active_record/associations/join_dependency.rb +97 -54
  32. data/lib/active_record/associations/nested_error.rb +47 -0
  33. data/lib/active_record/associations/preloader/association.rb +237 -54
  34. data/lib/active_record/associations/preloader/batch.rb +48 -0
  35. data/lib/active_record/associations/preloader/branch.rb +153 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +51 -17
  37. data/lib/active_record/associations/preloader.rb +55 -121
  38. data/lib/active_record/associations/singular_association.rb +16 -4
  39. data/lib/active_record/associations/through_association.rb +26 -15
  40. data/lib/active_record/associations.rb +454 -440
  41. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  42. data/lib/active_record/attribute_assignment.rb +11 -14
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +36 -11
  44. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  45. data/lib/active_record/attribute_methods/dirty.rb +75 -34
  46. data/lib/active_record/attribute_methods/primary_key.rb +53 -31
  47. data/lib/active_record/attribute_methods/query.rb +31 -22
  48. data/lib/active_record/attribute_methods/read.rb +16 -17
  49. data/lib/active_record/attribute_methods/serialization.rb +177 -35
  50. data/lib/active_record/attribute_methods/time_zone_conversion.rb +18 -15
  51. data/lib/active_record/attribute_methods/write.rb +16 -28
  52. data/lib/active_record/attribute_methods.rb +227 -100
  53. data/lib/active_record/attributes.rb +94 -56
  54. data/lib/active_record/autosave_association.rb +119 -73
  55. data/lib/active_record/base.rb +31 -21
  56. data/lib/active_record/callbacks.rb +168 -55
  57. data/lib/active_record/coders/column_serializer.rb +61 -0
  58. data/lib/active_record/coders/json.rb +1 -1
  59. data/lib/active_record/coders/yaml_column.rb +70 -25
  60. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +367 -565
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -57
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +277 -89
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +241 -69
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +122 -134
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +324 -72
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +17 -4
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +611 -211
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +425 -82
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +698 -211
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +464 -239
  76. data/lib/active_record/connection_adapters/column.rb +28 -1
  77. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  78. data/lib/active_record/connection_adapters/mysql/column.rb +2 -1
  79. data/lib/active_record/connection_adapters/mysql/database_statements.rb +32 -137
  80. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  81. data/lib/active_record/connection_adapters/mysql/quoting.rb +90 -43
  82. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +41 -7
  83. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +18 -1
  84. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +13 -4
  85. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +53 -15
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  87. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  88. data/lib/active_record/connection_adapters/mysql2_adapter.rb +127 -63
  89. data/lib/active_record/connection_adapters/pool_config.rb +83 -0
  90. data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
  91. data/lib/active_record/connection_adapters/postgresql/column.rb +54 -2
  92. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +127 -100
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +9 -5
  95. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  97. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -15
  99. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  101. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -4
  103. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +35 -8
  106. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  110. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -4
  111. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  112. data/lib/active_record/connection_adapters/postgresql/quoting.rb +139 -106
  113. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -2
  114. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +98 -4
  115. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +176 -4
  116. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -1
  117. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -118
  118. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  119. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -11
  120. data/lib/active_record/connection_adapters/postgresql_adapter.rb +585 -295
  121. data/lib/active_record/connection_adapters/schema_cache.rb +399 -60
  122. data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
  123. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  124. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +99 -48
  125. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +80 -54
  126. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +27 -1
  127. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  128. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  129. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +102 -24
  130. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +425 -174
  131. data/lib/active_record/connection_adapters/statement_pool.rb +7 -1
  132. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  133. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  134. data/lib/active_record/connection_adapters.rb +176 -0
  135. data/lib/active_record/connection_handling.rb +243 -115
  136. data/lib/active_record/core.rb +481 -199
  137. data/lib/active_record/counter_cache.rb +69 -32
  138. data/lib/active_record/database_configurations/connection_url_resolver.rb +107 -0
  139. data/lib/active_record/database_configurations/database_config.rb +77 -10
  140. data/lib/active_record/database_configurations/hash_config.rb +148 -26
  141. data/lib/active_record/database_configurations/url_config.rb +44 -45
  142. data/lib/active_record/database_configurations.rb +190 -114
  143. data/lib/active_record/delegated_type.rb +279 -0
  144. data/lib/active_record/deprecator.rb +7 -0
  145. data/lib/active_record/destroy_association_async_job.rb +38 -0
  146. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  147. data/lib/active_record/dynamic_matchers.rb +5 -6
  148. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  149. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  150. data/lib/active_record/encryption/cipher.rb +53 -0
  151. data/lib/active_record/encryption/config.rb +68 -0
  152. data/lib/active_record/encryption/configurable.rb +60 -0
  153. data/lib/active_record/encryption/context.rb +42 -0
  154. data/lib/active_record/encryption/contexts.rb +76 -0
  155. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  156. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  157. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  158. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  159. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  160. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  161. data/lib/active_record/encryption/encryptor.rb +171 -0
  162. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  163. data/lib/active_record/encryption/errors.rb +15 -0
  164. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  165. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  166. data/lib/active_record/encryption/key.rb +28 -0
  167. data/lib/active_record/encryption/key_generator.rb +53 -0
  168. data/lib/active_record/encryption/key_provider.rb +46 -0
  169. data/lib/active_record/encryption/message.rb +33 -0
  170. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  171. data/lib/active_record/encryption/message_serializer.rb +96 -0
  172. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  173. data/lib/active_record/encryption/properties.rb +76 -0
  174. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  175. data/lib/active_record/encryption/scheme.rb +100 -0
  176. data/lib/active_record/encryption.rb +58 -0
  177. data/lib/active_record/enum.rb +224 -73
  178. data/lib/active_record/errors.rb +254 -36
  179. data/lib/active_record/explain.rb +30 -17
  180. data/lib/active_record/explain_registry.rb +11 -6
  181. data/lib/active_record/explain_subscriber.rb +2 -2
  182. data/lib/active_record/fixture_set/file.rb +22 -15
  183. data/lib/active_record/fixture_set/model_metadata.rb +15 -6
  184. data/lib/active_record/fixture_set/render_context.rb +3 -1
  185. data/lib/active_record/fixture_set/table_row.rb +88 -16
  186. data/lib/active_record/fixture_set/table_rows.rb +4 -5
  187. data/lib/active_record/fixtures.rb +229 -116
  188. data/lib/active_record/future_result.rb +178 -0
  189. data/lib/active_record/gem_version.rb +4 -4
  190. data/lib/active_record/inheritance.rb +121 -48
  191. data/lib/active_record/insert_all.rb +178 -29
  192. data/lib/active_record/integration.rb +16 -14
  193. data/lib/active_record/internal_metadata.rb +132 -21
  194. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  195. data/lib/active_record/locking/optimistic.rb +64 -33
  196. data/lib/active_record/locking/pessimistic.rb +21 -8
  197. data/lib/active_record/log_subscriber.rb +61 -30
  198. data/lib/active_record/marshalling.rb +59 -0
  199. data/lib/active_record/message_pack.rb +124 -0
  200. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  201. data/lib/active_record/middleware/database_selector/resolver.rb +19 -19
  202. data/lib/active_record/middleware/database_selector.rb +25 -13
  203. data/lib/active_record/middleware/shard_selector.rb +62 -0
  204. data/lib/active_record/migration/command_recorder.rb +160 -55
  205. data/lib/active_record/migration/compatibility.rb +286 -43
  206. data/lib/active_record/migration/default_strategy.rb +22 -0
  207. data/lib/active_record/migration/execution_strategy.rb +19 -0
  208. data/lib/active_record/migration/join_table.rb +1 -2
  209. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  210. data/lib/active_record/migration.rb +421 -193
  211. data/lib/active_record/model_schema.rb +217 -125
  212. data/lib/active_record/nested_attributes.rb +62 -27
  213. data/lib/active_record/no_touching.rb +4 -4
  214. data/lib/active_record/normalization.rb +163 -0
  215. data/lib/active_record/persistence.rb +322 -319
  216. data/lib/active_record/promise.rb +84 -0
  217. data/lib/active_record/query_cache.rb +18 -15
  218. data/lib/active_record/query_logs.rb +193 -0
  219. data/lib/active_record/query_logs_formatter.rb +41 -0
  220. data/lib/active_record/querying.rb +54 -14
  221. data/lib/active_record/railtie.rb +250 -72
  222. data/lib/active_record/railties/console_sandbox.rb +2 -4
  223. data/lib/active_record/railties/controller_runtime.rb +25 -11
  224. data/lib/active_record/railties/databases.rake +312 -197
  225. data/lib/active_record/railties/job_runtime.rb +23 -0
  226. data/lib/active_record/readonly_attributes.rb +45 -3
  227. data/lib/active_record/reflection.rb +389 -146
  228. data/lib/active_record/relation/batches/batch_enumerator.rb +61 -16
  229. data/lib/active_record/relation/batches.rb +214 -73
  230. data/lib/active_record/relation/calculations.rb +379 -124
  231. data/lib/active_record/relation/delegation.rb +36 -23
  232. data/lib/active_record/relation/finder_methods.rb +159 -49
  233. data/lib/active_record/relation/from_clause.rb +5 -1
  234. data/lib/active_record/relation/merger.rb +41 -33
  235. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -11
  236. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -7
  237. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +20 -13
  238. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  239. data/lib/active_record/relation/predicate_builder.rb +79 -53
  240. data/lib/active_record/relation/query_attribute.rb +30 -12
  241. data/lib/active_record/relation/query_methods.rb +1156 -279
  242. data/lib/active_record/relation/record_fetch_warning.rb +12 -11
  243. data/lib/active_record/relation/spawn_methods.rb +10 -9
  244. data/lib/active_record/relation/where_clause.rb +100 -66
  245. data/lib/active_record/relation.rb +829 -194
  246. data/lib/active_record/result.rb +76 -56
  247. data/lib/active_record/runtime_registry.rb +71 -13
  248. data/lib/active_record/sanitization.rb +86 -47
  249. data/lib/active_record/schema.rb +39 -23
  250. data/lib/active_record/schema_dumper.rb +140 -33
  251. data/lib/active_record/schema_migration.rb +74 -29
  252. data/lib/active_record/scoping/default.rb +73 -19
  253. data/lib/active_record/scoping/named.rb +10 -28
  254. data/lib/active_record/scoping.rb +65 -35
  255. data/lib/active_record/secure_password.rb +60 -0
  256. data/lib/active_record/secure_token.rb +34 -8
  257. data/lib/active_record/serialization.rb +11 -4
  258. data/lib/active_record/signed_id.rb +138 -0
  259. data/lib/active_record/statement_cache.rb +26 -10
  260. data/lib/active_record/store.rb +19 -14
  261. data/lib/active_record/suppressor.rb +15 -17
  262. data/lib/active_record/table_metadata.rb +46 -36
  263. data/lib/active_record/tasks/database_tasks.rb +371 -205
  264. data/lib/active_record/tasks/mysql_database_tasks.rb +43 -36
  265. data/lib/active_record/tasks/postgresql_database_tasks.rb +54 -41
  266. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -13
  267. data/lib/active_record/test_databases.rb +5 -4
  268. data/lib/active_record/test_fixtures.rb +189 -104
  269. data/lib/active_record/testing/query_assertions.rb +121 -0
  270. data/lib/active_record/timestamp.rb +35 -25
  271. data/lib/active_record/token_for.rb +123 -0
  272. data/lib/active_record/touch_later.rb +31 -27
  273. data/lib/active_record/transaction.rb +132 -0
  274. data/lib/active_record/transactions.rb +131 -99
  275. data/lib/active_record/translation.rb +3 -5
  276. data/lib/active_record/type/adapter_specific_registry.rb +33 -18
  277. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  278. data/lib/active_record/type/internal/timezone.rb +7 -2
  279. data/lib/active_record/type/serialized.rb +11 -6
  280. data/lib/active_record/type/time.rb +14 -0
  281. data/lib/active_record/type/type_map.rb +17 -21
  282. data/lib/active_record/type/unsigned_integer.rb +0 -1
  283. data/lib/active_record/type.rb +7 -2
  284. data/lib/active_record/type_caster/connection.rb +4 -5
  285. data/lib/active_record/type_caster/map.rb +8 -5
  286. data/lib/active_record/validations/absence.rb +1 -1
  287. data/lib/active_record/validations/associated.rb +13 -8
  288. data/lib/active_record/validations/numericality.rb +36 -0
  289. data/lib/active_record/validations/presence.rb +5 -28
  290. data/lib/active_record/validations/uniqueness.rb +88 -18
  291. data/lib/active_record/validations.rb +15 -8
  292. data/lib/active_record/version.rb +1 -1
  293. data/lib/active_record.rb +446 -40
  294. data/lib/arel/alias_predication.rb +1 -1
  295. data/lib/arel/attributes/attribute.rb +4 -8
  296. data/lib/arel/collectors/bind.rb +8 -1
  297. data/lib/arel/collectors/composite.rb +15 -0
  298. data/lib/arel/collectors/sql_string.rb +7 -0
  299. data/lib/arel/collectors/substitute_binds.rb +7 -0
  300. data/lib/arel/crud.rb +30 -22
  301. data/lib/arel/delete_manager.rb +23 -4
  302. data/lib/arel/errors.rb +10 -0
  303. data/lib/arel/factory_methods.rb +4 -0
  304. data/lib/arel/filter_predications.rb +9 -0
  305. data/lib/arel/insert_manager.rb +2 -3
  306. data/lib/arel/nodes/binary.rb +82 -9
  307. data/lib/arel/nodes/bind_param.rb +8 -0
  308. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  309. data/lib/arel/nodes/casted.rb +22 -10
  310. data/lib/arel/nodes/cte.rb +36 -0
  311. data/lib/arel/nodes/delete_statement.rb +14 -13
  312. data/lib/arel/nodes/equality.rb +6 -9
  313. data/lib/arel/nodes/filter.rb +10 -0
  314. data/lib/arel/nodes/fragments.rb +35 -0
  315. data/lib/arel/nodes/function.rb +1 -0
  316. data/lib/arel/nodes/grouping.rb +3 -0
  317. data/lib/arel/nodes/homogeneous_in.rb +68 -0
  318. data/lib/arel/nodes/in.rb +8 -1
  319. data/lib/arel/nodes/infix_operation.rb +13 -1
  320. data/lib/arel/nodes/insert_statement.rb +2 -2
  321. data/lib/arel/nodes/join_source.rb +1 -1
  322. data/lib/arel/nodes/leading_join.rb +8 -0
  323. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  324. data/lib/arel/nodes/node.rb +122 -11
  325. data/lib/arel/nodes/ordering.rb +27 -0
  326. data/lib/arel/nodes/select_core.rb +2 -2
  327. data/lib/arel/nodes/select_statement.rb +2 -2
  328. data/lib/arel/nodes/sql_literal.rb +16 -0
  329. data/lib/arel/nodes/table_alias.rb +11 -3
  330. data/lib/arel/nodes/unary.rb +0 -1
  331. data/lib/arel/nodes/update_statement.rb +11 -4
  332. data/lib/arel/nodes.rb +10 -3
  333. data/lib/arel/predications.rb +31 -28
  334. data/lib/arel/select_manager.rb +18 -9
  335. data/lib/arel/table.rb +21 -10
  336. data/lib/arel/tree_manager.rb +8 -15
  337. data/lib/arel/update_manager.rb +25 -5
  338. data/lib/arel/visitors/dot.rb +94 -90
  339. data/lib/arel/visitors/mysql.rb +34 -6
  340. data/lib/arel/visitors/postgresql.rb +5 -16
  341. data/lib/arel/visitors/sqlite.rb +25 -1
  342. data/lib/arel/visitors/to_sql.rb +227 -81
  343. data/lib/arel/visitors/visitor.rb +2 -3
  344. data/lib/arel/visitors.rb +0 -7
  345. data/lib/arel.rb +37 -15
  346. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  347. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  348. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  349. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  350. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +6 -1
  351. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  352. data/lib/rails/generators/active_record/migration.rb +9 -3
  353. data/lib/rails/generators/active_record/model/USAGE +113 -0
  354. data/lib/rails/generators/active_record/model/model_generator.rb +49 -4
  355. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  356. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  357. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  358. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  359. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  360. metadata +117 -30
  361. data/lib/active_record/attribute_decorators.rb +0 -90
  362. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  363. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  364. data/lib/active_record/define_callbacks.rb +0 -22
  365. data/lib/active_record/null_relation.rb +0 -68
  366. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  367. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  368. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  369. data/lib/arel/attributes.rb +0 -22
  370. data/lib/arel/visitors/depth_first.rb +0 -204
  371. data/lib/arel/visitors/ibm_db.rb +0 -34
  372. data/lib/arel/visitors/informix.rb +0 -62
  373. data/lib/arel/visitors/mssql.rb +0 -157
  374. data/lib/arel/visitors/oracle.rb +0 -159
  375. data/lib/arel/visitors/oracle12.rb +0 -66
  376. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -3,12 +3,12 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  # = Active Record Has Many Through Association
6
- class HasManyThroughAssociation < HasManyAssociation #:nodoc:
6
+ class HasManyThroughAssociation < HasManyAssociation # :nodoc:
7
7
  include ThroughAssociation
8
8
 
9
9
  def initialize(owner, reflection)
10
10
  super
11
- @through_records = {}
11
+ @through_records = {}.compare_by_identity
12
12
  end
13
13
 
14
14
  def concat(*records)
@@ -54,21 +54,28 @@ module ActiveRecord
54
54
  # However, after insert_record has been called, the cache is cleared in
55
55
  # order to allow multiple instances of the same record in an association.
56
56
  def build_through_record(record)
57
- @through_records[record.object_id] ||= begin
57
+ @through_records[record] ||= begin
58
58
  ensure_mutable
59
59
 
60
60
  attributes = through_scope_attributes
61
61
  attributes[source_reflection.name] = record
62
- attributes[source_reflection.foreign_type] = options[:source_type] if options[:source_type]
63
62
 
64
- through_association.build(attributes)
63
+ through_association.build(attributes).tap do |new_record|
64
+ new_record.send("#{source_reflection.foreign_type}=", options[:source_type]) if options[:source_type]
65
+ end
65
66
  end
66
67
  end
67
68
 
69
+ attr_reader :through_scope
70
+
68
71
  def through_scope_attributes
69
- scope.where_values_hash(through_association.reflection.name.to_s).
70
- except!(through_association.reflection.foreign_key,
71
- through_association.reflection.klass.inheritance_column)
72
+ scope = through_scope || self.scope
73
+ attributes = scope.where_values_hash(through_association.reflection.klass.table_name)
74
+ except_keys = [
75
+ *Array(through_association.reflection.foreign_key),
76
+ through_association.reflection.klass.inheritance_column
77
+ ]
78
+ attributes.except!(*except_keys)
72
79
  end
73
80
 
74
81
  def save_through_record(record)
@@ -77,15 +84,22 @@ module ActiveRecord
77
84
  association.save!
78
85
  end
79
86
  ensure
80
- @through_records.delete(record.object_id)
87
+ @through_records.delete(record)
81
88
  end
82
89
 
83
90
  def build_record(attributes)
84
91
  ensure_not_nested
85
92
 
93
+ @through_scope = scope
86
94
  record = super
87
95
 
88
- inverse = source_reflection.inverse_of
96
+ inverse =
97
+ if source_reflection.polymorphic?
98
+ source_reflection.polymorphic_inverse_of(record.class)
99
+ else
100
+ source_reflection.inverse_of
101
+ end
102
+
89
103
  if inverse
90
104
  if inverse.collection?
91
105
  record.send(inverse.name) << build_through_record(record)
@@ -95,6 +109,8 @@ module ActiveRecord
95
109
  end
96
110
 
97
111
  record
112
+ ensure
113
+ @through_scope = nil
98
114
  end
99
115
 
100
116
  def remove_records(existing_records, records, method)
@@ -103,7 +119,7 @@ module ActiveRecord
103
119
  end
104
120
 
105
121
  def target_reflection_has_associated_record?
106
- !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
122
+ !(through_reflection.belongs_to? && Array(through_reflection.foreign_key).all? { |foreign_key_column| owner[foreign_key_column].blank? })
107
123
  end
108
124
 
109
125
  def update_through_counter?(method)
@@ -202,12 +218,13 @@ module ActiveRecord
202
218
  end
203
219
  end
204
220
 
205
- @through_records.delete(record.object_id)
221
+ @through_records.delete(record)
206
222
  end
207
223
  end
208
224
 
209
225
  def find_target
210
226
  return [] unless target_reflection_has_associated_record?
227
+ return scope.to_a if disable_joins
211
228
  super
212
229
  end
213
230
 
@@ -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
@@ -32,6 +32,23 @@ module ActiveRecord
32
32
  target.destroyed_by_association = reflection
33
33
  target.destroy
34
34
  throw(:abort) unless target.destroyed?
35
+ when :destroy_async
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
43
+
44
+ enqueue_destroy_association(
45
+ owner_model_name: owner.class.to_s,
46
+ owner_id: owner.id,
47
+ association_class: reflection.klass.to_s,
48
+ association_ids: [id],
49
+ association_primary_key_column: primary_key_column,
50
+ ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
51
+ )
35
52
  when :nullify
36
53
  target.update_columns(nullified_owner_attributes) if target.persisted?
37
54
  end
@@ -58,7 +75,7 @@ module ActiveRecord
58
75
  if save && !record.save
59
76
  nullify_owner_attributes(record)
60
77
  set_owner_attributes(target) if target
61
- raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
78
+ raise RecordNotSaved.new("Failed to save the new associated #{reflection.name}.", record)
62
79
  end
63
80
  end
64
81
  end
@@ -81,26 +98,33 @@ module ActiveRecord
81
98
  target.delete
82
99
  when :destroy
83
100
  target.destroyed_by_association = reflection
84
- target.destroy
101
+ if target.persisted?
102
+ target.destroy
103
+ end
85
104
  else
86
105
  nullify_owner_attributes(target)
87
106
  remove_inverse_instance(target)
88
107
 
89
108
  if target.persisted? && owner.persisted? && !target.save
90
109
  set_owner_attributes(target)
91
- raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " \
92
- "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
+ )
93
115
  end
94
116
  end
95
117
  end
96
118
 
97
119
  def nullify_owner_attributes(record)
98
- 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
99
123
  end
100
124
 
101
- def transaction_if(value)
125
+ def transaction_if(value, &block)
102
126
  if value
103
- reflection.klass.transaction { yield }
127
+ reflection.klass.transaction(&block)
104
128
  else
105
129
  yield
106
130
  end
@@ -108,7 +132,7 @@ module ActiveRecord
108
132
 
109
133
  def _create_record(attributes, raise_error = false, &block)
110
134
  unless owner.persisted?
111
- 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)
112
136
  end
113
137
 
114
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
@@ -14,7 +14,6 @@ module ActiveRecord
14
14
  super(reflection.klass, children)
15
15
 
16
16
  @reflection = reflection
17
- @tables = nil
18
17
  end
19
18
 
20
19
  def match?(other)
@@ -24,25 +23,48 @@ module ActiveRecord
24
23
 
25
24
  def join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
26
25
  joins = []
26
+ chain = []
27
+
28
+ reflection_chain = reflection.chain
29
+ reflection_chain.each_with_index do |reflection, index|
30
+ table, terminated = yield reflection, reflection_chain[index..]
31
+ @table ||= table
32
+
33
+ if terminated
34
+ foreign_table, foreign_klass = table, reflection.klass
35
+ break
36
+ end
37
+
38
+ chain << [reflection, table]
39
+ end
27
40
 
28
41
  # The chain starts with the target table, but we want to end with it here (makes
29
42
  # more sense in this context), so we reverse
30
- reflection.chain.reverse_each.with_index(1) do |reflection, i|
31
- table = tables[-i]
43
+ chain.reverse_each do |reflection, table|
32
44
  klass = reflection.klass
33
45
 
34
- join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
46
+ scope = reflection.join_scope(table, foreign_table, foreign_klass)
47
+
48
+ unless scope.references_values.empty?
49
+ associations = scope.eager_load_values | scope.includes_values
35
50
 
36
- arel = join_scope.arel(alias_tracker.aliases)
51
+ unless associations.empty?
52
+ scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
53
+ end
54
+ end
55
+
56
+ arel = scope.arel(alias_tracker.aliases)
37
57
  nodes = arel.constraints.first
38
58
 
39
- others = nodes.children.extract! do |node|
40
- 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
41
63
  end
42
64
 
43
- joins << table.create_join(table, table.create_on(nodes), join_type)
65
+ joins << join_type.new(table, Arel::Nodes::On.new(nodes))
44
66
 
45
- unless others.empty?
67
+ if others && !others.empty?
46
68
  joins.concat arel.join_sources
47
69
  append_constraints(joins.last, others)
48
70
  end
@@ -54,24 +76,26 @@ module ActiveRecord
54
76
  joins
55
77
  end
56
78
 
57
- def tables=(tables)
58
- @tables = tables
59
- @table = tables.first
60
- end
61
-
62
79
  def readonly?
63
80
  return @readonly if defined?(@readonly)
64
81
 
65
82
  @readonly = reflection.scope && reflection.scope_for(base_klass.unscoped).readonly_value
66
83
  end
67
84
 
85
+ def strict_loading?
86
+ return @strict_loading if defined?(@strict_loading)
87
+
88
+ @strict_loading = reflection.scope && reflection.scope_for(base_klass.unscoped).strict_loading_value
89
+ end
90
+
68
91
  private
69
92
  def append_constraints(join, constraints)
70
93
  if join.is_a?(Arel::Nodes::StringJoin)
71
- join_string = table.create_and(constraints.unshift(join.left))
72
- join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
94
+ join_string = Arel::Nodes::And.new(constraints.unshift join.left)
95
+ join.left = join_string
73
96
  else
74
- join.right.expr.children.concat(constraints)
97
+ right = join.right
98
+ right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
75
99
  end
76
100
  end
77
101
  end
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  # association.
18
18
  attr_reader :base_klass, :children
19
19
 
20
- delegate :table_name, :column_names, :primary_key, to: :base_klass
20
+ delegate :table_name, :column_names, :primary_key, :attribute_types, to: :base_klass
21
21
 
22
22
  def initialize(base_klass, children)
23
23
  @base_klass = base_klass
@@ -62,8 +62,8 @@ module ActiveRecord
62
62
  hash
63
63
  end
64
64
 
65
- def instantiate(row, aliases, &block)
66
- base_klass.instantiate(extract_record(row, aliases), &block)
65
+ def instantiate(row, aliases, column_types = {}, &block)
66
+ base_klass.instantiate(extract_record(row, aliases), column_types, &block)
67
67
  end
68
68
  end
69
69
  end
@@ -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)
@@ -34,7 +38,7 @@ module ActiveRecord
34
38
  Table = Struct.new(:node, :columns) do # :nodoc:
35
39
  def column_aliases
36
40
  t = node.table
37
- columns.map { |column| t[column.name].as Arel.sql column.alias }
41
+ columns.map { |column| t[column.name].as(column.alias) }
38
42
  end
39
43
  end
40
44
  Column = Struct.new(:name, :alias)
@@ -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
@@ -70,18 +74,26 @@ module ActiveRecord
70
74
  @join_type = join_type
71
75
  end
72
76
 
77
+ def base_klass
78
+ join_root.base_klass
79
+ end
80
+
73
81
  def reflections
74
82
  join_root.drop(1).map!(&:reflection)
75
83
  end
76
84
 
77
- def join_constraints(joins_to_add, alias_tracker)
85
+ def join_constraints(joins_to_add, alias_tracker, references)
78
86
  @alias_tracker = alias_tracker
87
+ @joined_tables = {}
88
+ @references = {}
89
+
90
+ references.each do |table_name|
91
+ @references[table_name.to_sym] = table_name if table_name.is_a?(Arel::Nodes::SqlLiteral)
92
+ end unless references.empty?
79
93
 
80
- construct_tables!(join_root)
81
94
  joins = make_join_constraints(join_root, join_type)
82
95
 
83
96
  joins.concat joins_to_add.flat_map { |oj|
84
- construct_tables!(oj.join_root)
85
97
  if join_root.match? oj.join_root
86
98
  walk(join_root, oj.join_root, oj.join_type)
87
99
  else
@@ -90,18 +102,35 @@ module ActiveRecord
90
102
  }
91
103
  end
92
104
 
93
- def instantiate(result_set, &block)
105
+ def instantiate(result_set, strict_loading_value, &block)
94
106
  primary_key = aliases.column_alias(join_root, join_root.primary_key)
95
107
 
96
- seen = Hash.new { |i, object_id|
97
- i[object_id] = Hash.new { |j, child_class|
108
+ seen = Hash.new { |i, parent|
109
+ i[parent] = Hash.new { |j, child_class|
98
110
  j[child_class] = {}
99
111
  }
100
- }
112
+ }.compare_by_identity
101
113
 
102
114
  model_cache = Hash.new { |h, klass| h[klass] = {} }
103
115
  parents = model_cache[join_root]
104
- column_aliases = aliases.column_aliases join_root
116
+
117
+ column_aliases = aliases.column_aliases(join_root)
118
+ column_names = []
119
+
120
+ result_set.columns.each do |name|
121
+ column_names << name unless /\At\d+_r\d+\z/.match?(name)
122
+ end
123
+
124
+ if column_names.empty?
125
+ column_types = {}
126
+ else
127
+ column_types = result_set.column_types
128
+ unless column_types.empty?
129
+ attribute_types = join_root.attribute_types
130
+ column_types = column_types.slice(*column_names).delete_if { |k, _| attribute_types.key?(k) }
131
+ end
132
+ column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) }
133
+ end
105
134
 
106
135
  message_bus = ActiveSupport::Notifications.instrumenter
107
136
 
@@ -113,8 +142,8 @@ module ActiveRecord
113
142
  message_bus.instrument("instantiation.active_record", payload) do
114
143
  result_set.each { |row_hash|
115
144
  parent_key = primary_key ? row_hash[primary_key] : row_hash
116
- parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, &block)
117
- construct(parent, join_root, row_hash, seen, model_cache)
145
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
146
+ construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
118
147
  }
119
148
  end
120
149
 
@@ -122,30 +151,36 @@ module ActiveRecord
122
151
  end
123
152
 
124
153
  def apply_column_aliases(relation)
154
+ @join_root_alias = relation.select_values.empty?
125
155
  relation._select!(-> { aliases.columns })
126
156
  end
127
157
 
158
+ def each(&block)
159
+ join_root.each(&block)
160
+ end
161
+
128
162
  protected
129
163
  attr_reader :join_root, :join_type
130
164
 
131
165
  private
132
- attr_reader :alias_tracker
166
+ attr_reader :alias_tracker, :join_root_alias
133
167
 
134
168
  def aliases
135
169
  @aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
136
- columns = join_part.column_names.each_with_index.map { |column_name, j|
170
+ column_names = if join_part == join_root && !join_root_alias
171
+ primary_key = join_root.primary_key
172
+ primary_key ? [primary_key] : []
173
+ else
174
+ join_part.column_names
175
+ end
176
+
177
+ columns = column_names.each_with_index.map { |column_name, j|
137
178
  Aliases::Column.new column_name, "t#{i}_r#{j}"
138
179
  }
139
180
  Aliases::Table.new(join_part, columns)
140
181
  }
141
182
  end
142
183
 
143
- def construct_tables!(join_root)
144
- join_root.each_children do |parent, child|
145
- child.tables = table_aliases_for(parent, child)
146
- end
147
- end
148
-
149
184
  def make_join_constraints(join_root, join_type)
150
185
  join_root.children.flat_map do |child|
151
186
  make_constraints(join_root, child, join_type)
@@ -155,23 +190,25 @@ module ActiveRecord
155
190
  def make_constraints(parent, child, join_type)
156
191
  foreign_table = parent.table
157
192
  foreign_klass = parent.base_klass
158
- joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
159
- joins.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
160
- end
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]
195
+ root = reflection == child.reflection
161
196
 
162
- def table_aliases_for(parent, node)
163
- node.reflection.chain.map { |reflection|
164
- alias_tracker.aliased_table_for(
165
- reflection.table_name,
166
- table_alias_for(reflection, parent, reflection != node.reflection),
167
- reflection.klass.type_caster
168
- )
169
- }
170
- end
197
+ if table && (!root || !terminated)
198
+ @joined_tables[remaining_reflection_chain] = [table, root] if root
199
+ next table, true
200
+ end
201
+
202
+ table_name = @references[reflection.name.to_sym]&.to_s
203
+
204
+ table = alias_tracker.aliased_table_for(reflection.klass.arel_table, table_name) do
205
+ name = reflection.alias_candidate(parent.table_name)
206
+ root ? name : "#{name}_join"
207
+ end
171
208
 
172
- def table_alias_for(reflection, parent, join)
173
- name = reflection.alias_candidate(parent.table_name)
174
- join ? "#{name}_join" : name
209
+ @joined_tables[remaining_reflection_chain] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
210
+ table
211
+ end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
175
212
  end
176
213
 
177
214
  def walk(left, right, join_type)
@@ -202,7 +239,7 @@ module ActiveRecord
202
239
  end
203
240
  end
204
241
 
205
- def construct(ar_parent, parent, row, seen, model_cache)
242
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
206
243
  return if ar_parent.nil?
207
244
 
208
245
  parent.children.each do |node|
@@ -211,38 +248,43 @@ module ActiveRecord
211
248
  other.loaded!
212
249
  elsif ar_parent.association_cached?(node.reflection.name)
213
250
  model = ar_parent.association(node.reflection.name).target
214
- construct(model, node, row, seen, model_cache)
251
+ construct(model, node, row, seen, model_cache, strict_loading_value)
215
252
  next
216
253
  end
217
254
 
218
- key = aliases.column_alias(node, node.primary_key)
219
- id = row[key]
220
- 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? }
221
264
  nil_association = ar_parent.association(node.reflection.name)
222
265
  nil_association.loaded!
223
266
  next
224
267
  end
225
268
 
226
- model = seen[ar_parent.object_id][node][id]
227
-
228
- if model
229
- construct(model, node, row, seen, model_cache)
230
- else
231
- model = construct_model(ar_parent, node, row, model_cache, id)
232
-
233
- seen[ar_parent.object_id][node][id] = model
234
- construct(model, node, row, seen, model_cache)
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
235
272
  end
273
+
274
+ construct(model, node, row, seen, model_cache, strict_loading_value)
236
275
  end
237
276
  end
238
277
 
239
- def construct_model(record, node, row, model_cache, id)
278
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
240
279
  other = record.association(node.reflection.name)
241
280
 
242
- model = model_cache[node][id] ||=
243
- 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|
283
+ m.strict_loading! if strict_loading_value
244
284
  other.set_inverse_instance(m)
245
285
  end
286
+ model_cache[node][id] = model if id
287
+ end
246
288
 
247
289
  if node.reflection.collection?
248
290
  other.target.push(model)
@@ -251,6 +293,7 @@ module ActiveRecord
251
293
  end
252
294
 
253
295
  model.readonly! if node.readonly?
296
+ model.strict_loading! if node.strict_loading?
254
297
  model
255
298
  end
256
299
  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 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