activerecord 6.1.7 → 7.2.0

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 (332) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +520 -1385
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +17 -14
  7. data/lib/active_record/association_relation.rb +2 -12
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +60 -21
  10. data/lib/active_record/associations/association_scope.rb +17 -12
  11. data/lib/active_record/associations/belongs_to_association.rb +37 -11
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
  13. data/lib/active_record/associations/builder/association.rb +11 -5
  14. data/lib/active_record/associations/builder/belongs_to.rb +41 -14
  15. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +4 -4
  19. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  20. data/lib/active_record/associations/collection_association.rb +46 -36
  21. data/lib/active_record/associations/collection_proxy.rb +44 -16
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/errors.rb +265 -0
  24. data/lib/active_record/associations/foreign_association.rb +10 -3
  25. data/lib/active_record/associations/has_many_association.rb +29 -19
  26. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  27. data/lib/active_record/associations/has_one_association.rb +20 -10
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  30. data/lib/active_record/associations/join_dependency.rb +23 -15
  31. data/lib/active_record/associations/nested_error.rb +47 -0
  32. data/lib/active_record/associations/preloader/association.rb +212 -53
  33. data/lib/active_record/associations/preloader/batch.rb +48 -0
  34. data/lib/active_record/associations/preloader/branch.rb +153 -0
  35. data/lib/active_record/associations/preloader/through_association.rb +50 -16
  36. data/lib/active_record/associations/preloader.rb +50 -121
  37. data/lib/active_record/associations/singular_association.rb +15 -3
  38. data/lib/active_record/associations/through_association.rb +25 -14
  39. data/lib/active_record/associations.rb +404 -509
  40. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  41. data/lib/active_record/attribute_assignment.rb +2 -14
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  43. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  44. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  45. data/lib/active_record/attribute_methods/primary_key.rb +47 -27
  46. data/lib/active_record/attribute_methods/query.rb +31 -19
  47. data/lib/active_record/attribute_methods/read.rb +14 -11
  48. data/lib/active_record/attribute_methods/serialization.rb +174 -37
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -9
  50. data/lib/active_record/attribute_methods/write.rb +12 -15
  51. data/lib/active_record/attribute_methods.rb +164 -52
  52. data/lib/active_record/attributes.rb +51 -49
  53. data/lib/active_record/autosave_association.rb +74 -57
  54. data/lib/active_record/base.rb +27 -5
  55. data/lib/active_record/callbacks.rb +18 -34
  56. data/lib/active_record/coders/column_serializer.rb +61 -0
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +70 -46
  59. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +327 -612
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -64
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +377 -142
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +345 -166
  75. data/lib/active_record/connection_adapters/column.rb +13 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -130
  78. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
  79. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  81. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  82. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  83. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +107 -68
  85. data/lib/active_record/connection_adapters/pool_config.rb +26 -16
  86. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  87. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  88. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
  89. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  94. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  97. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  100. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  102. data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
  103. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  104. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  105. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +173 -3
  106. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  107. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +401 -77
  108. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  109. data/lib/active_record/connection_adapters/postgresql_adapter.rb +518 -251
  110. data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
  111. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  112. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
  113. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
  114. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  115. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +66 -22
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
  119. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  120. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  121. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  122. data/lib/active_record/connection_adapters.rb +130 -6
  123. data/lib/active_record/connection_handling.rb +132 -146
  124. data/lib/active_record/core.rb +276 -251
  125. data/lib/active_record/counter_cache.rb +68 -34
  126. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -3
  127. data/lib/active_record/database_configurations/database_config.rb +34 -10
  128. data/lib/active_record/database_configurations/hash_config.rb +107 -31
  129. data/lib/active_record/database_configurations/url_config.rb +38 -13
  130. data/lib/active_record/database_configurations.rb +96 -60
  131. data/lib/active_record/delegated_type.rb +90 -20
  132. data/lib/active_record/deprecator.rb +7 -0
  133. data/lib/active_record/destroy_association_async_job.rb +4 -2
  134. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  135. data/lib/active_record/dynamic_matchers.rb +3 -3
  136. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +68 -0
  140. data/lib/active_record/encryption/configurable.rb +60 -0
  141. data/lib/active_record/encryption/context.rb +42 -0
  142. data/lib/active_record/encryption/contexts.rb +76 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  147. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  148. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  149. data/lib/active_record/encryption/encryptor.rb +170 -0
  150. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  151. data/lib/active_record/encryption/errors.rb +15 -0
  152. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  153. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  154. data/lib/active_record/encryption/key.rb +28 -0
  155. data/lib/active_record/encryption/key_generator.rb +53 -0
  156. data/lib/active_record/encryption/key_provider.rb +46 -0
  157. data/lib/active_record/encryption/message.rb +33 -0
  158. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  159. data/lib/active_record/encryption/message_serializer.rb +96 -0
  160. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  161. data/lib/active_record/encryption/properties.rb +76 -0
  162. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  163. data/lib/active_record/encryption/scheme.rb +100 -0
  164. data/lib/active_record/encryption.rb +56 -0
  165. data/lib/active_record/enum.rb +163 -63
  166. data/lib/active_record/errors.rb +210 -27
  167. data/lib/active_record/explain.rb +21 -12
  168. data/lib/active_record/explain_registry.rb +11 -6
  169. data/lib/active_record/explain_subscriber.rb +1 -1
  170. data/lib/active_record/fixture_set/file.rb +15 -1
  171. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  172. data/lib/active_record/fixture_set/render_context.rb +2 -0
  173. data/lib/active_record/fixture_set/table_row.rb +70 -14
  174. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  175. data/lib/active_record/fixtures.rb +179 -112
  176. data/lib/active_record/future_result.rb +178 -0
  177. data/lib/active_record/gem_version.rb +4 -4
  178. data/lib/active_record/inheritance.rb +85 -31
  179. data/lib/active_record/insert_all.rb +148 -32
  180. data/lib/active_record/integration.rb +14 -10
  181. data/lib/active_record/internal_metadata.rb +123 -23
  182. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  183. data/lib/active_record/locking/optimistic.rb +43 -27
  184. data/lib/active_record/locking/pessimistic.rb +15 -6
  185. data/lib/active_record/log_subscriber.rb +41 -29
  186. data/lib/active_record/marshalling.rb +56 -0
  187. data/lib/active_record/message_pack.rb +124 -0
  188. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  189. data/lib/active_record/middleware/database_selector.rb +23 -13
  190. data/lib/active_record/middleware/shard_selector.rb +62 -0
  191. data/lib/active_record/migration/command_recorder.rb +113 -16
  192. data/lib/active_record/migration/compatibility.rb +235 -46
  193. data/lib/active_record/migration/default_strategy.rb +22 -0
  194. data/lib/active_record/migration/execution_strategy.rb +19 -0
  195. data/lib/active_record/migration/join_table.rb +1 -1
  196. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  197. data/lib/active_record/migration.rb +374 -177
  198. data/lib/active_record/model_schema.rb +143 -159
  199. data/lib/active_record/nested_attributes.rb +48 -21
  200. data/lib/active_record/no_touching.rb +3 -3
  201. data/lib/active_record/normalization.rb +163 -0
  202. data/lib/active_record/persistence.rb +282 -283
  203. data/lib/active_record/promise.rb +84 -0
  204. data/lib/active_record/query_cache.rb +19 -25
  205. data/lib/active_record/query_logs.rb +189 -0
  206. data/lib/active_record/query_logs_formatter.rb +41 -0
  207. data/lib/active_record/querying.rb +44 -9
  208. data/lib/active_record/railtie.rb +234 -71
  209. data/lib/active_record/railties/controller_runtime.rb +25 -11
  210. data/lib/active_record/railties/databases.rake +189 -256
  211. data/lib/active_record/railties/job_runtime.rb +23 -0
  212. data/lib/active_record/readonly_attributes.rb +41 -3
  213. data/lib/active_record/reflection.rb +325 -103
  214. data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
  215. data/lib/active_record/relation/batches.rb +198 -63
  216. data/lib/active_record/relation/calculations.rb +300 -111
  217. data/lib/active_record/relation/delegation.rb +33 -22
  218. data/lib/active_record/relation/finder_methods.rb +123 -52
  219. data/lib/active_record/relation/merger.rb +26 -19
  220. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  221. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  222. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  223. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  224. data/lib/active_record/relation/predicate_builder.rb +29 -22
  225. data/lib/active_record/relation/query_attribute.rb +30 -12
  226. data/lib/active_record/relation/query_methods.rb +842 -150
  227. data/lib/active_record/relation/record_fetch_warning.rb +10 -9
  228. data/lib/active_record/relation/spawn_methods.rb +7 -6
  229. data/lib/active_record/relation/where_clause.rb +15 -36
  230. data/lib/active_record/relation.rb +736 -145
  231. data/lib/active_record/result.rb +67 -54
  232. data/lib/active_record/runtime_registry.rb +71 -13
  233. data/lib/active_record/sanitization.rb +84 -34
  234. data/lib/active_record/schema.rb +39 -23
  235. data/lib/active_record/schema_dumper.rb +90 -31
  236. data/lib/active_record/schema_migration.rb +74 -23
  237. data/lib/active_record/scoping/default.rb +72 -15
  238. data/lib/active_record/scoping/named.rb +5 -13
  239. data/lib/active_record/scoping.rb +65 -34
  240. data/lib/active_record/secure_password.rb +60 -0
  241. data/lib/active_record/secure_token.rb +21 -3
  242. data/lib/active_record/serialization.rb +6 -1
  243. data/lib/active_record/signed_id.rb +30 -9
  244. data/lib/active_record/statement_cache.rb +7 -7
  245. data/lib/active_record/store.rb +10 -10
  246. data/lib/active_record/suppressor.rb +13 -15
  247. data/lib/active_record/table_metadata.rb +7 -3
  248. data/lib/active_record/tasks/database_tasks.rb +277 -149
  249. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  250. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  251. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  252. data/lib/active_record/test_databases.rb +1 -1
  253. data/lib/active_record/test_fixtures.rb +173 -155
  254. data/lib/active_record/testing/query_assertions.rb +121 -0
  255. data/lib/active_record/timestamp.rb +32 -19
  256. data/lib/active_record/token_for.rb +123 -0
  257. data/lib/active_record/touch_later.rb +12 -7
  258. data/lib/active_record/transaction.rb +132 -0
  259. data/lib/active_record/transactions.rb +118 -41
  260. data/lib/active_record/translation.rb +3 -5
  261. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  262. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  263. data/lib/active_record/type/internal/timezone.rb +7 -2
  264. data/lib/active_record/type/serialized.rb +9 -7
  265. data/lib/active_record/type/time.rb +4 -0
  266. data/lib/active_record/type/type_map.rb +17 -20
  267. data/lib/active_record/type.rb +1 -2
  268. data/lib/active_record/type_caster/connection.rb +4 -4
  269. data/lib/active_record/validations/absence.rb +1 -1
  270. data/lib/active_record/validations/associated.rb +13 -7
  271. data/lib/active_record/validations/numericality.rb +5 -4
  272. data/lib/active_record/validations/presence.rb +5 -28
  273. data/lib/active_record/validations/uniqueness.rb +64 -15
  274. data/lib/active_record/validations.rb +12 -5
  275. data/lib/active_record/version.rb +1 -1
  276. data/lib/active_record.rb +444 -32
  277. data/lib/arel/alias_predication.rb +1 -1
  278. data/lib/arel/attributes/attribute.rb +0 -8
  279. data/lib/arel/collectors/bind.rb +2 -0
  280. data/lib/arel/collectors/composite.rb +7 -0
  281. data/lib/arel/collectors/sql_string.rb +1 -1
  282. data/lib/arel/collectors/substitute_binds.rb +1 -1
  283. data/lib/arel/crud.rb +28 -22
  284. data/lib/arel/delete_manager.rb +18 -4
  285. data/lib/arel/errors.rb +10 -0
  286. data/lib/arel/factory_methods.rb +4 -0
  287. data/lib/arel/filter_predications.rb +9 -0
  288. data/lib/arel/insert_manager.rb +2 -3
  289. data/lib/arel/nodes/binary.rb +6 -7
  290. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  291. data/lib/arel/nodes/casted.rb +1 -1
  292. data/lib/arel/nodes/cte.rb +36 -0
  293. data/lib/arel/nodes/delete_statement.rb +12 -13
  294. data/lib/arel/nodes/filter.rb +10 -0
  295. data/lib/arel/nodes/fragments.rb +35 -0
  296. data/lib/arel/nodes/function.rb +1 -0
  297. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  298. data/lib/arel/nodes/insert_statement.rb +2 -2
  299. data/lib/arel/nodes/leading_join.rb +8 -0
  300. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  301. data/lib/arel/nodes/node.rb +115 -5
  302. data/lib/arel/nodes/select_core.rb +2 -2
  303. data/lib/arel/nodes/select_statement.rb +2 -2
  304. data/lib/arel/nodes/sql_literal.rb +13 -0
  305. data/lib/arel/nodes/table_alias.rb +4 -0
  306. data/lib/arel/nodes/update_statement.rb +8 -3
  307. data/lib/arel/nodes.rb +7 -2
  308. data/lib/arel/predications.rb +14 -4
  309. data/lib/arel/select_manager.rb +11 -5
  310. data/lib/arel/table.rb +9 -6
  311. data/lib/arel/tree_manager.rb +8 -15
  312. data/lib/arel/update_manager.rb +20 -5
  313. data/lib/arel/visitors/dot.rb +81 -90
  314. data/lib/arel/visitors/mysql.rb +23 -5
  315. data/lib/arel/visitors/postgresql.rb +1 -22
  316. data/lib/arel/visitors/to_sql.rb +170 -36
  317. data/lib/arel/visitors/visitor.rb +2 -2
  318. data/lib/arel.rb +23 -4
  319. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  320. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  321. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  322. data/lib/rails/generators/active_record/migration.rb +3 -1
  323. data/lib/rails/generators/active_record/model/USAGE +113 -0
  324. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  325. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  326. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  327. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  328. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  329. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  330. metadata +100 -14
  331. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  332. data/lib/active_record/null_relation.rb +0 -67
