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
@@ -6,12 +6,13 @@ module ActiveRecord
6
6
  # See ActiveRecord::Attributes::ClassMethods for documentation
7
7
  module Attributes
8
8
  extend ActiveSupport::Concern
9
+ include ActiveModel::AttributeRegistration
9
10
 
10
- included do
11
- class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
12
- end
13
-
11
+ # = Active Record \Attributes
14
12
  module ClassMethods
13
+ # :method: attribute
14
+ # :call-seq: attribute(name, cast_type = nil, **options)
15
+ #
15
16
  # Defines an attribute with a type on this model. It will override the
16
17
  # type of existing attributes if needed. This allows control over how
17
18
  # values are converted to and from SQL when assigned to a model. It also
@@ -20,26 +21,34 @@ module ActiveRecord
20
21
  # your domain objects across much of Active Record, without having to
21
22
  # rely on implementation details or monkey patching.
22
23
  #
23
- # +name+ The name of the methods to define attribute methods for, and the
24
- # column which this will persist to.
24
+ # ==== Parameters
25
25
  #
26
- # +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
27
- # to be used for this attribute. See the examples below for more
28
- # information about providing custom type objects.
26
+ # [+name+]
27
+ # The name of the methods to define attribute methods for, and the
28
+ # column which this will persist to.
29
29
  #
30
- # ==== Options
30
+ # [+cast_type+]
31
+ # A symbol such as +:string+ or +:integer+, or a type object to be used
32
+ # for this attribute. If this parameter is not passed, the previously
33
+ # defined type (if any) will be used. Otherwise, the type will be
34
+ # ActiveModel::Type::Value. See the examples below for more information
35
+ # about providing custom type objects.
31
36
  #
32
- # The following options are accepted:
37
+ # ==== Options
33
38
  #
34
- # +default+ The default value to use when no value is provided. If this option
35
- # is not passed, the previous default value (if any) will be used.
36
- # Otherwise, the default will be +nil+.
39
+ # [+:default+]
40
+ # The default value to use when no value is provided. If this option is
41
+ # not passed, the previously defined default value (if any) on the
42
+ # superclass or in the schema will be used. Otherwise, the default will
43
+ # be +nil+.
37
44
  #
38
- # +array+ (PostgreSQL only) specifies that the type should be an array (see the
39
- # examples below).
45
+ # [+:array+]
46
+ # (PostgreSQL only) Specifies that the type should be an array. See the
47
+ # examples below.
40
48
  #
41
- # +range+ (PostgreSQL only) specifies that the type should be a range (see the
42
- # examples below).
49
+ # [+:range+]
50
+ # (PostgreSQL only) Specifies that the type should be a range. See the
51
+ # examples below.
43
52
  #
44
53
  # When using a symbol for +cast_type+, extra options are forwarded to the
45
54
  # constructor of the type object.
@@ -134,7 +143,7 @@ module ActiveRecord
134
143
  # expected API. It is recommended that your type objects inherit from an
135
144
  # existing type, or from ActiveRecord::Type::Value
136
145
  #
137
- # class MoneyType < ActiveRecord::Type::Integer
146
+ # class PriceType < ActiveRecord::Type::Integer
138
147
  # def cast(value)
139
148
  # if !value.kind_of?(Numeric) && value.include?('$')
140
149
  # price_in_dollars = value.gsub(/\$/, '').to_f
@@ -146,11 +155,11 @@ module ActiveRecord
146
155
  # end
147
156
  #
148
157
  # # config/initializers/types.rb
149
- # ActiveRecord::Type.register(:money, MoneyType)
158
+ # ActiveRecord::Type.register(:price, PriceType)
150
159
  #
151
160
  # # app/models/store_listing.rb
152
161
  # class StoreListing < ActiveRecord::Base
153
- # attribute :price_in_cents, :money
162
+ # attribute :price_in_cents, :price
154
163
  # end
155
164
  #
156
165
  # store_listing = StoreListing.new(price_in_cents: '$10.00')
@@ -170,13 +179,13 @@ module ActiveRecord
170
179
  # class Money < Struct.new(:amount, :currency)
171
180
  # end
172
181
  #
173
- # class MoneyType < Type::Value
182
+ # class PriceType < ActiveRecord::Type::Value
174
183
  # def initialize(currency_converter:)
