activerecord 5.2.8 → 7.0.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (364) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1393 -587
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +10 -9
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +19 -16
  9. data/lib/active_record/associations/association.rb +122 -47
  10. data/lib/active_record/associations/association_scope.rb +24 -24
  11. data/lib/active_record/associations/belongs_to_association.rb +67 -49
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -7
  13. data/lib/active_record/associations/builder/association.rb +52 -23
  14. data/lib/active_record/associations/builder/belongs_to.rb +44 -61
  15. data/lib/active_record/associations/builder/collection_association.rb +17 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  17. data/lib/active_record/associations/builder/has_many.rb +10 -3
  18. data/lib/active_record/associations/builder/has_one.rb +35 -3
  19. data/lib/active_record/associations/builder/singular_association.rb +5 -3
  20. data/lib/active_record/associations/collection_association.rb +59 -50
  21. data/lib/active_record/associations/collection_proxy.rb +32 -23
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/foreign_association.rb +20 -0
  24. data/lib/active_record/associations/has_many_association.rb +27 -14
  25. data/lib/active_record/associations/has_many_through_association.rb +26 -19
  26. data/lib/active_record/associations/has_one_association.rb +52 -37
  27. data/lib/active_record/associations/has_one_through_association.rb +6 -6
  28. data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  30. data/lib/active_record/associations/join_dependency.rb +97 -62
  31. data/lib/active_record/associations/preloader/association.rb +220 -60
  32. data/lib/active_record/associations/preloader/batch.rb +48 -0
  33. data/lib/active_record/associations/preloader/branch.rb +147 -0
  34. data/lib/active_record/associations/preloader/through_association.rb +85 -40
  35. data/lib/active_record/associations/preloader.rb +44 -105
  36. data/lib/active_record/associations/singular_association.rb +9 -17
  37. data/lib/active_record/associations/through_association.rb +4 -4
  38. data/lib/active_record/associations.rb +207 -66
  39. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  40. data/lib/active_record/attribute_assignment.rb +17 -19
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +19 -8
  42. data/lib/active_record/attribute_methods/dirty.rb +141 -47
  43. data/lib/active_record/attribute_methods/primary_key.rb +22 -27
  44. data/lib/active_record/attribute_methods/query.rb +6 -10
  45. data/lib/active_record/attribute_methods/read.rb +15 -55
  46. data/lib/active_record/attribute_methods/serialization.rb +77 -18
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +16 -18
  48. data/lib/active_record/attribute_methods/write.rb +18 -37
  49. data/lib/active_record/attribute_methods.rb +90 -153
  50. data/lib/active_record/attributes.rb +38 -12
  51. data/lib/active_record/autosave_association.rb +50 -50
  52. data/lib/active_record/base.rb +23 -18
  53. data/lib/active_record/callbacks.rb +159 -44
  54. data/lib/active_record/coders/yaml_column.rb +12 -3
  55. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  58. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +92 -464
  59. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -51
  60. data/lib/active_record/connection_adapters/abstract/database_statements.rb +209 -164
  61. data/lib/active_record/connection_adapters/abstract/query_cache.rb +38 -22
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +103 -82
  63. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  64. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +140 -110
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -94
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +16 -5
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +456 -159
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +169 -78
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +367 -162
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +311 -327
  71. data/lib/active_record/connection_adapters/column.rb +33 -11
  72. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  73. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  74. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  75. data/lib/active_record/connection_adapters/mysql/database_statements.rb +113 -45
  76. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  77. data/lib/active_record/connection_adapters/mysql/quoting.rb +71 -5
  78. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  79. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  80. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +25 -8
  81. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +143 -19
  82. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  83. data/lib/active_record/connection_adapters/mysql2_adapter.rb +63 -22
  84. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  85. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  86. data/lib/active_record/connection_adapters/postgresql/column.rb +53 -28
  87. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +56 -63
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +54 -16
  95. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  102. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +26 -12
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  106. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  107. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -52
  108. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -2
  109. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +39 -4
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +128 -91
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -1
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +149 -113
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +386 -182
  116. data/lib/active_record/connection_adapters/schema_cache.rb +161 -22
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +17 -6
  118. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +152 -0
  119. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +65 -18
  120. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  121. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +92 -26
  122. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +251 -204
  123. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  124. data/lib/active_record/connection_adapters.rb +53 -0
  125. data/lib/active_record/connection_handling.rb +292 -38
  126. data/lib/active_record/core.rb +385 -158
  127. data/lib/active_record/counter_cache.rb +8 -30
  128. data/lib/active_record/database_configurations/connection_url_resolver.rb +100 -0
  129. data/lib/active_record/database_configurations/database_config.rb +83 -0
  130. data/lib/active_record/database_configurations/hash_config.rb +154 -0
  131. data/lib/active_record/database_configurations/url_config.rb +53 -0
  132. data/lib/active_record/database_configurations.rb +256 -0
  133. data/lib/active_record/delegated_type.rb +250 -0
  134. data/lib/active_record/destroy_association_async_job.rb +36 -0
  135. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  136. data/lib/active_record/dynamic_matchers.rb +4 -5
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +44 -0
  140. data/lib/active_record/encryption/configurable.rb +61 -0
  141. data/lib/active_record/encryption/context.rb +35 -0
  142. data/lib/active_record/encryption/contexts.rb +72 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -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 +155 -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 +160 -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 +42 -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_serializer.rb +90 -0
  159. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  160. data/lib/active_record/encryption/properties.rb +76 -0
  161. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  162. data/lib/active_record/encryption/scheme.rb +99 -0
  163. data/lib/active_record/encryption.rb +55 -0
  164. data/lib/active_record/enum.rb +130 -51
  165. data/lib/active_record/errors.rb +129 -23
  166. data/lib/active_record/explain.rb +10 -6
  167. data/lib/active_record/explain_registry.rb +11 -6
  168. data/lib/active_record/explain_subscriber.rb +1 -1
  169. data/lib/active_record/fixture_set/file.rb +22 -15
  170. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  171. data/lib/active_record/fixture_set/render_context.rb +17 -0
  172. data/lib/active_record/fixture_set/table_row.rb +187 -0
  173. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  174. data/lib/active_record/fixtures.rb +206 -490
  175. data/lib/active_record/future_result.rb +139 -0
  176. data/lib/active_record/gem_version.rb +3 -3
  177. data/lib/active_record/inheritance.rb +104 -37
  178. data/lib/active_record/insert_all.rb +278 -0
  179. data/lib/active_record/integration.rb +69 -18
  180. data/lib/active_record/internal_metadata.rb +24 -9
  181. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  182. data/lib/active_record/locking/optimistic.rb +41 -26
  183. data/lib/active_record/locking/pessimistic.rb +18 -8
  184. data/lib/active_record/log_subscriber.rb +46 -35
  185. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  186. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  187. data/lib/active_record/middleware/database_selector.rb +82 -0
  188. data/lib/active_record/middleware/shard_selector.rb +60 -0
  189. data/lib/active_record/migration/command_recorder.rb +96 -44
  190. data/lib/active_record/migration/compatibility.rb +246 -64
  191. data/lib/active_record/migration/join_table.rb +1 -2
  192. data/lib/active_record/migration.rb +266 -187
  193. data/lib/active_record/model_schema.rb +165 -52
  194. data/lib/active_record/nested_attributes.rb +17 -19
  195. data/lib/active_record/no_touching.rb +11 -4
  196. data/lib/active_record/null_relation.rb +2 -7
  197. data/lib/active_record/persistence.rb +467 -92
  198. data/lib/active_record/query_cache.rb +21 -4
  199. data/lib/active_record/query_logs.rb +138 -0
  200. data/lib/active_record/querying.rb +51 -24
  201. data/lib/active_record/railtie.rb +224 -57
  202. data/lib/active_record/railties/console_sandbox.rb +2 -4
  203. data/lib/active_record/railties/controller_runtime.rb +31 -36
  204. data/lib/active_record/railties/databases.rake +369 -101
  205. data/lib/active_record/readonly_attributes.rb +15 -0
  206. data/lib/active_record/reflection.rb +170 -137
  207. data/lib/active_record/relation/batches/batch_enumerator.rb +44 -14
  208. data/lib/active_record/relation/batches.rb +46 -37
  209. data/lib/active_record/relation/calculations.rb +168 -96
  210. data/lib/active_record/relation/delegation.rb +37 -52
  211. data/lib/active_record/relation/finder_methods.rb +79 -58
  212. data/lib/active_record/relation/from_clause.rb +5 -1
  213. data/lib/active_record/relation/merger.rb +50 -51
  214. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  215. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  216. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  217. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
  218. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  219. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  220. data/lib/active_record/relation/predicate_builder.rb +58 -46
  221. data/lib/active_record/relation/query_attribute.rb +9 -10
  222. data/lib/active_record/relation/query_methods.rb +685 -208
  223. data/lib/active_record/relation/record_fetch_warning.rb +9 -11
  224. data/lib/active_record/relation/spawn_methods.rb +10 -10
  225. data/lib/active_record/relation/where_clause.rb +108 -64
  226. data/lib/active_record/relation.rb +515 -151
  227. data/lib/active_record/result.rb +78 -42
  228. data/lib/active_record/runtime_registry.rb +9 -13
  229. data/lib/active_record/sanitization.rb +29 -44
  230. data/lib/active_record/schema.rb +37 -31
  231. data/lib/active_record/schema_dumper.rb +74 -23
  232. data/lib/active_record/schema_migration.rb +7 -9
  233. data/lib/active_record/scoping/default.rb +62 -17
  234. data/lib/active_record/scoping/named.rb +17 -32
  235. data/lib/active_record/scoping.rb +70 -41
  236. data/lib/active_record/secure_token.rb +16 -8
  237. data/lib/active_record/serialization.rb +6 -4
  238. data/lib/active_record/signed_id.rb +116 -0
  239. data/lib/active_record/statement_cache.rb +49 -6
  240. data/lib/active_record/store.rb +88 -9
  241. data/lib/active_record/suppressor.rb +13 -17
  242. data/lib/active_record/table_metadata.rb +42 -43
  243. data/lib/active_record/tasks/database_tasks.rb +352 -94
  244. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  245. data/lib/active_record/tasks/postgresql_database_tasks.rb +41 -39
  246. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  247. data/lib/active_record/test_databases.rb +24 -0
  248. data/lib/active_record/test_fixtures.rb +287 -0
  249. data/lib/active_record/timestamp.rb +44 -34
  250. data/lib/active_record/touch_later.rb +23 -22
  251. data/lib/active_record/transactions.rb +67 -128
  252. data/lib/active_record/translation.rb +3 -3
  253. data/lib/active_record/type/adapter_specific_registry.rb +34 -19
  254. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  255. data/lib/active_record/type/internal/timezone.rb +2 -2
  256. data/lib/active_record/type/serialized.rb +7 -4
  257. data/lib/active_record/type/time.rb +10 -0
  258. data/lib/active_record/type/type_map.rb +17 -21
  259. data/lib/active_record/type/unsigned_integer.rb +0 -1
  260. data/lib/active_record/type.rb +9 -5
  261. data/lib/active_record/type_caster/connection.rb +15 -15
  262. data/lib/active_record/type_caster/map.rb +8 -8
  263. data/lib/active_record/validations/associated.rb +2 -3
  264. data/lib/active_record/validations/numericality.rb +35 -0
  265. data/lib/active_record/validations/uniqueness.rb +39 -31
  266. data/lib/active_record/validations.rb +4 -3
  267. data/lib/active_record.rb +209 -32
  268. data/lib/arel/alias_predication.rb +9 -0
  269. data/lib/arel/attributes/attribute.rb +33 -0
  270. data/lib/arel/collectors/bind.rb +29 -0
  271. data/lib/arel/collectors/composite.rb +39 -0
  272. data/lib/arel/collectors/plain_string.rb +20 -0
  273. data/lib/arel/collectors/sql_string.rb +27 -0
  274. data/lib/arel/collectors/substitute_binds.rb +35 -0
  275. data/lib/arel/crud.rb +48 -0
  276. data/lib/arel/delete_manager.rb +32 -0
  277. data/lib/arel/errors.rb +9 -0
  278. data/lib/arel/expressions.rb +29 -0
  279. data/lib/arel/factory_methods.rb +49 -0
  280. data/lib/arel/filter_predications.rb +9 -0
  281. data/lib/arel/insert_manager.rb +48 -0
  282. data/lib/arel/math.rb +45 -0
  283. data/lib/arel/nodes/and.rb +32 -0
  284. data/lib/arel/nodes/ascending.rb +23 -0
  285. data/lib/arel/nodes/binary.rb +126 -0
  286. data/lib/arel/nodes/bind_param.rb +44 -0
  287. data/lib/arel/nodes/case.rb +55 -0
  288. data/lib/arel/nodes/casted.rb +62 -0
  289. data/lib/arel/nodes/comment.rb +29 -0
  290. data/lib/arel/nodes/count.rb +12 -0
  291. data/lib/arel/nodes/delete_statement.rb +44 -0
  292. data/lib/arel/nodes/descending.rb +23 -0
  293. data/lib/arel/nodes/equality.rb +15 -0
  294. data/lib/arel/nodes/extract.rb +24 -0
  295. data/lib/arel/nodes/false.rb +16 -0
  296. data/lib/arel/nodes/filter.rb +10 -0
  297. data/lib/arel/nodes/full_outer_join.rb +8 -0
  298. data/lib/arel/nodes/function.rb +45 -0
  299. data/lib/arel/nodes/grouping.rb +11 -0
  300. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  301. data/lib/arel/nodes/in.rb +15 -0
  302. data/lib/arel/nodes/infix_operation.rb +92 -0
  303. data/lib/arel/nodes/inner_join.rb +8 -0
  304. data/lib/arel/nodes/insert_statement.rb +37 -0
  305. data/lib/arel/nodes/join_source.rb +20 -0
  306. data/lib/arel/nodes/matches.rb +18 -0
  307. data/lib/arel/nodes/named_function.rb +23 -0
  308. data/lib/arel/nodes/node.rb +51 -0
  309. data/lib/arel/nodes/node_expression.rb +13 -0
  310. data/lib/arel/nodes/ordering.rb +27 -0
  311. data/lib/arel/nodes/outer_join.rb +8 -0
  312. data/lib/arel/nodes/over.rb +15 -0
  313. data/lib/arel/nodes/regexp.rb +16 -0
  314. data/lib/arel/nodes/right_outer_join.rb +8 -0
  315. data/lib/arel/nodes/select_core.rb +67 -0
  316. data/lib/arel/nodes/select_statement.rb +41 -0
  317. data/lib/arel/nodes/sql_literal.rb +19 -0
  318. data/lib/arel/nodes/string_join.rb +11 -0
  319. data/lib/arel/nodes/table_alias.rb +31 -0
  320. data/lib/arel/nodes/terminal.rb +16 -0
  321. data/lib/arel/nodes/true.rb +16 -0
  322. data/lib/arel/nodes/unary.rb +44 -0
  323. data/lib/arel/nodes/unary_operation.rb +20 -0
  324. data/lib/arel/nodes/unqualified_column.rb +22 -0
  325. data/lib/arel/nodes/update_statement.rb +46 -0
  326. data/lib/arel/nodes/values_list.rb +9 -0
  327. data/lib/arel/nodes/window.rb +126 -0
  328. data/lib/arel/nodes/with.rb +11 -0
  329. data/lib/arel/nodes.rb +71 -0
  330. data/lib/arel/order_predications.rb +13 -0
  331. data/lib/arel/predications.rb +258 -0
  332. data/lib/arel/select_manager.rb +276 -0
  333. data/lib/arel/table.rb +117 -0
  334. data/lib/arel/tree_manager.rb +60 -0
  335. data/lib/arel/update_manager.rb +48 -0
  336. data/lib/arel/visitors/dot.rb +298 -0
  337. data/lib/arel/visitors/mysql.rb +99 -0
  338. data/lib/arel/visitors/postgresql.rb +110 -0
  339. data/lib/arel/visitors/sqlite.rb +38 -0
  340. data/lib/arel/visitors/to_sql.rb +955 -0
  341. data/lib/arel/visitors/visitor.rb +45 -0
  342. data/lib/arel/visitors.rb +13 -0
  343. data/lib/arel/window_predications.rb +9 -0
  344. data/lib/arel.rb +55 -0
  345. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  346. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  347. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  348. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  349. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  350. data/lib/rails/generators/active_record/migration.rb +19 -2
  351. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  352. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  353. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  354. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  355. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  356. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  357. metadata +162 -32
  358. data/lib/active_record/attribute_decorators.rb +0 -90
  359. data/lib/active_record/collection_cache_key.rb +0 -53
  360. data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
  361. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
  362. data/lib/active_record/define_callbacks.rb +0 -22
  363. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
  364. data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -3,8 +3,12 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  class JoinDependency # :nodoc:
