activerecord 5.1.0 → 5.2.0.rc1

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 (260) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +410 -530
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/examples/performance.rb +2 -0
  6. data/examples/simple.rb +2 -0
  7. data/lib/active_record/aggregations.rb +6 -5
  8. data/lib/active_record/association_relation.rb +4 -2
  9. data/lib/active_record/associations/alias_tracker.rb +23 -32
  10. data/lib/active_record/associations/association.rb +20 -21
  11. data/lib/active_record/associations/association_scope.rb +49 -49
  12. data/lib/active_record/associations/belongs_to_association.rb +12 -10
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +4 -7
  14. data/lib/active_record/associations/builder/association.rb +4 -7
  15. data/lib/active_record/associations/builder/belongs_to.rb +10 -6
  16. data/lib/active_record/associations/builder/collection_association.rb +1 -1
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -1
  18. data/lib/active_record/associations/builder/has_many.rb +2 -0
  19. data/lib/active_record/associations/builder/has_one.rb +2 -0
  20. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  21. data/lib/active_record/associations/collection_association.rb +50 -41
  22. data/lib/active_record/associations/collection_proxy.rb +22 -39
  23. data/lib/active_record/associations/foreign_association.rb +2 -0
  24. data/lib/active_record/associations/has_many_association.rb +4 -2
  25. data/lib/active_record/associations/has_many_through_association.rb +12 -18
  26. data/lib/active_record/associations/has_one_association.rb +5 -1
  27. data/lib/active_record/associations/has_one_through_association.rb +8 -7
  28. data/lib/active_record/associations/join_dependency/join_association.rb +17 -64
  29. data/lib/active_record/associations/join_dependency/join_base.rb +9 -8
  30. data/lib/active_record/associations/join_dependency/join_part.rb +2 -9
  31. data/lib/active_record/associations/join_dependency.rb +27 -44
  32. data/lib/active_record/associations/preloader/association.rb +53 -92
  33. data/lib/active_record/associations/preloader/through_association.rb +72 -73
  34. data/lib/active_record/associations/preloader.rb +17 -37
  35. data/lib/active_record/associations/singular_association.rb +14 -10
  36. data/lib/active_record/associations/through_association.rb +26 -11
  37. data/lib/active_record/associations.rb +68 -76
  38. data/lib/active_record/attribute_assignment.rb +2 -0
  39. data/lib/active_record/attribute_decorators.rb +3 -2
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -0
  41. data/lib/active_record/attribute_methods/dirty.rb +24 -214
  42. data/lib/active_record/attribute_methods/primary_key.rb +10 -13
  43. data/lib/active_record/attribute_methods/query.rb +2 -0
  44. data/lib/active_record/attribute_methods/read.rb +8 -2
  45. data/lib/active_record/attribute_methods/serialization.rb +23 -0
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +6 -8
  47. data/lib/active_record/attribute_methods/write.rb +22 -19
  48. data/lib/active_record/attribute_methods.rb +48 -12
  49. data/lib/active_record/attributes.rb +7 -6
  50. data/lib/active_record/autosave_association.rb +8 -11
  51. data/lib/active_record/base.rb +2 -0
  52. data/lib/active_record/callbacks.rb +8 -6
  53. data/lib/active_record/coders/json.rb +2 -0
  54. data/lib/active_record/coders/yaml_column.rb +2 -0
  55. data/lib/active_record/collection_cache_key.rb +14 -10
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +110 -35
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +175 -33
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -2
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +13 -24
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +2 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +15 -6
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +58 -3
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +31 -53
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +165 -85
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +45 -9
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +83 -97
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +118 -180
  69. data/lib/active_record/connection_adapters/column.rb +4 -2
  70. data/lib/active_record/connection_adapters/connection_specification.rb +17 -3
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +2 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +2 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -17
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +2 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +9 -10
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +5 -3
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +7 -10
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +30 -23
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +106 -1
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +2 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -2
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +6 -32
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +2 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +2 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +2 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  96. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +2 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +4 -2
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +4 -2
  103. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +3 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +2 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +2 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -1
  107. data/lib/active_record/connection_adapters/postgresql/quoting.rb +22 -1
  108. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +19 -25
  109. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +14 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +24 -11
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +20 -13
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +269 -126
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +2 -0
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +2 -0
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +64 -85
  116. data/lib/active_record/connection_adapters/schema_cache.rb +4 -2
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +2 -0
  118. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +2 -0
  119. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +18 -0
  120. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +2 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +6 -15
  122. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +3 -2
  123. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +71 -1
  124. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +92 -95
  125. data/lib/active_record/connection_adapters/statement_pool.rb +2 -0
  126. data/lib/active_record/connection_handling.rb +4 -2
  127. data/lib/active_record/core.rb +39 -60
  128. data/lib/active_record/counter_cache.rb +3 -2
  129. data/lib/active_record/define_callbacks.rb +5 -3
  130. data/lib/active_record/dynamic_matchers.rb +9 -9
  131. data/lib/active_record/enum.rb +17 -13
  132. data/lib/active_record/errors.rb +42 -3
  133. data/lib/active_record/explain.rb +3 -1
  134. data/lib/active_record/explain_registry.rb +2 -0
  135. data/lib/active_record/explain_subscriber.rb +2 -0
  136. data/lib/active_record/fixture_set/file.rb +2 -0
  137. data/lib/active_record/fixtures.rb +67 -60
  138. data/lib/active_record/gem_version.rb +4 -2
  139. data/lib/active_record/inheritance.rb +9 -9
  140. data/lib/active_record/integration.rb +58 -19
  141. data/lib/active_record/internal_metadata.rb +2 -0
  142. data/lib/active_record/legacy_yaml_adapter.rb +3 -1
  143. data/lib/active_record/locking/optimistic.rb +8 -6
  144. data/lib/active_record/locking/pessimistic.rb +9 -6
  145. data/lib/active_record/log_subscriber.rb +46 -4
  146. data/lib/active_record/migration/command_recorder.rb +11 -9
  147. data/lib/active_record/migration/compatibility.rb +74 -22
  148. data/lib/active_record/migration/join_table.rb +2 -0
  149. data/lib/active_record/migration.rb +181 -137
  150. data/lib/active_record/model_schema.rb +73 -58
  151. data/lib/active_record/nested_attributes.rb +18 -6
  152. data/lib/active_record/no_touching.rb +3 -1
  153. data/lib/active_record/null_relation.rb +2 -0
  154. data/lib/active_record/persistence.rb +153 -18
  155. data/lib/active_record/query_cache.rb +17 -12
  156. data/lib/active_record/querying.rb +4 -2
  157. data/lib/active_record/railtie.rb +61 -3
  158. data/lib/active_record/railties/console_sandbox.rb +2 -0
  159. data/lib/active_record/railties/controller_runtime.rb +2 -0
  160. data/lib/active_record/railties/databases.rake +47 -37
  161. data/lib/active_record/readonly_attributes.rb +3 -2
  162. data/lib/active_record/reflection.rb +131 -204
  163. data/lib/active_record/relation/batches/batch_enumerator.rb +2 -0
  164. data/lib/active_record/relation/batches.rb +32 -17
  165. data/lib/active_record/relation/calculations.rb +58 -20
  166. data/lib/active_record/relation/delegation.rb +10 -29
  167. data/lib/active_record/relation/finder_methods.rb +74 -85
  168. data/lib/active_record/relation/from_clause.rb +2 -8
  169. data/lib/active_record/relation/merger.rb +51 -20
  170. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -7
  171. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  172. data/lib/active_record/relation/predicate_builder/base_handler.rb +2 -2
  173. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +12 -1
  174. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +54 -0
  175. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -6
  176. data/lib/active_record/relation/predicate_builder/relation_handler.rb +6 -0
  177. data/lib/active_record/relation/predicate_builder.rb +53 -78
  178. data/lib/active_record/relation/query_attribute.rb +9 -2
  179. data/lib/active_record/relation/query_methods.rb +101 -95
  180. data/lib/active_record/relation/record_fetch_warning.rb +2 -0
  181. data/lib/active_record/relation/spawn_methods.rb +3 -1
  182. data/lib/active_record/relation/where_clause.rb +65 -67
  183. data/lib/active_record/relation/where_clause_factory.rb +5 -48
  184. data/lib/active_record/relation.rb +99 -202
  185. data/lib/active_record/result.rb +2 -0
  186. data/lib/active_record/runtime_registry.rb +2 -0
  187. data/lib/active_record/sanitization.rb +129 -121
  188. data/lib/active_record/schema.rb +4 -2
  189. data/lib/active_record/schema_dumper.rb +36 -26
  190. data/lib/active_record/schema_migration.rb +2 -0
  191. data/lib/active_record/scoping/default.rb +10 -7
  192. data/lib/active_record/scoping/named.rb +38 -12
  193. data/lib/active_record/scoping.rb +12 -10
  194. data/lib/active_record/secure_token.rb +2 -0
  195. data/lib/active_record/serialization.rb +2 -0
  196. data/lib/active_record/statement_cache.rb +22 -12
  197. data/lib/active_record/store.rb +3 -1
  198. data/lib/active_record/suppressor.rb +2 -0
  199. data/lib/active_record/table_metadata.rb +12 -3
  200. data/lib/active_record/tasks/database_tasks.rb +37 -25
  201. data/lib/active_record/tasks/mysql_database_tasks.rb +11 -50
  202. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -3
  203. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -3
  204. data/lib/active_record/timestamp.rb +5 -5
  205. data/lib/active_record/touch_later.rb +2 -0
  206. data/lib/active_record/transactions.rb +9 -7
  207. data/lib/active_record/translation.rb +2 -0
  208. data/lib/active_record/type/adapter_specific_registry.rb +2 -0
  209. data/lib/active_record/type/date.rb +2 -0
  210. data/lib/active_record/type/date_time.rb +2 -0
  211. data/lib/active_record/type/decimal_without_scale.rb +2 -0
  212. data/lib/active_record/type/hash_lookup_type_map.rb +2 -0
  213. data/lib/active_record/type/internal/timezone.rb +2 -0
  214. data/lib/active_record/type/json.rb +30 -0
  215. data/lib/active_record/type/serialized.rb +2 -0
  216. data/lib/active_record/type/text.rb +2 -0
  217. data/lib/active_record/type/time.rb +2 -0
  218. data/lib/active_record/type/type_map.rb +2 -0
  219. data/lib/active_record/type/unsigned_integer.rb +2 -0
  220. data/lib/active_record/type.rb +4 -1
  221. data/lib/active_record/type_caster/connection.rb +2 -0
  222. data/lib/active_record/type_caster/map.rb +3 -1
  223. data/lib/active_record/type_caster.rb +2 -0
  224. data/lib/active_record/validations/absence.rb +2 -0
  225. data/lib/active_record/validations/associated.rb +2 -0
  226. data/lib/active_record/validations/length.rb +2 -0
  227. data/lib/active_record/validations/presence.rb +2 -0
  228. data/lib/active_record/validations/uniqueness.rb +35 -5
  229. data/lib/active_record/validations.rb +2 -0
  230. data/lib/active_record/version.rb +2 -0
  231. data/lib/active_record.rb +11 -4
  232. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  233. data/lib/rails/generators/active_record/{model/templates/application_record.rb → application_record/templates/application_record.rb.tt} +0 -0
  234. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -1
  235. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +0 -0
  236. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +0 -0
  237. data/lib/rails/generators/active_record/migration.rb +2 -0
  238. data/lib/rails/generators/active_record/model/model_generator.rb +2 -23
  239. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +0 -0
  240. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  241. data/lib/rails/generators/active_record.rb +3 -1
  242. metadata +25 -37
  243. data/lib/active_record/associations/preloader/belongs_to.rb +0 -15
  244. data/lib/active_record/associations/preloader/collection_association.rb +0 -17
  245. data/lib/active_record/associations/preloader/has_many.rb +0 -15
  246. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  247. data/lib/active_record/associations/preloader/has_one.rb +0 -15
  248. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  249. data/lib/active_record/associations/preloader/singular_association.rb +0 -18
  250. data/lib/active_record/attribute/user_provided_default.rb +0 -30
  251. data/lib/active_record/attribute.rb +0 -240
  252. data/lib/active_record/attribute_mutation_tracker.rb +0 -113
  253. data/lib/active_record/attribute_set/builder.rb +0 -124
  254. data/lib/active_record/attribute_set/yaml_encoder.rb +0 -41
  255. data/lib/active_record/attribute_set.rb +0 -113
  256. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -10
  257. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  258. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +0 -88
  259. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -59
  260. data/lib/active_record/type/internal/abstract_json.rb +0 -33
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  class JoinDependency # :nodoc:
@@ -33,20 +35,14 @@ module ActiveRecord
33
35
  end
