activerecord 5.1.7 → 5.2.0

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 (261) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +372 -765
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -5
  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 +19 -27
  10. data/lib/active_record/associations/association.rb +16 -27
  11. data/lib/active_record/associations/association_scope.rb +38 -50
  12. data/lib/active_record/associations/belongs_to_association.rb +20 -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 +4 -5
  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 +43 -35
  22. data/lib/active_record/associations/collection_proxy.rb +12 -15
  23. data/lib/active_record/associations/foreign_association.rb +2 -0
  24. data/lib/active_record/associations/has_many_association.rb +3 -1
  25. data/lib/active_record/associations/has_many_through_association.rb +7 -18
  26. data/lib/active_record/associations/has_one_association.rb +4 -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 -56
  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 +23 -43
  32. data/lib/active_record/associations/preloader/association.rb +45 -61
  33. data/lib/active_record/associations/preloader/through_association.rb +71 -79
  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 +25 -10
  37. data/lib/active_record/associations.rb +31 -54
  38. data/lib/active_record/attribute_assignment.rb +2 -5
  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 +25 -214
  42. data/lib/active_record/attribute_methods/primary_key.rb +7 -6
  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 +21 -9
  48. data/lib/active_record/attribute_methods.rb +65 -24
  49. data/lib/active_record/attributes.rb +6 -5
  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 -10
  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 +11 -7
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +111 -38
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +157 -29
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +7 -2
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +13 -32
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +2 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +14 -5
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +57 -2
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +31 -53
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +158 -78
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +45 -9
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +81 -96
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +111 -183
  69. data/lib/active_record/connection_adapters/column.rb +3 -1
  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 -2
  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 -30
  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 +2 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +6 -0
  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 +3 -11
  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.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +2 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +2 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -1
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -1
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +2 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +4 -6
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +4 -2
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +3 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +2 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +2 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +3 -1
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +18 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +19 -25
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +14 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +24 -11
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +20 -13
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +246 -110
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +2 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +2 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +58 -82
  117. data/lib/active_record/connection_adapters/schema_cache.rb +4 -2
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +2 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +2 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +18 -1
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +2 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +6 -15
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +3 -2
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +71 -1
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +80 -90
  126. data/lib/active_record/connection_adapters/statement_pool.rb +2 -0
  127. data/lib/active_record/connection_handling.rb +4 -2
  128. data/lib/active_record/core.rb +39 -60
  129. data/lib/active_record/counter_cache.rb +15 -12
  130. data/lib/active_record/define_callbacks.rb +5 -3
  131. data/lib/active_record/dynamic_matchers.rb +9 -9
  132. data/lib/active_record/enum.rb +17 -13
  133. data/lib/active_record/errors.rb +54 -21
  134. data/lib/active_record/explain.rb +3 -1
  135. data/lib/active_record/explain_registry.rb +2 -0
  136. data/lib/active_record/explain_subscriber.rb +2 -0
  137. data/lib/active_record/fixture_set/file.rb +2 -0
  138. data/lib/active_record/fixtures.rb +67 -60
  139. data/lib/active_record/gem_version.rb +4 -2
  140. data/lib/active_record/inheritance.rb +49 -19
  141. data/lib/active_record/integration.rb +58 -19
  142. data/lib/active_record/internal_metadata.rb +2 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +3 -1
  144. data/lib/active_record/locking/optimistic.rb +14 -17
  145. data/lib/active_record/locking/pessimistic.rb +9 -6
  146. data/lib/active_record/log_subscriber.rb +43 -0
  147. data/lib/active_record/migration/command_recorder.rb +11 -9
  148. data/lib/active_record/migration/compatibility.rb +40 -2
  149. data/lib/active_record/migration/join_table.rb +2 -0
  150. data/lib/active_record/migration.rb +189 -139
  151. data/lib/active_record/model_schema.rb +16 -21
  152. data/lib/active_record/nested_attributes.rb +18 -6
  153. data/lib/active_record/no_touching.rb +3 -1
  154. data/lib/active_record/null_relation.rb +2 -0
  155. data/lib/active_record/persistence.rb +166 -16
  156. data/lib/active_record/query_cache.rb +11 -6
  157. data/lib/active_record/querying.rb +3 -1
  158. data/lib/active_record/railtie.rb +61 -3
  159. data/lib/active_record/railties/console_sandbox.rb +2 -0
  160. data/lib/active_record/railties/controller_runtime.rb +2 -0
  161. data/lib/active_record/railties/databases.rake +46 -36
  162. data/lib/active_record/readonly_attributes.rb +3 -2
  163. data/lib/active_record/reflection.rb +110 -192
  164. data/lib/active_record/relation/batches/batch_enumerator.rb +2 -0
  165. data/lib/active_record/relation/batches.rb +20 -5
  166. data/lib/active_record/relation/calculations.rb +30 -8
  167. data/lib/active_record/relation/delegation.rb +15 -27
  168. data/lib/active_record/relation/finder_methods.rb +75 -78
  169. data/lib/active_record/relation/from_clause.rb +2 -8
  170. data/lib/active_record/relation/merger.rb +51 -20
  171. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -7
  172. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  173. data/lib/active_record/relation/predicate_builder/base_handler.rb +2 -2
  174. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +12 -1
  175. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  176. data/lib/active_record/relation/predicate_builder/range_handler.rb +26 -9
  177. data/lib/active_record/relation/predicate_builder/relation_handler.rb +6 -0
  178. data/lib/active_record/relation/predicate_builder.rb +53 -78
  179. data/lib/active_record/relation/query_attribute.rb +26 -2
  180. data/lib/active_record/relation/query_methods.rb +89 -88
  181. data/lib/active_record/relation/record_fetch_warning.rb +2 -0
  182. data/lib/active_record/relation/spawn_methods.rb +3 -1
  183. data/lib/active_record/relation/where_clause.rb +65 -68
  184. data/lib/active_record/relation/where_clause_factory.rb +5 -48
  185. data/lib/active_record/relation.rb +95 -208
  186. data/lib/active_record/result.rb +2 -0
  187. data/lib/active_record/runtime_registry.rb +2 -0
  188. data/lib/active_record/sanitization.rb +129 -121
  189. data/lib/active_record/schema.rb +4 -2
  190. data/lib/active_record/schema_dumper.rb +36 -26
  191. data/lib/active_record/schema_migration.rb +2 -0
  192. data/lib/active_record/scoping/default.rb +6 -7
  193. data/lib/active_record/scoping/named.rb +21 -7
  194. data/lib/active_record/scoping.rb +9 -8
  195. data/lib/active_record/secure_token.rb +2 -0
  196. data/lib/active_record/serialization.rb +2 -0
  197. data/lib/active_record/statement_cache.rb +22 -12
  198. data/lib/active_record/store.rb +3 -1
  199. data/lib/active_record/suppressor.rb +2 -0
  200. data/lib/active_record/table_metadata.rb +12 -3
  201. data/lib/active_record/tasks/database_tasks.rb +26 -15
  202. data/lib/active_record/tasks/mysql_database_tasks.rb +9 -48
  203. data/lib/active_record/tasks/postgresql_database_tasks.rb +10 -2
  204. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -3
  205. data/lib/active_record/timestamp.rb +5 -12
  206. data/lib/active_record/touch_later.rb +2 -0
  207. data/lib/active_record/transactions.rb +9 -7
  208. data/lib/active_record/translation.rb +2 -0
  209. data/lib/active_record/type/adapter_specific_registry.rb +2 -0
  210. data/lib/active_record/type/date.rb +2 -0
  211. data/lib/active_record/type/date_time.rb +2 -0
  212. data/lib/active_record/type/decimal_without_scale.rb +2 -0
  213. data/lib/active_record/type/hash_lookup_type_map.rb +2 -0
  214. data/lib/active_record/type/internal/timezone.rb +2 -0
  215. data/lib/active_record/type/json.rb +30 -0
  216. data/lib/active_record/type/serialized.rb +2 -4
  217. data/lib/active_record/type/text.rb +2 -0
  218. data/lib/active_record/type/time.rb +2 -0
  219. data/lib/active_record/type/type_map.rb +2 -0
  220. data/lib/active_record/type/unsigned_integer.rb +2 -0
  221. data/lib/active_record/type.rb +4 -1
  222. data/lib/active_record/type_caster/connection.rb +2 -0
  223. data/lib/active_record/type_caster/map.rb +3 -1
  224. data/lib/active_record/type_caster.rb +2 -0
  225. data/lib/active_record/validations/absence.rb +2 -0
  226. data/lib/active_record/validations/associated.rb +2 -0
  227. data/lib/active_record/validations/length.rb +2 -0
  228. data/lib/active_record/validations/presence.rb +2 -0
  229. data/lib/active_record/validations/uniqueness.rb +35 -5
  230. data/lib/active_record/validations.rb +2 -0
  231. data/lib/active_record/version.rb +2 -0
  232. data/lib/active_record.rb +11 -4
  233. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  234. data/lib/rails/generators/active_record/{model/templates/application_record.rb → application_record/templates/application_record.rb.tt} +0 -0
  235. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -1
  236. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +0 -0
  237. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +0 -0
  238. data/lib/rails/generators/active_record/migration.rb +2 -0
  239. data/lib/rails/generators/active_record/model/model_generator.rb +2 -23
  240. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +0 -0
  241. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  242. data/lib/rails/generators/active_record.rb +3 -1
  243. metadata +24 -36
  244. data/lib/active_record/associations/preloader/belongs_to.rb +0 -15
  245. data/lib/active_record/associations/preloader/collection_association.rb +0 -17
  246. data/lib/active_record/associations/preloader/has_many.rb +0 -15
  247. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  248. data/lib/active_record/associations/preloader/has_one.rb +0 -15
  249. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  250. data/lib/active_record/associations/preloader/singular_association.rb +0 -18
  251. data/lib/active_record/attribute/user_provided_default.rb +0 -30
  252. data/lib/active_record/attribute.rb +0 -240
  253. data/lib/active_record/attribute_mutation_tracker.rb +0 -122
  254. data/lib/active_record/attribute_set/builder.rb +0 -126
  255. data/lib/active_record/attribute_set/yaml_encoder.rb +0 -41
  256. data/lib/active_record/attribute_set.rb +0 -113
  257. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -10
  258. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  259. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +0 -88
  260. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -59
  261. data/lib/active_record/type/internal/abstract_json.rb +0 -37