6
- autoload :JoinBase, "active_record/associations/join_dependency/join_base"
7
- autoload :JoinAssociation, "active_record/associations/join_dependency/join_association"
6
+ extend ActiveSupport::Autoload
7
+
8
+ eager_autoload do
9
+ autoload :JoinBase
10
+ autoload :JoinAssociation
11
+ end
8
12
 
9
13
  class Aliases # :nodoc:
10
14
  def initialize(tables)
@@ -14,10 +18,8 @@ module ActiveRecord
14
18
  i[column.name] = column.alias
15
19
  }
16
20
  }
17
- @name_and_alias_cache = tables.each_with_object({}) { |table, h|
18
- h[table.node] = table.columns.map { |column|
19
- [column.name, column.alias]
20
- }
21
+ @columns_cache = tables.each_with_object({}) { |table, h|
22
+ h[table.node] = table.columns
21
23
  }
22
24
  end
23
25
 
@@ -25,9 +27,8 @@ module ActiveRecord
25
27
  @tables.flat_map(&:column_aliases)
26
28
  end
27
29
 
28
- # An array of [column_name, alias] pairs for the table
29
30
  def column_aliases(node)
30
- @name_and_alias_cache[node]
31
+ @columns_cache[node]
31
32
  end
32
33
 
33
34
  def column_alias(node, column)
@@ -37,7 +38,7 @@ module ActiveRecord
37
38
  Table = Struct.new(:node, :columns) do # :nodoc:
38
39
  def column_aliases
39
40
  t = node.table
40
- columns.map { |column| t[column.name].as Arel.sql column.alias }
41
+ columns.map { |column| t[column.name].as(column.alias) }
41
42
  end
42
43
  end
43
44
  Column = Struct.new(:name, :alias)
@@ -67,43 +68,69 @@ module ActiveRecord
67
68
  end
68
69
  end
69
70
 
70
- def initialize(base, table, associations)
71
+ def initialize(base, table, associations, join_type)
71
72
  tree = self.class.make_tree associations
72
73
  @join_root = JoinBase.new(base, table, build(tree, base))
74
+ @join_type = join_type
75
+ end
76
+
77
+ def base_klass
78
+ join_root.base_klass
73
79
  end
74
80
 
75
81
  def reflections
76
82
  join_root.drop(1).map!(&:reflection)
77
83
  end
78
84
 
79
- def join_constraints(joins_to_add, join_type, alias_tracker)
85
+ def join_constraints(joins_to_add, alias_tracker, references)
80
86
  @alias_tracker = alias_tracker
87
+ @joined_tables = {}
88
+ @references = {}
89
+
90
+ references.each do |table_name|
91
+ @references[table_name.to_sym] = table_name if table_name.is_a?(Arel::Nodes::SqlLiteral)
92
+ end unless references.empty?
81
93
 
82
- construct_tables!(join_root)
83
94
  joins = make_join_constraints(join_root, join_type)
84
95
 
85
96
  joins.concat joins_to_add.flat_map { |oj|
86
- construct_tables!(oj.join_root)
87
97
  if join_root.match? oj.join_root
88
- walk join_root, oj.join_root
98
+ walk(join_root, oj.join_root, oj.join_type)
89
99
  else
90
- make_join_constraints(oj.join_root, join_type)
100
+ make_join_constraints(oj.join_root, oj.join_type)
91
101
  end
92
102
  }
93
103
  end
94
104
 
95
- def instantiate(result_set, &block)
105
+ def instantiate(result_set, strict_loading_value, &block)
96
106
  primary_key = aliases.column_alias(join_root, join_root.primary_key)
97
107
 
98
- seen = Hash.new { |i, object_id|
99
- i[object_id] = Hash.new { |j, child_class|
108
+ seen = Hash.new { |i, parent|
109
+ i[parent] = Hash.new { |j, child_class|
100
110
  j[child_class] = {}
101
111
  }
102
- }
112
+ }.compare_by_identity
103
113
 
104
114
  model_cache = Hash.new { |h, klass| h[klass] = {} }
105
115
  parents = model_cache[join_root]
106
- column_aliases = aliases.column_aliases join_root
116
+
117
+ column_aliases = aliases.column_aliases(join_root)
118
+ column_names = []
119
+
120
+ result_set.columns.each do |name|
121
+ column_names << name unless /\At\d+_r\d+\z/.match?(name)
122
+ end
123
+
124
+ if column_names.empty?
125
+ column_types = {}
126
+ else
127
+ column_types = result_set.column_types
128
+ unless column_types.empty?
129
+ attribute_types = join_root.attribute_types
130
+ column_types = column_types.slice(*column_names).delete_if { |k, _| attribute_types.key?(k) }
131
+ end
132
+ column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) }
133
+ end
107
134
 