175
184
  # @currency_converter = currency_converter
176
185
  # end
177
186
  #
178
- # # value will be the result of +deserialize+ or
179
- # # +cast+. Assumed to be an instance of +Money+ in
187
+ # # value will be the result of #deserialize or
188
+ # # #cast. Assumed to be an instance of Money in
180
189
  # # this case.
181
190
  # def serialize(value)
182
191
  # value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
@@ -185,19 +194,19 @@ module ActiveRecord
185
194
  # end
186
195
  #
187
196
  # # config/initializers/types.rb
188
- # ActiveRecord::Type.register(:money, MoneyType)
197
+ # ActiveRecord::Type.register(:price, PriceType)
189
198
  #
190
199
  # # app/models/product.rb
191
200
  # class Product < ActiveRecord::Base
192
201
  # currency_converter = ConversionRatesFromTheInternet.new
193
- # attribute :price_in_bitcoins, :money, currency_converter: currency_converter
202
+ # attribute :price_in_bitcoins, :price, currency_converter: currency_converter
194
203
  # end
195
204
  #
196
205
  # Product.where(price_in_bitcoins: Money.new(5, "USD"))
197
- # # => SELECT * FROM products WHERE price_in_bitcoins = 0.02230
206
+ # # SELECT * FROM products WHERE price_in_bitcoins = 0.02230
198
207
  #
199
208
  # Product.where(price_in_bitcoins: Money.new(5, "GBP"))
200
- # # => SELECT * FROM products WHERE price_in_bitcoins = 0.03412
209
+ # # SELECT * FROM products WHERE price_in_bitcoins = 0.03412
201
210
  #
202
211
  # ==== Dirty Tracking
203
212
  #
@@ -205,34 +214,31 @@ module ActiveRecord
205
214
  # tracking is performed. The methods +changed?+ and +changed_in_place?+
206
215
  # will be called from ActiveModel::Dirty. See the documentation for those
207
216
  # methods in ActiveModel::Type::Value for more details.
208
- def attribute(name, cast_type = Type::Value.new, **options)
209
- name = name.to_s
210
- reload_schema_from_cache
211
-
212
- self.attributes_to_define_after_schema_loads =
213
- attributes_to_define_after_schema_loads.merge(
214
- name => [cast_type, options]
215
- )
216
- end
217
+ #
218
+ #--
219
+ # Implemented by ActiveModel::AttributeRegistration#attribute.
217
220
 
218
- # This is the low level API which sits beneath +attribute+. It only
219
- # accepts type objects, and will do its work immediately instead of
220
- # waiting for the schema to load. Automatic schema detection and
221
- # ClassMethods#attribute both call this under the hood. While this method
221
+ # This API only accepts type objects, and will do its work immediately instead of
222
+ # waiting for the schema to load. While this method
222
223
  # is provided so it can be used by plugin authors, application code
223
224
  # should probably use ClassMethods#attribute.
224
225
  #
225
- # +name+ The name of the attribute being defined. Expected to be a +String+.
226
+ # ==== Parameters
227
+ #
228
+ # [+name+]
229
+ # The name of the attribute being defined. Expected to be a +String+.
226
230
  #
227
- # +cast_type+ The type object to use for this attribute.
231
+ # [+cast_type+]
232
+ # The type object to use for this attribute.
228
233
  #
229
- # +default+ The default value to use when no value is provided. If this option
230
- # is not passed, the previous default value (if any) will be used.
231
- # Otherwise, the default will be +nil+. A proc can also be passed, and
232
- # will be called once each time a new value is needed.
234
+ # [+default+]
235
+ # The default value to use when no value is provided. If this option
236
+ # is not passed, the previous default value (if any) will be used.
237
+ # Otherwise, the default will be +nil+. A proc can also be passed, and
238
+ # will be called once each time a new value is needed.
233
239
  #