34
36
 
35
37
  Table = Struct.new(:node, :columns) do # :nodoc:
36
- def table
37
- Arel::Nodes::TableAlias.new node.table, node.aliased_table_name
38
- end
39
-
40
38
  def column_aliases
41
- t = table
39
+ t = node.table
42
40
  columns.map { |column| t[column.name].as Arel.sql column.alias }
43
41
  end
44
42
  end
45
43
  Column = Struct.new(:name, :alias)
46
44
  end
47
45
 
48
- attr_reader :alias_tracker, :base_klass, :join_root
49
-
50
46
  def self.make_tree(associations)
51
47
  hash = {}
52
48
  walk_tree associations, hash
@@ -92,11 +88,11 @@ module ActiveRecord
92
88
  # associations # => [:appointments]
93
89
  # joins # => []
94
90
  #
95
- def initialize(base, associations, joins, eager_loading: true)
96
- @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins, base.type_caster)
91
+ def initialize(base, table, associations, alias_tracker, eager_loading: true)
92
+ @alias_tracker = alias_tracker
97
93
  @eager_loading = eager_loading
98
94
  tree = self.class.make_tree associations
99
- @join_root = JoinBase.new base, build(tree, base)
95
+ @join_root = JoinBase.new(base, table, build(tree, base))
100
96
  @join_root.children.each { |child| construct_tables! @join_root, child }