@@ -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,10 @@ 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)
97
- @eager_loading = eager_loading
91
+ def initialize(base, table, associations, alias_tracker)
92
+ @alias_tracker = alias_tracker
98
93
  tree = self.class.make_tree associations
99
- @join_root = JoinBase.new base, build(tree, base)
94
+ @join_root = JoinBase.new(base, table, build(tree, base))
100
95
  @join_root.children.each { |child| construct_tables! @join_root, child }
101
96
  end
102
97
 
@@ -104,22 +99,17 @@ module ActiveRecord
104
99
  join_root.drop(1).map!(&:reflection)
105
100
  end
106
101
 
107
- def join_constraints(outer_joins, join_type)
102
+ def join_constraints(joins_to_add, join_type)
108
103
  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
104
+ make_join_constraints(join_root, child, join_type)
115
105
  }
116
106
 
117
- joins.concat outer_joins.flat_map { |oj|
107
+ joins.concat joins_to_add.flat_map { |oj|
118
108
  if join_root.match? oj.join_root
119
109
  walk join_root, oj.join_root
120
110
  else
121
111
  oj.join_root.children.flat_map { |child|
122
- make_outer_joins oj.join_root, child
112
+ make_join_constraints(oj.join_root, child, join_type)
123
113
  }
124
114
  end
125
115
  }
@@ -165,6 +155,9 @@ module ActiveRecord
165
155
  parents.values
166
156
  end
167
157
 
158
+ protected
159
+ attr_reader :alias_tracker, :base_klass, :join_root
160
+
168
161
  private
169
162
 
170
163
  def make_constraints(parent, child, tables, join_type)
@@ -175,27 +168,15 @@ module ActiveRecord
175
168
  end
176
169
 
177
170
  def make_outer_joins(parent, child)
178
- tables = table_aliases_for(parent, child)
179
171
  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) }
