activerecord 6.0.1 → 6.1.7

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 +1314 -633
  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 +26 -15
  7. data/lib/active_record/associations/alias_tracker.rb +19 -16
  8. data/lib/active_record/associations/association.rb +55 -37
  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 +38 -13
  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 +73 -42
  28. data/lib/active_record/associations/preloader/association.rb +49 -25
  29. data/lib/active_record/associations/preloader/through_association.rb +2 -2
  30. data/lib/active_record/associations/preloader.rb +12 -7
  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 +119 -12
  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 +56 -41
  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 +24 -3
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +190 -136
  50. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -38
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -9
  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 +145 -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 +63 -77
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +136 -111
  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 +20 -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 +80 -66
  104. data/lib/active_record/connection_adapters/schema_cache.rb +130 -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 +57 -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 +218 -87
  114. data/lib/active_record/core.rb +269 -68
  115. data/lib/active_record/counter_cache.rb +4 -1
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -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 +125 -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 +42 -9
  137. data/lib/active_record/integration.rb +3 -5
  138. data/lib/active_record/internal_metadata.rb +18 -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 +6 -2
  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 +75 -21
  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 +117 -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 +45 -16
  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 +339 -188
  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 -83
  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 +9 -4
  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 +87 -20
  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 +25 -72
  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 +5 -9
  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 +30 -29
  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)
@@ -78,14 +78,18 @@ module ActiveRecord
78
78
  join_root.drop(1).map!(&:reflection)
79
79
  end
80
80
 
81
- def join_constraints(joins_to_add, alias_tracker)
81
+ def join_constraints(joins_to_add, alias_tracker, references)
82
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?
83
89
 
84
- construct_tables!(join_root)
85
90
  joins = make_join_constraints(join_root, join_type)
86
91
 
87
92
  joins.concat joins_to_add.flat_map { |oj|
88
- construct_tables!(oj.join_root)
89
93
  if join_root.match? oj.join_root
90
94
  walk(join_root, oj.join_root, oj.join_type)
91
95
  else
@@ -94,18 +98,35 @@ module ActiveRecord
94
98
  }
95
99
  end
96
100
 
97
- def instantiate(result_set, &block)
101
+ def instantiate(result_set, strict_loading_value, &block)
98
102
  primary_key = aliases.column_alias(join_root, join_root.primary_key)
99
103
 
100
- seen = Hash.new { |i, object_id|
101
- i[object_id] = Hash.new { |j, child_class|
104
+ seen = Hash.new { |i, parent|
105
+ i[parent] = Hash.new { |j, child_class|
102
106
  j[child_class] = {}
103
107
  }
104
- }
108
+ }.compare_by_identity
105
109
 
106
110
  model_cache = Hash.new { |h, klass| h[klass] = {} }
107
111
  parents = model_cache[join_root]
108
- 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
109
130
 
110
131
  message_bus = ActiveSupport::Notifications.instrumenter
111
132
 
@@ -117,8 +138,8 @@ module ActiveRecord
117
138
  message_bus.instrument("instantiation.active_record", payload) do
118
139
  result_set.each { |row_hash|
119
140
  parent_key = primary_key ? row_hash[primary_key] : row_hash
120
- parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, &block)
121
- 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)
122
143
  }
123
144
  end
124
145
 
@@ -126,30 +147,36 @@ module ActiveRecord
126
147
  end
127
148
 
128
149
  def apply_column_aliases(relation)
150
+ @join_root_alias = relation.select_values.empty?
129
151
  relation._select!(-> { aliases.columns })
130
152
  end
131
153
 
154
+ def each(&block)
155
+ join_root.each(&block)
156
+ end
157
+
132
158
  protected
133
159
  attr_reader :join_root, :join_type
134
160
 
135
161
  private
136
- attr_reader :alias_tracker
162
+ attr_reader :alias_tracker, :join_root_alias
137
163
 
138
164
  def aliases
139
165
  @aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