101
97
  end
102
98
 
@@ -104,29 +100,24 @@ module ActiveRecord
104
100
  join_root.drop(1).map!(&:reflection)
105
101
  end
106
102
 
107
- def join_constraints(outer_joins, join_type)
103
+ def join_constraints(joins_to_add, join_type)
108
104
  joins = join_root.children.flat_map { |child|
109
-
110
- if join_type == Arel::Nodes::OuterJoin
111
- make_left_outer_joins join_root, child
112
- else
113
- make_inner_joins join_root, child
114
- end
105
+ make_join_constraints(join_root, child, join_type)
115
106
  }
116
107
 
117
- joins.concat outer_joins.flat_map { |oj|
108
+ joins.concat joins_to_add.flat_map { |oj|
118
109
  if join_root.match? oj.join_root
119
110
  walk join_root, oj.join_root
120
111
  else
121
112
  oj.join_root.children.flat_map { |child|
122
- make_outer_joins oj.join_root, child
113
+ make_join_constraints(oj.join_root, child, join_type)
123
114
  }
124
115
  end
125
116
  }
126
117
  end
127
118
 
128
119
  def aliases
129
- Aliases.new join_root.each_with_index.map { |join_part, i|
120
+ @aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
130
121
  columns = join_part.column_names.each_with_index.map { |column_name, j|
131
122
  Aliases::Column.new column_name, "t#{i}_r#{j}"
132
123
  }
@@ -134,7 +125,7 @@ module ActiveRecord
134
125
  }
135
126
  end
136
127
 
137
- def instantiate(result_set, aliases)
128
+ def instantiate(result_set, &block)
138
129
  primary_key = aliases.column_alias(join_root, join_root.primary_key)
139
130
 
140
131
  seen = Hash.new { |i, object_id|
@@ -157,7 +148,7 @@ module ActiveRecord
157
148
  message_bus.instrument("instantiation.active_record", payload) do
158
149
  result_set.each { |row_hash|
159
150
  parent_key = primary_key ? row_hash[primary_key] : row_hash
160
- parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases)
151
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, &block)
161
152
  construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
162
153
  }
