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
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/array/wrap"
4
-
5
3
  module ActiveRecord
6
4
  module Associations
7
5
  # = Active Record Associations
@@ -21,7 +19,7 @@ module ActiveRecord
21
19
  # Associations in Active Record are middlemen between the object that
22
20
  # holds the association, known as the <tt>owner</tt>, and the associated
23
21
  # result set, known as the <tt>target</tt>. Association metadata is available in
24
- # <tt>reflection</tt>, which is an instance of <tt>ActiveRecord::Reflection::AssociationReflection</tt>.
22
+ # <tt>reflection</tt>, which is an instance of +ActiveRecord::Reflection::AssociationReflection+.
25
23
  #
26
24
  # For example, given
27
25
  #
@@ -34,8 +32,9 @@ module ActiveRecord
34
32
  # The association of <tt>blog.posts</tt> has the object +blog+ as its
35
33
  # <tt>owner</tt>, the collection of its posts as <tt>target</tt>, and
36
34
  # the <tt>reflection</tt> object represents a <tt>:has_many</tt> macro.
37
- class Association #:nodoc:
38
- attr_reader :owner, :target, :reflection
35
+ class Association # :nodoc:
36
+ attr_accessor :owner
37
+ attr_reader :target, :reflection, :disable_joins
39
38
 
40
39
  delegate :options, to: :reflection
41
40
 
@@ -43,23 +42,28 @@ module ActiveRecord
43
42
  reflection.check_validity!
44
43
 
45
44
  @owner, @reflection = owner, reflection
45
+ @disable_joins = @reflection.options[:disable_joins] || false
46
46
 
47
47
  reset
48
48
  reset_scope
49
+
50
+ @skip_strict_loading = nil
49
51
  end
50
52
 
51
53
  # Resets the \loaded flag to +false+ and sets the \target to +nil+.
52
54
  def reset
53
55
  @loaded = false
54
- @target = nil
55
56
  @stale_state = nil
56
- @inversed = false
57
+ end
58
+
59
+ def reset_negative_cache # :nodoc:
60
+ reset if loaded? && target.nil?
57
61
  end
58
62
 
59
63
  # Reloads the \target and returns +self+ on success.
60
64
  # The QueryCache is cleared if +force+ is true.
61
65
  def reload(force = false)
62
- klass.connection.clear_query_cache if force && klass
66
+ klass.connection_pool.clear_query_cache if force && klass
63
67
  reset
64
68
  reset_scope
65
69
  load_target
@@ -75,7 +79,6 @@ module ActiveRecord
75
79
  def loaded!
76
80
  @loaded = true
77
81
  @stale_state = stale_state
78
- @inversed = false
79
82
  end
80
83
 
81
84
  # The target is stale if the target no longer points to the record(s) that the
@@ -85,7 +88,7 @@ module ActiveRecord
85
88
  #
86
89
  # Note that if the target has not been loaded, it is not considered stale.
87
90
  def stale_target?
88
- !@inversed && loaded? && @stale_state != stale_state
91
+ loaded? && @stale_state != stale_state
89
92
  end
90
93
 
91
94
  # Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
@@ -95,7 +98,15 @@ module ActiveRecord
95
98
  end
96
99
 
97
100
  def scope
98
- target_scope.merge!(association_scope)
101
+ if disable_joins
102
+ DisableJoinsAssociationScope.create.scope(self)
103
+ elsif (scope = klass.current_scope) && scope.try(:proxy_association) == self
104
+ scope.spawn
105
+ elsif scope = klass.global_current_scope
106
+ target_scope.merge!(association_scope).merge!(scope)
107
+ else
108
+ target_scope.merge!(association_scope)
109
+ end
99
110
  end
100
111
 
101
112
  def reset_scope
@@ -126,9 +137,13 @@ module ActiveRecord
126
137
 
127
138
  def inversed_from(record)
128
139
  self.target = record
129
- @inversed = !!record
130
140
  end
131
- alias :inversed_from_queries :inversed_from
141
+
142
+ def inversed_from_queries(record)
143
+ if inversable?(record)
144
+ self.target = record
145
+ end
146
+ end
132
147
 
133
148
  # Returns the class of the target. belongs_to polymorphic overrides this to look at the
134
149
  # polymorphic_type field on the owner.
@@ -177,7 +192,7 @@ module ActiveRecord
177
192
  @reflection = @owner.class._reflect_on_association(reflection_name)