@@ -10,6 +10,8 @@ module ActiveRecord
10
10
  included do
11
11
  class_attribute :_reflections, instance_writer: false, default: {}
12
12
  class_attribute :aggregate_reflections, instance_writer: false, default: {}
13
+ class_attribute :automatic_scope_inversing, instance_writer: false, default: false
14
+ class_attribute :automatically_invert_plural_associations, instance_writer: false, default: false
13
15
  end
14
16
 
15
17
  class << self
@@ -20,12 +22,12 @@ module ActiveRecord
20
22
 
21
23
  def add_reflection(ar, name, reflection)
22
24
  ar.clear_reflections_cache
23
- name = -name.to_s
25
+ name = name.to_sym
24
26
  ar._reflections = ar._reflections.except(name).merge!(name => reflection)
25
27
  end
26
28
 
27
29
  def add_aggregate_reflection(ar, name, reflection)
28
- ar.aggregate_reflections = ar.aggregate_reflections.merge(-name.to_s => reflection)
30
+ ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_sym => reflection)
29
31
  end
30
32
 
31
33
  private
@@ -45,6 +47,8 @@ module ActiveRecord
45
47
  end
46
48
  end
47
49
 
50
+ # = Active Record Reflection
51
+ #
48
52
  # \Reflection enables the ability to examine the associations and aggregations of