108
135
  message_bus = ActiveSupport::Notifications.instrumenter
109
136
 
@@ -115,8 +142,8 @@ module ActiveRecord
115
142
  message_bus.instrument("instantiation.active_record", payload) do
116
143
  result_set.each { |row_hash|
117
144
  parent_key = primary_key ? row_hash[primary_key] : row_hash
118
- parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, &block)
119
- construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
145
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
146
+ construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
120
147
  }
121
148
  end
122
149
 
@@ -124,63 +151,73 @@ module ActiveRecord
124
151
  end
125
152
 
126
153
  def apply_column_aliases(relation)
154
+ @join_root_alias = relation.select_values.empty?
127
155
  relation._select!(-> { aliases.columns })
128
156
  end
129
157
 
158
+ def each(&block)
159
+ join_root.each(&block)
160
+ end
161
+
130
162
  protected
131
- attr_reader :alias_tracker, :join_root
163
+ attr_reader :join_root, :join_type
132
164
 
133
165
  private
166
+ attr_reader :alias_tracker, :join_root_alias
167
+
134
168
  def aliases
135
169
  @aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
136
- columns = join_part.column_names.each_with_index.map { |column_name, j|
170
+ column_names = if join_part == join_root && !join_root_alias
171
+ primary_key = join_root.primary_key
172
+ primary_key ? [primary_key] : []
173
+ else
174
+ join_part.column_names
175
+ end
176
+
177
+ columns = column_names.each_with_index.map { |column_name, j|
137
178
  Aliases::Column.new column_name, "t#{i}_r#{j}"
138
179
  }
139
180
  Aliases::Table.new(join_part, columns)
140
181
  }
