activerecord 6.0.0 → 6.1.4

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 (270) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1178 -600
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/lib/active_record/aggregations.rb +5 -6
  6. data/lib/active_record/association_relation.rb +30 -10
  7. data/lib/active_record/associations/alias_tracker.rb +19 -16
  8. data/lib/active_record/associations/association.rb +55 -29
  9. data/lib/active_record/associations/association_scope.rb +19 -15
  10. data/lib/active_record/associations/belongs_to_association.rb +23 -10
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
  12. data/lib/active_record/associations/builder/association.rb +32 -5
  13. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  14. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -3
  16. data/lib/active_record/associations/builder/has_many.rb +6 -2
  17. data/lib/active_record/associations/builder/has_one.rb +11 -14
  18. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  19. data/lib/active_record/associations/collection_association.rb +25 -8
  20. data/lib/active_record/associations/collection_proxy.rb +14 -7
  21. data/lib/active_record/associations/foreign_association.rb +13 -0
  22. data/lib/active_record/associations/has_many_association.rb +24 -3
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  24. data/lib/active_record/associations/has_one_association.rb +15 -1
  25. data/lib/active_record/associations/join_dependency/join_association.rb +39 -16
  26. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  27. data/lib/active_record/associations/join_dependency.rb +77 -42
  28. data/lib/active_record/associations/preloader/association.rb +51 -25
  29. data/lib/active_record/associations/preloader/through_association.rb +2 -2
  30. data/lib/active_record/associations/preloader.rb +13 -8
  31. data/lib/active_record/associations/singular_association.rb +1 -1
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +120 -13
  34. data/lib/active_record/attribute_assignment.rb +10 -9
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -10
  36. data/lib/active_record/attribute_methods/dirty.rb +3 -13
  37. data/lib/active_record/attribute_methods/primary_key.rb +6 -4
  38. data/lib/active_record/attribute_methods/query.rb +3 -6
  39. data/lib/active_record/attribute_methods/read.rb +8 -12
  40. data/lib/active_record/attribute_methods/serialization.rb +11 -6
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  42. data/lib/active_record/attribute_methods/write.rb +12 -21
  43. data/lib/active_record/attribute_methods.rb +64 -54
  44. data/lib/active_record/attributes.rb +33 -9
  45. data/lib/active_record/autosave_association.rb +63 -44
  46. data/lib/active_record/base.rb +2 -14
  47. data/lib/active_record/callbacks.rb +153 -24
  48. data/lib/active_record/coders/yaml_column.rb +12 -3
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +202 -138
  50. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +87 -38
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +5 -10
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  54. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +152 -116
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +141 -52
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +267 -105
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +94 -36
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +76 -79
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +149 -115
  62. data/lib/active_record/connection_adapters/column.rb +15 -1
  63. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  64. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  65. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  66. data/lib/active_record/connection_adapters/mysql/database_statements.rb +30 -36
  67. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  68. data/lib/active_record/connection_adapters/mysql/quoting.rb +18 -3
  69. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -7
  70. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  71. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
  72. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -13
  73. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  74. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -13
  75. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  76. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  77. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  78. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +21 -56
  79. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +0 -1
  80. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  81. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  83. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +0 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  87. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  91. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -6
  92. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  97. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +7 -3
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +1 -1
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +72 -54
  101. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  102. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  103. data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -65
  104. data/lib/active_record/connection_adapters/schema_cache.rb +106 -15
  105. data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
  106. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +38 -12
  107. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -2
  108. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  109. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -5
  110. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -57
  111. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  112. data/lib/active_record/connection_adapters.rb +52 -0
  113. data/lib/active_record/connection_handling.rb +219 -81
  114. data/lib/active_record/core.rb +268 -71
  115. data/lib/active_record/counter_cache.rb +4 -1
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  117. data/lib/active_record/database_configurations/database_config.rb +52 -9
  118. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  119. data/lib/active_record/database_configurations/url_config.rb +15 -41
  120. data/lib/active_record/database_configurations.rb +124 -85
  121. data/lib/active_record/delegated_type.rb +209 -0
  122. data/lib/active_record/destroy_association_async_job.rb +36 -0
  123. data/lib/active_record/dynamic_matchers.rb +2 -3
  124. data/lib/active_record/enum.rb +80 -38
  125. data/lib/active_record/errors.rb +47 -12
  126. data/lib/active_record/explain.rb +9 -5
  127. data/lib/active_record/explain_subscriber.rb +1 -1
  128. data/lib/active_record/fixture_set/file.rb +10 -17
  129. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  130. data/lib/active_record/fixture_set/render_context.rb +1 -1
  131. data/lib/active_record/fixture_set/table_row.rb +2 -3
  132. data/lib/active_record/fixture_set/table_rows.rb +0 -1
  133. data/lib/active_record/fixtures.rb +58 -12
  134. data/lib/active_record/gem_version.rb +2 -2
  135. data/lib/active_record/inheritance.rb +40 -21
  136. data/lib/active_record/insert_all.rb +43 -10
  137. data/lib/active_record/integration.rb +3 -5
  138. data/lib/active_record/internal_metadata.rb +16 -7
  139. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  140. data/lib/active_record/locking/optimistic.rb +33 -18
  141. data/lib/active_record/locking/pessimistic.rb +6 -2
  142. data/lib/active_record/log_subscriber.rb +28 -9
  143. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  144. data/lib/active_record/middleware/database_selector/resolver.rb +14 -14
  145. data/lib/active_record/middleware/database_selector.rb +4 -2
  146. data/lib/active_record/migration/command_recorder.rb +53 -45
  147. data/lib/active_record/migration/compatibility.rb +71 -20
  148. data/lib/active_record/migration/join_table.rb +0 -1
  149. data/lib/active_record/migration.rb +115 -85
  150. data/lib/active_record/model_schema.rb +120 -15
  151. data/lib/active_record/nested_attributes.rb +2 -5
  152. data/lib/active_record/no_touching.rb +1 -1
  153. data/lib/active_record/null_relation.rb +0 -1
  154. data/lib/active_record/persistence.rb +50 -46
  155. data/lib/active_record/query_cache.rb +15 -5
  156. data/lib/active_record/querying.rb +12 -7
  157. data/lib/active_record/railtie.rb +65 -45
  158. data/lib/active_record/railties/console_sandbox.rb +2 -4
  159. data/lib/active_record/railties/databases.rake +280 -99
  160. data/lib/active_record/readonly_attributes.rb +4 -0
  161. data/lib/active_record/reflection.rb +77 -63
  162. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  163. data/lib/active_record/relation/batches.rb +38 -32
  164. data/lib/active_record/relation/calculations.rb +106 -45
  165. data/lib/active_record/relation/delegation.rb +9 -7
  166. data/lib/active_record/relation/finder_methods.rb +55 -17
  167. data/lib/active_record/relation/from_clause.rb +5 -1
  168. data/lib/active_record/relation/merger.rb +27 -26
  169. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  170. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  171. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
  172. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  173. data/lib/active_record/relation/predicate_builder.rb +59 -40
  174. data/lib/active_record/relation/query_methods.rb +344 -181
  175. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  176. data/lib/active_record/relation/spawn_methods.rb +8 -8
  177. data/lib/active_record/relation/where_clause.rb +111 -62
  178. data/lib/active_record/relation.rb +116 -82
  179. data/lib/active_record/result.rb +41 -34
  180. data/lib/active_record/runtime_registry.rb +2 -2
  181. data/lib/active_record/sanitization.rb +6 -17
  182. data/lib/active_record/schema_dumper.rb +34 -4
  183. data/lib/active_record/schema_migration.rb +2 -8
  184. data/lib/active_record/scoping/default.rb +1 -4
  185. data/lib/active_record/scoping/named.rb +7 -18
  186. data/lib/active_record/scoping.rb +0 -1
  187. data/lib/active_record/secure_token.rb +16 -8
  188. data/lib/active_record/serialization.rb +5 -3
  189. data/lib/active_record/signed_id.rb +116 -0
  190. data/lib/active_record/statement_cache.rb +20 -4
  191. data/lib/active_record/store.rb +3 -3
  192. data/lib/active_record/suppressor.rb +2 -2
  193. data/lib/active_record/table_metadata.rb +42 -36
  194. data/lib/active_record/tasks/database_tasks.rb +140 -113
  195. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -36
  196. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -27
  197. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -10
  198. data/lib/active_record/test_databases.rb +5 -4
  199. data/lib/active_record/test_fixtures.rb +79 -16
  200. data/lib/active_record/timestamp.rb +4 -7
  201. data/lib/active_record/touch_later.rb +20 -21
  202. data/lib/active_record/transactions.rb +26 -73
  203. data/lib/active_record/type/adapter_specific_registry.rb +2 -5
  204. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  205. data/lib/active_record/type/serialized.rb +6 -3
  206. data/lib/active_record/type/time.rb +10 -0
  207. data/lib/active_record/type/type_map.rb +0 -1
  208. data/lib/active_record/type/unsigned_integer.rb +0 -1
  209. data/lib/active_record/type.rb +8 -2
  210. data/lib/active_record/type_caster/connection.rb +0 -1
  211. data/lib/active_record/type_caster/map.rb +8 -5
  212. data/lib/active_record/validations/associated.rb +1 -2
  213. data/lib/active_record/validations/numericality.rb +35 -0
  214. data/lib/active_record/validations/uniqueness.rb +24 -4
  215. data/lib/active_record/validations.rb +3 -3
  216. data/lib/active_record.rb +7 -13
  217. data/lib/arel/attributes/attribute.rb +4 -0
  218. data/lib/arel/collectors/bind.rb +5 -0
  219. data/lib/arel/collectors/composite.rb +8 -0
  220. data/lib/arel/collectors/sql_string.rb +7 -0
  221. data/lib/arel/collectors/substitute_binds.rb +7 -0
  222. data/lib/arel/nodes/binary.rb +82 -8
  223. data/lib/arel/nodes/bind_param.rb +8 -0
  224. data/lib/arel/nodes/casted.rb +21 -9
  225. data/lib/arel/nodes/equality.rb +6 -9
  226. data/lib/arel/nodes/grouping.rb +3 -0
  227. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  228. data/lib/arel/nodes/in.rb +8 -1
  229. data/lib/arel/nodes/infix_operation.rb +13 -1
  230. data/lib/arel/nodes/join_source.rb +1 -1
  231. data/lib/arel/nodes/node.rb +7 -6
  232. data/lib/arel/nodes/ordering.rb +27 -0
  233. data/lib/arel/nodes/sql_literal.rb +3 -0
  234. data/lib/arel/nodes/table_alias.rb +7 -3
  235. data/lib/arel/nodes/unary.rb +0 -1
  236. data/lib/arel/nodes.rb +3 -1
  237. data/lib/arel/predications.rb +17 -24
  238. data/lib/arel/select_manager.rb +1 -2
  239. data/lib/arel/table.rb +13 -5
  240. data/lib/arel/visitors/dot.rb +14 -3
  241. data/lib/arel/visitors/mysql.rb +11 -1
  242. data/lib/arel/visitors/postgresql.rb +15 -5
  243. data/lib/arel/visitors/sqlite.rb +0 -1
  244. data/lib/arel/visitors/to_sql.rb +89 -79
  245. data/lib/arel/visitors/visitor.rb +0 -1
  246. data/lib/arel/visitors.rb +0 -7
  247. data/lib/arel.rb +15 -12
  248. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  249. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  250. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  251. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  252. data/lib/rails/generators/active_record/migration.rb +6 -2
  253. data/lib/rails/generators/active_record/model/model_generator.rb +38 -2
  254. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  255. metadata +27 -24
  256. data/lib/active_record/attribute_decorators.rb +0 -90
  257. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  258. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  259. data/lib/active_record/define_callbacks.rb +0 -22
  260. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  261. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  262. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  263. data/lib/arel/attributes.rb +0 -22
  264. data/lib/arel/visitors/depth_first.rb +0 -204
  265. data/lib/arel/visitors/ibm_db.rb +0 -34
  266. data/lib/arel/visitors/informix.rb +0 -62
  267. data/lib/arel/visitors/mssql.rb +0 -157
  268. data/lib/arel/visitors/oracle.rb +0 -159
  269. data/lib/arel/visitors/oracle12.rb +0 -66
  270. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -34,7 +34,7 @@ module ActiveRecord