49
53
  # Active Record classes and objects. This information, for example,
50
54
  # can be used in a form builder that takes an Active Record object
@@ -64,7 +68,7 @@ module ActiveRecord
64
68
  # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
65
69
  #
66
70
  def reflect_on_aggregation(aggregation)
67
- aggregate_reflections[aggregation.to_s]
71
+ aggregate_reflections[aggregation.to_sym]
68
72
  end
69
73
 
70
74
  # Returns a Hash of name of the reflection as the key and an AssociationReflection as the value.
@@ -72,6 +76,10 @@ module ActiveRecord
72
76
  # Account.reflections # => {"balance" => AggregateReflection}
73
77
  #
74
78
  def reflections
79
+ normalized_reflections.stringify_keys
80
+ end
81
+
82
+ def normalized_reflections # :nodoc
75
83
  @__reflections ||= begin
76
84
  ref = {}
77
85
 
@@ -80,13 +88,13 @@ module ActiveRecord
80
88
 
81
89
  if parent_reflection
82
90
  parent_name = parent_reflection.name
83
- ref[parent_name.to_s] = parent_reflection
91
+ ref[parent_name] = parent_reflection
84
92
  else
85
93
  ref[name] = reflection
86
94
  end
87
95
  end
88
96
 
89
- ref
97
+ ref.freeze
90
98
  end
91
99
  end
92
100
 
@@ -101,7 +109,7 @@ module ActiveRecord
101
109
  # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
102
110
  #
103
111
  def reflect_on_all_associations(macro = nil)
104
- association_reflections = reflections.values
112
+ association_reflections = normalized_reflections.values
105
113
  association_reflections.select! { |reflection| reflection.macro == macro } if macro
106
114
  association_reflections
107
115
  end
@@ -112,21 +120,31 @@ module ActiveRecord
112
120
  # Invoice.reflect_on_association(:line_items).macro # returns :has_many
113
121
  #
114
122
  def reflect_on_association(association)