163
154
  end
@@ -165,6 +156,9 @@ module ActiveRecord
165
156
  parents.values
166
157
  end
167
158
 
159
+ protected
160
+ attr_reader :alias_tracker, :base_klass, :join_root
161
+
168
162
  private
169
163
 
170
164
  def make_constraints(parent, child, tables, join_type)
@@ -175,34 +169,23 @@ module ActiveRecord
175
169
  end
176
170
 
177
171
  def make_outer_joins(parent, child)
178
- tables = table_aliases_for(parent, child)
179
- join_type = Arel::Nodes::OuterJoin
180
- info = make_constraints parent, child, tables, join_type
181
-
182
- [info] + child.children.flat_map { |c| make_outer_joins(child, c) }
183
- end
184
-
185
- def make_left_outer_joins(parent, child)
186
- tables = child.tables
187
172
  join_type = Arel::Nodes::OuterJoin
188
- info = make_constraints parent, child, tables, join_type
189
-
190
- [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) }
173
+ make_join_constraints(parent, child, join_type, true)
191
174
  end
192
175
 
193
- def make_inner_joins(parent, child)
194
- tables = child.tables
195
- join_type = Arel::Nodes::InnerJoin
196
- info = make_constraints parent, child, tables, join_type
176
+ def make_join_constraints(parent, child, join_type, aliasing = false)
177
+ tables = aliasing ? table_aliases_for(parent, child) : child.tables
178
+ joins = make_constraints(parent, child, tables, join_type)
197
179
 
198
- [info] + child.children.flat_map { |c| make_inner_joins(child, c) }
180
+ joins.concat child.children.flat_map { |c| make_join_constraints(child, c, join_type, aliasing) }
199
181
  end
200
182
 
201
183
  def table_aliases_for(parent, node)
202
184
  node.reflection.chain.map { |reflection|
203
185
  alias_tracker.aliased_table_for(
204
186
  reflection.table_name,
205
- table_alias_for(reflection, parent, reflection != node.reflection)
187
+ table_alias_for(reflection, parent, reflection != node.reflection),
188
+ reflection.klass.type_caster
206
189
  )
207
190
  }
208
191
  end
@@ -214,8 +197,7 @@ module ActiveRecord
214
197
 
