omg-activerecord 8.0.0.alpha1

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 (412) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +355 -0
  3. data/MIT-LICENSE +22 -0
  4. data/README.rdoc +219 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record/aggregations.rb +287 -0
  8. data/lib/active_record/association_relation.rb +50 -0
  9. data/lib/active_record/associations/alias_tracker.rb +90 -0
  10. data/lib/active_record/associations/association.rb +417 -0
  11. data/lib/active_record/associations/association_scope.rb +175 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +163 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  14. data/lib/active_record/associations/builder/association.rb +170 -0
  15. data/lib/active_record/associations/builder/belongs_to.rb +160 -0
  16. data/lib/active_record/associations/builder/collection_association.rb +80 -0
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -0
  18. data/lib/active_record/associations/builder/has_many.rb +23 -0
  19. data/lib/active_record/associations/builder/has_one.rb +61 -0
  20. data/lib/active_record/associations/builder/singular_association.rb +48 -0
  21. data/lib/active_record/associations/collection_association.rb +535 -0
  22. data/lib/active_record/associations/collection_proxy.rb +1163 -0
  23. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  24. data/lib/active_record/associations/errors.rb +265 -0
  25. data/lib/active_record/associations/foreign_association.rb +40 -0
  26. data/lib/active_record/associations/has_many_association.rb +167 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +232 -0
  28. data/lib/active_record/associations/has_one_association.rb +142 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +106 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  33. data/lib/active_record/associations/join_dependency.rb +301 -0
  34. data/lib/active_record/associations/nested_error.rb +47 -0
  35. data/lib/active_record/associations/preloader/association.rb +316 -0
  36. data/lib/active_record/associations/preloader/batch.rb +48 -0
  37. data/lib/active_record/associations/preloader/branch.rb +153 -0
  38. data/lib/active_record/associations/preloader/through_association.rb +150 -0
  39. data/lib/active_record/associations/preloader.rb +135 -0
  40. data/lib/active_record/associations/singular_association.rb +76 -0
  41. data/lib/active_record/associations/through_association.rb +132 -0
  42. data/lib/active_record/associations.rb +1897 -0
  43. data/lib/active_record/asynchronous_queries_tracker.rb +64 -0
  44. data/lib/active_record/attribute_assignment.rb +82 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +106 -0
  46. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  47. data/lib/active_record/attribute_methods/dirty.rb +262 -0
  48. data/lib/active_record/attribute_methods/primary_key.rb +158 -0
  49. data/lib/active_record/attribute_methods/query.rb +50 -0
  50. data/lib/active_record/attribute_methods/read.rb +46 -0
  51. data/lib/active_record/attribute_methods/serialization.rb +232 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +94 -0
  53. data/lib/active_record/attribute_methods/write.rb +49 -0
  54. data/lib/active_record/attribute_methods.rb +542 -0
  55. data/lib/active_record/attributes.rb +307 -0
  56. data/lib/active_record/autosave_association.rb +586 -0
  57. data/lib/active_record/base.rb +338 -0
  58. data/lib/active_record/callbacks.rb +452 -0
  59. data/lib/active_record/coders/column_serializer.rb +61 -0
  60. data/lib/active_record/coders/json.rb +15 -0
  61. data/lib/active_record/coders/yaml_column.rb +95 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +290 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +210 -0
  64. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +923 -0
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +31 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +747 -0
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +319 -0
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +239 -0
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +24 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +190 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +961 -0
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +106 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1883 -0
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +676 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +1218 -0
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1016 -0
  78. data/lib/active_record/connection_adapters/column.rb +122 -0
  79. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  80. data/lib/active_record/connection_adapters/mysql/column.rb +28 -0
  81. data/lib/active_record/connection_adapters/mysql/database_statements.rb +95 -0
  82. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
  83. data/lib/active_record/connection_adapters/mysql/quoting.rb +114 -0
  84. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +106 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +106 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +97 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +300 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
  89. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +96 -0
  90. data/lib/active_record/connection_adapters/mysql2_adapter.rb +196 -0
  91. data/lib/active_record/connection_adapters/pool_config.rb +83 -0
  92. data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
  93. data/lib/active_record/connection_adapters/postgresql/column.rb +82 -0
  94. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +231 -0
  95. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +91 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +54 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +31 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +20 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +109 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +42 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +74 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +124 -0
  115. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  116. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  117. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  118. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +125 -0
  119. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +45 -0
  120. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  121. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  122. data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -0
  123. data/lib/active_record/connection_adapters/postgresql/quoting.rb +238 -0
  124. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +71 -0
  125. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +169 -0
  126. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +392 -0
  127. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +127 -0
  128. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1162 -0
  129. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
  130. data/lib/active_record/connection_adapters/postgresql/utils.rb +79 -0
  131. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1182 -0
  132. data/lib/active_record/connection_adapters/schema_cache.rb +478 -0
  133. data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
  134. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  135. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +145 -0
  136. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  137. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +116 -0
  138. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +37 -0
  139. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +39 -0
  140. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +47 -0
  141. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +221 -0
  142. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +843 -0
  143. data/lib/active_record/connection_adapters/statement_pool.rb +67 -0
  144. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +69 -0
  145. data/lib/active_record/connection_adapters/trilogy_adapter.rb +212 -0
  146. data/lib/active_record/connection_adapters.rb +176 -0
  147. data/lib/active_record/connection_handling.rb +413 -0
  148. data/lib/active_record/core.rb +836 -0
  149. data/lib/active_record/counter_cache.rb +230 -0
  150. data/lib/active_record/database_configurations/connection_url_resolver.rb +105 -0
  151. data/lib/active_record/database_configurations/database_config.rb +104 -0
  152. data/lib/active_record/database_configurations/hash_config.rb +172 -0
  153. data/lib/active_record/database_configurations/url_config.rb +78 -0
  154. data/lib/active_record/database_configurations.rb +309 -0
  155. data/lib/active_record/delegated_type.rb +289 -0
  156. data/lib/active_record/deprecator.rb +7 -0
  157. data/lib/active_record/destroy_association_async_job.rb +38 -0
  158. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  159. data/lib/active_record/dynamic_matchers.rb +121 -0
  160. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  161. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  162. data/lib/active_record/encryption/cipher.rb +53 -0
  163. data/lib/active_record/encryption/config.rb +70 -0
  164. data/lib/active_record/encryption/configurable.rb +60 -0
  165. data/lib/active_record/encryption/context.rb +42 -0
  166. data/lib/active_record/encryption/contexts.rb +76 -0
  167. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  168. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  169. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  170. data/lib/active_record/encryption/encrypted_attribute_type.rb +184 -0
  171. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  172. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  173. data/lib/active_record/encryption/encryptor.rb +177 -0
  174. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  175. data/lib/active_record/encryption/errors.rb +15 -0
  176. data/lib/active_record/encryption/extended_deterministic_queries.rb +159 -0
  177. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  178. data/lib/active_record/encryption/key.rb +28 -0
  179. data/lib/active_record/encryption/key_generator.rb +53 -0
  180. data/lib/active_record/encryption/key_provider.rb +46 -0
  181. data/lib/active_record/encryption/message.rb +33 -0
  182. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  183. data/lib/active_record/encryption/message_serializer.rb +96 -0
  184. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  185. data/lib/active_record/encryption/properties.rb +76 -0
  186. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  187. data/lib/active_record/encryption/scheme.rb +107 -0
  188. data/lib/active_record/encryption.rb +58 -0
  189. data/lib/active_record/enum.rb +424 -0
  190. data/lib/active_record/errors.rb +614 -0
  191. data/lib/active_record/explain.rb +63 -0
  192. data/lib/active_record/explain_registry.rb +37 -0
  193. data/lib/active_record/explain_subscriber.rb +34 -0
  194. data/lib/active_record/fixture_set/file.rb +89 -0
  195. data/lib/active_record/fixture_set/model_metadata.rb +42 -0
  196. data/lib/active_record/fixture_set/render_context.rb +19 -0
  197. data/lib/active_record/fixture_set/table_row.rb +208 -0
  198. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  199. data/lib/active_record/fixtures.rb +850 -0
  200. data/lib/active_record/future_result.rb +182 -0
  201. data/lib/active_record/gem_version.rb +17 -0
  202. data/lib/active_record/inheritance.rb +366 -0
  203. data/lib/active_record/insert_all.rb +328 -0
  204. data/lib/active_record/integration.rb +209 -0
  205. data/lib/active_record/internal_metadata.rb +164 -0
  206. data/lib/active_record/legacy_yaml_adapter.rb +15 -0
  207. data/lib/active_record/locale/en.yml +48 -0
  208. data/lib/active_record/locking/optimistic.rb +228 -0
  209. data/lib/active_record/locking/pessimistic.rb +102 -0
  210. data/lib/active_record/log_subscriber.rb +149 -0
  211. data/lib/active_record/marshalling.rb +56 -0
  212. data/lib/active_record/message_pack.rb +124 -0
  213. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  214. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  215. data/lib/active_record/middleware/database_selector.rb +87 -0
  216. data/lib/active_record/middleware/shard_selector.rb +62 -0
  217. data/lib/active_record/migration/command_recorder.rb +406 -0
  218. data/lib/active_record/migration/compatibility.rb +490 -0
  219. data/lib/active_record/migration/default_strategy.rb +22 -0
  220. data/lib/active_record/migration/execution_strategy.rb +19 -0
  221. data/lib/active_record/migration/join_table.rb +16 -0
  222. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  223. data/lib/active_record/migration.rb +1626 -0
  224. data/lib/active_record/model_schema.rb +635 -0
  225. data/lib/active_record/nested_attributes.rb +633 -0
  226. data/lib/active_record/no_touching.rb +65 -0
  227. data/lib/active_record/normalization.rb +163 -0
  228. data/lib/active_record/persistence.rb +968 -0
  229. data/lib/active_record/promise.rb +84 -0
  230. data/lib/active_record/query_cache.rb +56 -0
  231. data/lib/active_record/query_logs.rb +247 -0
  232. data/lib/active_record/query_logs_formatter.rb +30 -0
  233. data/lib/active_record/querying.rb +122 -0
  234. data/lib/active_record/railtie.rb +440 -0
  235. data/lib/active_record/railties/console_sandbox.rb +5 -0
  236. data/lib/active_record/railties/controller_runtime.rb +65 -0
  237. data/lib/active_record/railties/databases.rake +641 -0
  238. data/lib/active_record/railties/job_runtime.rb +23 -0
  239. data/lib/active_record/readonly_attributes.rb +66 -0
  240. data/lib/active_record/reflection.rb +1287 -0
  241. data/lib/active_record/relation/batches/batch_enumerator.rb +115 -0
  242. data/lib/active_record/relation/batches.rb +491 -0
  243. data/lib/active_record/relation/calculations.rb +679 -0
  244. data/lib/active_record/relation/delegation.rb +154 -0
  245. data/lib/active_record/relation/finder_methods.rb +661 -0
  246. data/lib/active_record/relation/from_clause.rb +30 -0
  247. data/lib/active_record/relation/merger.rb +192 -0
  248. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  249. data/lib/active_record/relation/predicate_builder/association_query_value.rb +76 -0
  250. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  251. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +60 -0
  252. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  253. data/lib/active_record/relation/predicate_builder/relation_handler.rb +24 -0
  254. data/lib/active_record/relation/predicate_builder.rb +181 -0
  255. data/lib/active_record/relation/query_attribute.rb +68 -0
  256. data/lib/active_record/relation/query_methods.rb +2235 -0
  257. data/lib/active_record/relation/record_fetch_warning.rb +52 -0
  258. data/lib/active_record/relation/spawn_methods.rb +78 -0
  259. data/lib/active_record/relation/where_clause.rb +218 -0
  260. data/lib/active_record/relation.rb +1495 -0
  261. data/lib/active_record/result.rb +249 -0
  262. data/lib/active_record/runtime_registry.rb +82 -0
  263. data/lib/active_record/sanitization.rb +254 -0
  264. data/lib/active_record/schema.rb +77 -0
  265. data/lib/active_record/schema_dumper.rb +364 -0
  266. data/lib/active_record/schema_migration.rb +106 -0
  267. data/lib/active_record/scoping/default.rb +205 -0
  268. data/lib/active_record/scoping/named.rb +202 -0
  269. data/lib/active_record/scoping.rb +136 -0
  270. data/lib/active_record/secure_password.rb +60 -0
  271. data/lib/active_record/secure_token.rb +66 -0
  272. data/lib/active_record/serialization.rb +29 -0
  273. data/lib/active_record/signed_id.rb +137 -0
  274. data/lib/active_record/statement_cache.rb +164 -0
  275. data/lib/active_record/store.rb +299 -0
  276. data/lib/active_record/suppressor.rb +59 -0
  277. data/lib/active_record/table_metadata.rb +85 -0
  278. data/lib/active_record/tasks/database_tasks.rb +681 -0
  279. data/lib/active_record/tasks/mysql_database_tasks.rb +120 -0
  280. data/lib/active_record/tasks/postgresql_database_tasks.rb +147 -0
  281. data/lib/active_record/tasks/sqlite_database_tasks.rb +89 -0
  282. data/lib/active_record/test_databases.rb +24 -0
  283. data/lib/active_record/test_fixtures.rb +321 -0
  284. data/lib/active_record/testing/query_assertions.rb +121 -0
  285. data/lib/active_record/timestamp.rb +177 -0
  286. data/lib/active_record/token_for.rb +123 -0
  287. data/lib/active_record/touch_later.rb +70 -0
  288. data/lib/active_record/transaction.rb +132 -0
  289. data/lib/active_record/transactions.rb +523 -0
  290. data/lib/active_record/translation.rb +22 -0
  291. data/lib/active_record/type/adapter_specific_registry.rb +144 -0
  292. data/lib/active_record/type/date.rb +9 -0
  293. data/lib/active_record/type/date_time.rb +9 -0
  294. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  295. data/lib/active_record/type/hash_lookup_type_map.rb +57 -0
  296. data/lib/active_record/type/internal/timezone.rb +22 -0
  297. data/lib/active_record/type/json.rb +30 -0
  298. data/lib/active_record/type/serialized.rb +76 -0
  299. data/lib/active_record/type/text.rb +11 -0
  300. data/lib/active_record/type/time.rb +35 -0
  301. data/lib/active_record/type/type_map.rb +58 -0
  302. data/lib/active_record/type/unsigned_integer.rb +16 -0
  303. data/lib/active_record/type.rb +83 -0
  304. data/lib/active_record/type_caster/connection.rb +33 -0
  305. data/lib/active_record/type_caster/map.rb +23 -0
  306. data/lib/active_record/type_caster.rb +9 -0
  307. data/lib/active_record/validations/absence.rb +25 -0
  308. data/lib/active_record/validations/associated.rb +65 -0
  309. data/lib/active_record/validations/length.rb +26 -0
  310. data/lib/active_record/validations/numericality.rb +36 -0
  311. data/lib/active_record/validations/presence.rb +45 -0
  312. data/lib/active_record/validations/uniqueness.rb +295 -0
  313. data/lib/active_record/validations.rb +101 -0
  314. data/lib/active_record/version.rb +10 -0
  315. data/lib/active_record.rb +616 -0
  316. data/lib/arel/alias_predication.rb +9 -0
  317. data/lib/arel/attributes/attribute.rb +33 -0
  318. data/lib/arel/collectors/bind.rb +31 -0
  319. data/lib/arel/collectors/composite.rb +46 -0
  320. data/lib/arel/collectors/plain_string.rb +20 -0
  321. data/lib/arel/collectors/sql_string.rb +27 -0
  322. data/lib/arel/collectors/substitute_binds.rb +35 -0
  323. data/lib/arel/crud.rb +48 -0
  324. data/lib/arel/delete_manager.rb +32 -0
  325. data/lib/arel/errors.rb +19 -0
  326. data/lib/arel/expressions.rb +29 -0
  327. data/lib/arel/factory_methods.rb +53 -0
  328. data/lib/arel/filter_predications.rb +9 -0
  329. data/lib/arel/insert_manager.rb +48 -0
  330. data/lib/arel/math.rb +45 -0
  331. data/lib/arel/nodes/ascending.rb +23 -0
  332. data/lib/arel/nodes/binary.rb +125 -0
  333. data/lib/arel/nodes/bind_param.rb +44 -0
  334. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  335. data/lib/arel/nodes/case.rb +55 -0
  336. data/lib/arel/nodes/casted.rb +62 -0
  337. data/lib/arel/nodes/comment.rb +29 -0
  338. data/lib/arel/nodes/count.rb +12 -0
  339. data/lib/arel/nodes/cte.rb +36 -0
  340. data/lib/arel/nodes/delete_statement.rb +44 -0
  341. data/lib/arel/nodes/descending.rb +23 -0
  342. data/lib/arel/nodes/equality.rb +15 -0
  343. data/lib/arel/nodes/extract.rb +24 -0
  344. data/lib/arel/nodes/false.rb +16 -0
  345. data/lib/arel/nodes/filter.rb +10 -0
  346. data/lib/arel/nodes/fragments.rb +35 -0
  347. data/lib/arel/nodes/full_outer_join.rb +8 -0
  348. data/lib/arel/nodes/function.rb +45 -0
  349. data/lib/arel/nodes/grouping.rb +11 -0
  350. data/lib/arel/nodes/homogeneous_in.rb +68 -0
  351. data/lib/arel/nodes/in.rb +15 -0
  352. data/lib/arel/nodes/infix_operation.rb +92 -0
  353. data/lib/arel/nodes/inner_join.rb +8 -0
  354. data/lib/arel/nodes/insert_statement.rb +37 -0
  355. data/lib/arel/nodes/join_source.rb +20 -0
  356. data/lib/arel/nodes/leading_join.rb +8 -0
  357. data/lib/arel/nodes/matches.rb +18 -0
  358. data/lib/arel/nodes/named_function.rb +23 -0
  359. data/lib/arel/nodes/nary.rb +39 -0
  360. data/lib/arel/nodes/node.rb +161 -0
  361. data/lib/arel/nodes/node_expression.rb +13 -0
  362. data/lib/arel/nodes/ordering.rb +27 -0
  363. data/lib/arel/nodes/outer_join.rb +8 -0
  364. data/lib/arel/nodes/over.rb +15 -0
  365. data/lib/arel/nodes/regexp.rb +16 -0
  366. data/lib/arel/nodes/right_outer_join.rb +8 -0
  367. data/lib/arel/nodes/select_core.rb +67 -0
  368. data/lib/arel/nodes/select_statement.rb +41 -0
  369. data/lib/arel/nodes/sql_literal.rb +32 -0
  370. data/lib/arel/nodes/string_join.rb +11 -0
  371. data/lib/arel/nodes/table_alias.rb +35 -0
  372. data/lib/arel/nodes/terminal.rb +16 -0
  373. data/lib/arel/nodes/true.rb +16 -0
  374. data/lib/arel/nodes/unary.rb +44 -0
  375. data/lib/arel/nodes/unary_operation.rb +20 -0
  376. data/lib/arel/nodes/unqualified_column.rb +22 -0
  377. data/lib/arel/nodes/update_statement.rb +46 -0
  378. data/lib/arel/nodes/values_list.rb +9 -0
  379. data/lib/arel/nodes/window.rb +126 -0
  380. data/lib/arel/nodes/with.rb +11 -0
  381. data/lib/arel/nodes.rb +75 -0
  382. data/lib/arel/order_predications.rb +13 -0
  383. data/lib/arel/predications.rb +260 -0
  384. data/lib/arel/select_manager.rb +276 -0
  385. data/lib/arel/table.rb +121 -0
  386. data/lib/arel/tree_manager.rb +65 -0
  387. data/lib/arel/update_manager.rb +49 -0
  388. data/lib/arel/visitors/dot.rb +299 -0
  389. data/lib/arel/visitors/mysql.rb +111 -0
  390. data/lib/arel/visitors/postgresql.rb +99 -0
  391. data/lib/arel/visitors/sqlite.rb +38 -0
  392. data/lib/arel/visitors/to_sql.rb +1033 -0
  393. data/lib/arel/visitors/visitor.rb +45 -0
  394. data/lib/arel/visitors.rb +13 -0
  395. data/lib/arel/window_predications.rb +9 -0
  396. data/lib/arel.rb +73 -0
  397. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  398. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
  399. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  400. data/lib/rails/generators/active_record/migration/migration_generator.rb +76 -0
  401. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +29 -0
  402. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
  403. data/lib/rails/generators/active_record/migration.rb +54 -0
  404. data/lib/rails/generators/active_record/model/USAGE +113 -0
  405. data/lib/rails/generators/active_record/model/model_generator.rb +94 -0
  406. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  407. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  408. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  409. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  410. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  411. data/lib/rails/generators/active_record.rb +19 -0
  412. metadata +505 -0
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class Preloader
6
+ class Branch # :nodoc:
7
+ attr_reader :association, :children, :parent
8
+ attr_reader :scope, :associate_by_default
9
+ attr_writer :preloaded_records
10
+
11
+ def initialize(association:, children:, parent:, associate_by_default:, scope:)
12
+ @association = if association
13
+ begin
14
+ @association = association.to_sym
15
+ rescue NoMethodError
16
+ raise ArgumentError, "Association names must be Symbol or String, got: #{association.class.name}"
17
+ end
18
+ end
19
+ @parent = parent
20
+ @scope = scope
21
+ @associate_by_default = associate_by_default
22
+
23
+ @children = build_children(children)
24
+ @loaders = nil
25
+ end
26
+
27
+ def future_classes
28
+ (immediate_future_classes + children.flat_map(&:future_classes)).uniq
29
+ end
30
+
31
+ def immediate_future_classes
32
+ if parent.done?
33
+ loaders.flat_map(&:future_classes).uniq
34
+ else
35
+ likely_reflections.reject(&:polymorphic?).flat_map do |reflection|
36
+ reflection.
37
+ chain.
38
+ map(&:klass)
39
+ end.uniq
40
+ end
41
+ end
42
+
43
+ def target_classes
44
+ if done?
45
+ preloaded_records.map(&:klass).uniq
46
+ elsif parent.done?
47
+ loaders.map(&:klass).uniq
48
+ else
49
+ likely_reflections.reject(&:polymorphic?).map(&:klass).uniq
50
+ end
51
+ end
52
+
53
+ def likely_reflections
54
+ parent_classes = parent.target_classes
55
+ parent_classes.filter_map do |parent_klass|
56
+ parent_klass._reflect_on_association(@association)
57
+ end
58
+ end
59
+
60
+ def root?
61
+ parent.nil?
62
+ end
63
+
64
+ def source_records
65
+ @parent.preloaded_records
66
+ end
67
+
68
+ def preloaded_records
69
+ @preloaded_records ||= loaders.flat_map(&:preloaded_records)
70
+ end
71
+
72
+ def done?
73
+ root? || (@loaders && @loaders.all?(&:run?))
74
+ end
75
+
76
+ def runnable_loaders
77
+ loaders.flat_map(&:runnable_loaders).reject(&:run?)
78
+ end
79
+
80
+ def grouped_records
81
+ h = {}
82
+ polymorphic_parent = !root? && parent.polymorphic?
83
+ source_records.each do |record|
84
+ reflection = record.class._reflect_on_association(association)
85
+ next if polymorphic_parent && !reflection || !record.association(association).klass
86
+ (h[reflection] ||= []) << record
87
+ end
88
+ h
89
+ end
90
+
91
+ def preloaders_for_reflection(reflection, reflection_records)
92
+ reflection_records.group_by do |record|
93
+ klass = record.association(association).klass
94
+
95
+ if reflection.scope && reflection.scope.arity != 0
96
+ # For instance dependent scopes, the scope is potentially
97
+ # different for each record. To allow this we'll group each
98
+ # object separately into its own preloader
99
+ reflection_scope = reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass, record).inject(&:merge!)
100
+ end
101
+
102
+ [klass, reflection_scope]
103
+ end.map do |(rhs_klass, reflection_scope), rs|
104
+ preloader_for(reflection).new(rhs_klass, rs, reflection, scope, reflection_scope, associate_by_default)
105
+ end
106
+ end
107
+
108
+ def polymorphic?
109
+ return false if root?
110
+ return @polymorphic if defined?(@polymorphic)
111
+
112
+ @polymorphic = source_records.any? do |record|
113
+ reflection = record.class._reflect_on_association(association)
114
+ reflection && reflection.options[:polymorphic]
115
+ end
116
+ end
117
+
118
+ def loaders
119
+ @loaders ||=
120
+ grouped_records.flat_map do |reflection, reflection_records|
121
+ preloaders_for_reflection(reflection, reflection_records)
122
+ end
123
+ end
124
+
125
+ private
126
+ def build_children(children)
127
+ Array.wrap(children).flat_map { |association|
128
+ Array(association).flat_map { |parent, child|
129
+ Branch.new(
130
+ parent: self,
131
+ association: parent,
132
+ children: child,
133
+ associate_by_default: associate_by_default,
134
+ scope: scope
135
+ )
136
+ }
137
+ }
138
+ end
139
+
140
+ # Returns a class containing the logic needed to load preload the data
141
+ # and attach it to a relation. The class returned implements a `run` method
142
+ # that accepts a preloader.
143
+ def preloader_for(reflection)
144
+ if reflection.options[:through]
145
+ ThroughAssociation
146
+ else
147
+ Association
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class Preloader
6
+ class ThroughAssociation < Association # :nodoc:
7
+ def preloaded_records
8
+ @preloaded_records ||= source_preloaders.flat_map(&:preloaded_records)
9
+ end
10
+
11
+ def records_by_owner
12
+ @records_by_owner ||= owners.each_with_object({}) do |owner, result|
13
+ if loaded?(owner)
14
+ result[owner] = target_for(owner)
15
+ next
16
+ end
17
+
18
+ through_records = through_records_by_owner[owner] || []
19
+
20
+ if owners.first.association(through_reflection.name).loaded?
21
+ if source_type = reflection.options[:source_type]
22
+ through_records = through_records.select do |record|
23
+ record[reflection.foreign_type] == source_type
24
+ end
25
+ end
26
+ end
27
+
28
+ records = through_records.flat_map do |record|
29
+ source_records_by_owner[record]
30
+ end
31
+
32
+ records.compact!
33
+ records.sort_by! { |rhs| preload_index[rhs] } if scope.order_values.any?
34
+ records.uniq! if scope.distinct_value
35
+ result[owner] = records
36
+ end
37
+ end
38
+
39
+ def runnable_loaders
40
+ if data_available?
41
+ [self]
42
+ elsif through_preloaders.all?(&:run?)
43
+ source_preloaders.flat_map(&:runnable_loaders)
44
+ else
45
+ through_preloaders.flat_map(&:runnable_loaders)
46
+ end
47
+ end
48
+
49
+ def future_classes
50
+ if run?
51
+ []
52
+ elsif through_preloaders.all?(&:run?)
53
+ source_preloaders.flat_map(&:future_classes).uniq
54
+ else
55
+ through_classes = through_preloaders.flat_map(&:future_classes)
56
+ source_classes = source_reflection.
57
+ chain.
58
+ reject { |reflection| reflection.respond_to?(:polymorphic?) && reflection.polymorphic? }.
59
+ map(&:klass)
60
+ (through_classes + source_classes).uniq
61
+ end
62
+ end
63
+
64
+ private
65
+ def data_available?
66
+ owners.all? { |owner| loaded?(owner) } ||
67
+ through_preloaders.all?(&:run?) && source_preloaders.all?(&:run?)
68
+ end
69
+
70
+ def source_preloaders
71
+ @source_preloaders ||= ActiveRecord::Associations::Preloader.new(records: middle_records, associations: source_reflection.name, scope: scope, associate_by_default: false).loaders
72
+ end
73
+
74
+ def middle_records
75
+ through_records_by_owner.values.flatten
76
+ end
77
+
78
+ def through_preloaders
79
+ @through_preloaders ||= ActiveRecord::Associations::Preloader.new(records: owners, associations: through_reflection.name, scope: through_scope, associate_by_default: false).loaders
80
+ end
81
+
82
+ def through_reflection
83
+ reflection.through_reflection
84
+ end
85
+
86
+ def source_reflection
87
+ reflection.source_reflection
88
+ end
89
+
90
+ def source_records_by_owner
91
+ @source_records_by_owner ||= source_preloaders.map(&:records_by_owner).reduce(:merge)
92
+ end
93
+
94
+ def through_records_by_owner
95
+ @through_records_by_owner ||= through_preloaders.map(&:records_by_owner).reduce(:merge)
96
+ end
97
+
98
+ def preload_index
99
+ @preload_index ||= preloaded_records.each_with_object({}).with_index do |(record, result), index|
100
+ result[record] = index
101
+ end
102
+ end
103
+
104
+ def through_scope
105
+ scope = through_reflection.klass.unscoped
106
+ options = reflection.options
107
+
108
+ return scope if options[:disable_joins]
109
+
110
+ values = reflection_scope.values
111
+ if annotations = values[:annotate]
112
+ scope.annotate!(*annotations)
113
+ end
114
+
115
+ if options[:source_type]
116
+ scope.where! reflection.foreign_type => options[:source_type]
117
+ elsif !reflection_scope.where_clause.empty?
118
+ scope.where_clause = reflection_scope.where_clause
119
+
120
+ if includes = values[:includes]
121
+ scope.includes!(source_reflection.name => includes)
122
+ else
123
+ scope.includes!(source_reflection.name)
124
+ end
125
+
126
+ if values[:references] && !values[:references].empty?
127
+ scope.references_values |= values[:references]
128
+ else
129
+ scope.references!(source_reflection.table_name)
130
+ end
131
+
132
+ if joins = values[:joins]
133
+ scope.joins!(source_reflection.name => joins)
134
+ end
135
+
136
+ if left_outer_joins = values[:left_outer_joins]
137
+ scope.left_outer_joins!(source_reflection.name => left_outer_joins)
138
+ end
139
+
140
+ if scope.eager_loading? && order_values = values[:order]
141
+ scope = scope.order(order_values)
142
+ end
143
+ end
144
+
145
+ cascade_strict_loading(scope)
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/enumerable"
4
+
5
+ module ActiveRecord
6
+ module Associations
7
+ # = Active Record \Preloader
8
+ #
9
+ # Implements the details of eager loading of Active Record associations.
10
+ #
11
+ # Suppose that you have the following two Active Record models:
12
+ #
13
+ # class Author < ActiveRecord::Base
14
+ # # columns: name, age
15
+ # has_many :books
16
+ # end
17
+ #
18
+ # class Book < ActiveRecord::Base
19
+ # # columns: title, sales, author_id
20
+ # end
21
+ #
22
+ # When you load an author with all associated books Active Record will make
23
+ # multiple queries like this:
24
+ #
25
+ # Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a
26
+ #
27
+ # # SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
28
+ # # SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
29
+ #
30
+ # Active Record saves the ids of the records from the first query to use in
31
+ # the second. Depending on the number of associations involved there can be
32
+ # arbitrarily many SQL queries made.
33
+ #
34
+ # However, if there is a WHERE clause that spans across tables Active
35
+ # Record will fall back to a slightly more resource-intensive single query:
36
+ #
37
+ # Author.includes(:books).where(books: {title: 'Illiad'}).to_a
38
+ # # SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
39
+ # # `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2
40
+ # # FROM `authors`
41
+ # # LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id`
42
+ # # WHERE `books`.`title` = 'Illiad'
43
+ #
44
+ # This could result in many rows that contain redundant data and it performs poorly at scale
45
+ # and is therefore only used when necessary.
46
+ class Preloader # :nodoc:
47
+ extend ActiveSupport::Autoload
48
+
49
+ eager_autoload do
50
+ autoload :Association, "active_record/associations/preloader/association"
51
+ autoload :Batch, "active_record/associations/preloader/batch"
52
+ autoload :Branch, "active_record/associations/preloader/branch"
53
+ autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
54
+ end
55
+
56
+ attr_reader :records, :associations, :scope, :associate_by_default
57
+
58
+ # Eager loads the named associations for the given Active Record record(s).
59
+ #
60
+ # In this description, 'association name' shall refer to the name passed
61
+ # to an association creation method. For example, a model that specifies
62
+ # <tt>belongs_to :author</tt>, <tt>has_many :buyers</tt> has association
63
+ # names +:author+ and +:buyers+.
64
+ #
65
+ # == Parameters
66
+ # +records+ is an array of ActiveRecord::Base. This array needs not be flat,
67
+ # i.e. +records+ itself may also contain arrays of records. In any case,
68
+ # +preload_associations+ will preload all associations records by
69
+ # flattening +records+.
70
+ #
71
+ # +associations+ specifies one or more associations that you want to
72
+ # preload. It may be:
73
+ # - a Symbol or a String which specifies a single association name. For
74
+ # example, specifying +:books+ allows this method to preload all books
75
+ # for an Author.
76
+ # - an Array which specifies multiple association names. This array
77
+ # is processed recursively. For example, specifying <tt>[:avatar, :books]</tt>
78
+ # allows this method to preload an author's avatar as well as all of their
79
+ # books.
80
+ # - a Hash which specifies multiple association names, as well as
81
+ # association names for the to-be-preloaded association objects. For
82
+ # example, specifying <tt>{ author: :avatar }</tt> will preload a
83
+ # book's author, as well as that author's avatar.
84
+ #
85
+ # +:associations+ has the same format as the arguments to
86
+ # ActiveRecord::QueryMethods#includes. So +associations+ could look like
87
+ # this:
88
+ #
89
+ # :books
90
+ # [ :books, :author ]
91
+ # { author: :avatar }
92
+ # [ :books, { author: :avatar } ]
93
+ #
94
+ # +available_records+ is an array of ActiveRecord::Base. The Preloader
95
+ # will try to use the objects in this array to preload the requested
96
+ # associations before querying the database. This can save database
97
+ # queries by reusing in-memory objects. The optimization is only applied
98
+ # to single associations (i.e. :belongs_to, :has_one) with no scopes.
99
+ def initialize(records:, associations:, scope: nil, available_records: [], associate_by_default: true)
100
+ @records = records
101
+ @associations = associations
102
+ @scope = scope
103
+ @available_records = available_records || []
104
+ @associate_by_default = associate_by_default
105
+
106
+ @tree = Branch.new(
107
+ parent: nil,
108
+ association: nil,
109
+ children: @associations,
110
+ associate_by_default: @associate_by_default,
111
+ scope: @scope
112
+ )
113
+ @tree.preloaded_records = @records
114
+ end
115
+
116
+ def empty?
117
+ associations.nil? || records.length == 0
118
+ end
119
+
120
+ def call
121
+ Batch.new([self], available_records: @available_records).call
122
+
123
+ loaders
124
+ end
125
+
126
+ def branches
127
+ @tree.children
128
+ end
129
+
130
+ def loaders
131
+ branches.flat_map(&:loaders)
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class SingularAssociation < Association # :nodoc:
6
+ # Implements the reader method, e.g. foo.bar for Foo.has_one :bar
7
+ def reader
8
+ ensure_klass_exists!
9
+
10
+ if !loaded? || stale_target?
11
+ reload
12
+ end
13
+
14
+ target
15
+ end
16
+
17
+ # Resets the \loaded flag to +false+ and sets the \target to +nil+.
18
+ def reset
19
+ super
20
+ @target = nil
21
+ @future_target = nil
22
+ end
23
+
24
+ # Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
25
+ def writer(record)
26
+ replace(record)
27
+ end
28
+
29
+ def build(attributes = nil, &block)
30
+ record = build_record(attributes, &block)
31
+ set_new_record(record)
32
+ record
33
+ end
34
+
35
+ # Implements the reload reader method, e.g. foo.reload_bar for
36
+ # Foo.has_one :bar
37
+ def force_reload_reader
38
+ reload(true)
39
+ target
40
+ end
41
+
42
+ private
43
+ def scope_for_create
44
+ super.except!(*Array(klass.primary_key))
45
+ end
46
+
47
+ def find_target(async: false)
48
+ if disable_joins
49
+ if async
50
+ scope.load_async.then(&:first)
51
+ else
52
+ scope.first
53
+ end
54
+ else
55
+ super.then(&:first)
56
+ end
57
+ end
58
+
59
+ def replace(record)
60
+ raise NotImplementedError, "Subclasses must implement a replace(record) method"
61
+ end
62
+
63
+ def set_new_record(record)
64
+ replace(record)
65
+ end
66
+
67
+ def _create_record(attributes, raise_error = false, &block)
68
+ record = build_record(attributes, &block)
69
+ saved = record.save
70
+ set_new_record(record)
71
+ raise RecordInvalid.new(record) if !saved && raise_error
72
+ record
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ # = Active Record Through Association
6
+ module ThroughAssociation # :nodoc:
7
+ delegate :source_reflection, to: :reflection
8
+
9
+ private
10
+ def transaction(&block)
11
+ through_reflection.klass.transaction(&block)
12
+ end
13
+
14
+ def through_reflection
15
+ @through_reflection ||= begin
16
+ refl = reflection.through_reflection
17
+
18
+ while refl.through_reflection?
19
+ refl = refl.through_reflection
20
+ end
21
+
22
+ refl
23
+ end
24
+ end
25
+
26
+ def through_association
27
+ @through_association ||= owner.association(through_reflection.name)
28
+ end
29
+
30
+ # We merge in these scopes for two reasons:
31
+ #
32
+ # 1. To get the default_scope conditions for any of the other reflections in the chain
33
+ # 2. To get the type conditions for any STI models in the chain
34
+ def target_scope
35
+ scope = super
36
+ reflection.chain.drop(1).each do |reflection|
37
+ relation = reflection.klass.scope_for_association
38
+ scope.merge!(
39
+ relation.except(:select, :create_with, :includes, :preload, :eager_load, :joins, :left_outer_joins)
40
+ )
41
+ end
42
+ scope
43
+ end
44
+
45
+ # Construct attributes for :through pointing to owner and associate. This is used by the
46
+ # methods which create and delete records on the association.
47
+ #
48
+ # We only support indirectly modifying through associations which have a belongs_to source.
49
+ # This is the "has_many :tags, through: :taggings" situation, where the join model
50
+ # typically has a belongs_to on both side. In other words, associations which could also
51
+ # be represented as has_and_belongs_to_many associations.
52
+ #
53
+ # We do not support creating/deleting records on the association where the source has
54
+ # some other type, because this opens up a whole can of worms, and in basically any
55
+ # situation it is more natural for the user to just create or modify their join records
56
+ # directly as required.
57
+ def construct_join_attributes(*records)
58
+ ensure_mutable
59
+
60
+ association_primary_key = source_reflection.association_primary_key(reflection.klass)
61
+
62
+ if Array(association_primary_key) == reflection.klass.composite_query_constraints_list && !options[:source_type]
63
+ join_attributes = { source_reflection.name => records }
64
+ else
65
+ assoc_pk_values = records.map { |record| record._read_attribute(association_primary_key) }
66
+ join_attributes = { source_reflection.foreign_key => assoc_pk_values }
67
+ end
68
+
69
+ if options[:source_type]
70
+ join_attributes[source_reflection.foreign_type] = [ options[:source_type] ]
71
+ end
72
+
73
+ if records.count == 1
74
+ join_attributes.transform_values!(&:first)
75
+ else
76
+ join_attributes
77
+ end
78
+ end
79
+
80
+ # Note: this does not capture all cases, for example it would be impractical
81
+ # to try to properly support stale-checking for nested associations.
82
+ def stale_state
83
+ if through_reflection.belongs_to?
84
+ Array(through_reflection.foreign_key).filter_map do |foreign_key_column|
85
+ owner[foreign_key_column]
86
+ end.presence
87
+ end
88
+ end
89
+
90
+ def foreign_key_present?
91
+ through_reflection.belongs_to? && Array(through_reflection.foreign_key).all? do |foreign_key_column|
92
+ !owner[foreign_key_column].nil?
93
+ end
94
+ end
95
+
96
+ def ensure_mutable
97
+ unless source_reflection.belongs_to?
98
+ if reflection.has_one?
99
+ raise HasOneThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
100
+ else
101
+ raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
102
+ end
103
+ end
104
+ end
105
+
106
+ def ensure_not_nested
107
+ if reflection.nested?
108
+ if reflection.has_one?
109
+ raise HasOneThroughNestedAssociationsAreReadonly.new(owner, reflection)
110
+ else
111
+ raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
112
+ end
113
+ end
114
+ end
115
+
116
+ def build_record(attributes)
117
+ if source_reflection.collection?
118
+ inverse = source_reflection.inverse_of
119
+ target = through_association.target
120
+
121
+ if inverse && target && !target.is_a?(Array)
122
+ Array(target.id).zip(Array(inverse.foreign_key)).map do |primary_key_value, foreign_key_column|
123
+ attributes[foreign_key_column] = primary_key_value
124
+ end
125
+ end
126
+ end
127
+
128
+ super
129
+ end
130
+ end
131
+ end
132
+ end