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
@@ -1,10 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mutex_m"
4
3
  require "active_support/core_ext/module/delegation"
5
4
 
6
5
  module ActiveRecord
7
6
  module Delegation # :nodoc:
7
+ class << self
8
+ def delegated_classes
9
+ [
10
+ ActiveRecord::Relation,
11
+ ActiveRecord::Associations::CollectionProxy,
12
+ ActiveRecord::AssociationRelation,
13
+ ActiveRecord::DisableJoinsAssociationRelation,
14
+ ]
15
+ end
16
+
17
+ def uncacheable_methods
18
+ @uncacheable_methods ||= (
19
+ delegated_classes.flat_map(&:public_instance_methods) - ActiveRecord::Relation.public_instance_methods
20
+ ).to_set.freeze
21
+ end
22
+ end
23
+
8
24
  module DelegateCache # :nodoc:
9
25
  def relation_delegate_class(klass)
10
26
  @relation_delegate_cache[klass]
@@ -12,11 +28,7 @@ module ActiveRecord
12
28
 
13
29
  def initialize_relation_delegate_cache
14
30
  @relation_delegate_cache = cache = {}
15
- [
16
- ActiveRecord::Relation,
17
- ActiveRecord::Associations::CollectionProxy,
18
- ActiveRecord::AssociationRelation
19
- ].each do |klass|
31
+ Delegation.delegated_classes.each do |klass|
20
32
  delegate = Class.new(klass) {
21
33
  include ClassSpecificRelation
22
34
  }
@@ -54,24 +66,22 @@ module ActiveRecord
54
66
  end
55
67
 
56
68
  class GeneratedRelationMethods < Module # :nodoc:
57
- include Mutex_m
69
+ MUTEX = Mutex.new
58
70
 
59
71
  def generate_method(method)
60
- synchronize do
72
+ MUTEX.synchronize do
61
73
  return if method_defined?(method)
62
74
 
