activerecord 5.2.6 → 6.1.3.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 (316) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1038 -571
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +13 -12
  7. data/lib/active_record/aggregations.rb +9 -8
  8. data/lib/active_record/association_relation.rb +30 -10
  9. data/lib/active_record/associations.rb +137 -25
  10. data/lib/active_record/associations/alias_tracker.rb +19 -16
  11. data/lib/active_record/associations/association.rb +95 -42
  12. data/lib/active_record/associations/association_scope.rb +23 -21
  13. data/lib/active_record/associations/belongs_to_association.rb +54 -46
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -6
  15. data/lib/active_record/associations/builder/association.rb +45 -22
  16. data/lib/active_record/associations/builder/belongs_to.rb +29 -59
  17. data/lib/active_record/associations/builder/collection_association.rb +8 -17
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  19. data/lib/active_record/associations/builder/has_many.rb +8 -2
  20. data/lib/active_record/associations/builder/has_one.rb +33 -2
  21. data/lib/active_record/associations/builder/singular_association.rb +3 -1
  22. data/lib/active_record/associations/collection_association.rb +31 -29
  23. data/lib/active_record/associations/collection_proxy.rb +25 -21
  24. data/lib/active_record/associations/foreign_association.rb +20 -0
  25. data/lib/active_record/associations/has_many_association.rb +26 -13
  26. data/lib/active_record/associations/has_many_through_association.rb +24 -18
  27. data/lib/active_record/associations/has_one_association.rb +43 -31
  28. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  29. data/lib/active_record/associations/join_dependency.rb +91 -60
  30. data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
  31. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  32. data/lib/active_record/associations/preloader.rb +47 -34
  33. data/lib/active_record/associations/preloader/association.rb +71 -43
  34. data/lib/active_record/associations/preloader/through_association.rb +49 -40
  35. data/lib/active_record/associations/singular_association.rb +3 -17
  36. data/lib/active_record/associations/through_association.rb +1 -1
  37. data/lib/active_record/attribute_assignment.rb +17 -19
  38. data/lib/active_record/attribute_methods.rb +81 -143
  39. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -7
  40. data/lib/active_record/attribute_methods/dirty.rb +101 -40
  41. data/lib/active_record/attribute_methods/primary_key.rb +20 -25
  42. data/lib/active_record/attribute_methods/query.rb +4 -8
  43. data/lib/active_record/attribute_methods/read.rb +14 -56
  44. data/lib/active_record/attribute_methods/serialization.rb +12 -7
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  46. data/lib/active_record/attribute_methods/write.rb +18 -34
  47. data/lib/active_record/attributes.rb +46 -9
  48. data/lib/active_record/autosave_association.rb +57 -42
  49. data/lib/active_record/base.rb +4 -17
  50. data/lib/active_record/callbacks.rb +158 -43
  51. data/lib/active_record/coders/yaml_column.rb +1 -2
  52. data/lib/active_record/connection_adapters.rb +50 -0
  53. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +272 -130
  54. data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -36
  55. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -146
  56. data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -14
  57. data/lib/active_record/connection_adapters/abstract/quoting.rb +98 -47
  58. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -110
  60. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +207 -90
  61. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -4
  62. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +385 -144
  63. data/lib/active_record/connection_adapters/abstract/transaction.rb +155 -68
  64. data/lib/active_record/connection_adapters/abstract_adapter.rb +228 -98
  65. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +243 -275
  66. data/lib/active_record/connection_adapters/column.rb +30 -12
  67. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  68. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  69. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  70. data/lib/active_record/connection_adapters/mysql/database_statements.rb +86 -32
  71. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +59 -7
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +18 -7
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +139 -19
  77. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +53 -18
  79. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  80. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +37 -28
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +38 -54
  83. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  93. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  97. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  99. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  100. data/lib/active_record/connection_adapters/postgresql/quoting.rb +47 -10
  101. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  102. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +19 -4
  103. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  104. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  105. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +120 -100
  106. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  107. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  108. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -120
  109. data/lib/active_record/connection_adapters/schema_cache.rb +127 -21
  110. data/lib/active_record/connection_adapters/sql_type_metadata.rb +19 -6
  111. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  112. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
  113. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  114. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +77 -13
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +174 -186
  116. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  117. data/lib/active_record/connection_handling.rb +293 -33
  118. data/lib/active_record/core.rb +323 -97
  119. data/lib/active_record/counter_cache.rb +8 -30
  120. data/lib/active_record/database_configurations.rb +272 -0
  121. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  122. data/lib/active_record/database_configurations/database_config.rb +80 -0
  123. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  124. data/lib/active_record/database_configurations/url_config.rb +53 -0
  125. data/lib/active_record/delegated_type.rb +209 -0
  126. data/lib/active_record/destroy_association_async_job.rb +36 -0
  127. data/lib/active_record/dynamic_matchers.rb +3 -4
  128. data/lib/active_record/enum.rb +111 -37
  129. data/lib/active_record/errors.rb +62 -19
  130. data/lib/active_record/explain.rb +10 -6
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/file.rb +10 -17
  133. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  134. data/lib/active_record/fixture_set/render_context.rb +17 -0
  135. data/lib/active_record/fixture_set/table_row.rb +152 -0
  136. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  137. data/lib/active_record/fixtures.rb +200 -481
  138. data/lib/active_record/gem_version.rb +4 -4
  139. data/lib/active_record/inheritance.rb +53 -24
  140. data/lib/active_record/insert_all.rb +208 -0
  141. data/lib/active_record/integration.rb +67 -17
  142. data/lib/active_record/internal_metadata.rb +26 -9
  143. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  144. data/lib/active_record/locking/optimistic.rb +37 -23
  145. data/lib/active_record/locking/pessimistic.rb +9 -5
  146. data/lib/active_record/log_subscriber.rb +35 -35
  147. data/lib/active_record/middleware/database_selector.rb +77 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  149. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  150. data/lib/active_record/migration.rb +206 -157
  151. data/lib/active_record/migration/command_recorder.rb +96 -44
  152. data/lib/active_record/migration/compatibility.rb +142 -64
  153. data/lib/active_record/migration/join_table.rb +0 -1
  154. data/lib/active_record/model_schema.rb +148 -22
  155. data/lib/active_record/nested_attributes.rb +4 -7
  156. data/lib/active_record/no_touching.rb +8 -1
  157. data/lib/active_record/null_relation.rb +0 -1
  158. data/lib/active_record/persistence.rb +267 -59
  159. data/lib/active_record/query_cache.rb +21 -4
  160. data/lib/active_record/querying.rb +40 -23
  161. data/lib/active_record/railtie.rb +115 -58
  162. data/lib/active_record/railties/console_sandbox.rb +2 -4
  163. data/lib/active_record/railties/controller_runtime.rb +30 -35
  164. data/lib/active_record/railties/databases.rake +408 -78
  165. data/lib/active_record/readonly_attributes.rb +4 -0
  166. data/lib/active_record/reflection.rb +109 -93
  167. data/lib/active_record/relation.rb +374 -104
  168. data/lib/active_record/relation/batches.rb +44 -35
  169. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  170. data/lib/active_record/relation/calculations.rb +153 -90
  171. data/lib/active_record/relation/delegation.rb +35 -50
  172. data/lib/active_record/relation/finder_methods.rb +64 -39
  173. data/lib/active_record/relation/from_clause.rb +5 -1
  174. data/lib/active_record/relation/merger.rb +32 -40
  175. data/lib/active_record/relation/predicate_builder.rb +62 -45
  176. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  177. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
  180. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  181. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  182. data/lib/active_record/relation/query_attribute.rb +13 -8
  183. data/lib/active_record/relation/query_methods.rb +475 -186
  184. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  185. data/lib/active_record/relation/spawn_methods.rb +9 -9
  186. data/lib/active_record/relation/where_clause.rb +111 -61
  187. data/lib/active_record/result.rb +64 -38
  188. data/lib/active_record/runtime_registry.rb +2 -2
  189. data/lib/active_record/sanitization.rb +22 -41
  190. data/lib/active_record/schema.rb +2 -11
  191. data/lib/active_record/schema_dumper.rb +54 -9
  192. data/lib/active_record/schema_migration.rb +7 -9
  193. data/lib/active_record/scoping.rb +8 -9
  194. data/lib/active_record/scoping/default.rb +4 -6
  195. data/lib/active_record/scoping/named.rb +17 -24
  196. data/lib/active_record/secure_token.rb +16 -8
  197. data/lib/active_record/serialization.rb +5 -3
  198. data/lib/active_record/signed_id.rb +116 -0
  199. data/lib/active_record/statement_cache.rb +49 -6
  200. data/lib/active_record/store.rb +88 -9
  201. data/lib/active_record/suppressor.rb +2 -2
  202. data/lib/active_record/table_metadata.rb +42 -43
  203. data/lib/active_record/tasks/database_tasks.rb +277 -81
  204. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  205. data/lib/active_record/tasks/postgresql_database_tasks.rb +27 -32
  206. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  207. data/lib/active_record/test_databases.rb +24 -0
  208. data/lib/active_record/test_fixtures.rb +246 -0
  209. data/lib/active_record/timestamp.rb +43 -32
  210. data/lib/active_record/touch_later.rb +23 -22
  211. data/lib/active_record/transactions.rb +62 -118
  212. data/lib/active_record/translation.rb +1 -1
  213. data/lib/active_record/type.rb +10 -5
  214. data/lib/active_record/type/adapter_specific_registry.rb +3 -13
  215. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  216. data/lib/active_record/type/serialized.rb +6 -3
  217. data/lib/active_record/type/time.rb +10 -0
  218. data/lib/active_record/type/type_map.rb +0 -1
  219. data/lib/active_record/type/unsigned_integer.rb +0 -1
  220. data/lib/active_record/type_caster/connection.rb +15 -15
  221. data/lib/active_record/type_caster/map.rb +8 -8
  222. data/lib/active_record/validations.rb +4 -3
  223. data/lib/active_record/validations/associated.rb +1 -2
  224. data/lib/active_record/validations/numericality.rb +35 -0
  225. data/lib/active_record/validations/uniqueness.rb +38 -30
  226. data/lib/arel.rb +54 -0
  227. data/lib/arel/alias_predication.rb +9 -0
  228. data/lib/arel/attributes/attribute.rb +41 -0
  229. data/lib/arel/collectors/bind.rb +29 -0
  230. data/lib/arel/collectors/composite.rb +39 -0
  231. data/lib/arel/collectors/plain_string.rb +20 -0
  232. data/lib/arel/collectors/sql_string.rb +27 -0
  233. data/lib/arel/collectors/substitute_binds.rb +35 -0
  234. data/lib/arel/crud.rb +42 -0
  235. data/lib/arel/delete_manager.rb +18 -0
  236. data/lib/arel/errors.rb +9 -0
  237. data/lib/arel/expressions.rb +29 -0
  238. data/lib/arel/factory_methods.rb +49 -0
  239. data/lib/arel/insert_manager.rb +49 -0
  240. data/lib/arel/math.rb +45 -0
  241. data/lib/arel/nodes.rb +70 -0
  242. data/lib/arel/nodes/and.rb +32 -0
  243. data/lib/arel/nodes/ascending.rb +23 -0
  244. data/lib/arel/nodes/binary.rb +126 -0
  245. data/lib/arel/nodes/bind_param.rb +44 -0
  246. data/lib/arel/nodes/case.rb +55 -0
  247. data/lib/arel/nodes/casted.rb +62 -0
  248. data/lib/arel/nodes/comment.rb +29 -0
  249. data/lib/arel/nodes/count.rb +12 -0
  250. data/lib/arel/nodes/delete_statement.rb +45 -0
  251. data/lib/arel/nodes/descending.rb +23 -0
  252. data/lib/arel/nodes/equality.rb +15 -0
  253. data/lib/arel/nodes/extract.rb +24 -0
  254. data/lib/arel/nodes/false.rb +16 -0
  255. data/lib/arel/nodes/full_outer_join.rb +8 -0
  256. data/lib/arel/nodes/function.rb +44 -0
  257. data/lib/arel/nodes/grouping.rb +11 -0
  258. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  259. data/lib/arel/nodes/in.rb +15 -0
  260. data/lib/arel/nodes/infix_operation.rb +92 -0
  261. data/lib/arel/nodes/inner_join.rb +8 -0
  262. data/lib/arel/nodes/insert_statement.rb +37 -0
  263. data/lib/arel/nodes/join_source.rb +20 -0
  264. data/lib/arel/nodes/matches.rb +18 -0
  265. data/lib/arel/nodes/named_function.rb +23 -0
  266. data/lib/arel/nodes/node.rb +51 -0
  267. data/lib/arel/nodes/node_expression.rb +13 -0
  268. data/lib/arel/nodes/ordering.rb +27 -0
  269. data/lib/arel/nodes/outer_join.rb +8 -0
  270. data/lib/arel/nodes/over.rb +15 -0
  271. data/lib/arel/nodes/regexp.rb +16 -0
  272. data/lib/arel/nodes/right_outer_join.rb +8 -0
  273. data/lib/arel/nodes/select_core.rb +67 -0
  274. data/lib/arel/nodes/select_statement.rb +41 -0
  275. data/lib/arel/nodes/sql_literal.rb +19 -0
  276. data/lib/arel/nodes/string_join.rb +11 -0
  277. data/lib/arel/nodes/table_alias.rb +31 -0
  278. data/lib/arel/nodes/terminal.rb +16 -0
  279. data/lib/arel/nodes/true.rb +16 -0
  280. data/lib/arel/nodes/unary.rb +44 -0
  281. data/lib/arel/nodes/unary_operation.rb +20 -0
  282. data/lib/arel/nodes/unqualified_column.rb +22 -0
  283. data/lib/arel/nodes/update_statement.rb +41 -0
  284. data/lib/arel/nodes/values_list.rb +9 -0
  285. data/lib/arel/nodes/window.rb +126 -0
  286. data/lib/arel/nodes/with.rb +11 -0
  287. data/lib/arel/order_predications.rb +13 -0
  288. data/lib/arel/predications.rb +250 -0
  289. data/lib/arel/select_manager.rb +270 -0
  290. data/lib/arel/table.rb +118 -0
  291. data/lib/arel/tree_manager.rb +72 -0
  292. data/lib/arel/update_manager.rb +34 -0
  293. data/lib/arel/visitors.rb +13 -0
  294. data/lib/arel/visitors/dot.rb +308 -0
  295. data/lib/arel/visitors/mysql.rb +93 -0
  296. data/lib/arel/visitors/postgresql.rb +120 -0
  297. data/lib/arel/visitors/sqlite.rb +38 -0
  298. data/lib/arel/visitors/to_sql.rb +899 -0
  299. data/lib/arel/visitors/visitor.rb +45 -0
  300. data/lib/arel/window_predications.rb +9 -0
  301. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  302. data/lib/rails/generators/active_record/migration.rb +19 -2
  303. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  304. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  305. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  306. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  307. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  308. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  309. metadata +119 -34
  310. data/lib/active_record/attribute_decorators.rb +0 -90
  311. data/lib/active_record/collection_cache_key.rb +0 -53
  312. data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
  313. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
  314. data/lib/active_record/define_callbacks.rb +0 -22
  315. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
  316. data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_record/associations/join_dependency/join_part"
