activerecord 5.2.8.1 → 6.1.6.1

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 +1255 -596
  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 +9 -8
  7. data/lib/active_record/association_relation.rb +30 -10
  8. data/lib/active_record/associations/alias_tracker.rb +19 -16
  9. data/lib/active_record/associations/association.rb +100 -41
  10. data/lib/active_record/associations/association_scope.rb +23 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +55 -48
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -6
  13. data/lib/active_record/associations/builder/association.rb +45 -22
  14. data/lib/active_record/associations/builder/belongs_to.rb +29 -59
  15. data/lib/active_record/associations/builder/collection_association.rb +8 -17
  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 +8 -2
  18. data/lib/active_record/associations/builder/has_one.rb +33 -2
  19. data/lib/active_record/associations/builder/singular_association.rb +3 -1
  20. data/lib/active_record/associations/collection_association.rb +44 -34
  21. data/lib/active_record/associations/collection_proxy.rb +25 -21
  22. data/lib/active_record/associations/foreign_association.rb +20 -0
  23. data/lib/active_record/associations/has_many_association.rb +26 -13
  24. data/lib/active_record/associations/has_many_through_association.rb +24 -18
  25. data/lib/active_record/associations/has_one_association.rb +43 -31
  26. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  27. data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
  28. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  29. data/lib/active_record/associations/join_dependency.rb +91 -60
  30. data/lib/active_record/associations/preloader/association.rb +69 -43
  31. data/lib/active_record/associations/preloader/through_association.rb +49 -40
  32. data/lib/active_record/associations/preloader.rb +47 -34
  33. data/lib/active_record/associations/singular_association.rb +3 -17
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/associations.rb +137 -25
  36. data/lib/active_record/attribute_assignment.rb +17 -19
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -7
  38. data/lib/active_record/attribute_methods/dirty.rb +101 -40
  39. data/lib/active_record/attribute_methods/primary_key.rb +20 -25
  40. data/lib/active_record/attribute_methods/query.rb +4 -8
  41. data/lib/active_record/attribute_methods/read.rb +14 -56
  42. data/lib/active_record/attribute_methods/serialization.rb +12 -7
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  44. data/lib/active_record/attribute_methods/write.rb +18 -34
  45. data/lib/active_record/attribute_methods.rb +81 -143
  46. data/lib/active_record/attributes.rb +46 -9
  47. data/lib/active_record/autosave_association.rb +57 -42
  48. data/lib/active_record/base.rb +4 -17
  49. data/lib/active_record/callbacks.rb +158 -43
  50. data/lib/active_record/coders/yaml_column.rb +1 -2
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +272 -130
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -36
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -146
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -14
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +98 -47
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -110
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +211 -90
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -4
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +385 -144
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +167 -69
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +229 -99
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +243 -275
  64. data/lib/active_record/connection_adapters/column.rb +30 -12
  65. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  66. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  67. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  68. data/lib/active_record/connection_adapters/mysql/database_statements.rb +88 -32
  69. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  70. data/lib/active_record/connection_adapters/mysql/quoting.rb +59 -7
  71. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  72. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  73. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +18 -7
  74. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +142 -19
  75. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +53 -18
  77. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  78. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  79. data/lib/active_record/connection_adapters/postgresql/column.rb +37 -28
  80. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -54
  81. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  93. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  94. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  96. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  97. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  98. data/lib/active_record/connection_adapters/postgresql/quoting.rb +47 -10
  99. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  100. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +19 -4
  101. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  102. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  103. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +120 -100
  104. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  105. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  106. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -120
  107. data/lib/active_record/connection_adapters/schema_cache.rb +159 -21
  108. data/lib/active_record/connection_adapters/sql_type_metadata.rb +17 -6
  109. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +146 -0
  110. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
  111. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  112. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +77 -13
  113. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +174 -186
  114. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  115. data/lib/active_record/connection_adapters.rb +52 -0
  116. data/lib/active_record/connection_handling.rb +293 -33
  117. data/lib/active_record/core.rb +333 -98
  118. data/lib/active_record/counter_cache.rb +8 -30
  119. data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -0
  120. data/lib/active_record/database_configurations/database_config.rb +80 -0
  121. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  122. data/lib/active_record/database_configurations/url_config.rb +53 -0
  123. data/lib/active_record/database_configurations.rb +273 -0
  124. data/lib/active_record/delegated_type.rb +209 -0
  125. data/lib/active_record/destroy_association_async_job.rb +36 -0
  126. data/lib/active_record/dynamic_matchers.rb +3 -4
  127. data/lib/active_record/enum.rb +108 -36
  128. data/lib/active_record/errors.rb +62 -19
  129. data/lib/active_record/explain.rb +10 -6
  130. data/lib/active_record/explain_subscriber.rb +1 -1
  131. data/lib/active_record/fixture_set/file.rb +10 -17
  132. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  133. data/lib/active_record/fixture_set/render_context.rb +17 -0
  134. data/lib/active_record/fixture_set/table_row.rb +152 -0
  135. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  136. data/lib/active_record/fixtures.rb +200 -481
  137. data/lib/active_record/gem_version.rb +3 -3
  138. data/lib/active_record/inheritance.rb +53 -24
  139. data/lib/active_record/insert_all.rb +212 -0
  140. data/lib/active_record/integration.rb +67 -17
  141. data/lib/active_record/internal_metadata.rb +28 -9
  142. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  143. data/lib/active_record/locking/optimistic.rb +37 -23
  144. data/lib/active_record/locking/pessimistic.rb +9 -5
  145. data/lib/active_record/log_subscriber.rb +35 -35
  146. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  147. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  148. data/lib/active_record/middleware/database_selector.rb +77 -0
  149. data/lib/active_record/migration/command_recorder.rb +96 -44
  150. data/lib/active_record/migration/compatibility.rb +145 -64
  151. data/lib/active_record/migration/join_table.rb +0 -1
  152. data/lib/active_record/migration.rb +206 -157
  153. data/lib/active_record/model_schema.rb +148 -22
  154. data/lib/active_record/nested_attributes.rb +4 -7
  155. data/lib/active_record/no_touching.rb +8 -1
  156. data/lib/active_record/null_relation.rb +0 -1
  157. data/lib/active_record/persistence.rb +267 -59
  158. data/lib/active_record/query_cache.rb +21 -4
  159. data/lib/active_record/querying.rb +40 -23
  160. data/lib/active_record/railtie.rb +116 -59
  161. data/lib/active_record/railties/console_sandbox.rb +2 -4
  162. data/lib/active_record/railties/controller_runtime.rb +30 -35
  163. data/lib/active_record/railties/databases.rake +411 -80
  164. data/lib/active_record/readonly_attributes.rb +4 -0
  165. data/lib/active_record/reflection.rb +109 -93
  166. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  167. data/lib/active_record/relation/batches.rb +44 -35
  168. data/lib/active_record/relation/calculations.rb +157 -90
  169. data/lib/active_record/relation/delegation.rb +35 -50
  170. data/lib/active_record/relation/finder_methods.rb +64 -39
  171. data/lib/active_record/relation/from_clause.rb +5 -1
  172. data/lib/active_record/relation/merger.rb +32 -40
  173. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  174. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  179. data/lib/active_record/relation/predicate_builder.rb +62 -45
  180. data/lib/active_record/relation/query_attribute.rb +13 -8
  181. data/lib/active_record/relation/query_methods.rb +476 -187
  182. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  183. data/lib/active_record/relation/spawn_methods.rb +9 -9
  184. data/lib/active_record/relation/where_clause.rb +115 -62
  185. data/lib/active_record/relation.rb +379 -115
  186. data/lib/active_record/result.rb +64 -38
  187. data/lib/active_record/runtime_registry.rb +2 -2
  188. data/lib/active_record/sanitization.rb +22 -41
  189. data/lib/active_record/schema.rb +2 -11
  190. data/lib/active_record/schema_dumper.rb +54 -9
  191. data/lib/active_record/schema_migration.rb +7 -9
  192. data/lib/active_record/scoping/default.rb +4 -8
  193. data/lib/active_record/scoping/named.rb +17 -24
  194. data/lib/active_record/scoping.rb +8 -9
  195. data/lib/active_record/secure_token.rb +16 -8
  196. data/lib/active_record/serialization.rb +5 -3
  197. data/lib/active_record/signed_id.rb +116 -0
  198. data/lib/active_record/statement_cache.rb +49 -6
  199. data/lib/active_record/store.rb +88 -9
  200. data/lib/active_record/suppressor.rb +2 -2
  201. data/lib/active_record/table_metadata.rb +42 -43
  202. data/lib/active_record/tasks/database_tasks.rb +277 -81
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +27 -32
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  206. data/lib/active_record/test_databases.rb +24 -0
  207. data/lib/active_record/test_fixtures.rb +287 -0
  208. data/lib/active_record/timestamp.rb +43 -32
  209. data/lib/active_record/touch_later.rb +23 -22
  210. data/lib/active_record/transactions.rb +62 -118
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +3 -13
  213. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  214. data/lib/active_record/type/serialized.rb +6 -3
  215. data/lib/active_record/type/time.rb +10 -0
  216. data/lib/active_record/type/type_map.rb +0 -1
  217. data/lib/active_record/type/unsigned_integer.rb +0 -1
  218. data/lib/active_record/type.rb +10 -5
  219. data/lib/active_record/type_caster/connection.rb +15 -15
  220. data/lib/active_record/type_caster/map.rb +8 -8
  221. data/lib/active_record/validations/associated.rb +1 -2
  222. data/lib/active_record/validations/numericality.rb +35 -0
  223. data/lib/active_record/validations/uniqueness.rb +38 -30
  224. data/lib/active_record/validations.rb +4 -3
  225. data/lib/active_record.rb +13 -12
  226. data/lib/arel/alias_predication.rb +9 -0
  227. data/lib/arel/attributes/attribute.rb +41 -0
  228. data/lib/arel/collectors/bind.rb +29 -0
  229. data/lib/arel/collectors/composite.rb +39 -0
  230. data/lib/arel/collectors/plain_string.rb +20 -0
  231. data/lib/arel/collectors/sql_string.rb +27 -0
  232. data/lib/arel/collectors/substitute_binds.rb +35 -0
  233. data/lib/arel/crud.rb +42 -0
  234. data/lib/arel/delete_manager.rb +18 -0
  235. data/lib/arel/errors.rb +9 -0
  236. data/lib/arel/expressions.rb +29 -0
  237. data/lib/arel/factory_methods.rb +49 -0
  238. data/lib/arel/insert_manager.rb +49 -0
  239. data/lib/arel/math.rb +45 -0
  240. data/lib/arel/nodes/and.rb +32 -0
  241. data/lib/arel/nodes/ascending.rb +23 -0
  242. data/lib/arel/nodes/binary.rb +126 -0
  243. data/lib/arel/nodes/bind_param.rb +44 -0
  244. data/lib/arel/nodes/case.rb +55 -0
  245. data/lib/arel/nodes/casted.rb +62 -0
  246. data/lib/arel/nodes/comment.rb +29 -0
  247. data/lib/arel/nodes/count.rb +12 -0
  248. data/lib/arel/nodes/delete_statement.rb +45 -0
  249. data/lib/arel/nodes/descending.rb +23 -0
  250. data/lib/arel/nodes/equality.rb +15 -0
  251. data/lib/arel/nodes/extract.rb +24 -0
  252. data/lib/arel/nodes/false.rb +16 -0
  253. data/lib/arel/nodes/full_outer_join.rb +8 -0
  254. data/lib/arel/nodes/function.rb +44 -0
  255. data/lib/arel/nodes/grouping.rb +11 -0
  256. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  257. data/lib/arel/nodes/in.rb +15 -0
  258. data/lib/arel/nodes/infix_operation.rb +92 -0
  259. data/lib/arel/nodes/inner_join.rb +8 -0
  260. data/lib/arel/nodes/insert_statement.rb +37 -0
  261. data/lib/arel/nodes/join_source.rb +20 -0
  262. data/lib/arel/nodes/matches.rb +18 -0
  263. data/lib/arel/nodes/named_function.rb +23 -0
  264. data/lib/arel/nodes/node.rb +51 -0
  265. data/lib/arel/nodes/node_expression.rb +13 -0
  266. data/lib/arel/nodes/ordering.rb +27 -0
  267. data/lib/arel/nodes/outer_join.rb +8 -0
  268. data/lib/arel/nodes/over.rb +15 -0
  269. data/lib/arel/nodes/regexp.rb +16 -0
  270. data/lib/arel/nodes/right_outer_join.rb +8 -0
  271. data/lib/arel/nodes/select_core.rb +67 -0
  272. data/lib/arel/nodes/select_statement.rb +41 -0
  273. data/lib/arel/nodes/sql_literal.rb +19 -0
  274. data/lib/arel/nodes/string_join.rb +11 -0
  275. data/lib/arel/nodes/table_alias.rb +31 -0
  276. data/lib/arel/nodes/terminal.rb +16 -0
  277. data/lib/arel/nodes/true.rb +16 -0
  278. data/lib/arel/nodes/unary.rb +44 -0
  279. data/lib/arel/nodes/unary_operation.rb +20 -0
  280. data/lib/arel/nodes/unqualified_column.rb +22 -0
  281. data/lib/arel/nodes/update_statement.rb +41 -0
  282. data/lib/arel/nodes/values_list.rb +9 -0
  283. data/lib/arel/nodes/window.rb +126 -0
  284. data/lib/arel/nodes/with.rb +11 -0
  285. data/lib/arel/nodes.rb +70 -0
  286. data/lib/arel/order_predications.rb +13 -0
  287. data/lib/arel/predications.rb +250 -0
  288. data/lib/arel/select_manager.rb +270 -0
  289. data/lib/arel/table.rb +118 -0
  290. data/lib/arel/tree_manager.rb +72 -0
  291. data/lib/arel/update_manager.rb +34 -0
  292. data/lib/arel/visitors/dot.rb +308 -0
  293. data/lib/arel/visitors/mysql.rb +93 -0
  294. data/lib/arel/visitors/postgresql.rb +120 -0
  295. data/lib/arel/visitors/sqlite.rb +38 -0
  296. data/lib/arel/visitors/to_sql.rb +899 -0
  297. data/lib/arel/visitors/visitor.rb +45 -0
  298. data/lib/arel/visitors.rb +13 -0
  299. data/lib/arel/window_predications.rb +9 -0
  300. data/lib/arel.rb +54 -0
  301. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  302. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  303. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  304. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  305. data/lib/rails/generators/active_record/migration.rb +19 -2
  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 +116 -30
  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