172
+ make_join_constraints(parent, child, join_type, true)
183
173
  end
184
174
 
185
- def make_left_outer_joins(parent, child)
186
- tables = child.tables
187
- join_type = Arel::Nodes::OuterJoin
188
- info = make_constraints parent, child, tables, join_type
175
+ def make_join_constraints(parent, child, join_type, aliasing = false)
176
+ tables = aliasing ? table_aliases_for(parent, child) : child.tables
177
+ joins = make_constraints(parent, child, tables, join_type)
189
178
 
190
- [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) }
191
- end
192
-
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
197
-
198
- [info] + child.children.flat_map { |c| make_inner_joins(child, c) }
179
+ joins.concat child.children.flat_map { |c| make_join_constraints(child, c, join_type, aliasing) }
199
180
  end
200
181
 
201
182
  def table_aliases_for(parent, node)
@@ -215,8 +196,7 @@ module ActiveRecord
215
196
 
216
197
  def table_alias_for(reflection, parent, join)
217
198
  name = "#{reflection.plural_name}_#{parent.table_name}"
218
- name << "_join" if join
219
- name
199
+ join ? "#{name}_join" : name
220
200
  end
221
201
 
222
202
  def walk(left, right)
@@ -240,12 +220,11 @@ module ActiveRecord
240
220
  reflection.check_eager_loadable!