63
- if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !DELEGATION_RESERVED_METHOD_NAMES.include?(method.to_s)
64
- definition = RUBY_VERSION >= "2.7" ? "..." : "*args, &block"
75
+ if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
65
76
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
66
- def #{method}(#{definition})
67
- scoping { klass.#{method}(#{definition}) }
77
+ def #{method}(...)
78
+ scoping { klass.#{method}(...) }
68
79
  end
69
80
  RUBY
70
81
  else
71
- define_method(method) do |*args, &block|
72
- scoping { klass.public_send(method, *args, &block) }
82
+ define_method(method) do |*args, **kwargs, &block|
83
+ scoping { klass.public_send(method, *args, **kwargs, &block) }
73
84
  end
74
- ruby2_keywords(method) if respond_to?(:ruby2_keywords, true)
75
85
  end
76
86
  end
77
87
  end
@@ -85,12 +95,12 @@ module ActiveRecord
85
95
  # may vary depending on the klass of a relation, so we create a subclass of Relation
86
96
  # for each different klass, and the delegations are compiled into that subclass only.
87
97
 
88
- delegate :to_xml, :encode_with, :length, :each, :join,
98
+ delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
89
99
  :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
90
- :to_sentence, :to_formatted_s, :as_json,
100
+ :to_sentence, :to_fs, :to_formatted_s, :as_json,
91
101
  :shuffle, :split, :slice, :index, :rindex, to: :records
92
102
 
93
- delegate :primary_key, :connection, to: :klass
103
+ delegate :primary_key, :lease_connection, :connection, :with_connection, :transaction, to: :klass
94
104
 
95
105
  module ClassSpecificRelation # :nodoc:
96
106
  extend ActiveSupport::Concern
@@ -102,15 +112,16 @@ module ActiveRecord
102
112
  end
103
113
 
104
114
  private
105
- def method_missing(method, *args, &block)
115
+ def method_missing(method, ...)
106
116
  if @klass.respond_to?(method)
107
- @klass.generate_relation_method(method)
108
- scoping { @klass.public_send(method, *args, &block) }
117
+ unless Delegation.uncacheable_methods.include?(method)
118
+ @klass.generate_relation_method(method)
119
+ end
120
+ scoping { @klass.public_send(method, ...) }
109
121
  else
110
122
  super
111
123
  end
112
124
  end
113
- ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
114
125
  end
115
126
 
116
127
  module ClassMethods # :nodoc:
@@ -6,7 +6,9 @@ module ActiveRecord
6
6
  module FinderMethods
7
7
  ONE_AS_ONE = "1 AS one"
8
8
 
9
- # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
9
+ # Find by id - This can either be a specific id (ID), a list of ids (ID, ID, ID), or an array of ids ([ID, ID, ID]).
10
+ # `ID` refers to an "identifier". For models with a single-column primary key, `ID` will be a single value,
11
+ # and for models with a composite primary key, it will be an array of values.
10
12
  # If one or more records cannot be found for the requested ids, then ActiveRecord::RecordNotFound will be raised.
11
13
  # If the primary key is an integer, find by id coerces its arguments by using +to_i+.
12
14
  #
@@ -14,10 +16,31 @@ module ActiveRecord
14
16
  # Person.find("1") # returns the object for ID = 1
15
17
  # Person.find("31-sarah") # returns the object for ID = 31
16
18
  # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
17
- # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
19
+ # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17), or with composite primary key [7, 17]
18
20
  # Person.find([1]) # returns an array for the object with ID = 1
19
21
  # Person.where("administrator = 1").order("created_on DESC").find(1)
20
22
  #
23
+ # ==== Find a record for a composite primary key model
24
+ # TravelRoute.primary_key = [:origin, :destination]
25
+ #
26
+ # TravelRoute.find(["Ottawa", "London"])
27
+ # => #<TravelRoute origin: "Ottawa", destination: "London">
28
+ #
29
+ # TravelRoute.find([["Paris", "Montreal"]])
30
+ # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
31
+ #
32
+ # TravelRoute.find(["New York", "Las Vegas"], ["New York", "Portland"])
33
+ # => [
34
+ # #<TravelRoute origin: "New York", destination: "Las Vegas">,
35
+ # #<TravelRoute origin: "New York", destination: "Portland">
36
+ # ]
37
+ #
38
+ # TravelRoute.find([["Berlin", "London"], ["Barcelona", "Lisbon"]])
39
+ # => [
40
+ # #<TravelRoute origin: "Berlin", destination: "London">,
41
+ # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
42
+ # ]
43
+ #
21
44
  # NOTE: The returned records are in the same order as the ids you provide.
22
45
  # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
23
46
  # method and provide an explicit ActiveRecord::QueryMethods#order option.
@@ -64,6 +87,14 @@ module ActiveRecord
64
87
  #
65
88
  # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
66
89
  # # returns an Array of the required fields.
90
+ #
91
+ # ==== Edge Cases
92
+ #
93
+ # Person.find(37) # raises ActiveRecord::RecordNotFound exception if the record with the given ID does not exist.
94
+ # Person.find([37]) # raises ActiveRecord::RecordNotFound exception if the record with the given ID in the input array does not exist.
95
+ # Person.find(nil) # raises ActiveRecord::RecordNotFound exception if the argument is nil.
96
+ # Person.find([]) # returns an empty array if the argument is an empty array.
97
+ # Person.find # raises ActiveRecord::RecordNotFound exception if the argument is not provided.
67
98
  def find(*args)
68
99
  return super if block_given?
69
100
  find_with_ids(*args)
@@ -104,6 +135,32 @@ module ActiveRecord
104
135
  take || raise_record_not_found_exception!
105
136
  end
106
137
 
138
+ # Finds the sole matching record. Raises ActiveRecord::RecordNotFound if no
139
+ # record is found. Raises ActiveRecord::SoleRecordExceeded if more than one
140
+ # record is found.
141
+ #
142
+ # Product.where(["price = %?", price]).sole
143
+ def sole
144
+ found, undesired = first(2)
145
+
146
+ if found.nil?
147
+ raise_record_not_found_exception!
148
+ elsif undesired.present?
149
+ raise ActiveRecord::SoleRecordExceeded.new(self)
150
+ else
151
+ found
152
+ end
153
+ end
154
+
155
+ # Finds the sole matching record. Raises ActiveRecord::RecordNotFound if no
156
+ # record is found. Raises ActiveRecord::SoleRecordExceeded if more than one
157
+ # record is found.
158
+ #
159
+ # Product.find_sole_by(["price = %?", price])
160
+ def find_sole_by(arg, *args)
161
+ where(arg, *args).sole
162
+ end
163
+
107
164
  # Find the first record (or first N records if a parameter is supplied).
108
165
  # If no order is defined it will order by primary key.
109
166
  #
@@ -114,8 +171,6 @@ module ActiveRecord
114
171
  # Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3
115
172
  #
116
173
  def first(limit = nil)
117
- check_reorder_deprecation unless loaded?
118
-
119
174
  if limit
120
175
  find_nth_with_limit(0, limit)
121
176
  else
@@ -300,6 +355,8 @@ module ActiveRecord
300
355
  # Person.exists?
301
356
  # Person.where(name: 'Spartacus', rating: 4).exists?
302
357
  def exists?(conditions = :none)
358
+ return false if @none
359
+
303
360
  if Base === conditions
304
361
  raise ArgumentError, <<-MSG.squish
305
362
  You are passing an instance of ActiveRecord::Base to `exists?`.
@@ -317,7 +374,11 @@ module ActiveRecord
317
374
  relation = construct_relation_for_exists(conditions)
318
375
  return false if relation.where_clause.contradiction?
319
376
 
320
- skip_query_cache_if_necessary { connection.select_rows(relation.arel, "#{name} Exists?").size == 1 }
377
+ skip_query_cache_if_necessary do
378
+ with_connection do |c|
379
+ c.select_rows(relation.arel, "#{name} Exists?").size == 1
380
+ end
381
+ end
321
382
  end
322
383
 
323
384
  # Returns true if the relation contains the given record or false otherwise.
@@ -326,10 +387,20 @@ module ActiveRecord
326
387
  # compared to the records in memory. If the relation is unloaded, an
327
388
  # efficient existence query is performed, as in #exists?.
328
389
  def include?(record)
390
+ # The existing implementation relies on receiving an Active Record instance as the input parameter named record.
391
+ # Any non-Active Record object passed to this implementation is guaranteed to return `false`.
392
+ return false unless record.is_a?(klass)
393
+
329
394
  if loaded? || offset_value || limit_value || having_clause.any?
330
395
  records.include?(record)
331
396
  else
332
- record.is_a?(klass) && exists?(record.id)
397
+ id = if record.class.composite_primary_key?
398
+ record.class.primary_key.zip(record.id).to_h
399
+ else
400
+ record.id
401
+ end
402
+
403
+ exists?(id)
333
404
  end
334
405
  end
335
406
 
@@ -364,17 +435,6 @@ module ActiveRecord
364
435
  end
365
436
 
366
437
  private
367
- def check_reorder_deprecation
368
- if !order_values.empty? && order_values.all?(&:blank?)
369
- blank_value = order_values.first
370
- ActiveSupport::Deprecation.warn(<<~MSG.squish)
371
- `.reorder(#{blank_value.inspect})` with `.first` / `.first!` no longer
372
- takes non-deterministic result in Rails 7.0.
373
- To continue taking non-deterministic result, use `.take` / `.take!` instead.
374
- MSG
375
- end
376
- end
377
-
378
438
  def construct_relation_for_exists(conditions)
379
439
  conditions = sanitize_forbidden_attributes(conditions)
380
440
 
@@ -400,7 +460,7 @@ module ActiveRecord
400
460
  )
401
461
  relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
402
462
 
403
- if eager_loading && !(
463
+ if eager_loading && has_limit_or_offset? && !(
404
464
  using_limitable_reflections?(join_dependency.reflections) &&
405
465
  using_limitable_reflections?(
406
466
  construct_join_dependency(
@@ -409,12 +469,12 @@ module ActiveRecord
409
469
  ), nil
410
470
  ).reflections
411
471
  )
412
- )
413
- if has_limit_or_offset?
414
- limited_ids = limited_ids_for(relation)
415
- limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
472
+ )
473
+ relation = skip_query_cache_if_necessary do
474
+ klass.with_connection do |c|
475
+ c.distinct_relation_for_primary_key(relation)
476
+ end
416
477
  end