215
198
  def table_alias_for(reflection, parent, join)
216
199
  name = "#{reflection.plural_name}_#{parent.table_name}"
217
- name << "_join" if join
218
- name
200
+ join ? "#{name}_join" : name
219
201
  end
220
202
 
221
203
  def walk(left, right)
@@ -243,7 +225,7 @@ module ActiveRecord
243
225
  raise EagerLoadPolymorphicError.new(reflection)
244
226
  end
245
227
 
246
- JoinAssociation.new reflection, build(right, reflection.klass)
228
+ JoinAssociation.new(reflection, build(right, reflection.klass), alias_tracker)
247
229
  end.compact
248
230
  end
249
231
 
@@ -275,7 +257,8 @@ module ActiveRecord
275
257
  else
276
258
  model = construct_model(ar_parent, node, row, model_cache, id, aliases)
277
259
 
278
- if node.reflection.scope_for(node.base_klass).readonly_value
260
+ if node.reflection.scope &&
261
+ node.reflection.scope_for(node.base_klass.unscoped).readonly_value
279
262
  model.readonly!
280
263
  end
281
264
 
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  class Preloader
4
6
  class Association #:nodoc:
5
- attr_reader :owners, :reflection, :preload_scope, :model, :klass
6
7
  attr_reader :preloaded_records
7
8
 
8
9
  def initialize(klass, owners, reflection, preload_scope)
@@ -11,86 +12,65 @@ module ActiveRecord
11
12
  @reflection = reflection
12
13
  @preload_scope = preload_scope
13
14
  @model = owners.first && owners.first.class
14
- @scope = nil
15
15
  @preloaded_records = []
16
16
  end
17
17
 
18
18
  def run(preloader)
19
- preload(preloader)
20
- end
21
-
22
- def preload(preloader)
23
- raise NotImplementedError
24
- end
25
-
26
- def scope
27
- @scope ||= build_scope
28
- end
29
-
30
- def records_for(ids)
31
- scope.where(association_key_name => ids)
32
- end
33
-
34
- def table
35
- klass.arel_table
36
- end
37
-
38
- # The name of the key on the associated records
39
- def association_key_name
40
- raise NotImplementedError
41
- end
42
-
43
- # This is overridden by HABTM as the condition should be on the foreign_key column in
44
- # the join table
45
- def association_key
46
- klass.arel_attribute(association_key_name, table)
47
- end
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
48
24
 
49
- # The name of the key on the model which declares the association
50
- def owner_key_name
51
- raise NotImplementedError
25
+ owners.each do |owner|
26
+ associate_records_to_owner(owner, records[convert_key(owner[owner_key_name])] || [])
27
+ end
52
28
  end
53
29
 
54
- def options
55
- reflection.options
56
- end
30
+ protected
31
+ attr_reader :owners, :reflection, :preload_scope, :model, :klass
57
32
 
58
33
  private
34
+ # The name of the key on the associated records
35
+ def association_key_name
36
+ reflection.join_primary_key(klass)
37
+ end
59
38
 
60
- def associated_records_by_owner(preloader)
61
- records = load_records do |record|
62
- owner = owners_by_key[convert_key(record[association_key_name])]
63
- association = owner.association(reflection.name)
64
- association.set_inverse_instance(record)
65
- end
39
+ # The name of the key on the model which declares the association
40
+ def owner_key_name
41
+ reflection.join_foreign_key
42
+ end
66
43
 
67
- owners.each_with_object({}) do |owner, result|
68
- result[owner] = records[convert_key(owner[owner_key_name])] || []
44
+ def associate_records_to_owner(owner, records)
45
+ association = owner.association(reflection.name)
46
+ if reflection.collection?
47
+ association.loaded!
48
+ association.target.concat(records)
49
+ else
50
+ association.target = records.first
69
51
  end
70
52
  end
71
53
 
72
54
  def owner_keys
73
- unless defined?(@owner_keys)
74
- @owner_keys = owners.map do |owner|
75
- owner[owner_key_name]
76
- end
77
- @owner_keys.uniq!
78
- @owner_keys.compact!
79
- end
80
- @owner_keys
55
+ @owner_keys ||= owners_by_key.keys
81
56
  end
82
57
 
83
58
  def owners_by_key
84
59
  unless defined?(@owners_by_key)
85
60
  @owners_by_key = owners.each_with_object({}) do |owner, h|
86
- h[convert_key(owner[owner_key_name])] = owner
61
+ key = convert_key(owner[owner_key_name])
62
+ h[key] = owner if key
87
63
  end
88
64
  end
89
65
  @owners_by_key
90
66
  end
91
67
 
92
68
  def key_conversion_required?