241
221
 
242
222
  if reflection.polymorphic?
243
- next unless @eager_loading
244
223
  raise EagerLoadPolymorphicError.new(reflection)
245
224
  end
246
225
 
247
- JoinAssociation.new reflection, build(right, reflection.klass)
248
- end.compact
226
+ JoinAssociation.new(reflection, build(right, reflection.klass), alias_tracker)
227
+ end
249
228
  end
250
229
 
251
230
  def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
@@ -276,7 +255,8 @@ module ActiveRecord
276
255
  else
277
256
  model = construct_model(ar_parent, node, row, model_cache, id, aliases)
278
257
 
279
- if node.reflection.scope_for(node.base_klass).readonly_value
258
+ if node.reflection.scope &&
259
+ node.reflection.scope_for(node.base_klass.unscoped).readonly_value
280
260
  model.readonly!
281
261
  end
282
262
 
@@ -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,79 +12,54 @@ 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
+ association.loaded!
47
+ if reflection.collection?
48
+ association.target.concat(records)
49
+ else
50
+ association.target = records.first unless records.empty?
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
@@ -106,11 +82,11 @@ module ActiveRecord
106
82
  end
107
83
 
108
84
  def association_key_type
109
- @klass.type_for_attribute(association_key_name.to_s).type
85
+ @klass.type_for_attribute(association_key_name).type
110
86
  end
111
87
 
112
88
  def owner_key_type
113
- @model.type_for_attribute(owner_key_name.to_s).type
89
+ @model.type_for_attribute(owner_key_name).type
114
90
  end
115
91
 
116
92
  def load_records(&block)
@@ -119,26 +95,34 @@ module ActiveRecord
119
95
  # Make several smaller queries if necessary or make one query if the adapter supports it
120
96
  slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
121
97
  @preloaded_records = slices.flat_map do |slice|
122
- records_for(slice).load(&block)
98
+ records_for(slice, &block)
123
99
  end
124
100
  @preloaded_records.group_by do |record|
125
101
  convert_key(record[association_key_name])