141
182
  end
142
183
 
143
- def construct_tables!(join_root)
144
- join_root.each_children do |parent, child|
145
- child.tables = table_aliases_for(parent, child)
146
- end
147
- end
148
-
149
184
  def make_join_constraints(join_root, join_type)
150
185
  join_root.children.flat_map do |child|
151
186
  make_constraints(join_root, child, join_type)
152
187
  end
153
188
  end
154
189
 
155
- def make_constraints(parent, child, join_type = Arel::Nodes::OuterJoin)
190
+ def make_constraints(parent, child, join_type)
156
191
  foreign_table = parent.table
157
192
  foreign_klass = parent.base_klass
158
- joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
159
- joins.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
160
- end
193
+ child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection|
194
+ table, terminated = @joined_tables[reflection]
195
+ root = reflection == child.reflection
161
196
 
162
- def table_aliases_for(parent, node)
163
- node.reflection.chain.map { |reflection|
164
- alias_tracker.aliased_table_for(
165
- reflection.table_name,
166
- table_alias_for(reflection, parent, reflection != node.reflection),
167
- reflection.klass.type_caster
168
- )
169
- }
170
- end
197
+ if table && (!root || !terminated)
198
+ @joined_tables[reflection] = [table, root] if root
199
+ next table, true
200
+ end
201
+
202
+ table_name = @references[reflection.name.to_sym]&.to_s
171
203
 