93
- @key_conversion_required ||= association_key_type != owner_key_type
69
+ unless defined?(@key_conversion_required)
70
+ @key_conversion_required = (association_key_type != owner_key_type)
71
+ end
72
+
73
+ @key_conversion_required
94
74
  end
95
75
 
96
76
  def convert_key(key)
@@ -102,11 +82,11 @@ module ActiveRecord
102
82
  end
103
83
 
104
84
  def association_key_type
105
- @klass.type_for_attribute(association_key_name.to_s).type
85
+ @klass.type_for_attribute(association_key_name).type
106
86
  end
107
87
 
108
88
  def owner_key_type
109
- @model.type_for_attribute(owner_key_name.to_s).type
89
+ @model.type_for_attribute(owner_key_name).type
110
90
  end
111
91
 
112
92
  def load_records(&block)
@@ -115,54 +95,35 @@ module ActiveRecord
115
95
  # Make several smaller queries if necessary or make one query if the adapter supports it
116
96
  slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
117
97
  @preloaded_records = slices.flat_map do |slice|
118
- records_for(slice).load(&block)
98
+ records_for(slice, &block)
119
99
  end
120
100
  @preloaded_records.group_by do |record|
121
101
  convert_key(record[association_key_name])
122
102
  end
123
103
  end
124
104
 
125
- def reflection_scope
126
- @reflection_scope ||= reflection.scope_for(klass)
105
+ def records_for(ids, &block)
106
+ scope.where(association_key_name => ids).load(&block)
127
107
  end
128
108
 
129
- def build_scope
130
- scope = klass.unscoped
131
-
132
- values = reflection_scope.values
133
- preload_values = preload_scope.values
134
-
135
- scope.where_clause = reflection_scope.where_clause + preload_scope.where_clause
136
- scope.references_values = Array(values[:references]) + Array(preload_values[:references])
137
-
138
- if preload_values[:select] || values[:select]
139
- scope._select!(preload_values[:select] || values[:select])
140
- end
141
- scope.includes! preload_values[:includes] || values[:includes]
142
- if preload_scope.joins_values.any?
143
- scope.joins!(preload_scope.joins_values)
144
- else
145
- scope.joins!(reflection_scope.joins_values)
146
- end
147
-
148
- if order_values = preload_values[:order] || values[:order]
149
- scope.order!(order_values)
150
- end
109
+ def scope
110
+ @scope ||= build_scope
111
+ end
151
112
 
152
- if preload_values[:reordering] || values[:reordering]
153
- scope.reordering_value = true
154
- end
113
+ def reflection_scope
114
+ @reflection_scope ||= reflection.scope ? reflection.scope_for(klass.unscoped) : klass.unscoped
115
+ end
155
116
 
156
- if preload_values[:readonly] || values[:readonly]
157
- scope.readonly!
158
- end
117
+ def build_scope
118
+ scope = klass.scope_for_association
159
119
 
160
- if options[:as]
161
- scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
120
+ if reflection.type
121
+ scope.where!(reflection.type => model.base_class.sti_name)
162
122
  end
163
123
 
164
- scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope])
165
- klass.default_scoped.merge(scope)
124
+ scope.merge!(reflection_scope) if reflection.scope
125
+ scope.merge!(preload_scope) if preload_scope
126
+ scope
166
127
  end
167
128
  end
168
129
  end
@@ -1,106 +1,105 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  class Preloader
4
- module ThroughAssociation #:nodoc:
5
- def through_reflection
6
- reflection.through_reflection
7
- end
8
-
9
- def source_reflection
10
- reflection.source_reflection
11
- end
12
-
13
- def associated_records_by_owner(preloader)
14
- preloader.preload(owners,
15
- through_reflection.name,
16
- through_scope)
17
-
18
- through_records = owners.map do |owner|
19
- association = owner.association through_reflection.name
20
-
21
- center = target_records_from_association(association)
22
- [owner, Array(center)]
23
- end
24
-
25
- reset_association owners, through_reflection.name
26
-
27
- middle_records = through_records.flat_map { |(_, rec)| rec }
28
-
29
- preloaders = preloader.preload(middle_records,
30
- source_reflection.name,
31
- reflection_scope)
32
-
6
+ class ThroughAssociation < Association # :nodoc:
7
+ def run(preloader)
8
+ already_loaded = owners.first.association(through_reflection.name).loaded?
9
+ through_scope = through_scope()
10
+ reflection_scope = target_reflection_scope
11
+ through_preloaders = preloader.preload(owners, through_reflection.name, through_scope)
12
+ middle_records = through_preloaders.flat_map(&:preloaded_records)
13
+ preloaders = preloader.preload(middle_records, source_reflection.name, reflection_scope)
33
14
  @preloaded_records = preloaders.flat_map(&:preloaded_records)