115
- reflections[association.to_s]
123
+ normalized_reflections[association.to_sym]
116
124
  end
117
125
 
118
- def _reflect_on_association(association) #:nodoc:
119
- _reflections[association.to_s]
126
+ def _reflect_on_association(association) # :nodoc:
127
+ _reflections[association.to_sym]
120
128
  end
121
129
 
122
130
  # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
123
131
  def reflect_on_all_autosave_associations
124
- reflections.values.select { |reflection| reflection.options[:autosave] }
132
+ reflections = normalized_reflections.values
133
+ reflections.select! { |reflection| reflection.options[:autosave] }
134
+ reflections
125
135
  end
126
136
 
127
137
  def clear_reflections_cache # :nodoc:
128
138
  @__reflections = nil
129
139
  end
140
+
141
+ private
142
+ def inherited(subclass)
143
+ super
144
+ subclass.class_eval do
145
+ @__reflections = nil
146
+ end
147
+ end
130
148
  end
131
149
 
132
150
  # Holds all the methods that are shared between MacroReflection and ThroughReflection.
@@ -143,6 +161,14 @@ module ActiveRecord
143
161
  # PolymorphicReflection
144
162
  # RuntimeReflection
145
163
  class AbstractReflection # :nodoc:
164
+ def initialize
165
+ @class_name = nil
166
+ @counter_cache_column = nil
167
+ @inverse_of = nil
168
+ @inverse_which_updates_counter_cache_defined = false
169
+ @inverse_which_updates_counter_cache = nil
170
+ end
171
+
146
172
  def through_reflection?
147
173
  false
148
174
  end
@@ -182,10 +208,14 @@ module ActiveRecord
182
208
 
183
209
  scope_chain_items.inject(klass_scope, &:merge!)
184
210
 
185
- primary_key = join_primary_key
186
- foreign_key = join_foreign_key
211
+ primary_key_column_names = Array(join_primary_key)
212
+ foreign_key_column_names = Array(join_foreign_key)
213
+
214
+ primary_foreign_key_pairs = primary_key_column_names.zip(foreign_key_column_names)
187
215
 
188
- klass_scope.where!(table[primary_key].eq(foreign_table[foreign_key]))
216
+ primary_foreign_key_pairs.each do |primary_key_column_name, foreign_key_column_name|
217
+ klass_scope.where!(table[primary_key_column_name].eq(foreign_table[foreign_key_column_name]))
218
+ end
189
219
 
190
220
  if klass.finder_needs_type_condition?
191
221
  klass_scope.where!(klass.send(:type_condition, table))
@@ -194,9 +224,9 @@ module ActiveRecord
194
224
  klass_scope
195
225
  end
196
226
 
197
- def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
227
+ def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
198
228
  if scope
199
- [scope_for(build_scope(table, predicate_builder, klass))]
229
+ [scope_for(build_scope(table, predicate_builder, klass), record)]
200
230
  else
201
231
  []
202
232
  end
@@ -212,14 +242,16 @@ module ActiveRecord
212
242
  end
213
243
 
214
244
  def counter_cache_column
215
- @counter_cache_column ||= if belongs_to?
216
- if options[:counter_cache] == true
217
- -"#{active_record.name.demodulize.underscore.pluralize}_count"
218
- elsif options[:counter_cache]
219
- -options[:counter_cache].to_s
245
+ @counter_cache_column ||= begin
246
+ counter_cache = options[:counter_cache]
247
+
248
+ if belongs_to?
249
+ if counter_cache
250
+ counter_cache[:column] || -"#{active_record.name.demodulize.underscore.pluralize}_count"
251
+ end
252
+ else
253
+ -((counter_cache && -counter_cache[:column]) || "#{name}_count")
220
254
  end
221
- else
222
- -(options[:counter_cache]&.to_s || "#{name}_count")
223
255
  end
224
256
  end
225
257
 
@@ -230,14 +262,17 @@ module ActiveRecord
230
262
  end
231
263
 
232
264
  def check_validity_of_inverse!
233
- unless polymorphic?
234
- if has_inverse? && inverse_of.nil?
265
+ if !polymorphic? && has_inverse?
266
+ if inverse_of.nil?
235
267
  raise InverseOfAssociationNotFoundError.new(self)
236
268
  end
269
+ if inverse_of == self
270
+ raise InverseOfAssociationRecursiveError.new(self)
271
+ end
237
272
  end
238
273
  end
239
274
 
240
- # This shit is nasty. We need to avoid the following situation:
275
+ # We need to avoid the following situation:
241
276
  #
242
277
  # * An associated record is deleted via record.destroy
243
278
  # * Hence the callbacks run, and they find a belongs_to on the record with a
@@ -248,10 +283,16 @@ module ActiveRecord
248
283
  #
249
284
  # Hence this method.
250
285
  def inverse_which_updates_counter_cache
251
- return @inverse_which_updates_counter_cache if defined?(@inverse_which_updates_counter_cache)
252
- @inverse_which_updates_counter_cache = klass.reflect_on_all_associations(:belongs_to).find do |inverse|
253
- inverse.counter_cache_column == counter_cache_column
286
+ unless @inverse_which_updates_counter_cache_defined
287
+ if counter_cache_column
288
+ inverse_candidates = inverse_of ? [inverse_of] : klass.reflect_on_all_associations(:belongs_to)
289
+ @inverse_which_updates_counter_cache = inverse_candidates.find do |inverse|
290
+ inverse.counter_cache_column == counter_cache_column && (inverse.polymorphic? || inverse.klass == active_record)
291
+ end
292
+ end
293
+ @inverse_which_updates_counter_cache_defined = true
254
294
  end
295
+ @inverse_which_updates_counter_cache
255
296
  end
256
297
  alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
257
298
 
@@ -259,7 +300,7 @@ module ActiveRecord
259
300
  inverse_of && inverse_which_updates_counter_cache == inverse_of