34
34
  Table = Struct.new(:node, :columns) do # :nodoc:
35
35
  def column_aliases
36
36
  t = node.table
37
- columns.map { |column| t[column.name].as Arel.sql column.alias }
37
+ columns.map { |column| t[column.name].as(column.alias) }
38
38
  end
39
39
  end
40
40
  Column = Struct.new(:name, :alias)
@@ -70,18 +70,26 @@ module ActiveRecord
70
70
  @join_type = join_type
71
71
  end
72
72
 
73
+ def base_klass
74
+ join_root.base_klass
75
+ end
76
+
73
77
  def reflections
74
78
  join_root.drop(1).map!(&:reflection)
75
79
  end
76
80
 
77
- def join_constraints(joins_to_add, alias_tracker)
81
+ def join_constraints(joins_to_add, alias_tracker, references)
78
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?
79
89
 
80
- construct_tables!(join_root)
81
90
  joins = make_join_constraints(join_root, join_type)
82
91
 
83
92
  joins.concat joins_to_add.flat_map { |oj|
84
- construct_tables!(oj.join_root)
85
93
  if join_root.match? oj.join_root
86
94
  walk(join_root, oj.join_root, oj.join_type)
87
95
  else
@@ -90,18 +98,35 @@ module ActiveRecord
90
98
  }