34
15
 
35
- middle_to_pl = preloaders.each_with_object({}) do |pl, h|
36
- pl.owners.each { |middle|
37
- h[middle] = pl
38
- }
39
- end
40
-
41
- through_records.each_with_object({}) do |(lhs, center), records_by_owner|
42
- pl_to_middle = center.group_by { |record| middle_to_pl[record] }
43
-
44
- records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
45
- rhs_records = middles.flat_map { |r|
46
- association = r.association source_reflection.name
47
-
48
- target_records_from_association(association)
49
- }.compact
50
-
51
- # Respect the order on `reflection_scope` if it exists, else use the natural order.
52
- if reflection_scope.values[:order].present?
53
- @id_map ||= id_to_index_map @preloaded_records
54
- rhs_records.sort_by { |rhs| @id_map[rhs] }
55
- else
56
- rhs_records
16
+ owners.each do |owner|
17
+ through_records = Array(owner.association(through_reflection.name).target)
18
+ if already_loaded
19
+ if source_type = reflection.options[:source_type]
20
+ through_records = through_records.select do |record|
21
+ record[reflection.foreign_type] == source_type
22
+ end
57
23
  end
24
+ else
25
+ owner.association(through_reflection.name).reset if through_scope
26
+ end
27
+ result = through_records.flat_map do |record|
28
+ association = record.association(source_reflection.name)
29
+ target = association.target
30
+ association.reset if preload_scope
31
+ target
32
+ end
33
+ result.compact!
34
+ if reflection_scope
35
+ result.sort_by! { |rhs| preload_index[rhs] } if reflection_scope.order_values.any?
36
+ result.uniq! if reflection_scope.distinct_value
58
37
  end
38
+ associate_records_to_owner(owner, result)
59
39
  end
60
40
  end
61
41
 
62
42
  private
63
-
64
- def id_to_index_map(ids)
65
- id_map = {}
66
- ids.each_with_index { |id, index| id_map[id] = index }
67
- id_map
43
+ def through_reflection
44
+ reflection.through_reflection
68
45
  end
69
46
 
70
- def reset_association(owners, association_name)
71
- should_reset = (through_scope != through_reflection.klass.unscoped) ||
72
- (reflection.options[:source_type] && through_reflection.collection?)
47
+ def source_reflection
48
+ reflection.source_reflection
49
+ end
73
50
 
74
- # Don't cache the association - we would only be caching a subset
75
- if should_reset
76
- owners.each { |owner|
77
- owner.association(association_name).reset
78
- }
51
+ def preload_index
52
+ @preload_index ||= @preloaded_records.each_with_object({}).with_index do |(id, result), index|
53
+ result[id] = index
79
54
  end
80
55
  end
81
56
 
82
57
  def through_scope
83
58
  scope = through_reflection.klass.unscoped
59
+ options = reflection.options
84
60
 
85
61
  if options[:source_type]
86
62
  scope.where! reflection.foreign_type => options[:source_type]
87
- else
88
- unless reflection_scope.where_clause.empty?
89
- scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
90
- scope.where_clause = reflection_scope.where_clause
63
+ elsif !reflection_scope.where_clause.empty?
64
+ scope.where_clause = reflection_scope.where_clause
65
+ values = reflection_scope.values
66
+
67
+ if includes = values[:includes]
68
+ scope.includes!(source_reflection.name => includes)
69
+ else
70
+ scope.includes!(source_reflection.name)
91
71
  end
92
72
 
93
- scope.references! reflection_scope.values[:references]
94
- if scope.eager_loading? && order_values = reflection_scope.values[:order]
73
+ if values[:references] && !values[:references].empty?
74
+ scope.references!(values[:references])
75
+ else
76
+ scope.references!(source_reflection.table_name)
77
+ end
78
+
79
+ if joins = values[:joins]
80
+ scope.joins!(source_reflection.name => joins)
81
+ end
82
+
83
+ if left_outer_joins = values[:left_outer_joins]
84
+ scope.left_outer_joins!(source_reflection.name => left_outer_joins)
85
+ end
86
+
87
+ if scope.eager_loading? && order_values = values[:order]
95
88
  scope = scope.order(order_values)
96
89
  end
97
90
  end
98
91
 
99
- scope
92
+ scope unless scope.empty_scope?
100
93
  end
101
94
 
102
- def target_records_from_association(association)
103
- association.loaded? ? association.target : association.reader
95
+ def target_reflection_scope
96
+ if preload_scope
97
+ reflection_scope.merge(preload_scope)
98
+ elsif reflection.scope
99
+ reflection_scope
100
+ else
101
+ nil
102
+ end
104
103
  end