234
- # +user_provided_default+ Whether the default value should be cast using
235
- # +cast+ or +deserialize+.
240
+ # [+user_provided_default+]
241
+ # Whether the default value should be cast using +cast+ or +deserialize+.
236
242
  def define_attribute(
237
243
  name,
238
244
  cast_type,
@@ -243,19 +249,39 @@ module ActiveRecord
243
249
  define_default_attribute(name, default, cast_type, from_user: user_provided_default)
244
250
  end
245
251
 
246
- def load_schema! # :nodoc:
247
- super
248
- attributes_to_define_after_schema_loads.each do |name, (type, options)|
249
- if type.is_a?(Symbol)
250
- type = ActiveRecord::Type.lookup(type, **options.except(:default))
252
+ def _default_attributes # :nodoc:
253
+ @default_attributes ||= begin
254
+ attributes_hash = with_connection do |connection|
255
+ columns_hash.transform_values do |column|
256
+ ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(connection, column))
257
+ end
251
258
  end
252
259
 
253
- define_attribute(name, type, **options.slice(:default))
260
+ attribute_set = ActiveModel::AttributeSet.new(attributes_hash)
261
+ apply_pending_attribute_modifications(attribute_set)
262
+ attribute_set
254
263
  end
255
264
  end
256
265
 
257
- private
266
+ ##
267
+ # :method: type_for_attribute
268
+ # :call-seq: type_for_attribute(attribute_name, &block)
269
+ #
270
+ # See ActiveModel::Attributes::ClassMethods#type_for_attribute.
271
+ #
272
+ # This method will access the database and load the model's schema if
273
+ # necessary.
274
+ #--
275
+ # Implemented by ActiveModel::AttributeRegistration::ClassMethods#type_for_attribute.
276
+
277
+ ##
278
+ protected
279
+ def reload_schema_from_cache(*)
280
+ reset_default_attributes!
281
+ super
282
+ end
258
283
 
284
+ private
259
285
  NO_DEFAULT_PROVIDED = Object.new # :nodoc:
260
286
  private_constant :NO_DEFAULT_PROVIDED
261
287
 
@@ -274,6 +300,18 @@ module ActiveRecord
274
300
  end
275
301
  _default_attributes[name] = default_attribute
276
302
  end
303
+
304
+ def reset_default_attributes
305
+ reload_schema_from_cache
306
+ end
307
+
308
+ def resolve_type_name(name, **options)
309
+ Type.lookup(name, **options, adapter: Type.adapter_name_from(self))
310
+ end
311
+
312
+ def type_for_column(connection, column)
313
+ hook_attribute_type(column.name, super)
314
+ end
277
315
  end
278
316
  end
279
317
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_record/associations/nested_error"
4
+
3
5
  module ActiveRecord
4
6
  # = Active Record Autosave Association
5
7
  #
@@ -26,12 +28,12 @@ module ActiveRecord
26
28
  #
27
29
  # Child records are validated unless <tt>:validate</tt> is +false+.
28
30
  #
29
- # == Callbacks
31
+ # == \Callbacks
30
32
  #
31
33
  # Association with autosave option defines several callbacks on your
32
- # model (before_save, after_create, after_update). Please note that
34
+ # model (around_save, before_save, after_create, after_update). Please note that
33
35
  # callbacks are executed in the order they were defined in
34
- # model. You should avoid modifying the association content, before
36
+ # model. You should avoid modifying the association content before
35
37
  # autosave callbacks are executed. Placing your callbacks after
36
38
  # associations is usually a good practice.
37
39
  #
@@ -91,8 +93,9 @@ module ActiveRecord
91
93
  # post.save # => saves both post and comment
92
94
  #
93
95
  # post = Post.create(title: 'ruby rocks')
94
- # post.comments.create(body: 'hello world')
95
- # post.save # => saves both post and comment
96
+ # comment = post.comments.create(body: 'hello world')
97
+ # comment.body = 'hi everyone'
98
+ # post.save # => saves post, but not comment
96
99
  #
97
100
  # When <tt>:autosave</tt> is true all children are saved, no matter whether they
98
101
  # are new records or not:
@@ -102,11 +105,10 @@ module ActiveRecord
102
105
  # end
103
106
  #
104
107
  # post = Post.create(title: 'ruby rocks')
105
- # post.comments.create(body: 'hello world')
106
- # post.comments[0].body = 'hi everyone'
108
+ # comment = post.comments.create(body: 'hello world')
109
+ # comment.body = 'hi everyone'
107
110
  # post.comments.build(body: "good morning.")
108
- # post.title += "!"
109
- # post.save # => saves both post and comments.
111
+ # post.save # => saves post and both comments.
110
112
  #