172
- def table_alias_for(reflection, parent, join)
173
- name = "#{reflection.plural_name}_#{parent.table_name}"
174
- join ? "#{name}_join" : name
204
+ table = alias_tracker.aliased_table_for(reflection.klass.arel_table, table_name) do
205
+ name = reflection.alias_candidate(parent.table_name)
206
+ root ? name : "#{name}_join"
207
+ end
208
+
209
+ @joined_tables[reflection] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
210
+ table
211
+ end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
175
212
  end
176
213
 
177
- def walk(left, right)
214
+ def walk(left, right, join_type)
178
215
  intersection, missing = right.children.map { |node1|
179
216
  [left.children.find { |node2| node1.match? node2 }, node1]
180
217
  }.partition(&:first)
181
218
 
182
- joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r) }
183
- joins.concat missing.flat_map { |_, n| make_constraints(left, n) }
219
+ joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r, join_type) }
220
+ joins.concat missing.flat_map { |_, n| make_constraints(left, n, join_type) }
184
221
  end
185
222
 
186
223
  def find_reflection(klass, name)
@@ -202,7 +239,7 @@ module ActiveRecord
202
239
  end
203
240
  end
204
241
 
205
- def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
242
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
206
243
  return if ar_parent.nil?
207
244
 
208
245
  parent.children.each do |node|