4
+ require "active_support/core_ext/array/extract"
4
5
 
5
6
  module ActiveRecord
6
7
  module Associations
@@ -13,7 +14,6 @@ module ActiveRecord
13
14
  super(reflection.klass, children)
14
15
 
15
16
  @reflection = reflection
16
- @tables = nil
17
17
  end
18
18
 
19
19
  def match?(other)
@@ -23,26 +23,47 @@ module ActiveRecord
23
23
 
24
24
  def join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
25
25
  joins = []
26
+ chain = []
27
+
28
+ reflection.chain.each do |reflection|
29
+ table, terminated = yield reflection
30
+ @table ||= table
31
+
32
+ if terminated
33
+ foreign_table, foreign_klass = table, reflection.klass
34
+ break
35
+ end
36
+
37
+ chain << [reflection, table]
38
+ end
26
39
 
27
40
  # The chain starts with the target table, but we want to end with it here (makes
28
41
  # more sense in this context), so we reverse
29
- reflection.chain.reverse_each.with_index(1) do |reflection, i|
30
- table = tables[-i]
42
+ chain.reverse_each do |reflection, table|
31
43
  klass = reflection.klass
32
44
 
33
- join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
45
+ scope = reflection.join_scope(table, foreign_table, foreign_klass)
46
+
47
+ unless scope.references_values.empty?
48
+ associations = scope.eager_load_values | scope.includes_values
49
+
50
+ unless associations.empty?
51
+ scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
52
+ end
53
+ end
34
54
 