178
193
  end
179
194
 
180
- def initialize_attributes(record, except_from_scope_attributes = nil) #:nodoc:
195
+ def initialize_attributes(record, except_from_scope_attributes = nil) # :nodoc:
181
196
  except_from_scope_attributes ||= {}
182
197
  skip_assign = [reflection.foreign_key, reflection.type].compact
183
198
  assigned_keys = record.changed_attribute_names_to_save
@@ -187,27 +202,69 @@ module ActiveRecord
187
202
  set_inverse_instance(record)
188
203
  end
189
204
 
190
- def create(attributes = {}, &block)
205
+ def create(attributes = nil, &block)
191
206
  _create_record(attributes, &block)
192
207
  end
193
208
 
194
- def create!(attributes = {}, &block)
209
+ def create!(attributes = nil, &block)
195
210
  _create_record(attributes, true, &block)
196
211
  end
197
212
 
213
+ # Whether the association represent a single record
214
+ # or a collection of records.
215
+ def collection?
216
+ false
217
+ end
218
+
198
219
  private
220
+ # Reader and writer methods call this so that consistent errors are presented
221
+ # when the association target class does not exist.
222
+ def ensure_klass_exists!
223
+ klass
224
+ end
225
+
199
226
  def find_target
227
+ if violates_strict_loading?
228
+ Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
229
+ end
230
+
200
231
  scope = self.scope
201
232
  return scope.to_a if skip_statement_cache?(scope)
202
233
 
203
- conn = klass.connection
204
- sc = reflection.association_scope_cache(conn, owner) do |params|
234
+ sc = reflection.association_scope_cache(klass, owner) do |params|
205
235
  as = AssociationScope.create { params.bind }
206
236
  target_scope.merge!(as.scope(self))
207
237
  end
208
238
 
209
239
  binds = AssociationScope.get_bind_values(owner, reflection.chain)
210
- sc.execute(binds, conn) { |record| set_inverse_instance(record) } || []
240
+ klass.with_connection do |c|
241
+ sc.execute(binds, c) do |record|
242
+ set_inverse_instance(record)
243
+ if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many
244
+ record.strict_loading!
245
+ else
246
+ record.strict_loading!(false, mode: owner.strict_loading_mode)
247
+ end
248
+ end
249
+ end
250
+ end
251
+
252
+ def skip_strict_loading(&block)
253
+ skip_strict_loading_was = @skip_strict_loading
254
+ @skip_strict_loading = true
255
+ yield
256
+ ensure
257
+ @skip_strict_loading = skip_strict_loading_was
258
+ end
259
+
260
+ def violates_strict_loading?
261
+ return if @skip_strict_loading
262
+
263
+ return unless owner.validation_context.nil?
264
+
265
+ return reflection.strict_loading? if reflection.options.key?(:strict_loading)
266
+
267
+ owner.strict_loading? && !owner.strict_loading_n_plus_one_only?
211
268
  end
212
269
 
213
270
  # The scope for this association.
@@ -218,7 +275,11 @@ module ActiveRecord
218
275
  # actually gets built.
219
276
  def association_scope
220
277
  if klass
221
- @association_scope ||= AssociationScope.scope(self)
278
+ @association_scope ||= if disable_joins
279
+ DisableJoinsAssociationScope.scope(self)
280
+ else
281
+ AssociationScope.scope(self)
282
+ end
222
283
  end
223
284
  end
224
285
 
@@ -236,25 +297,6 @@ module ActiveRecord
236
297
  !loaded? && (!owner.new_record? || foreign_key_present?) && klass
237
298
  end
238
299
 
239
- def creation_attributes
240
- attributes = {}
241
-
242
- if (reflection.has_one? || reflection.collection?) && !options[:through]
243
- attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
244
-
245
- if reflection.type
246
- attributes[reflection.type] = owner.class.polymorphic_name
247
- end
248
- end
249
-
250
- attributes
251
- end
252
-
253
- # Sets the owner attributes on the given record
254
- def set_owner_attributes(record)
255
- creation_attributes.each { |key, value| record[key] = value }
256
- end
257
-
258
300
  # Returns true if there is a foreign key present on the owner which
259
301
  # references the target. This is used to determine whether we can load
260
302
  # the target if the owner is currently a new record (and therefore
@@ -269,7 +311,7 @@ module ActiveRecord
269
311
 