126
102
  end
127
103
  end
128
104
 
105
+ def records_for(ids, &block)
106
+ scope.where(association_key_name => ids).load(&block)
107
+ end
108
+
109
+ def scope
110
+ @scope ||= build_scope
111
+ end
112
+
129
113
  def reflection_scope
130
- @reflection_scope ||= reflection.scope_for(klass)
114
+ @reflection_scope ||= reflection.scope ? reflection.scope_for(klass.unscoped) : klass.unscoped
131
115
  end
132
116
 
133
117
  def build_scope
134
118
  scope = klass.scope_for_association
135
119
 
136
120
  if reflection.type
137
- scope.where!(reflection.type => model.base_class.sti_name)
121
+ scope.where!(reflection.type => model.polymorphic_name)
138
122
  end
139
123
 
140
- scope.merge!(reflection_scope)
141
- scope.merge!(preload_scope) if preload_scope != NULL_RELATION
124
+ scope.merge!(reflection_scope) if reflection.scope
125
+ scope.merge!(preload_scope) if preload_scope
142
126
  scope
143
127
  end
144
128
  end
@@ -1,113 +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
84
- values = reflection_scope.values
59
+ options = reflection.options
85
60
 
86
61
  if options[:source_type]
87
62
  scope.where! reflection.foreign_type => options[:source_type]
88
- else
89
- unless reflection_scope.where_clause.empty?
90
- scope.includes_values = Array(values[:includes] || options[:source])
91
- scope.where_clause = reflection_scope.where_clause
92
- if joins = values[:joins]
93
- scope.joins!(source_reflection.name => joins)
94
- end
95
- if left_outer_joins = values[:left_outer_joins]
96
- scope.left_outer_joins!(source_reflection.name => left_outer_joins)
97
- end
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)
71
+ end
72
+
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)
98
85
  end
99
86
 
100
- scope.references! values[:references]
101
87
  if scope.eager_loading? && order_values = values[:order]
102
88
  scope = scope.order(order_values)
103
89
  end
104
90
  end
105
91
 
106
- scope
92
+ scope unless scope.empty_scope?
107
93
  end
108
94
 
109
- def target_records_from_association(association)
110
- 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
111
103
  end
112
104
  end
113
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  class SingularAssociation < Association #:nodoc:
@@ -30,24 +32,22 @@ module ActiveRecord
30
32
  end
31
33
 
32
34
  private
33
-
34
- def create_scope
35
- scope.scope_for_create.stringify_keys.except(klass.primary_key)
35
+ def scope_for_create
36
+ super.except!(klass.primary_key)
36
37
  end
37
38
 
38
39
  def find_target
39
- return scope.take if skip_statement_cache?
40
+ scope = self.scope
41
+ return scope.take if skip_statement_cache?(scope)
40
42
 
41
43
  conn = klass.connection
42
- sc = reflection.association_scope_cache(conn, owner) do
43
- StatementCache.create(conn) { |params|
44
- as = AssociationScope.create { params.bind }
45
- target_scope.merge(as.scope(self, conn)).limit(1)
46
- }
44
+ sc = reflection.association_scope_cache(conn, owner) do |params|
45
+ as = AssociationScope.create { params.bind }
46
+ target_scope.merge!(as.scope(self)).limit(1)
47
47
  end
48
48
 
49
49
  binds = AssociationScope.get_bind_values(owner, reflection.chain)
50
- sc.execute(binds, klass, conn) do |record|
50
+ sc.execute(binds, conn) do |record|
51
51
  set_inverse_instance record
52
52
  end.first
53
53
  rescue ::RangeError
@@ -63,6 +63,10 @@ module ActiveRecord
63
63
  end
64
64
 
65
65
  def _create_record(attributes, raise_error = false)
66
+ unless owner.persisted?
67
+ raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
68
+ end
69
+
66
70
  record = build_record(attributes)
67
71
  yield(record) if block_given?
68
72
  saved = record.save