35
- arel = join_scope.arel(alias_tracker.aliases)
55
+ arel = scope.arel(alias_tracker.aliases)
36
56
  nodes = arel.constraints.first
37
57
 
38
- others, children = nodes.children.partition do |node|
39
- !fetch_arel_attribute(node) { |attr| attr.relation.name == table.name }
58
+ if nodes.is_a?(Arel::Nodes::And)
59
+ others = nodes.children.extract! do |node|
60
+ !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
61
+ end
40
62
  end
41
- nodes = table.create_and(children)
42
63
 
43
- joins << table.create_join(table, table.create_on(nodes), join_type)
64
+ joins << join_type.new(table, Arel::Nodes::On.new(nodes))
44
65
 
45
- unless others.empty?
66
+ if others && !others.empty?
46
67
  joins.concat arel.join_sources
47
68
  append_constraints(joins.last, others)
48
69
  end
@@ -54,25 +75,26 @@ module ActiveRecord
54
75
  joins
55
76
  end
56
77
 
57
- def tables=(tables)
58
- @tables = tables
59
- @table = tables.first
78
+ def readonly?
79
+ return @readonly if defined?(@readonly)
80
+
81
+ @readonly = reflection.scope && reflection.scope_for(base_klass.unscoped).readonly_value
60
82
  end