270
312
  # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
271
313
  # the kind of the class of the associated objects. Meant to be used as
272
- # a sanity check when you are about to assign an associated record.
314
+ # a safety check when you are about to assign an associated record.
273
315
  def raise_on_type_mismatch!(record)
274
316
  unless record.is_a?(reflection.klass)
275
317
  fresh_class = reflection.class_name.safe_constantize
@@ -302,7 +344,8 @@ module ActiveRecord
302
344
 
303
345
  # Returns true if record contains the foreign_key
304
346
  def foreign_key_for?(record)
305
- record.has_attribute?(reflection.foreign_key)
347
+ foreign_key = Array(reflection.foreign_key)
348
+ foreign_key.all? { |key| record._has_attribute?(key) }
306
349
  end
307
350
 
308
351
  # This should be implemented to return the values of the relevant key(s) on the owner,
@@ -327,6 +370,28 @@ module ActiveRecord
327
370
  klass.scope_attributes? ||
328
371
  reflection.source_reflection.active_record.default_scopes.any?
329
372
  end
373
+
374
+ def enqueue_destroy_association(options)
375
+ job_class = owner.class.destroy_association_async_job
376
+
377
+ if job_class
378
+ owner._after_commit_jobs.push([job_class, options])
379
+ end
380
+ end
381
+
382
+ def inversable?(record)
383
+ record &&
384
+ ((!record.persisted? || !owner.persisted?) || matches_foreign_key?(record))
385
+ end
386
+
387
+ def matches_foreign_key?(record)
388
+ if foreign_key_for?(record)
389
+ record.read_attribute(reflection.foreign_key) == owner.id ||
390
+ (foreign_key_for?(owner) && owner.read_attribute(reflection.foreign_key) == record.id)
391
+ else
392
+ owner.read_attribute(reflection.foreign_key) == record.id
393
+ end
394
+ end
330
395
  end
331
396
  end
332
397
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Associations
5
- class AssociationScope #:nodoc:
5
+ class AssociationScope # :nodoc:
6
6
  def self.scope(association)
7
7
  INSTANCE.scope(association)
8
8
  end
@@ -35,7 +35,7 @@ module ActiveRecord
35
35
  binds = []
36
36
  last_reflection = chain.last
37
37
 
38
- binds << last_reflection.join_id_for(owner)
38
+ binds.push(*last_reflection.join_id_for(owner))
39
39
  if last_reflection.type
40
40
  binds << owner.class.polymorphic_name
41
41
  end
@@ -52,17 +52,19 @@ module ActiveRecord
52
52
  attr_reader :value_transformation
53
53
 
54
54
  def join(table, constraint)
55
- table.create_join(table, table.create_on(constraint))
55
+ Arel::Nodes::LeadingJoin.new(table, Arel::Nodes::On.new(constraint))
56
56
  end
57
57
 
58
58
  def last_chain_scope(scope, reflection, owner)
59
- join_keys = reflection.join_keys
60
- key = join_keys.key
61
- foreign_key = join_keys.foreign_key
59
+ primary_key = Array(reflection.join_primary_key)
60
+ foreign_key = Array(reflection.join_foreign_key)
62
61
 
63
62
  table = reflection.aliased_table
64
- value = transform_value(owner[foreign_key])
65
- scope = apply_scope(scope, table, key, value)
63
+ primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
64
+ primary_key_foreign_key_pairs.each do |join_key, foreign_key|
65
+ value = transform_value(owner._read_attribute(foreign_key))
66
+ scope = apply_scope(scope, table, join_key, value)
67
+ end
66
68
 
67
69
  if reflection.type
68
70
  polymorphic_type = transform_value(owner.class.polymorphic_name)
@@ -77,20 +79,23 @@ module ActiveRecord
77
79
  end
78
80
 
79
81
  def next_chain_scope(scope, reflection, next_reflection)
80
- join_keys = reflection.join_keys
81
- key = join_keys.key
82
- foreign_key = join_keys.foreign_key
82
+ primary_key = Array(reflection.join_primary_key)
83
+ foreign_key = Array(reflection.join_foreign_key)
83
84
 
84
85
  table = reflection.aliased_table
85
86
  foreign_table = next_reflection.aliased_table