111
113
  # Destroying one of the associated models as part of the parent's save action
112
114
  # is as simple as marking it for destruction:
@@ -127,10 +129,18 @@ module ActiveRecord
127
129
  # Now it _is_ removed from the database:
128
130
  #
129
131
  # Comment.find_by(id: id).nil? # => true
132
+ #
133
+ # === Caveats
134
+ #
135
+ # Note that autosave will only trigger for already-persisted association records
136
+ # if the records themselves have been changed. This is to protect against
137
+ # <tt>SystemStackError</tt> caused by circular association validations. The one
138
+ # exception is if a custom validation context is used, in which case the validations
139
+ # will always fire on the associated records.
130
140
  module AutosaveAssociation
131
141
  extend ActiveSupport::Concern
132
142
 
133
- module AssociationBuilderExtension #:nodoc:
143
+ module AssociationBuilderExtension # :nodoc:
134
144
  def self.build(model, reflection)
135
145
  model.send(:add_autosave_association_callbacks, reflection)
136
146
  end
@@ -142,14 +152,13 @@ module ActiveRecord
142
152
 
143
153
  included do
144
154
  Associations::Builder::Association.extensions << AssociationBuilderExtension
145
- mattr_accessor :index_nested_attribute_errors, instance_writer: false, default: false
146
155
  end
147
156
 
148
157
  module ClassMethods # :nodoc:
149
158
  private
150
-
151
159
  def define_non_cyclic_method(name, &block)
152
- return if instance_methods(false).include?(name)
160
+ return if method_defined?(name, false)
161
+
153
162
  define_method(name) do |*args|
154
163
  result = true; @_already_called ||= {}
155
164
  # Loop prevention for validation of associations
@@ -181,15 +190,14 @@ module ActiveRecord
181
190
  save_method = :"autosave_associated_records_for_#{reflection.name}"
182
191
 
183
192
  if reflection.collection?
184
- before_save :before_save_collection_association
185
- after_save :after_save_collection_association
193
+ around_save :around_save_collection_association
186
194
 
187
195
  define_non_cyclic_method(save_method) { save_collection_association(reflection) }
188
196
  # Doesn't use after_save as that would save associations added in after_create/after_update twice
189
197
  after_create save_method
190
198
  after_update save_method
191
199
  elsif reflection.has_one?
192
- define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method)
200
+ define_non_cyclic_method(save_method) { save_has_one_association(reflection) }
193
201
  # Configures two callbacks instead of a single after_save so that
194
202
  # the model may rely on their execution order relative to its
195
203
  # own callbacks.
@@ -267,12 +275,16 @@ module ActiveRecord
267
275
  end
268
276
 
269
277
  private
278
+ def init_internals
279
+ super
280
+ @_already_called = nil
281
+ end
270
282
 
271
283
  # Returns the record for an association collection that should be validated
272
284
  # or saved. If +autosave+ is +false+ only new records will be returned,
273
285
  # unless the parent is/was a new record itself.
274
286
  def associated_records_to_validate_or_save(association, new_record, autosave)
275
- if new_record
287
+ if new_record || custom_validation_context?
276
288
  association && association.target
277
289
  elsif autosave
278
290
  association.target.find_all(&:changed_for_autosave?)
@@ -281,8 +293,9 @@ module ActiveRecord
281
293
  end
282
294
  end
283
295
 
284
- # go through nested autosave associations that are loaded in memory (without loading
285
- # any new ones), and return true if is changed for autosave
296
+ # Go through nested autosave associations that are loaded in memory (without loading
297
+ # any new ones), and return true if any are changed for autosave.
298
+ # Returns false if already called to prevent an infinite loop.
286
299
  def nested_records_changed_for_autosave?
287
300
  @_nested_records_changed_for_autosave_already_called ||= false
288
301
  return false if @_nested_records_changed_for_autosave_already_called
@@ -304,7 +317,7 @@ module ActiveRecord
304
317
  def validate_single_association(reflection)
305
318
  association = association_instance_get(reflection.name)
306
319
  record = association && association.reader
307
- association_valid?(reflection, record) if record && record.changed_for_autosave?
320
+ association_valid?(association, record) if record && (record.changed_for_autosave? || custom_validation_context?)
308
321
  end
309
322
 