61
83
 
62
- private
63
- def fetch_arel_attribute(value)
64
- case value
65
- when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
66
- yield value.left.is_a?(Arel::Attributes::Attribute) ? value.left : value.right
67
- end
68
- end
84
+ def strict_loading?
85
+ return @strict_loading if defined?(@strict_loading)
69
86
 
87
+ @strict_loading = reflection.scope && reflection.scope_for(base_klass.unscoped).strict_loading_value
88
+ end
89
+
90
+ private
70
91
  def append_constraints(join, constraints)
71
92
  if join.is_a?(Arel::Nodes::StringJoin)
72
- join_string = table.create_and(constraints.unshift(join.left))
93
+ join_string = Arel::Nodes::And.new(constraints.unshift join.left)
73
94
  join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
74
95
  else
75
- join.right.expr.children.concat(constraints)
96
+ right = join.right
97
+ right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
76
98
  end
77
99
  end
78
100
  end
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  # association.
18
18
  attr_reader :base_klass, :children
19
19
 
20
- delegate :table_name, :column_names, :primary_key, to: :base_klass
20
+ delegate :table_name, :column_names, :primary_key, :attribute_types, to: :base_klass
21
21
 
22
22
  def initialize(base_klass, children)
23
23
  @base_klass = base_klass
@@ -54,16 +54,16 @@ module ActiveRecord
54
54
  length = column_names_with_alias.length