86
- constraint = table[key].eq(foreign_table[foreign_key])
87
+
88
+ primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
89
+ constraints = primary_key_foreign_key_pairs.map do |join_primary_key, foreign_key|
90
+ table[join_primary_key].eq(foreign_table[foreign_key])
91
+ end.inject(&:and)
87
92
 
88
93
  if reflection.type
89
94
  value = transform_value(next_reflection.klass.polymorphic_name)
90
95
  scope = apply_scope(scope, table, reflection.type, value)
91
96
  end
92
97
 
93
- scope.joins!(join(foreign_table, constraint))
98
+ scope.joins!(join(foreign_table, constraints))
94
99
  end
95
100
 
96
101
  class ReflectionProxy < SimpleDelegator # :nodoc:
@@ -108,11 +113,9 @@ module ActiveRecord
108
113
  name = reflection.name
109
114
  chain = [Reflection::RuntimeReflection.new(reflection, association)]
110
115
  reflection.chain.drop(1).each do |refl|
111
- aliased_table = tracker.aliased_table_for(
112
- refl.table_name,
113
- refl.alias_candidate(name),
114
- refl.klass.type_caster
115
- )
116
+ aliased_table = tracker.aliased_table_for(refl.klass.arel_table) do
117
+ refl.alias_candidate(name)
118
+ end
116
119
  chain << ReflectionProxy.new(refl, aliased_table)
117
120
  end
118
121
  chain
@@ -127,17 +130,23 @@ module ActiveRecord
127
130
 
128
131
  chain_head = chain.first
129
132
  chain.reverse_each do |reflection|
130
- # Exclude the scope of the association itself, because that
131
- # was already merged in the #scope method.
132
133
  reflection.constraints.each do |scope_chain_item|
133
134
  item = eval_scope(reflection, scope_chain_item, owner)
134
135
 
135
136
  if scope_chain_item == chain_head.scope
136
137
  scope.merge! item.except(:where, :includes, :unscope, :order)
138
+ elsif !item.references_values.empty?
139
+ scope.merge! item.only(:joins, :left_outer_joins)
140
+
141
+ associations = item.eager_load_values | item.includes_values
142
+
143
+ unless associations.empty?
144
+ scope.joins! item.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
145
+ end
137
146
  end
138
147
 
139
148
  reflection.all_includes do
140
- scope.includes! item.includes_values
149
+ scope.includes_values |= item.includes_values
141
150
  end
142
151
 
143
152
  scope.unscope!(*item.unscope_values)
@@ -3,16 +3,38 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  # = Active Record Belongs To Association
6
- class BelongsToAssociation < SingularAssociation #:nodoc:
6
+ class BelongsToAssociation < SingularAssociation # :nodoc:
7
7
  def handle_dependency
8
8
  return unless load_target
9
9
 
10
10
  case options[:dependent]
11
11
  when :destroy
12
- target.destroy
13
- raise ActiveRecord::Rollback unless target.destroyed?
12
+ raise ActiveRecord::Rollback unless target.destroy
13
+ when :destroy_async
14
+ if reflection.foreign_key.is_a?(Array)
15
+ primary_key_column = reflection.active_record_primary_key
16
+ id = reflection.foreign_key.map { |col| owner.public_send(col) }
17
+ else
18
+ primary_key_column = reflection.active_record_primary_key
19
+ id = owner.public_send(reflection.foreign_key)
20
+ end
21
+
22
+ association_class = if reflection.polymorphic?
23
+ owner.public_send(reflection.foreign_type)
24
+ else
25
+ reflection.klass
26
+ end
27
+
28
+ enqueue_destroy_association(
29
+ owner_model_name: owner.class.to_s,
30
+ owner_id: owner.id,
31
+ association_class: association_class.to_s,
32
+ association_ids: [id],
33
+ association_primary_key_column: primary_key_column,
34
+ ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
35
+ )
14
36
  else
15
- target.send(options[:dependent])
37
+ target.public_send(options[:dependent])
16
38
  end
17
39
  end
18
40
 
@@ -44,7 +66,8 @@ module ActiveRecord
44
66
 
45
67
  def decrement_counters_before_last_save
46
68
  if reflection.polymorphic?
47
- model_was = owner.attribute_before_last_save(reflection.foreign_type).try(:constantize)
69
+ model_type_was = owner.attribute_before_last_save(reflection.foreign_type)
70
+ model_was = owner.class.polymorphic_class_for(model_type_was) if model_type_was
48
71
  else
49
72
  model_was = klass
50
73
  end