260
301
  end
261
302
 
262
- # Returns whether a counter cache should be used for this association.
303
+ # Returns whether this association has a counter cache.
263
304
  #
264
305
  # The counter_cache option must be given on either the owner or inverse
265
306
  # association, and the column must be present on the owner.
@@ -269,6 +310,17 @@ module ActiveRecord
269
310
  active_record.has_attribute?(counter_cache_column)
270
311
  end
271
312
 
313
+ # Returns whether this association has a counter cache and its column values were backfilled
314
+ # (and so it is used internally by methods like +size+/+any?+/etc).
315
+ def has_active_cached_counter?
316
+ return false unless has_cached_counter?
317
+
318
+ counter_cache = options[:counter_cache] ||
319
+ (inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache])
320
+
321
+ counter_cache[:active] != false
322
+ end
323
+
272
324
  def counter_must_be_updated_by_has_many?
273
325
  !inverse_updates_counter_in_memory? && has_cached_counter?
274
326
  end
@@ -293,6 +345,12 @@ module ActiveRecord
293
345
  options[:strict_loading]
294
346
  end
295
347
 
348
+ def strict_loading_violation_message(owner)
349
+ message = +"`#{owner}` is marked for strict_loading."
350
+ message << " The #{polymorphic? ? "polymorphic association" : "#{klass} association"}"
351
+ message << " named `:#{name}` cannot be lazily loaded."
352
+ end
353
+
296
354
  protected
297
355
  def actual_source_reflection # FIXME: this is a horrible name
298
356
  self
@@ -306,6 +364,12 @@ module ActiveRecord
306
364
  def primary_key(klass)
307
365
  klass.primary_key || raise(UnknownPrimaryKey.new(klass))
308
366
  end
367
+
368
+ def ensure_option_not_given_as_class!(option_name)
369
+ if options[option_name] && options[option_name].class == Class
370
+ raise ArgumentError, "A class was passed to `:#{option_name}` but we are expecting a string."
371
+ end
372
+ end
309
373
  end
310
374
 
311
375
  # Base class for AggregateReflection and AssociationReflection. Objects of
@@ -330,9 +394,10 @@ module ActiveRecord
330
394
  attr_reader :plural_name # :nodoc:
331
395
 
332
396
  def initialize(name, scope, options, active_record)
397
+ super()
333
398
  @name = name
334
399
  @scope = scope
335
- @options = options
400
+ @options = normalize_options(options)
336
401
  @active_record = active_record
337
402
  @klass = options[:anonymous_class]
338
403
  @plural_name = active_record.pluralize_table_names ?
@@ -363,13 +428,17 @@ module ActiveRecord
363
428
  # a new association object. Use +build_association+ or +create_association+
364
429
  # instead. This allows plugins to hook into association object creation.
365
430
  def klass
366
- @klass ||= compute_class(class_name)
431
+ @klass ||= compute_class(compute_name(class_name))
367
432
  end
368
433
 
369
434
  def compute_class(name)
370
435
  name.constantize
371
436
  end
372
437
 
438
+ def compute_name(name) # :nodoc:
439
+ active_record.name.demodulize == name ? "::#{name}" : name
440
+ end
441
+
373
442
  # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
374
443
  # and +other_aggregation+ has an options hash assigned to it.
375
444
  def ==(other_aggregation)
@@ -388,11 +457,31 @@ module ActiveRecord
388
457
  def derive_class_name
389
458
  name.to_s.camelize
390
459
  end
460
+
461
+ def normalize_options(options)
462
+ counter_cache = options.delete(:counter_cache)
463
+
464
+ if counter_cache
465
+ active = true
466
+
467
+ case counter_cache
468
+ when String, Symbol
469
+ column = -counter_cache.to_s
470
+ when Hash
471
+ active = counter_cache.fetch(:active, true)
472
+ column = counter_cache[:column]&.to_s
473
+ end
474
+
475
+ options[:counter_cache] = { active: active, column: column }
476
+ end
477
+
478
+ options
479
+ end
391
480
  end
392
481
 
393
482
  # Holds all the metadata about an aggregation as it was specified in the
394
483
  # Active Record class.
395
- class AggregateReflection < MacroReflection #:nodoc:
484
+ class AggregateReflection < MacroReflection # :nodoc:
396
485
  def mapping
397
486
  mapping = options[:mapping] || [name, name]
398
487
  mapping.first.is_a?(Array) ? mapping : [mapping]
@@ -401,12 +490,29 @@ module ActiveRecord
401
490
 
402
491
  # Holds all the metadata about an association as it was specified in the
403
492
  # Active Record class.
404
- class AssociationReflection < MacroReflection #:nodoc:
493
+ class AssociationReflection < MacroReflection # :nodoc:
405
494
  def compute_class(name)
406
495
  if polymorphic?
407
496
  raise ArgumentError, "Polymorphic associations do not support computing the class."
408
497
  end