91
99
  end
92
100
 
93
- def instantiate(result_set, &block)
101
+ def instantiate(result_set, strict_loading_value, &block)
94
102
  primary_key = aliases.column_alias(join_root, join_root.primary_key)
95
103
 
96
- seen = Hash.new { |i, object_id|
97
- i[object_id] = Hash.new { |j, child_class|
104
+ seen = Hash.new { |i, parent|
105
+ i[parent] = Hash.new { |j, child_class|
98
106
  j[child_class] = {}
99
107
  }
100
- }
108
+ }.compare_by_identity
101
109
 
102
110
  model_cache = Hash.new { |h, klass| h[klass] = {} }
103
111
  parents = model_cache[join_root]
104
- 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
105
130
 
106
131
  message_bus = ActiveSupport::Notifications.instrumenter
107
132
 
@@ -113,8 +138,8 @@ module ActiveRecord
113
138
  message_bus.instrument("instantiation.active_record", payload) do
114
139
  result_set.each { |row_hash|
115
140
  parent_key = primary_key ? row_hash[primary_key] : row_hash
116
- parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, &block)
117
- construct(parent, join_root, row_hash, seen, model_cache)
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)
118
143
  }
119
144
  end
120
145
 
@@ -122,30 +147,36 @@ module ActiveRecord
122
147
  end