140
- 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|
141
174
  Aliases::Column.new column_name, "t#{i}_r#{j}"
142
175
  }
143
176
  Aliases::Table.new(join_part, columns)
144
177
  }
145
178
  end
146
179
 
147
- def construct_tables!(join_root)
148
- join_root.each_children do |parent, child|
149
- child.tables = table_aliases_for(parent, child)
150
- end
151
- end
152
-
153
180
  def make_join_constraints(join_root, join_type)
154
181
  join_root.children.flat_map do |child|
155
182
  make_constraints(join_root, child, join_type)
@@ -159,23 +186,25 @@ module ActiveRecord
159
186
  def make_constraints(parent, child, join_type)
160
187
  foreign_table = parent.table
161
188
  foreign_klass = parent.base_klass
162
- joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
163
- joins.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
164
- 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
165
192
 
166
- def table_aliases_for(parent, node)
167
- node.reflection.chain.map { |reflection|
168
- alias_tracker.aliased_table_for(
169
- reflection.table_name,
170
- table_alias_for(reflection, parent, reflection != node.reflection),
171
- reflection.klass.type_caster
172
- )
173
- }
174
- 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
175
204
 
176
- def table_alias_for(reflection, parent, join)
177
- name = reflection.alias_candidate(parent.table_name)
178
- 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) }
179
208
  end
180
209
 
181
210
  def walk(left, right, join_type)
@@ -206,7 +235,7 @@ module ActiveRecord
206
235
  end
207
236
  end
208
237
 
209
- def construct(ar_parent, parent, row, seen, model_cache)
238
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
210
239
  return if ar_parent.nil?
211
240
 
212
241
  parent.children.each do |node|
@@ -215,7 +244,7 @@ module ActiveRecord
215
244
  other.loaded!
216
245
  elsif ar_parent.association_cached?(node.reflection.name)
217
246
  model = ar_parent.association(node.reflection.name).target
218
- construct(model, node, row, seen, model_cache)
247
+ construct(model, node, row, seen, model_cache, strict_loading_value)
219
248
  next
220
249
  end
221
250
 
@@ -227,24 +256,25 @@ module ActiveRecord
227
256
  next
228
257
  end
229
258
 
230
- model = seen[ar_parent.object_id][node][id]
259
+ model = seen[ar_parent][node][id]
231
260
 
232
261
  if model
233
- construct(model, node, row, seen, model_cache)
262
+ construct(model, node, row, seen, model_cache, strict_loading_value)
234
263
  else
235
- model = construct_model(ar_parent, node, row, model_cache, id)
264
+ model = construct_model(ar_parent, node, row, model_cache, id, strict_loading_value)
236
265
 
237
- seen[ar_parent.object_id][node][id] = model
238
- 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)
239
268
  end
240
269
  end
241
270
  end
242
271
 
243
- def construct_model(record, node, row, model_cache, id)
272
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
244
273
  other = record.association(node.reflection.name)
245
274
 
246
275
  model = model_cache[node][id] ||=
247
276
  node.instantiate(row, aliases.column_aliases(node)) do |m|
277
+ m.strict_loading! if strict_loading_value
248
278
  other.set_inverse_instance(m)
249
279
  end
250
280
 
@@ -255,6 +285,7 @@ module ActiveRecord
255
285
  end
256
286
 
257
287
  model.readonly! if node.readonly?
288
+ model.strict_loading! if node.strict_loading?
258
289
  model
259
290
  end
260
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,7 @@ 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 ||= reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(klass.unscoped, &:merge!)
117
133
  end
118
134
 
119
135
  def build_scope
@@ -123,9 +139,17 @@ module ActiveRecord
123
139
  scope.where!(reflection.type => model.polymorphic_name)
124
140
  end
125
141
 
126
- scope.merge!(reflection_scope) if reflection.scope
127
- scope.merge!(preload_scope) if preload_scope
128
- 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
129
153
  end
130
154
  end
131
155
  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
@@ -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