@@ -57,6 +80,14 @@ module ActiveRecord
57
80
  end
58
81
 
59
82
  def target_changed?
83
+ owner.attribute_changed?(reflection.foreign_key) || (!foreign_key_present? && target&.new_record?)
84
+ end
85
+
86
+ def target_previously_changed?
87
+ owner.attribute_previously_changed?(reflection.foreign_key)
88
+ end
89
+
90
+ def saved_change_to_target?
60
91
  owner.saved_change_to_attribute?(reflection.foreign_key)
61
92
  end
62
93
 
@@ -66,9 +97,11 @@ module ActiveRecord
66
97
  raise_on_type_mismatch!(record)
67
98
  set_inverse_instance(record)
68
99
  @updated = true
100
+ elsif target
101
+ remove_inverse_instance(target)
69
102
  end
70
103
 
71
- replace_keys(record)
104
+ replace_keys(record, force: true)
72
105
 
73
106
  self.target = record
74
107
  end
@@ -96,8 +129,25 @@ module ActiveRecord
96
129
  reflection.counter_cache_column && owner.persisted?
97
130
  end
98
131
 
99
- def replace_keys(record)
100
- owner[reflection.foreign_key] = record ? record._read_attribute(primary_key(record.class)) : nil
132
+ def replace_keys(record, force: false)
133
+ reflection_fk = reflection.foreign_key
134
+ if reflection_fk.is_a?(Array)
135
+ target_key_values = record ? Array(primary_key(record.class)).map { |key| record._read_attribute(key) } : []
136
+
137
+ if force || reflection_fk.map { |fk| owner._read_attribute(fk) } != target_key_values
138
+ owner_pk = Array(owner.class.primary_key)
139
+ reflection_fk.each_with_index do |key, index|
140
+ next if record.nil? && owner_pk.include?(key)
141
+ owner[key] = target_key_values[index]
142
+ end
143
+ end
144
+ else
145
+ target_key_value = record ? record._read_attribute(primary_key(record.class)) : nil
146
+
147
+ if force || owner._read_attribute(reflection_fk) != target_key_value
148
+ owner[reflection_fk] = target_key_value
149
+ end
150
+ end
101
151
  end
102
152
 
103
153
  def primary_key(klass)
@@ -105,19 +155,24 @@ module ActiveRecord
105
155
  end
106
156
 
107
157
  def foreign_key_present?
108
- owner._read_attribute(reflection.foreign_key)
158
+ Array(reflection.foreign_key).all? { |fk| owner._read_attribute(fk) }
109
159
  end
110
160
 
111
- # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
112
- # has_one associations.
113
161
  def invertible_for?(record)
114
162
  inverse = inverse_reflection_for(record)
115
- inverse && inverse.has_one?
163
+ inverse && (inverse.has_one? || inverse.klass.has_many_inversing)
116
164
  end
117
165
 
118
166
  def stale_state