@@ -211,7 +248,7 @@ module ActiveRecord
211
248
  other.loaded!
212
249
  elsif ar_parent.association_cached?(node.reflection.name)
213
250
  model = ar_parent.association(node.reflection.name).target
214
- construct(model, node, row, rs, seen, model_cache, aliases)
251
+ construct(model, node, row, seen, model_cache, strict_loading_value)
215
252
  next
216
253
  end
217
254
 
@@ -223,29 +260,25 @@ module ActiveRecord
223
260
  next
224
261
  end
225
262
 
226
- model = seen[ar_parent.object_id][node][id]
263
+ model = seen[ar_parent][node][id]
227
264
 
228
265
  if model
229
- construct(model, node, row, rs, seen, model_cache, aliases)
266
+ construct(model, node, row, seen, model_cache, strict_loading_value)
230
267
  else
231
- model = construct_model(ar_parent, node, row, model_cache, id, aliases)
232
-
233
- if node.reflection.scope &&
234
- node.reflection.scope_for(node.base_klass.unscoped).readonly_value
235
- model.readonly!
236
- end
268
+ model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
237
269
 
238
- seen[ar_parent.object_id][node][id] = model
239
- construct(model, node, row, rs, seen, model_cache, aliases)
270
+ seen[ar_parent][node][id] = model
271
+ construct(model, node, row, seen, model_cache, strict_loading_value)
240
272
  end
241
273
  end
242
274
  end
243
275
 
244
- def construct_model(record, node, row, model_cache, id, aliases)
276
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
245
277
  other = record.association(node.reflection.name)
246
278
 
247
279
  model = model_cache[node][id] ||=
248
280
  node.instantiate(row, aliases.column_aliases(node)) do |m|
281
+ m.strict_loading! if strict_loading_value
249
282
  other.set_inverse_instance(m)
250
283
  end
251
284
 
@@ -255,6 +288,8 @@ module ActiveRecord
255
288
  other.target = model
256
289
  end
257
290
 
291
+ model.readonly! if node.readonly?
292
+ model.strict_loading! if node.strict_loading?
258
293
  model
259
294
  end
260
295
  end
@@ -3,38 +3,223 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  class Preloader
6
- class Association #:nodoc:
7
- attr_reader :preloaded_records
6
+ class Association # :nodoc:
7
+ class LoaderQuery
8
+ attr_reader :scope, :association_key_name
8
9
 
9
- def initialize(klass, owners, reflection, preload_scope)
10
+ def initialize(scope, association_key_name)
11
+ @scope = scope
12
+ @association_key_name = association_key_name
13
+ end
14
+
15
+ def eql?(other)
16
+ association_key_name == other.association_key_name &&
17
+ scope.table_name == other.scope.table_name &&
18
+ scope.values_for_queries == other.scope.values_for_queries
19
+ end
20
+
21
+ def hash
22
+ [association_key_name, scope.table_name, scope.values_for_queries].hash
23
+ end
24
+
25
+ def records_for(loaders)
26
+ LoaderRecords.new(loaders, self).records
27
+ end
28
+
29
+ def load_records_in_batch(loaders)
30
+ raw_records = records_for(loaders)
31
+
32
+ loaders.each do |loader|
33
+ loader.load_records(raw_records)
34
+ loader.run
35
+ end
36
+ end
37
+
38
+ def load_records_for_keys(keys, &block)
39
+ scope.where(association_key_name => keys).load(&block)
40
+ end
41
+ end
42
+
43
+ class LoaderRecords
44
+ def initialize(loaders, loader_query)
45
+ @loader_query = loader_query
46
+ @loaders = loaders
47
+ @keys_to_load = Set.new
48
+ @already_loaded_records_by_key = {}
49
+
50
+ populate_keys_to_load_and_already_loaded_records
51
+ end
52
+
53
+ def records
54
+ load_records + already_loaded_records
55
+ end
56
+
57
+ private
58
+ attr_reader :loader_query, :loaders, :keys_to_load, :already_loaded_records_by_key
59
+
60
+ def populate_keys_to_load_and_already_loaded_records
61
+ loaders.each do |loader|
62
+ loader.owners_by_key.each do |key, owners|
63
+ if loaded_owner = owners.find { |owner| loader.loaded?(owner) }
64
+ already_loaded_records_by_key[key] = loader.target_for(loaded_owner)
65
+ else
66
+ keys_to_load << key
67
+ end
68
+ end
69
+ end
70
+
71
+ @keys_to_load.subtract(already_loaded_records_by_key.keys)
72
+ end
73
+
74
+ def load_records
75
+ loader_query.load_records_for_keys(keys_to_load) do |record|
76
+ loaders.each { |l| l.set_inverse(record) }
77
+ end
78
+ end
79
+
80
+ def already_loaded_records
81
+ already_loaded_records_by_key.values.flatten
82
+ end
83
+ end
84
+
85
+ attr_reader :klass
86
+
87
+ def initialize(klass, owners, reflection, preload_scope, reflection_scope, associate_by_default)
10
88
  @klass = klass