55
55
 
56
56
  while index < length
57
- column_name, alias_name = column_names_with_alias[index]
58
- hash[column_name] = row[alias_name]
57
+ column = column_names_with_alias[index]
58
+ hash[column.name] = row[column.alias]
59
59
  index += 1
60
60
  end
61
61
 
62
62
  hash
63
63
  end
64
64
 
65
- def instantiate(row, aliases, &block)
66
- base_klass.instantiate(extract_record(row, aliases), &block)
65
+ def instantiate(row, aliases, column_types = {}, &block)
66
+ base_klass.instantiate(extract_record(row, aliases), column_types, &block)
67
67
  end
68
68
  end
69
69
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/enumerable"
4
+
3
5
  module ActiveRecord
4
6
  module Associations
5
7
  # Implements the details of eager loading of Active Record associations.
@@ -58,7 +60,7 @@ module ActiveRecord
58
60
  # == Parameters
59
61
  # +records+ is an array of ActiveRecord::Base. This array needs not be flat,
60
62
  # i.e. +records+ itself may also contain arrays of records. In any case,
61
- # +preload_associations+ will preload the all associations records by
63
+ # +preload_associations+ will preload all associations records by
62
64
  # flattening +records+.
63
65
  #
64
66
  # +associations+ specifies one or more associations that you want to