310
323
  # Validate the associated records if <tt>:validate</tt> or
@@ -313,7 +326,7 @@ module ActiveRecord
313
326
  def validate_collection_association(reflection)
314
327
  if association = association_instance_get(reflection.name)
315
328
  if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
316
- records.each_with_index { |record, index| association_valid?(reflection, record, index) }
329
+ records.each { |record| association_valid?(association, record) }
317
330
  end
318
331
  end
319
332
  end
@@ -321,53 +334,44 @@ module ActiveRecord
321
334
  # Returns whether or not the association is valid and applies any errors to
322
335
  # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
323
336
  # enabled records if they're marked_for_destruction? or destroyed.
324
- def association_valid?(reflection, record, index = nil)
325
- return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
337
+ def association_valid?(association, record)
338
+ return true if record.destroyed? || (association.options[:autosave] && record.marked_for_destruction?)
326
339
 
327
- context = validation_context unless [:create, :update].include?(validation_context)
340
+ context = validation_context if custom_validation_context?
341
+ return true if record.valid?(context)
328
342
 
329
- unless valid = record.valid?(context)
330
- if reflection.options[:autosave]
331
- indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
332
-
333
- record.errors.each do |attribute, message|
334
- attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
335
- errors[attribute] << message
336
- errors[attribute].uniq!
337
- end
343
+ if context || record.changed_for_autosave?
344
+ associated_errors = record.errors.objects
345
+ else
346
+ # If there are existing invalid records in the DB, we should still be able to reference them.
347
+ # Unless a record (no matter where in the association chain) is invalid and is being changed.
348
+ associated_errors = record.errors.objects.select { |error| error.is_a?(Associations::NestedError) }
349
+ end
338
350
 
339
- record.errors.details.each_key do |attribute|
340
- reflection_attribute =
341
- normalize_reflection_attribute(indexed_attribute, reflection, index, attribute).to_sym
351
+ if association.options[:autosave]
352
+ return if equal?(record)
342
353
 
343
- record.errors.details[attribute].each do |error|
344
- errors.details[reflection_attribute] << error
345
- errors.details[reflection_attribute].uniq!
346
- end
347
- end
348
- else
349
- errors.add(reflection.name)
350
- end
354
+ associated_errors.each { |error|
355
+ errors.objects.append(
356
+ Associations::NestedError.new(association, error)
357
+ )
358
+ }
359
+ elsif associated_errors.any?
360
+ errors.add(association.reflection.name)
351
361
  end
352
- valid
353
- end
354
362
 
355
- def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
356
- if indexed_attribute
357
- "#{reflection.name}[#{index}].#{attribute}"
358
- else
359
- "#{reflection.name}.#{attribute}"
360
- end
363
+ errors.any?
361
364
  end
362
365
 
363
- # Is used as a before_save callback to check while saving a collection
366
+ # Is used as an around_save callback to check while saving a collection
364
367
  # association whether or not the parent was a new record before saving.
365
- def before_save_collection_association
366
- @new_record_before_save = new_record?
367
- end
368
+ def around_save_collection_association
369
+ previously_new_record_before_save = (@new_record_before_save ||= false)
370
+ @new_record_before_save = !previously_new_record_before_save && new_record?
368
371
 
369
- def after_save_collection_association
370
- @new_record_before_save = false
372
+ yield
373
+ ensure
374
+ @new_record_before_save = previously_new_record_before_save
371
375
  end
372
376
 
373
377
  # Saves any new associated records, or all loaded autosave associations if
@@ -402,6 +406,8 @@ module ActiveRecord
402
406
  saved = true
403
407
 
404
408
  if autosave != false && (new_record_before_save || record.new_record?)
409
+ association.set_inverse_instance(record)
410
+
405
411
  if autosave
406
412
  saved = association.insert_record(record, false)
407
413
  elsif !reflection.nested?
@@ -432,7 +438,9 @@ module ActiveRecord
432
438
  # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
433
439
  def save_has_one_association(reflection)
434
440
  association = association_instance_get(reflection.name)
435
- record = association && association.load_target
441
+ return unless association && association.loaded?
442
+
443
+ record = association.load_target
436
444
 
437
445
  if record && !record.destroyed?
438
446
  autosave = reflection.options[:autosave]