409
- active_record.send(:compute_type, name)
498
+
499
+ begin
500
+ klass = active_record.send(:compute_type, name)
501
+ rescue NameError => error
502
+ if error.name.match?(/(?:\A|::)#{name}\z/)
503
+ message = "Missing model class #{name} for the #{active_record}##{self.name} association."
504
+ message += " You can specify a different model class with the :class_name option." unless options[:class_name]
505
+ raise NameError.new(message, name)
506
+ else
507
+ raise
508
+ end
509
+ end
510
+
511
+ unless klass < ActiveRecord::Base
512
+ raise ArgumentError, "The #{name} model class for the #{active_record}##{self.name} association is not an ActiveRecord::Base subclass."
513
+ end
514
+
515
+ klass
410
516
  end
411
517
 
412
518
  attr_reader :type, :foreign_type
@@ -416,11 +522,23 @@ module ActiveRecord
416
522
  super
417
523
  @type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
418
524
  @foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
419
- @constructable = calculate_constructable(macro, options)
525
+ @join_table = nil
526
+ @foreign_key = nil
527
+ @association_foreign_key = nil
528
+ @association_primary_key = nil
529
+ if options[:query_constraints]
530
+ ActiveRecord.deprecator.warn <<~MSG.squish
531
+ Setting `query_constraints:` option on `#{active_record}.#{macro} :#{name}` is deprecated.
532
+ To maintain current behavior, use the `foreign_key` option instead.
533
+ MSG
534
+ end
420
535
 
421
- if options[:class_name] && options[:class_name].class == Class
422
- raise ArgumentError, "A class was passed to `:class_name` but we are expecting a string."
536
+ # If the foreign key is an array, set query constraints options and don't use the foreign key
537
+ if options[:foreign_key].is_a?(Array)
538
+ options[:query_constraints] = options.delete(:foreign_key)
423
539
  end
540
+
541
+ ensure_option_not_given_as_class!(:class_name)
424
542
  end
425
543
 
426
544
  def association_scope_cache(klass, owner, &block)
@@ -428,19 +546,33 @@ module ActiveRecord
428
546
  if polymorphic?
429
547
  key = [key, owner._read_attribute(@foreign_type)]
430
548
  end
431
- klass.cached_find_by_statement(key, &block)
432
- end
433
-
434
- def constructable? # :nodoc:
435
- @constructable
549
+ klass.with_connection do |connection|
550
+ klass.cached_find_by_statement(connection, key, &block)
551
+ end
436
552
  end
437
553
 
438
554
  def join_table
439
555
  @join_table ||= -(options[:join_table]&.to_s || derive_join_table)
440
556
  end
441
557
 
442
- def foreign_key
443
- @foreign_key ||= -(options[:foreign_key]&.to_s || derive_foreign_key)
558
+ def foreign_key(infer_from_inverse_of: true)
559
+ @foreign_key ||= if options[:foreign_key]
560
+ if options[:foreign_key].is_a?(Array)
561
+ options[:foreign_key].map { |fk| fk.to_s.freeze }.freeze
562
+ else
563
+ options[:foreign_key].to_s.freeze
564
+ end
565
+ elsif options[:query_constraints]
566
+ options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze
567
+ else
568
+ derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)
569
+
570
+ if active_record.has_query_constraints?
571
+ derived_fk = derive_fk_query_constraints(derived_fk)
572
+ end
573
+
574
+ derived_fk
575
+ end
444
576
  end
445
577
 
446
578
  def association_foreign_key
@@ -452,36 +584,62 @@ module ActiveRecord
452
584
  end
453
585
 
454
586
  def active_record_primary_key
455
- @active_record_primary_key ||= -(options[:primary_key]&.to_s || primary_key(active_record))
587
+ custom_primary_key = options[:primary_key]
588
+ @active_record_primary_key ||= if custom_primary_key
589
+ if custom_primary_key.is_a?(Array)
590
+ custom_primary_key.map { |pk| pk.to_s.freeze }.freeze
591
+ else
592
+ custom_primary_key.to_s.freeze
593
+ end
594
+ elsif active_record.has_query_constraints? || options[:query_constraints]
595
+ active_record.query_constraints_list
596
+ elsif active_record.composite_primary_key?
597
+ # If active_record has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
598
+ primary_key = primary_key(active_record)
599
+ primary_key.include?("id") ? "id" : primary_key.freeze
600
+ else
601
+ primary_key(active_record).freeze
602
+ end
456
603
  end
457
604
 
458
605
  def join_primary_key(klass = nil)
459
606
  foreign_key
460
607
  end
461
608
 
609
+ def join_primary_type
610
+ type
611
+ end
612
+
462
613
  def join_foreign_key
463
614
  active_record_primary_key
464
615
  end
465
616
 
466
617
  def check_validity!
467
618
  check_validity_of_inverse!
619
+
620
+ if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
621
+ if (has_one? || collection?) && Array(active_record_primary_key).length != Array(foreign_key).length
622
+ raise CompositePrimaryKeyMismatchError.new(self)
623
+ elsif belongs_to? && Array(association_primary_key).length != Array(foreign_key).length
624
+ raise CompositePrimaryKeyMismatchError.new(self)
625
+ end
626
+ end
468
627
  end
469
628
 
470
- def check_preloadable!
629
+ def check_eager_loadable!
471
630
  return unless scope
472
631
 
473
632
  unless scope.arity == 0
474
633
  raise ArgumentError, <<-MSG.squish
475
634
  The association scope '#{name}' is instance dependent (the scope
476
- block takes an argument). Preloading instance dependent scopes is
477
- not supported.
635
+ block takes an argument). Eager loading instance dependent scopes
636
+ is not supported.
478
637
  MSG
479
638
  end
480
639
  end
481
- alias :check_eager_loadable! :check_preloadable!
482
640
 
483
641
  def join_id_for(owner) # :nodoc:
484
- owner[join_foreign_key]
642
+ Array(join_foreign_key).map { |key| owner._read_attribute(key) }
485
643
  end
486
644
 
487
645
  def through_reflection
@@ -563,8 +721,9 @@ module ActiveRecord
563
721
  options[:polymorphic]
564
722
  end
565
723
 
566
- VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
567
- INVALID_AUTOMATIC_INVERSE_OPTIONS = [:through, :foreign_key]
724
+ def polymorphic_name
725
+ active_record.polymorphic_name
726
+ end
568
727
 
569
728
  def add_as_source(seed)
570
729
  seed
@@ -583,10 +742,6 @@ module ActiveRecord
583
742
  end
584
743
 
585
744
  private
586
- def calculate_constructable(macro, options)
587
- true
588
- end
589
-
590
745
  # Attempts to find the inverse association name automatically.
591
746
  # If it cannot find a suitable inverse association name, it returns
592
747
  # +nil+.
@@ -605,14 +760,20 @@ module ActiveRecord
605
760
 