123
148
 
124
149
  def apply_column_aliases(relation)
150
+ @join_root_alias = relation.select_values.empty?
125
151
  relation._select!(-> { aliases.columns })
126
152
  end
127
153
 
154
+ def each(&block)
155
+ join_root.each(&block)
156
+ end
157
+
128
158
  protected
129
159
  attr_reader :join_root, :join_type
130
160
 
131
161
  private
132
- attr_reader :alias_tracker
162
+ attr_reader :alias_tracker, :join_root_alias
133
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)
@@ -155,23 +186,25 @@ module ActiveRecord
155
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
199
+
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
171
204
 
172
- def table_alias_for(reflection, parent, join)
173
- name = reflection.alias_candidate(parent.table_name)
174
- join ? "#{name}_join" : name
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
210
  def walk(left, right, join_type)
@@ -202,7 +235,7 @@ module ActiveRecord
202
235
  end
203
236
  end
204
237
 
205
- def construct(ar_parent, parent, row, seen, model_cache)
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, seen, model_cache)
247
+ construct(model, node, row, seen, model_cache, strict_loading_value)
215
248
  next
216
249
  end
217
250
 
@@ -223,24 +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, seen, model_cache)
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)
264
+ model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
232
265
 
233
- seen[ar_parent.object_id][node][id] = model
234
- construct(model, node, row, seen, model_cache)
266
+ seen[ar_parent][node][id] = model
267
+ construct(model, node, row, seen, model_cache, strict_loading_value)
235
268
  end