11
- @owners = owners
89
+ @owners = owners.uniq(&:__id__)
12
90
  @reflection = reflection
13
91
  @preload_scope = preload_scope
92
+ @reflection_scope = reflection_scope
93
+ @associate = associate_by_default || !preload_scope || preload_scope.empty_scope?
14
94
  @model = owners.first && owners.first.class
15
- @preloaded_records = []
95
+ @run = false
16
96
  end
17
97
 
18
- def run(preloader)
19
- records = load_records do |record|
20
- owner = owners_by_key[convert_key(record[association_key_name])]
21
- association = owner.association(reflection.name)
22
- association.set_inverse_instance(record)
98
+ def table_name
99
+ @klass.table_name
100
+ end
101
+
102
+ def future_classes
103
+ if run?
104
+ []
105
+ else
106
+ [@klass]
23
107
  end
108
+ end
109
+
110
+ def runnable_loaders
111
+ [self]
112
+ end
113
+
114
+ def run?
115
+ @run
116
+ end
117
+
118
+ def run
119
+ return self if run?
120
+ @run = true
121
+
122
+ records = records_by_owner
24
123
 
25
124
  owners.each do |owner|
26
- associate_records_to_owner(owner, records[convert_key(owner[owner_key_name])] || [])
125
+ associate_records_to_owner(owner, records[owner] || [])
126
+ end if @associate
127
+
128
+ self
129
+ end
130
+
131
+ def records_by_owner
132
+ load_records unless defined?(@records_by_owner)
133
+
134
+ @records_by_owner
135
+ end
136
+
137
+ def preloaded_records
138
+ load_records unless defined?(@preloaded_records)
139
+
140
+ @preloaded_records
141
+ end
142
+
143
+ # The name of the key on the associated records
144
+ def association_key_name
145
+ reflection.join_primary_key(klass)
146
+ end
147
+
148
+ def loader_query
149
+ LoaderQuery.new(scope, association_key_name)
150
+ end
151
+
152
+ def owners_by_key
153
+ @owners_by_key ||= owners.each_with_object({}) do |owner, result|
154
+ key = convert_key(owner[owner_key_name])
155
+ (result[key] ||= []) << owner if key
27
156
  end
28
157
  end
29
158
 
30
- protected
31
- attr_reader :owners, :reflection, :preload_scope, :model, :klass
159
+ def loaded?(owner)
160
+ owner.association(reflection.name).loaded?
161
+ end
32
162
 
33
- private
34
- # The name of the key on the associated records
35
- def association_key_name
36
- reflection.join_primary_key(klass)
163
+ def target_for(owner)
164
+ Array.wrap(owner.association(reflection.name).target)
165
+ end
166
+
167
+ def scope
168
+ @scope ||= build_scope
169
+ end
170
+
171
+ def set_inverse(record)
172
+ if owners = owners_by_key[convert_key(record[association_key_name])]
173
+ # Processing only the first owner
174
+ # because the record is modified but not an owner
175
+ association = owners.first.association(reflection.name)
176
+ association.set_inverse_instance(record)
177
+ end
178
+ end
179
+
180
+ def load_records(raw_records = nil)
181
+ # owners can be duplicated when a relation has a collection association join
182
+ # #compare_by_identity makes such owners different hash keys
183
+ @records_by_owner = {}.compare_by_identity
184
+ raw_records ||= loader_query.records_for([self])
185
+
186
+ @preloaded_records = raw_records.select do |record|
187
+ assignments = false
188
+
189
+ owners_by_key[convert_key(record[association_key_name])]&.each do |owner|
190
+ entries = (@records_by_owner[owner] ||= [])
191
+
192
+ if reflection.collection? || entries.empty?
193
+ entries << record
194
+ assignments = true
195
+ end
196
+ end
197
+
198
+ assignments
37
199
  end