105
104
  end
106
105
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  # Implements the details of eager loading of Active Record associations.
@@ -42,20 +44,10 @@ module ActiveRecord
42
44
  extend ActiveSupport::Autoload
43
45
 
44
46
  eager_autoload do
45
- autoload :Association, "active_record/associations/preloader/association"
46
- autoload :SingularAssociation, "active_record/associations/preloader/singular_association"
47
- autoload :CollectionAssociation, "active_record/associations/preloader/collection_association"
48
- autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
49
-
50
- autoload :HasMany, "active_record/associations/preloader/has_many"
51
- autoload :HasManyThrough, "active_record/associations/preloader/has_many_through"
52
- autoload :HasOne, "active_record/associations/preloader/has_one"
53
- autoload :HasOneThrough, "active_record/associations/preloader/has_one_through"
54
- autoload :BelongsTo, "active_record/associations/preloader/belongs_to"
47
+ autoload :Association, "active_record/associations/preloader/association"
48
+ autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
55
49
  end
56
50
 
57
- NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, [])
58
-
59
51
  # Eager loads the named associations for the given Active Record record(s).
60
52
  #
61
53
  # In this description, 'association name' shall refer to the name passed
@@ -91,14 +83,13 @@ module ActiveRecord
91
83
  # { author: :avatar }
92
84
  # [ :books, { author: :avatar } ]
93
85
  def preload(records, associations, preload_scope = nil)
94
- records = Array.wrap(records).compact.uniq
95
- associations = Array.wrap(associations)
96
- preload_scope = preload_scope || NULL_RELATION
86
+ records = Array.wrap(records).compact
97
87
 
98
88
  if records.empty?
99
89
  []
100
90
  else
101
- associations.flat_map { |association|
91
+ records.uniq!
92
+ Array.wrap(associations).flat_map { |association|
102
93
  preloaders_on association, records, preload_scope
103
94
  }
104
95
  end
@@ -147,7 +138,7 @@ module ActiveRecord
147
138
  def preloaders_for_one(association, records, scope)
148
139
  grouped_records(association, records).flat_map do |reflection, klasses|
149
140
  klasses.map do |rhs_klass, rs|
150
- loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope)
141
+ loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
151
142
  loader.run self
152
143
  loader
153
144
  end
@@ -159,6 +150,7 @@ module ActiveRecord
159
150
  records.each do |record|
160
151
  next unless record
161
152
  assoc = record.association(association)
153
+ next unless assoc.klass
162
154
  klasses = h[assoc.reflection] ||= {}
163
155
  (klasses[assoc.klass] ||= []) << record
164
156
  end
@@ -166,8 +158,6 @@ module ActiveRecord
166
158
  end
167
159
 
168
160
  class AlreadyLoaded # :nodoc:
169
- attr_reader :owners, :reflection
170
-
171
161
  def initialize(klass, owners, reflection, preload_scope)
172
162
  @owners = owners
173
163
  @reflection = reflection
@@ -178,34 +168,24 @@ module ActiveRecord
178
168
  def preloaded_records
179
169
  owners.flat_map { |owner| owner.association(reflection.name).target }
180
170
  end
181
- end
182
171
 
183
- class NullPreloader # :nodoc:
184
- def self.new(klass, owners, reflection, preload_scope); self; end
185
- def self.run(preloader); end
186
- def self.preloaded_records; []; end
187
- def self.owners; []; end
172
+ protected
173
+ attr_reader :owners, :reflection
188
174
  end
189
175
 
190
176
  # Returns a class containing the logic needed to load preload the data
191
- # and attach it to a relation. For example +Preloader::Association+ or
192
- # +Preloader::HasManyThrough+. The class returned implements a `run` method
177
+ # and attach it to a relation. The class returned implements a `run` method
193
178
  # that accepts a preloader.
194
- def preloader_for(reflection, owners, rhs_klass)
195
- return NullPreloader unless rhs_klass
196
-
179
+ def preloader_for(reflection, owners)
197
180
  if owners.first.association(reflection.name).loaded?
198
181
  return AlreadyLoaded
199
182
  end
200
183
  reflection.check_preloadable!
201
184
 
202
- case reflection.macro
203
- when :has_many
204
- reflection.options[:through] ? HasManyThrough : HasMany
205
- when :has_one
206
- reflection.options[:through] ? HasOneThrough : HasOne
207
- when :belongs_to
208
- BelongsTo
185
+ if reflection.options[:through]
186
+ ThroughAssociation
187
+ else
188
+ Association
209
189
  end
210
190
  end
211
191
  end