417
- relation.limit_value = relation.offset_value = nil
418
478
  end
419
479
 
420
480
  if block_given?
@@ -424,18 +484,6 @@ module ActiveRecord
424
484
  end
425
485
  end
426
486
 
427
- def limited_ids_for(relation)
428
- values = @klass.connection.columns_for_distinct(
429
- connection.visitor.compile(table[primary_key]),
430
- relation.order_values
431
- )
432
-
433
- relation = relation.except(:select).select(values).distinct!
434
-
435
- id_rows = skip_query_cache_if_necessary { @klass.connection.select_rows(relation.arel, "SQL") }
436
- id_rows.map(&:last)
437
- end
438
-
439
487
  def using_limitable_reflections?(reflections)
440
488
  reflections.none?(&:collection?)
441
489
  end
@@ -443,10 +491,17 @@ module ActiveRecord
443
491
  def find_with_ids(*ids)
444
492
  raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
445
493
 
446
- expects_array = ids.first.kind_of?(Array)
494
+ expects_array = if klass.composite_primary_key?
495
+ ids.first.first.is_a?(Array)
496
+ else
497
+ ids.first.is_a?(Array)
498
+ end
499
+
447
500
  return [] if expects_array && ids.first.empty?
448
501
 
449
- ids = ids.flatten.compact.uniq
502
+ ids = ids.first if expects_array
503
+
504
+ ids = ids.compact.uniq
450
505
 