236
269
  end
237
270
  end
238
271
 
239
- def construct_model(record, node, row, model_cache, id)
272
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
240
273
  other = record.association(node.reflection.name)
241
274
 
242
275
  model = model_cache[node][id] ||=
243
276
  node.instantiate(row, aliases.column_aliases(node)) do |m|
277
+ m.strict_loading! if strict_loading_value
244
278
  other.set_inverse_instance(m)
245
279
  end
246
280
 
@@ -251,6 +285,7 @@ module ActiveRecord
251
285
  end
252
286
 
253
287
  model.readonly! if node.readonly?
288
+ model.strict_loading! if node.strict_loading?
254
289
  model
255
290
  end
256
291
  end
@@ -4,46 +4,62 @@ module ActiveRecord
4
4
  module Associations
5
5
  class Preloader
6
6
  class Association #:nodoc:
7
- def initialize(klass, owners, reflection, preload_scope)
7
+ def initialize(klass, owners, reflection, preload_scope, associate_by_default = true)
8
8
  @klass = klass
9
- @owners = owners
9
+ @owners = owners.uniq(&:__id__)
10
10
  @reflection = reflection
11
11
  @preload_scope = preload_scope
12
+ @associate = associate_by_default || !preload_scope || preload_scope.empty_scope?
12
13
  @model = owners.first && owners.first.class
13
14
  end
14
15
 
15
16
  def run
16
- if !preload_scope || preload_scope.empty_scope?
17
- owners.each do |owner|
18
- associate_records_to_owner(owner, records_by_owner[owner] || [])
19
- end
20
- else
21
- # Custom preload scope is used and
22
- # the association can not be marked as loaded
23
- # Loading into a Hash instead
24
- records_by_owner
25
- end
17
+ records = records_by_owner
18
+
19
+ owners.each do |owner|
20
+ associate_records_to_owner(owner, records[owner] || [])
21
+ end if @associate
22
+
26
23
  self
27
24
  end
28
25
 
29
26
  def records_by_owner
30
- # owners can be duplicated when a relation has a collection association join
31
- # #compare_by_identity makes such owners different hash keys
32
- @records_by_owner ||= preloaded_records.each_with_object({}.compare_by_identity) do |record, result|
33
- owners_by_key[convert_key(record[association_key_name])].each do |owner|
34
- (result[owner] ||= []) << record
35
- end
36
- end
27
+ load_records unless defined?(@records_by_owner)
28
+
29
+ @records_by_owner
37
30
  end
38
31
 
39
32
  def preloaded_records
40
- return @preloaded_records if defined?(@preloaded_records)
41
- @preloaded_records = owner_keys.empty? ? [] : records_for(owner_keys)
33
+ load_records unless defined?(@preloaded_records)
34
+
35
+ @preloaded_records
42
36
  end
43
37
 
44
38
  private
45
39
  attr_reader :owners, :reflection, :preload_scope, :model, :klass
46
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
+
47
63
  # The name of the key on the associated records
48
64
  def association_key_name
49
65
  reflection.join_primary_key(klass)
@@ -113,7 +129,9 @@ module ActiveRecord
113
129
  end
114
130
 
115
131
  def reflection_scope
116
- @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
117
135
  end
118
136
 
119
137
  def build_scope
@@ -123,9 +141,17 @@ module ActiveRecord
123
141
  scope.where!(reflection.type => model.polymorphic_name)
124
142
  end
125
143
 