200
+ end
201
+
202
+ def associate_records_from_unscoped(unscoped_records)
203
+ return if unscoped_records.nil? || unscoped_records.empty?
204
+ return if !reflection_scope.empty_scope?
205
+ return if preload_scope && !preload_scope.empty_scope?
206
+ return if reflection.collection?
207
+
208
+ unscoped_records.select { |r| r[association_key_name].present? }.each do |record|
209
+ owners = owners_by_key[convert_key(record[association_key_name])]
210
+ owners&.each_with_index do |owner, i|
211
+ association = owner.association(reflection.name)
212
+ association.target = record
213
+
214
+ if i == 0 # Set inverse on first owner
215
+ association.set_inverse_instance(record)
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ private
222
+ attr_reader :owners, :reflection, :preload_scope, :model
38
223
 
39
224
  # The name of the key on the model which declares the association
40
225
  def owner_key_name
@@ -42,29 +227,17 @@ module ActiveRecord
42
227
  end
43
228
 
44
229
  def associate_records_to_owner(owner, records)
230
+ return if loaded?(owner)
231
+
45
232
  association = owner.association(reflection.name)
46
- association.loaded!
233
+
47
234
  if reflection.collection?
48
- association.target.concat(records)
235
+ association.target = records
49
236
  else
50
- association.target = records.first unless records.empty?
237
+ association.target = records.first
51
238
  end
52
239
  end
53
240
 
54
- def owner_keys
55
- @owner_keys ||= owners_by_key.keys
56
- end
57
-
58
- def owners_by_key
59
- unless defined?(@owners_by_key)
60
- @owners_by_key = owners.each_with_object({}) do |owner, h|
61
- key = convert_key(owner[owner_key_name])
62
- h[key] = owner if key
63
- end
64
- end
65
- @owners_by_key
66
- end
67
-
68
241
  def key_conversion_required?
69
242
  unless defined?(@key_conversion_required)
70
243
  @key_conversion_required = (association_key_type != owner_key_type)
@@ -89,41 +262,28 @@ module ActiveRecord
89
262
  @model.type_for_attribute(owner_key_name).type
90
263
  end
91
264
 
92
- def load_records(&block)
93
- return {} if owner_keys.empty?
94
- # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
95
- # Make several smaller queries if necessary or make one query if the adapter supports it
96
- slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
97
- @preloaded_records = slices.flat_map do |slice|
98
- records_for(slice, &block)
99
- end
100
- @preloaded_records.group_by do |record|
101
- convert_key(record[association_key_name])
102
- end
103
- end
104
-
105
- def records_for(ids, &block)
106
- scope.where(association_key_name => ids).load(&block)
107
- end
108
-
109
- def scope
110
- @scope ||= build_scope
111
- end
112
-
113
265
  def reflection_scope
114
- @reflection_scope ||= reflection.scope ? reflection.scope_for(klass.unscoped) : klass.unscoped
266
+ @reflection_scope ||= reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(klass.unscoped, &:merge!)
115
267
  end
116
268
 
117
269
  def build_scope
118
270
  scope = klass.scope_for_association
119
271
 
120
- if reflection.type
272
+ if reflection.type && !reflection.through_reflection?
121
273
  scope.where!(reflection.type => model.polymorphic_name)
122
274
  end
123
275
 
124
- scope.merge!(reflection_scope) if reflection.scope
125
- scope.merge!(preload_scope) if preload_scope
126
- scope
276
+ scope.merge!(reflection_scope) unless reflection_scope.empty_scope?
277
+
278
+ if preload_scope && !preload_scope.empty_scope?
279
+ scope.merge!(preload_scope)
280
+ end
281
+
282
+ cascade_strict_loading(scope)
283
+ end
284
+
285
+ def cascade_strict_loading(scope)
286
+ preload_scope&.strict_loading_value ? scope.strict_loading : scope
127
287
  end
128
288
  end
129
289
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class Preloader
6
+ class Batch # :nodoc:
7
+ def initialize(preloaders, available_records:)
8
+ @preloaders = preloaders.reject(&:empty?)
9
+ @available_records = available_records.flatten.group_by { |r| r.class.base_class }
10
+ end
11
+
12
+ def call
13
+ branches = @preloaders.flat_map(&:branches)
14
+ until branches.empty?
15
+ loaders = branches.flat_map(&:runnable_loaders)
16
+
17
+ loaders.each { |loader| loader.associate_records_from_unscoped(@available_records[loader.klass.base_class]) }
18
+
19
+ if loaders.any?
20
+ future_tables = branches.flat_map do |branch|
21
+ branch.future_classes - branch.runnable_loaders.map(&:klass)
22
+ end.map(&:table_name).uniq
23
+
24
+ target_loaders = loaders.reject { |l| future_tables.include?(l.table_name) }
25
+ target_loaders = loaders if target_loaders.empty?
26
+
27
+ group_and_load_similar(target_loaders)
28
+ target_loaders.each(&:run)
29
+ end
30
+
31
+ finished, in_progress = branches.partition(&:done?)
32
+
33
+ branches = in_progress + finished.flat_map(&:children)
34
+ end
35
+ end
36
+
37
+ private
38
+ attr_reader :loaders
39
+
40
+ def group_and_load_similar(loaders)
41
+ loaders.grep_v(ThroughAssociation).group_by(&:loader_query).each_pair do |query, similar_loaders|
42
+ query.load_records_in_batch(similar_loaders)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end