@@ -88,44 +90,46 @@ module ActiveRecord
88
90
  if records.empty?
89
91
  []
90
92
  else
91
- records.uniq!
92
93
  Array.wrap(associations).flat_map { |association|
93
94
  preloaders_on association, records, preload_scope
94
95
  }
95
96
  end
96
97
  end
97
98
 
98
- private
99
+ def initialize(associate_by_default: true)
100
+ @associate_by_default = associate_by_default
101
+ end
99
102
 
103
+ private
100
104
  # Loads all the given data into +records+ for the +association+.
101
- def preloaders_on(association, records, scope)
105
+ def preloaders_on(association, records, scope, polymorphic_parent = false)
102
106
  case association
103
107
  when Hash
104
- preloaders_for_hash(association, records, scope)
105
- when Symbol
106
- preloaders_for_one(association, records, scope)
107
- when String
108
- preloaders_for_one(association.to_sym, records, scope)
108
+ preloaders_for_hash(association, records, scope, polymorphic_parent)
109
+ when Symbol, String
110
+ preloaders_for_one(association, records, scope, polymorphic_parent)
109
111
  else
110
112
  raise ArgumentError, "#{association.inspect} was not recognized for preload"
111
113
  end
112
114
  end
113
115
 
114
- def preloaders_for_hash(association, records, scope)
116
+ def preloaders_for_hash(association, records, scope, polymorphic_parent)
115
117
  association.flat_map { |parent, child|
116
- loaders = preloaders_for_one parent, records, scope
117
-
118
- recs = loaders.flat_map(&:preloaded_records).uniq
119
- loaders.concat Array.wrap(child).flat_map { |assoc|
120
- preloaders_on assoc, recs, scope
121
- }
122
- loaders
118
+ grouped_records(parent, records, polymorphic_parent).flat_map do |reflection, reflection_records|
119
+ loaders = preloaders_for_reflection(reflection, reflection_records, scope)
120
+ recs = loaders.flat_map(&:preloaded_records).uniq
121
+ child_polymorphic_parent = reflection && reflection.options[:polymorphic]
122
+ loaders.concat Array.wrap(child).flat_map { |assoc|
123
+ preloaders_on assoc, recs, scope, child_polymorphic_parent
124
+ }
125
+ loaders
126
+ end
123
127
  }
124
128
  end
125
129
 
126
130
  # Loads all the given data into +records+ for a singular +association+.
127
131
  #
128
- # Functions by instantiating a preloader class such as Preloader::HasManyThrough and
132
+ # Functions by instantiating a preloader class such as Preloader::Association and
129
133
  # call the +run+ method for each passed in class in the +records+ argument.