@@ -14,10 +14,8 @@ module ActiveRecord
14
14
  i[column.name] = column.alias
15
15
  }
16
16
  }
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
- }
17
+ @columns_cache = tables.each_with_object({}) { |table, h|
18
+ h[table.node] = table.columns
21
19
  }
22
20
  end
23
21
 
@@ -25,9 +23,8 @@ module ActiveRecord
25
23
  @tables.flat_map(&:column_aliases)
26
24
  end
27
25
 
28
- # An array of [column_name, alias] pairs for the table
29
26
  def column_aliases(node)
30
- @name_and_alias_cache[node]
27
+ @columns_cache[node]
31
28
  end
32
29
 
33
30
  def column_alias(node, column)
@@ -37,7 +34,7 @@ module ActiveRecord
37
34
  Table = Struct.new(:node, :columns) do # :nodoc:
38
35
  def column_aliases
39
36
  t = node.table
40
- columns.map { |column| t[column.name].as Arel.sql column.alias }
37
+ columns.map { |column| t[column.name].as(column.alias) }
41
38
  end
42
39
  end
43
40
  Column = Struct.new(:name, :alias)
@@ -67,43 +64,69 @@ module ActiveRecord
67
64
  end
68
65
  end
69
66
 
70
- def initialize(base, table, associations)
67
+ def initialize(base, table, associations, join_type)
71
68
  tree = self.class.make_tree associations