451
506
  model_name = @klass.name
452
507
 
@@ -470,7 +525,12 @@ module ActiveRecord
470
525
  MSG
471
526
  end
472
527
 
473
- relation = where(primary_key => id)
528
+ relation = if klass.composite_primary_key?
529
+ where(primary_key.zip(id).to_h)
530
+ else
531
+ where(primary_key => id)
532
+ end
533
+
474
534
  record = relation.take
475
535
 
476
536
  raise_record_not_found_exception!(id, 0, 1) unless record
@@ -481,7 +541,9 @@ module ActiveRecord
481
541
  def find_some(ids)
482
542
  return find_some_ordered(ids) unless order_values.present?
483
543
 
484
- result = where(primary_key => ids).to_a
544
+ relation = where(primary_key => ids)
545
+ relation = relation.select(table[primary_key]) unless select_values.empty?
546
+ result = relation.to_a
485
547
 
486
548
  expected_size =
487
549
  if limit_value && ids.size > limit_value
@@ -505,13 +567,13 @@ module ActiveRecord
505
567
  def find_some_ordered(ids)
506
568
  ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
507
569
 
508
- result = except(:limit, :offset).where(primary_key => ids).records
570
+ relation = except(:limit, :offset)
571
+ relation = relation.where(primary_key => ids)
572
+ relation = relation.select(table[primary_key]) unless select_values.empty?
573
+ result = relation.records
509
574
 
510
575
  if result.size == ids.size
511
- pk_type = @klass.type_for_attribute(primary_key)
512
-
513
- records_by_id = result.index_by(&:id)
514
- ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
576
+ result.in_order_of(:id, ids.map { |id| @klass.type_for_attribute(primary_key).cast(id) })
515
577
  else
516
578
  raise_record_not_found_exception!(ids, result.size, ids.size)
517
579
  end
@@ -563,10 +625,10 @@ module ActiveRecord
563
625
  else
564
626
  relation = ordered_relation
565
627
 
566
- if equal?(relation) || has_limit_or_offset?
628
+ if relation.order_values.empty? || relation.has_limit_or_offset?
567
629
  relation.records[-index]
568
630
  else
569
- relation.last(index)[-index]
631
+ relation.reverse_order.offset(index - 1).first
570
632
  end
571
633
  end
572
634
  end
@@ -576,15 +638,24 @@ module ActiveRecord
576
638
  end
577
639
 
578
640
  def ordered_relation
579
- if order_values.empty? && (implicit_order_column || primary_key)
580
- if implicit_order_column && primary_key && implicit_order_column != primary_key
581
- order(table[implicit_order_column].asc, table[primary_key].asc)
582
- else
583
- order(table[implicit_order_column || primary_key].asc)
584
- end
641
+ if order_values.empty? && (implicit_order_column || !query_constraints_list.nil? || primary_key)
642
+ order(_order_columns.map { |column| table[column].asc })
585
643
  else