126
- scope.merge!(reflection_scope) if reflection.scope
127
- scope.merge!(preload_scope) if preload_scope
128
- 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
129
155
  end
130
156
  end
131
157
  end
@@ -4,7 +4,7 @@ module ActiveRecord
4
4
  module Associations
5
5
  class Preloader
6
6
  class ThroughAssociation < Association # :nodoc:
7
- PRELOADER = ActiveRecord::Associations::Preloader.new
7
+ PRELOADER = ActiveRecord::Associations::Preloader.new(associate_by_default: false)
8
8
 
9
9
  def initialize(*)
10
10
  super
@@ -90,7 +90,7 @@ module ActiveRecord
90
90
  end
91
91
 
92
92
  if values[:references] && !values[:references].empty?
93
- scope.references!(values[:references])
93
+ scope.references_values |= values[:references]
94
94
  else
95
95
  scope.references!(source_reflection.table_name)
96
96
  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
@@ -94,8 +96,11 @@ module ActiveRecord
94
96
  end
95
97
  end
96
98
 
97
- private
99
+ def initialize(associate_by_default: true)
100
+ @associate_by_default = associate_by_default
101
+ end
98
102
 
103
+ private
99
104
  # Loads all the given data into +records+ for the +association+.
100
105
  def preloaders_on(association, records, scope, polymorphic_parent = false)
101
106
  case association
@@ -112,7 +117,7 @@ module ActiveRecord
112
117
  association.flat_map { |parent, child|
113
118
  grouped_records(parent, records, polymorphic_parent).flat_map do |reflection, reflection_records|
114
119
  loaders = preloaders_for_reflection(reflection, reflection_records, scope)
115
- recs = loaders.flat_map(&:preloaded_records)
120
+ recs = loaders.flat_map(&:preloaded_records).uniq
116
121
  child_polymorphic_parent = reflection && reflection.options[:polymorphic]
117
122
  loaders.concat Array.wrap(child).flat_map { |assoc|
118
123
  preloaders_on assoc, recs, scope, child_polymorphic_parent
@@ -143,7 +148,7 @@ module ActiveRecord
143
148
 
144
149
  def preloaders_for_reflection(reflection, records, scope)
145
150
  records.group_by { |record| record.association(reflection.name).klass }.map do |rhs_klass, rs|
146
- preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope).run
151
+ preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope, @associate_by_default).run
147
152
  end
148
153
  end
149
154
 
@@ -158,7 +163,7 @@ module ActiveRecord
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
@@ -172,8 +177,8 @@ module ActiveRecord
172
177
  end
173
178
 
174
179
  def records_by_owner
175
- @records_by_owner ||= owners.each_with_object({}) do |owner, result|
176
- result[owner] = Array(owner.association(reflection.name).target)
180
+ @records_by_owner ||= owners.index_with do |owner|
181
+ Array(owner.association(reflection.name).target)
177
182
  end
178
183
  end
179
184
 
@@ -185,7 +190,7 @@ module ActiveRecord
185
190
  # and attach it to a relation. The class returned implements a `run` method
186
191
  # that accepts a preloader.
187
192
  def preloader_for(reflection, owners)
188
- if owners.first.association(reflection.name).loaded?
193
+ if owners.all? { |o| o.association(reflection.name).loaded? }
189
194
  return AlreadyLoaded
190
195
  end
191
196
  reflection.check_preloadable!
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  replace(record)
18
18
  end
19
19
 
20
- def build(attributes = {}, &block)
20
+ def build(attributes = nil, &block)
21
21
  record = build_record(attributes, &block)
22
22
  set_new_record(record)
23
23
  record
@@ -32,7 +32,7 @@ module ActiveRecord
32
32
  reflection.chain.drop(1).each do |reflection|
33
33
  relation = reflection.klass.scope_for_association
34
34
  scope.merge!(
35
- relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
35
+ relation.except(:select, :create_with, :includes, :preload, :eager_load, :joins, :left_outer_joins)
36
36
  )
37
37
  end
38
38
  scope