606
761
  begin
607
762
  reflection = klass._reflect_on_association(inverse_name)
608
- rescue NameError
763
+ if !reflection && active_record.automatically_invert_plural_associations
764
+ plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
765
+ reflection = klass._reflect_on_association(plural_inverse_name)
766
+ end
767
+ rescue NameError => error
768
+ raise unless error.name.to_s == class_name
769
+
609
770
  # Give up: we couldn't compute the klass type so we won't be able
610
771
  # to find any associations either.
611
772
  reflection = false
612
773
  end
613
774
 
614
775
  if valid_inverse_reflection?(reflection)
615
- inverse_name
776
+ reflection.name
616
777
  end
617
778
  end
618
779
  end
@@ -623,9 +784,10 @@ module ActiveRecord
623
784
  # with the current reflection's klass name.
624
785
  def valid_inverse_reflection?(reflection)
625
786
  reflection &&
787
+ reflection != self &&
626
788
  foreign_key == reflection.foreign_key &&
627
789
  klass <= reflection.active_record &&
628
- can_find_inverse_of_automatically?(reflection)
790
+ can_find_inverse_of_automatically?(reflection, true)
629
791
  end
630
792
 
631
793
  # Checks to see if the reflection doesn't have any options that prevent
@@ -634,14 +796,25 @@ module ActiveRecord
634
796
  # have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
635
797
  # Third, we must not have options such as <tt>:foreign_key</tt>
636
798
  # which prevent us from correctly guessing the inverse association.
637
- #
638
- # Anything with a scope can additionally ruin our attempt at finding an
639
- # inverse, so we exclude reflections with scopes.
640
- def can_find_inverse_of_automatically?(reflection)
799
+ def can_find_inverse_of_automatically?(reflection, inverse_reflection = false)
641
800
  reflection.options[:inverse_of] != false &&
642
- VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
643
- !INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
801
+ !reflection.options[:through] &&
802
+ !reflection.options[:foreign_key] &&
803
+ scope_allows_automatic_inverse_of?(reflection, inverse_reflection)
804
+ end
805
+
806
+ # Scopes on the potential inverse reflection prevent automatic
807
+ # <tt>inverse_of</tt>, since the scope could exclude the owner record
808
+ # we would inverse from. Scopes on the reflection itself allow for
809
+ # automatic <tt>inverse_of</tt> as long as
810
+ # <tt>config.active_record.automatic_scope_inversing<tt> is set to
811
+ # +true+ (the default for new applications).
812
+ def scope_allows_automatic_inverse_of?(reflection, inverse_reflection)
813
+ if inverse_reflection
644
814
  !reflection.scope
815
+ else
816
+ !reflection.scope || reflection.klass.automatic_scope_inversing
817
+ end
645
818
  end
646
819
 
647
820
  def derive_class_name
@@ -650,13 +823,55 @@ module ActiveRecord
650
823
  class_name.camelize
651
824
  end
652
825
 
653
- def derive_foreign_key
826
+ def derive_foreign_key(infer_from_inverse_of: true)
654
827
  if belongs_to?
655
828
  "#{name}_id"
656
829
  elsif options[:as]
657
830
  "#{options[:as]}_id"
831
+ elsif options[:inverse_of] && infer_from_inverse_of
832
+ inverse_of.foreign_key(infer_from_inverse_of: false)
833
+ else
834
+ active_record.model_name.to_s.foreign_key
835
+ end
836
+ end
837
+
838
+ def derive_fk_query_constraints(foreign_key)
839
+ primary_query_constraints = active_record.query_constraints_list
840
+ owner_pk = active_record.primary_key
841
+
842
+ if primary_query_constraints.size > 2
843
+ raise ArgumentError, <<~MSG.squish
844
+ The query constraints list on the `#{active_record}` model has more than 2
845
+ attributes. Active Record is unable to derive the query constraints
846
+ for the association. You need to explicitly define the query constraints
847
+ for this association.
848
+ MSG
849
+ end
850
+
851
+ if !primary_query_constraints.include?(owner_pk)
852
+ raise ArgumentError, <<~MSG.squish
853
+ The query constraints on the `#{active_record}` model does not include the primary
854
+ key so Active Record is unable to derive the foreign key constraints for
855
+ the association. You need to explicitly define the query constraints for this
856
+ association.
857
+ MSG
858
+ end
859
+
860
+ return foreign_key if primary_query_constraints.include?(foreign_key)
861
+
862
+ first_key, last_key = primary_query_constraints
863
+
864
+ if first_key == owner_pk
865
+ [foreign_key, last_key.to_s]
866
+ elsif last_key == owner_pk
867
+ [first_key.to_s, foreign_key]
658
868
  else
659
- active_record.name.foreign_key
869
+ raise ArgumentError, <<~MSG.squish
870
+ Active Record couldn't correctly interpret the query constraints
871
+ for the `#{active_record}` model. The query constraints on `#{active_record}` are
872
+ `#{primary_query_constraints}` and the foreign key is `#{foreign_key}`.
873
+ You need to explicitly set the query constraints for this association.
874
+ MSG
660
875
  end
661
876
  end
662
877
 
@@ -691,11 +906,6 @@ module ActiveRecord
691
906
  Associations::HasOneAssociation
692
907
  end
693
908
  end
694
-
695
- private
696
- def calculate_constructable(macro, options)
697
- !options[:through]
698
- end
699
909
  end
700
910
 
701
911
  class BelongsToReflection < AssociationReflection # :nodoc:
@@ -714,7 +924,17 @@ module ActiveRecord
714
924
  # klass option is necessary to support loading polymorphic associations
715
925
  def association_primary_key(klass = nil)
716
926
  if primary_key = options[:primary_key]