72
69
  @join_root = JoinBase.new(base, table, build(tree, base))
70
+ @join_type = join_type
71
+ end
72
+
73
+ def base_klass
74
+ join_root.base_klass
73
75
  end
74
76
 
75
77
  def reflections
76
78
  join_root.drop(1).map!(&:reflection)
77
79
  end
78
80
 
79
- def join_constraints(joins_to_add, join_type, alias_tracker)
81
+ def join_constraints(joins_to_add, alias_tracker, references)
80
82
  @alias_tracker = alias_tracker
83
+ @joined_tables = {}
84
+ @references = {}
85
+
86
+ references.each do |table_name|
87
+ @references[table_name.to_sym] = table_name if table_name.is_a?(Arel::Nodes::SqlLiteral)
88
+ end unless references.empty?
81
89
 
82
- construct_tables!(join_root)
83
90
  joins = make_join_constraints(join_root, join_type)
84
91
 
85
92
  joins.concat joins_to_add.flat_map { |oj|
86
- construct_tables!(oj.join_root)
87
93
  if join_root.match? oj.join_root
88
- walk join_root, oj.join_root
94
+ walk(join_root, oj.join_root, oj.join_type)
89
95
  else
90
- make_join_constraints(oj.join_root, join_type)
96
+ make_join_constraints(oj.join_root, oj.join_type)
91
97
  end
92
98
  }