@@ -440,14 +448,19 @@ module ActiveRecord
440
448
  if autosave && record.marked_for_destruction?
441
449
  record.destroy
442
450
  elsif autosave != false
443
- key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
451
+ primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
452
+ primary_key_value = primary_key.map { |key| _read_attribute(key) }
444
453
 
445
- if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)
454
+ if (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
446
455
  unless reflection.through_reflection
447
- record[reflection.foreign_key] = key
448
- if inverse_reflection = reflection.inverse_of
449
- record.association(inverse_reflection.name).loaded!
456
+ foreign_key = Array(reflection.foreign_key)
457
+ primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
458
+
459
+ primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
460
+ association_id = _read_attribute(primary_key)
461
+ record[foreign_key] = association_id unless record[foreign_key] == association_id
450
462
  end
463
+ association.set_inverse_instance(record)
451
464
  end
452
465
 
453
466
  saved = record.save(validate: !autosave)
@@ -459,16 +472,28 @@ module ActiveRecord
459
472
  end
460
473
 
461
474
  # If the record is new or it has changed, returns true.
462
- def record_changed?(reflection, record, key)
475
+ def _record_changed?(reflection, record, key)
463
476
  record.new_record? ||
464
- association_foreign_key_changed?(reflection, record, key) ||
477
+ (association_foreign_key_changed?(reflection, record, key) ||
478
+ inverse_polymorphic_association_changed?(reflection, record)) ||
465
479
  record.will_save_change_to_attribute?(reflection.foreign_key)
466
480
  end
467
481
 
468
482
  def association_foreign_key_changed?(reflection, record, key)
469
483
  return false if reflection.through_reflection?
470
484
 
471
- record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key
485
+ foreign_key = Array(reflection.foreign_key)
486
+ return false unless foreign_key.all? { |key| record._has_attribute?(key) }
487
+
488
+ foreign_key.map { |key| record._read_attribute(key) } != Array(key)
489
+ end
490
+
491
+ def inverse_polymorphic_association_changed?(reflection, record)
492
+ return false unless reflection.inverse_of&.polymorphic?
493
+
494
+ class_name = record._read_attribute(reflection.inverse_of.foreign_type)
495
+
496
+ reflection.active_record.polymorphic_name != class_name
472
497
  end
473
498
 
474
499
  # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
@@ -483,14 +508,21 @@ module ActiveRecord
483
508
  autosave = reflection.options[:autosave]
484
509
 
485
510
  if autosave && record.marked_for_destruction?
486
- self[reflection.foreign_key] = nil
511
+ foreign_key = Array(reflection.foreign_key)
512
+ foreign_key.each { |key| self[key] = nil }
487
513
  record.destroy
488
514
  elsif autosave != false
489
515
  saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
490
516
 
491
517
  if association.updated?
492
- association_id = record.send(reflection.options[:primary_key] || :id)
493
- self[reflection.foreign_key] = association_id
518
+ primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
519
+ foreign_key = Array(reflection.foreign_key)
520
+
521
+ primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
522
+ primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
523
+ association_id = record._read_attribute(primary_key)
524
+ self[foreign_key] = association_id unless self[foreign_key] == association_id
525
+ end
494
526
  association.loaded!
495
527
  end
496
528
 
@@ -499,10 +531,24 @@ module ActiveRecord
499
531
  end
500
532
  end
501
533
 
502
- def _ensure_no_duplicate_errors
503
- errors.messages.each_key do |attribute|
504
- errors[attribute].uniq!
534
+ def compute_primary_key(reflection, record)
535
+ if primary_key_options = reflection.options[:primary_key]
536
+ primary_key_options
537
+ elsif reflection.options[:query_constraints] && (query_constraints = record.class.query_constraints_list)
538
+ query_constraints
539
+ elsif record.class.has_query_constraints? && !reflection.options[:foreign_key]
540
+ record.class.query_constraints_list
541
+ elsif record.class.composite_primary_key?
542
+ # If record has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
543
+ primary_key = record.class.primary_key
544
+ primary_key.include?("id") ? "id" : primary_key
545
+ else
546
+ record.class.primary_key
505
547
  end
506
548
  end
549
+
550
+ def _ensure_no_duplicate_errors
551
+ errors.uniq!
552
+ end
507
553
  end
508
554
  end