586
644
  self
587
645
  end
588
646
  end
647
+
648
+ def _order_columns
649
+ oc = []
650
+
651
+ oc << implicit_order_column if implicit_order_column
652
+ oc << query_constraints_list if query_constraints_list
653
+
654
+ if primary_key && query_constraints_list.nil?
655
+ oc << primary_key
656
+ end
657
+
658
+ oc.flatten.uniq.compact
659
+ end
589
660
  end
590
661
  end
@@ -7,16 +7,15 @@ module ActiveRecord
7
7
  class HashMerger # :nodoc:
8
8
  attr_reader :relation, :hash
9
9
 
10
- def initialize(relation, hash, rewhere = nil)
10
+ def initialize(relation, hash)
11
11
  hash.assert_valid_keys(*Relation::VALUE_METHODS)
12
12
 
13
13
  @relation = relation
14
14
  @hash = hash
15
- @rewhere = rewhere
16
15
  end
17
16
 
18
17
  def merge
19
- Merger.new(relation, other, @rewhere).merge
18
+ Merger.new(relation, other).merge
20
19
  end
21
20
 
22
21
  # Applying values to a relation has some side effects. E.g.
@@ -44,37 +43,33 @@ module ActiveRecord
44
43
  class Merger # :nodoc:
45
44
  attr_reader :relation, :values, :other
46
45
 
47
- def initialize(relation, other, rewhere = nil)
46
+ def initialize(relation, other)
48
47
  @relation = relation
49
48
  @values = other.values
50
49
  @other = other
51
- @rewhere = rewhere
52
50
  end
53
51
 