93
99
  end
94
100
 
95
- def instantiate(result_set, &block)
101
+ def instantiate(result_set, strict_loading_value, &block)
96
102
  primary_key = aliases.column_alias(join_root, join_root.primary_key)
97
103
 
98
- seen = Hash.new { |i, object_id|
99
- i[object_id] = Hash.new { |j, child_class|
104
+ seen = Hash.new { |i, parent|
105
+ i[parent] = Hash.new { |j, child_class|
100
106
  j[child_class] = {}
101
107
  }
102
- }
108
+ }.compare_by_identity
103
109
 
104
110
  model_cache = Hash.new { |h, klass| h[klass] = {} }
105
111
  parents = model_cache[join_root]
106
- column_aliases = aliases.column_aliases join_root
112
+
113
+ column_aliases = aliases.column_aliases(join_root)
114
+ column_names = []
115
+
116
+ result_set.columns.each do |name|
117
+ column_names << name unless /\At\d+_r\d+\z/.match?(name)
118
+ end
119
+
120
+ if column_names.empty?
121
+ column_types = {}
122
+ else
123
+ column_types = result_set.column_types
124
+ unless column_types.empty?
125
+ attribute_types = join_root.attribute_types
126
+ column_types = column_types.slice(*column_names).delete_if { |k, _| attribute_types.key?(k) }
127
+ end
128
+ column_aliases += column_names.map! { |name| Aliases::Column.new(name, name) }
129
+ end
107
130
 
108
131
  message_bus = ActiveSupport::Notifications.instrumenter
109
132
 
@@ -115,8 +138,8 @@ module ActiveRecord
115
138
  message_bus.instrument("instantiation.active_record", payload) do
116
139
  result_set.each { |row_hash|
117
140
  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)
141
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, column_types, &block)
142
+ construct(parent, join_root, row_hash, seen, model_cache, strict_loading_value)
120
143
  }