130
134
  #
131
135
  # Not all records have the same class, so group then preload group on the reflection
@@ -135,41 +139,50 @@ module ActiveRecord
135
139
  # Additionally, polymorphic belongs_to associations can have multiple associated
136
140
  # classes, depending on the polymorphic_type field. So we group by the classes as
137
141
  # well.
138
- def preloaders_for_one(association, records, scope)
139
- grouped_records(association, records).flat_map do |reflection, klasses|
140
- klasses.map do |rhs_klass, rs|
141
- loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
142
- loader.run self
143
- loader
142
+ def preloaders_for_one(association, records, scope, polymorphic_parent)
143
+ grouped_records(association, records, polymorphic_parent)
144
+ .flat_map do |reflection, reflection_records|
145
+ preloaders_for_reflection reflection, reflection_records, scope
144
146
  end
147
+ end
148
+
149
+ def preloaders_for_reflection(reflection, records, scope)
150
+ records.group_by { |record| record.association(reflection.name).klass }.map do |rhs_klass, rs|
151
+ preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope, @associate_by_default).run
145
152
  end
146
153
  end
147
154
 
148
- def grouped_records(association, records)
155
+ def grouped_records(association, records, polymorphic_parent)
149
156
  h = {}
150
157
  records.each do |record|
151
- next unless record
152
- assoc = record.association(association)
153
- next unless assoc.klass
154
- klasses = h[assoc.reflection] ||= {}
155
- (klasses[assoc.klass] ||= []) << record
158
+ reflection = record.class._reflect_on_association(association)
159
+ next if polymorphic_parent && !reflection || !record.association(association).klass
160
+ (h[reflection] ||= []) << record
156
161
  end
157
162
  h
158
163
  end
159
164
 
160
165
  class AlreadyLoaded # :nodoc:
161
- def initialize(klass, owners, reflection, preload_scope)
166
+ def initialize(klass, owners, reflection, preload_scope, associate_by_default = true)
162
167
  @owners = owners
163
168
  @reflection = reflection
164
169
  end
165
170
 
166
- def run(preloader); end
171
+ def run
172
+ self
173
+ end
167
174
 
168
175
  def preloaded_records
169
- owners.flat_map { |owner| owner.association(reflection.name).target }
176
+ @preloaded_records ||= records_by_owner.flat_map(&:last)
177
+ end
178
+
179
+ def records_by_owner
180
+ @records_by_owner ||= owners.index_with do |owner|
181
+ Array(owner.association(reflection.name).target)
182
+ end
170
183
  end
171
184
 
172
- protected
185
+ private
173
186
  attr_reader :owners, :reflection
174
187
  end
175
188
 
@@ -4,33 +4,62 @@ module ActiveRecord
4
4
  module Associations
5
5
  class Preloader
6
6
  class Association #:nodoc:
7
- attr_reader :preloaded_records
8
-
9
- def initialize(klass, owners, reflection, preload_scope)
7
+ def initialize(klass, owners, reflection, preload_scope, associate_by_default = true)
10
8
  @klass = klass
11
- @owners = owners
9
+ @owners = owners.uniq(&:__id__)
12
10
  @reflection = reflection
13
11
  @preload_scope = preload_scope
12
+ @associate = associate_by_default || !preload_scope || preload_scope.empty_scope?
14
13
  @model = owners.first && owners.first.class
15
- @preloaded_records = []
16
14
  end
17
15
 
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)
23
- end
16
+ def run
17
+ records = records_by_owner
24
18
 
25
19
  owners.each do |owner|
26
- associate_records_to_owner(owner, records[convert_key(owner[owner_key_name])] || [])
27
- end
20
+ associate_records_to_owner(owner, records[owner] || [])
21
+ end if @associate
22
+
23
+ self
28
24
  end
29
25
 