54
- NORMAL_VALUES = Relation::VALUE_METHODS -
55
- Relation::CLAUSE_METHODS -
56
- [:includes, :preload, :joins, :left_outer_joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
57
-
58
- def normal_values
59
- NORMAL_VALUES
60
- end
52
+ NORMAL_VALUES = Relation::VALUE_METHODS - Relation::CLAUSE_METHODS -
53
+ [
54
+ :select, :includes, :preload, :joins, :left_outer_joins,
55
+ :order, :reverse_order, :lock, :create_with, :reordering
56
+ ]
61
57
 
62
58
  def merge
63
- normal_values.each do |name|
59
+ NORMAL_VALUES.each do |name|
64
60
  value = values[name]
65
61
  # The unless clause is here mostly for performance reasons (since the `send` call might be moderately
66
62
  # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
67
63
  # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
68
64
  # don't fall through the cracks.
69
65
  unless value.nil? || (value.blank? && false != value)
70
- if name == :select
71
- relation._select!(*value)
72
- else
73
- relation.public_send("#{name}!", *value)
74
- end
66
+ relation.public_send(:"#{name}!", *value)
75
67
  end
76
68
  end
77
69
 
70
+ relation.none! if other.null_relation?
71
+
72
+ merge_select_values
78
73
  merge_multi_values
79
74
  merge_single_values
80
75
  merge_clauses
@@ -86,6 +81,18 @@ module ActiveRecord
86
81
  end
87
82
 
88
83
  private
84
+ def merge_select_values
85
+ return if other.select_values.empty?
86
+
87
+ if other.klass == relation.klass
88
+ relation.select_values |= other.select_values
89
+ else
90
+ relation.select_values |= other.instance_eval do
91
+ arel_columns(select_values)
92
+ end
93
+ end
94
+ end
95
+
89
96
  def merge_preloads
90
97
  return if other.preload_values.empty? && other.includes_values.empty?
91
98
 
@@ -169,7 +176,7 @@ module ActiveRecord
169
176
  def merge_clauses
170
177
  relation.from_clause = other.from_clause if replace_from_clause?
171
178
 
172
- where_clause = relation.where_clause.merge(other.where_clause, @rewhere)
179
+ where_clause = relation.where_clause.merge(other.where_clause)
173
180
  relation.where_clause = where_clause unless where_clause.empty?
174
181
 
175
182
  having_clause = relation.having_clause.merge(other.having_clause)
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  return attribute.in([]) if value.empty?
14
14
 
15
15
  values = value.map { |x| x.is_a?(Base) ? x.id : x }
16
- nils = values.extract!(&:nil?)
16
+ nils = values.compact!
17
17
  ranges = values.extract! { |v| v.is_a?(Range) }
18
18
 
19
19
  values_predicate =
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  else Arel::Nodes::HomogeneousIn.new(values, attribute, :in)
24
24
  end
25
25
 
26
- unless nils.empty?
26
+ if nils
27
27
  values_predicate = values_predicate.or(attribute.eq(nil))
28
28
  end
29
29
 
@@ -9,7 +9,14 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  def queries
12
- [ associated_table.join_foreign_key => ids ]
12
+ if associated_table.join_foreign_key.is_a?(Array)
13
+ id_list = ids
14
+ id_list = id_list.pluck(primary_key) if id_list.is_a?(Relation)
15
+
16
+ id_list.map { |ids_set| associated_table.join_foreign_key.zip(ids_set).to_h }
17
+ else
18
+ [ associated_table.join_foreign_key => ids ]
19
+ end
13
20
  end
14
21
 
15
22
  private
@@ -18,11 +25,14 @@ module ActiveRecord
18
25
  def ids
19
26
  case value
20
27
  when Relation
21
- value.select_values.empty? ? value.select(primary_key) : value
28
+ relation = value
29
+ relation = relation.select(primary_key) if select_clause?
30
+ relation = relation.where(primary_type => polymorphic_name) if polymorphic_clause?
31
+ relation
22
32
  when Array
23
33
  value.map { |v| convert_to_id(v) }
24
34
  else
25
- convert_to_id(value)
35
+ [convert_to_id(value)]
26
36
  end
27
37
  end
28
38
 
@@ -30,8 +40,32 @@ module ActiveRecord
30
40
  associated_table.join_primary_key
31
41
  end
32
42
 
43
+ def primary_type
44
+ associated_table.join_primary_type
45
+ end
46
+
47
+ def polymorphic_name
48
+ associated_table.polymorphic_name_association
49
+ end
50
+
51
+ def select_clause?
52
+ value.select_values.empty?
53
+ end
54
+
55
+ def polymorphic_clause?
56
+ primary_type && !value.where_values_hash.has_key?(primary_type)
57
+ end
58
+
33
59
  def convert_to_id(value)
34
- if value.respond_to?(primary_key)
60
+ if primary_key.is_a?(Array)
61
+ primary_key.map do |attribute|
62
+ if attribute == "id"
63
+ value.id_value
64
+ else
65
+ value.public_send(attribute)
66
+ end
67
+ end
68
+ elsif value.respond_to?(primary_key)
35
69
  value.public_send(primary_key)
36
70
  else
37
71
  value
@@ -34,19 +34,22 @@ module ActiveRecord
34
34
  end
35
35
 
36
36
  def klass(value)
37
- case value
38
- when Base
37
+ if value.is_a?(Base)
39
38
  value.class
40
- when Relation
39
+ elsif value.is_a?(Relation)
41
40
  value.klass
42
41
  end
43
42
  end
44
43
 
45
44
  def convert_to_id(value)
46
- case value
47
- when Base
48
- value._read_attribute(primary_key(value))
49
- when Relation
45
+ if value.is_a?(Base)
46
+ primary_key = primary_key(value)
47
+ if primary_key.is_a?(Array)
48
+ primary_key.map { |column| value._read_attribute(column) }
49
+ else
50
+ value._read_attribute(primary_key)
51
+ end
52
+ elsif value.is_a?(Relation)
50
53
  value.select(primary_key(value))
51
54
  else
52
55
  value
@@ -9,7 +9,11 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  if value.select_values.empty?
12
- value = value.select(value.table[value.klass.primary_key])
12
+ if value.klass.composite_primary_key?
13
+ raise ArgumentError, "Cannot map composite primary key #{value.klass.primary_key} to #{attribute.name}"
14
+ else
15
+ value = value.select(value.table[value.klass.primary_key])
16
+ end
13
17
  end
14
18
 
15
19
  attribute.in(value.arel)