121
144
  end
122
145
 
@@ -124,63 +147,73 @@ module ActiveRecord
124
147
  end
125
148
 
126
149
  def apply_column_aliases(relation)
150
+ @join_root_alias = relation.select_values.empty?
127
151
  relation._select!(-> { aliases.columns })
128
152
  end
129
153
 
154
+ def each(&block)
155
+ join_root.each(&block)
156
+ end
157
+
130
158
  protected
131
- attr_reader :alias_tracker, :join_root
159
+ attr_reader :join_root, :join_type
132
160
 
133
161
  private
162
+ attr_reader :alias_tracker, :join_root_alias
163
+
134
164
  def aliases
135
165
  @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|
166
+ column_names = if join_part == join_root && !join_root_alias
167
+ primary_key = join_root.primary_key
168
+ primary_key ? [primary_key] : []
169
+ else
170
+ join_part.column_names
171
+ end
172
+
173
+ columns = column_names.each_with_index.map { |column_name, j|
137
174
  Aliases::Column.new column_name, "t#{i}_r#{j}"
138
175
  }
139
176
  Aliases::Table.new(join_part, columns)
140
177
  }
141
178
  end
142
179
 
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
180
  def make_join_constraints(join_root, join_type)
150
181
  join_root.children.flat_map do |child|
151
182
  make_constraints(join_root, child, join_type)
152
183
  end
153
184
  end
154
185
 
155
- def make_constraints(parent, child, join_type = Arel::Nodes::OuterJoin)
186
+ def make_constraints(parent, child, join_type)
156
187
  foreign_table = parent.table
157
188
  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