119
- result = owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
120
- result && result.to_s
167
+ foreign_key = reflection.foreign_key
168
+ if foreign_key.is_a?(Array)
169
+ attributes = foreign_key.map do |fk|
170
+ owner._read_attribute(fk) { |n| owner.send(:missing_attribute, n, caller) }
171
+ end
172
+ attributes if attributes.any?
173
+ else
174
+ owner._read_attribute(foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
175
+ end
121
176
  end
122
177
  end
123
178
  end
@@ -3,20 +3,33 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  # = Active Record Belongs To Polymorphic Association
6
- class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
6
+ class BelongsToPolymorphicAssociation < BelongsToAssociation # :nodoc:
7
7
  def klass
8
8
  type = owner[reflection.foreign_type]
9
- type.presence && type.constantize
9
+ type.presence && owner.class.polymorphic_class_for(type)
10
10
  end
11
11
 
12
12
  def target_changed?
13
+ super || owner.attribute_changed?(reflection.foreign_type)
14
+ end
15
+
16
+ def target_previously_changed?
17
+ super || owner.attribute_previously_changed?(reflection.foreign_type)
18
+ end
19
+
20
+ def saved_change_to_target?
13
21
  super || owner.saved_change_to_attribute?(reflection.foreign_type)
14
22
  end
15
23
 
16
24
  private
17
- def replace_keys(record)
25
+ def replace_keys(record, force: false)
18
26
  super
19
- owner[reflection.foreign_type] = record ? record.class.polymorphic_name : nil
27
+
28
+ target_type = record ? record.class.polymorphic_name : nil
29
+
30
+ if force || owner._read_attribute(reflection.foreign_type) != target_type
31
+ owner[reflection.foreign_type] = target_type
32
+ end
20
33
  end
21
34
 
22
35
  def inverse_reflection_for(record)
@@ -28,8 +41,9 @@ module ActiveRecord
28
41
  end
29
42
 
30
43
  def stale_state
31
- foreign_key = super
32
- 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
33
47
  end
34
48
  end
35
49
  end
@@ -12,13 +12,15 @@
12
12
  # - HasManyAssociation
13
13
 
14
14
  module ActiveRecord::Associations::Builder # :nodoc:
15
- class Association #:nodoc:
15
+ class Association # :nodoc:
16
16
  class << self
17
17
  attr_accessor :extensions
18
18
  end
19
19
  self.extensions = []
20
20
 
21
- VALID_OPTIONS = [:class_name, :anonymous_class, :foreign_key, :validate] # :nodoc:
21
+ VALID_OPTIONS = [
22
+ :class_name, :anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading, :query_constraints
23
+ ].freeze # :nodoc:
22
24
 
23
25
  def self.build(model, name, scope, options, &block)
24
26
  if model.dangerous_attribute_method?(name)
@@ -31,6 +33,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
31
33
  define_accessors model, reflection
32
34
  define_callbacks model, reflection
33
35
  define_validations model, reflection
36
+ define_change_tracking_methods model, reflection
34
37
  reflection
35
38
  end
36
39
 
@@ -72,8 +75,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
72
75
 
73
76
  def self.define_callbacks(model, reflection)
74
77
  if dependent = reflection.options[:dependent]
75
- check_dependent_options(dependent)
78
+ check_dependent_options(dependent, model)
76
79
  add_destroy_callbacks(model, reflection)
80
+ add_after_commit_jobs_callback(model, dependent)
77
81
  end
78
82
 
79
83
  Association.extensions.each do |extension|
@@ -114,11 +118,19 @@ module ActiveRecord::Associations::Builder # :nodoc:
114
118
  # noop
115
119
  end
116
120
 
121
+ def self.define_change_tracking_methods(model, reflection)
122
+ # noop
123
+ end
124
+
117
125
  def self.valid_dependent_options
118
126
  raise NotImplementedError
119
127
  end
120
128
 
121
- def self.check_dependent_options(dependent)
129
+ def self.check_dependent_options(dependent, model)
130
+ if dependent == :destroy_async && !model.destroy_association_async_job
131
+ err_message = "A valid destroy_association_async_job is required to use `dependent: :destroy_async` on associations"
132
+ raise ActiveRecord::ConfigurationError, err_message
133
+ end
122
134
  unless valid_dependent_options.include? dependent
123
135
  raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
124
136
  end
@@ -126,11 +138,32 @@ module ActiveRecord::Associations::Builder # :nodoc:
126
138
 
127
139
  def self.add_destroy_callbacks(model, reflection)
128
140
  name = reflection.name
129
- model.before_destroy lambda { |o| o.association(name).handle_dependency }
141
+ model.before_destroy(->(o) { o.association(name).handle_dependency })
142
+ end
143
+
144
+ def self.add_after_commit_jobs_callback(model, dependent)
145
+ if dependent == :destroy_async
146
+ mixin = model.generated_association_methods
147
+
148
+ unless mixin.method_defined?(:_after_commit_jobs)
149
+ model.after_commit(-> do
150
+ _after_commit_jobs.each do |job_class, job_arguments|
151
+ job_class.perform_later(**job_arguments)
152
+ end
153
+ end)
154
+
155
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
156
+ def _after_commit_jobs
157
+ @_after_commit_jobs ||= []
158
+ end
159
+ CODE
160
+ end
161
+ end
130
162
  end
131
163
 
132
164
  private_class_method :build_scope, :macro, :valid_options, :validate_options, :define_extensions,
133
165
  :define_callbacks, :define_accessors, :define_readers, :define_writers, :define_validations,
134
- :valid_dependent_options, :check_dependent_options, :add_destroy_callbacks
166
+ :define_change_tracking_methods, :valid_dependent_options, :check_dependent_options,
167
+ :add_destroy_callbacks, :add_after_commit_jobs_callback
135
168
  end
136
169
  end