30
- protected
31
- attr_reader :owners, :reflection, :preload_scope, :model, :klass
26
+ def records_by_owner
27
+ load_records unless defined?(@records_by_owner)
28
+
29
+ @records_by_owner
30
+ end
31
+
32
+ def preloaded_records
33
+ load_records unless defined?(@preloaded_records)
34
+
35
+ @preloaded_records
36
+ end
32
37
 
33
38
  private
39
+ attr_reader :owners, :reflection, :preload_scope, :model, :klass
40
+
41
+ def load_records
42
+ # owners can be duplicated when a relation has a collection association join
43
+ # #compare_by_identity makes such owners different hash keys
44
+ @records_by_owner = {}.compare_by_identity
45
+ raw_records = owner_keys.empty? ? [] : records_for(owner_keys)
46
+
47
+ @preloaded_records = raw_records.select do |record|
48
+ assignments = false
49
+
50
+ owners_by_key[convert_key(record[association_key_name])].each do |owner|
51
+ entries = (@records_by_owner[owner] ||= [])
52
+
53
+ if reflection.collection? || entries.empty?
54
+ entries << record
55
+ assignments = true
56
+ end
57
+ end
58
+
59
+ assignments
60
+ end
61
+ end
62
+
34
63
  # The name of the key on the associated records
35
64
  def association_key_name
36
65
  reflection.join_primary_key(klass)
@@ -43,11 +72,10 @@ module ActiveRecord
43
72
 
44
73
  def associate_records_to_owner(owner, records)
45
74
  association = owner.association(reflection.name)
46
- association.loaded!
47
75
  if reflection.collection?
48
- association.target.concat(records)
76
+ association.target = records
49
77
  else
50
- association.target = records.first unless records.empty?
78
+ association.target = records.first
51
79
  end
52
80
  end
53
81
 
@@ -56,13 +84,10 @@ module ActiveRecord
56
84
  end
57
85
 
58
86
  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
87
+ @owners_by_key ||= owners.each_with_object({}) do |owner, result|
88
+ key = convert_key(owner[owner_key_name])
89
+ (result[key] ||= []) << owner if key
64
90
  end
65
- @owners_by_key
66
91
  end
67
92
 
68
93
  def key_conversion_required?
@@ -89,41 +114,44 @@ module ActiveRecord
89
114
  @model.type_for_attribute(owner_key_name).type
90
115
  end
91
116
 
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])
117
+ def records_for(ids)
118
+ scope.where(association_key_name => ids).load do |record|
119
+ # Processing only the first owner
120
+ # because the record is modified but not an owner
121
+ owner = owners_by_key[convert_key(record[association_key_name])].first
122
+ association = owner.association(reflection.name)
123
+ association.set_inverse_instance(record)
102
124
  end
103
125
  end
104
126
 
105
- def records_for(ids, &block)
106
- scope.where(association_key_name => ids).load(&block)
107
- end
108
-
109
127
  def scope
110
128
  @scope ||= build_scope
111
129
  end
112
130
 
113
131
  def reflection_scope
114
- @reflection_scope ||= reflection.scope ? reflection.scope_for(klass.unscoped) : klass.unscoped
132
+ @reflection_scope ||= begin
133
+ reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(&:merge!) || klass.unscoped
134
+ end
115
135
  end
116
136
 
117
137
  def build_scope
118
138
  scope = klass.scope_for_association
119
139
 
120
- if reflection.type
140
+ if reflection.type && !reflection.through_reflection?
121
141
  scope.where!(reflection.type => model.polymorphic_name)
122
142
  end
123
143
 
124
- scope.merge!(reflection_scope) if reflection.scope
125
- scope.merge!(preload_scope) if preload_scope
126
- scope
144
+ scope.merge!(reflection_scope) unless reflection_scope.empty_scope?
145
+
146
+ if preload_scope && !preload_scope.empty_scope?
147
+ scope.merge!(preload_scope)
148
+ end
149
+
150
+ if preload_scope && preload_scope.strict_loading_value
151
+ scope.strict_loading
152
+ else
153
+ scope
154
+ end
127
155
  end
128
156
  end
129
157
  end