189
+ child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection|
190
+ table, terminated = @joined_tables[reflection]
191
+ root = reflection == child.reflection
161
192
 
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
193
+ if table && (!root || !terminated)
194
+ @joined_tables[reflection] = [table, root] if root
195
+ next table, true
196
+ end
197
+
198
+ table_name = @references[reflection.name.to_sym]&.to_s
171
199
 
172
- def table_alias_for(reflection, parent, join)
173
- name = "#{reflection.plural_name}_#{parent.table_name}"
174
- join ? "#{name}_join" : name
200
+ table = alias_tracker.aliased_table_for(reflection.klass.arel_table, table_name) do
201
+ name = reflection.alias_candidate(parent.table_name)
202
+ root ? name : "#{name}_join"
203
+ end
204
+
205
+ @joined_tables[reflection] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
206
+ table
207
+ end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
175
208
  end
176
209
 
177
- def walk(left, right)
210
+ def walk(left, right, join_type)
178
211
  intersection, missing = right.children.map { |node1|
179
212
  [left.children.find { |node2| node1.match? node2 }, node1]
180
213
  }.partition(&:first)
181
214
 
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) }
215
+ joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r, join_type) }
216
+ joins.concat missing.flat_map { |_, n| make_constraints(left, n, join_type) }
184
217
  end
185
218
 
186
219
  def find_reflection(klass, name)
@@ -202,7 +235,7 @@ module ActiveRecord
202
235
  end
203
236
  end
204
237
 
205
- def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
238
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
206
239
  return if ar_parent.nil?
207
240
 
208
241
  parent.children.each do |node|
@@ -211,7 +244,7 @@ module ActiveRecord
211
244
  other.loaded!
212
245
  elsif ar_parent.association_cached?(node.reflection.name)
213
246
  model = ar_parent.association(node.reflection.name).target
214
- construct(model, node, row, rs, seen, model_cache, aliases)
247
+ construct(model, node, row, seen, model_cache, strict_loading_value)
215
248
  next
216
249
  end
217
250
 
@@ -223,29 +256,25 @@ module ActiveRecord
223
256
  next
224
257
  end
225
258
 
226
- model = seen[ar_parent.object_id][node][id]
259
+ model = seen[ar_parent][node][id]
227
260
 
228
261
  if model
229
- construct(model, node, row, rs, seen, model_cache, aliases)
262
+ construct(model, node, row, seen, model_cache, strict_loading_value)
230
263
  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
264
+ model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
237
265
 
238
- seen[ar_parent.object_id][node][id] = model
239
- construct(model, node, row, rs, seen, model_cache, aliases)
266
+ seen[ar_parent][node][id] = model
267
+ construct(model, node, row, seen, model_cache, strict_loading_value)
240
268
  end
241
269
  end
242
270
  end
243
271
 
244
- def construct_model(record, node, row, model_cache, id, aliases)
272
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
245
273
  other = record.association(node.reflection.name)
246
274
 
247
275
  model = model_cache[node][id] ||=
248
276
  node.instantiate(row, aliases.column_aliases(node)) do |m|
277
+ m.strict_loading! if strict_loading_value
249
278
  other.set_inverse_instance(m)
250
279
  end
251
280
 
@@ -255,6 +284,8 @@ module ActiveRecord
255
284
  other.target = model
256
285
  end
257
286
 
287
+ model.readonly! if node.readonly?
288
+ model.strict_loading! if node.strict_loading?
258
289
  model
259
290
  end
260
291
  end
@@ -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,42 @@ 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 ||= reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(klass.unscoped, &:merge!)
115
133
  end
116
134
 
117
135
  def build_scope
118
136
  scope = klass.scope_for_association
119
137
 
120
- if reflection.type
138
+ if reflection.type && !reflection.through_reflection?
121
139
  scope.where!(reflection.type => model.polymorphic_name)
122
140
  end
123
141
 
124
- scope.merge!(reflection_scope) if reflection.scope
125
- scope.merge!(preload_scope) if preload_scope
126
- scope
142
+ scope.merge!(reflection_scope) unless reflection_scope.empty_scope?
143
+
144
+ if preload_scope && !preload_scope.empty_scope?
145
+ scope.merge!(preload_scope)
146
+ end
147
+
148
+ if preload_scope && preload_scope.strict_loading_value
149
+ scope.strict_loading
150
+ else
151
+ scope
152
+ end
127
153
  end
128
154
  end
129
155
  end