717
- @association_primary_key ||= -primary_key.to_s
927
+ @association_primary_key ||= if primary_key.is_a?(Array)
928
+ primary_key.map { |pk| pk.to_s.freeze }.freeze
929
+ else
930
+ -primary_key.to_s
931
+ end
932
+ elsif (klass || self.klass).has_query_constraints? || options[:query_constraints]
933
+ (klass || self.klass).composite_query_constraints_list
934
+ elsif (klass || self.klass).composite_primary_key?
935
+ # If klass has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
936
+ primary_key = (klass || self.klass).primary_key
937
+ primary_key.include?("id") ? "id" : primary_key
718
938
  else
719
939
  primary_key(klass || self.klass)
720
940
  end
@@ -733,13 +953,9 @@ module ActiveRecord
733
953
  end
734
954
 
735
955
  private
736
- def can_find_inverse_of_automatically?(_)
956
+ def can_find_inverse_of_automatically?(*)
737
957
  !polymorphic? && super
738
958
  end
739
-
740
- def calculate_constructable(macro, options)
741
- !polymorphic?
742
- end
743
959
  end
744
960
 
745
961
  class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
@@ -752,14 +968,17 @@ module ActiveRecord
752
968
 
753
969
  # Holds all the metadata about a :through association as it was specified
754
970
  # in the Active Record class.
755
- class ThroughReflection < AbstractReflection #:nodoc:
971
+ class ThroughReflection < AbstractReflection # :nodoc:
756
972
  delegate :foreign_key, :foreign_type, :association_foreign_key, :join_id_for, :type,
757
973
  :active_record_primary_key, :join_foreign_key, to: :source_reflection
758
974
 
759
975
  def initialize(delegate_reflection)
976
+ super()
760
977
  @delegate_reflection = delegate_reflection
761
978
  @klass = delegate_reflection.options[:anonymous_class]
762
979
  @source_reflection_name = delegate_reflection.options[:source]
980
+
981
+ ensure_option_not_given_as_class!(:source_type)
763
982
  end
764
983
 
765
984
  def through_reflection?
@@ -767,7 +986,7 @@ module ActiveRecord
767
986
  end
768
987
 
769
988
  def klass
770
- @klass ||= delegate_reflection.compute_class(class_name)
989
+ @klass ||= delegate_reflection.compute_class(compute_name(class_name))
771
990
  end
772
991
 
773
992
  # Returns the source of the through reflection. It checks both a singularized
@@ -788,6 +1007,8 @@ module ActiveRecord
788
1007
  # # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
789
1008
  #
790
1009
  def source_reflection
1010
+ return unless source_reflection_name
1011
+
791
1012
  through_reflection.klass._reflect_on_association(source_reflection_name)
792
1013
  end
793
1014
 
@@ -840,8 +1061,8 @@ module ActiveRecord
840
1061
  source_reflection.scopes + super
841
1062
  end
842
1063
 
843
- def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
844
- source_reflection.join_scopes(table, predicate_builder, klass) + super
1064
+ def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
1065
+ source_reflection.join_scopes(table, predicate_builder, klass, record) + super
845
1066
  end
846
1067
 
847
1068
  def has_scope?
@@ -888,24 +1109,23 @@ module ActiveRecord
888
1109
  end
889
1110
 
890
1111
  def source_reflection_name # :nodoc:
891
- return @source_reflection_name if @source_reflection_name
892
-
893
- names = [name.to_s.singularize, name].collect(&:to_sym).uniq
894
- names = names.find_all { |n|
895
- through_reflection.klass._reflect_on_association(n)
896
- }
897
-
898
- if names.length > 1
899
- raise AmbiguousSourceReflectionForThroughAssociation.new(
900
- active_record.name,
901
- macro,
902
- name,
903
- options,
904
- source_reflection_names
905
- )
1112
+ @source_reflection_name ||= begin
1113
+ names = [name.to_s.singularize, name].collect(&:to_sym).uniq
1114
+ names = names.find_all { |n|
1115
+ through_reflection.klass._reflect_on_association(n)
1116
+ }
1117
+
1118
+ if names.length > 1
1119
+ raise AmbiguousSourceReflectionForThroughAssociation.new(
1120
+ active_record.name,
1121
+ macro,
1122
+ name,
1123
+ options,
1124
+ source_reflection_names
1125
+ )
1126
+ end
1127
+ names.first
906
1128
  end
907
-
908
- @source_reflection_name = names.first
909
1129
  end
910
1130
 
911
1131
  def source_options
@@ -946,7 +1166,7 @@ module ActiveRecord
946
1166
  end
947
1167
 
948
1168
  if parent_reflection.nil?
949
- reflections = active_record.reflections.keys.map(&:to_sym)
1169
+ reflections = active_record.normalized_reflections.keys
950
1170
 
951
1171
  if reflections.index(through_reflection.name) > reflections.index(name)
952
1172
  raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
@@ -1009,13 +1229,14 @@ module ActiveRecord
1009
1229
  :name, :scope_for, to: :@reflection
1010
1230
 
1011
1231
  def initialize(reflection, previous_reflection)
1232
+ super()
1012
1233
  @reflection = reflection
1013
1234
  @previous_reflection = previous_reflection
1014
1235
  end
1015
1236
 
1016
- def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
1017
- scopes = @previous_reflection.join_scopes(table, predicate_builder) + super
1018
- scopes << build_scope(table, predicate_builder, klass).instance_exec(nil, &source_type_scope)
1237
+ def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
1238
+ scopes = @previous_reflection.join_scopes(table, predicate_builder, klass, record) + super
1239
+ scopes << build_scope(table, predicate_builder, klass).instance_exec(record, &source_type_scope)
1019
1240
  end
1020
1241
 
1021
1242
  def constraints
@@ -1034,6 +1255,7 @@ module ActiveRecord
1034
1255
  delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection
1035
1256
 
1036
1257
  def initialize(reflection, association)
1258
+ super()
1037
1259
  @reflection = reflection
1038
1260
  @association = association
1039
1261
  end