activerecord 5.1.7 → 5.2.0.beta1

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 (259) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +221 -900
  3. data/README.rdoc +3 -3
  4. data/examples/performance.rb +2 -0
  5. data/examples/simple.rb +2 -0
  6. data/lib/active_record.rb +10 -3
  7. data/lib/active_record/aggregations.rb +2 -0
  8. data/lib/active_record/association_relation.rb +2 -0
  9. data/lib/active_record/associations.rb +13 -42
  10. data/lib/active_record/associations/alias_tracker.rb +17 -17
  11. data/lib/active_record/associations/association.rb +11 -22
  12. data/lib/active_record/associations/association_scope.rb +32 -44
  13. data/lib/active_record/associations/belongs_to_association.rb +6 -4
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -1
  15. data/lib/active_record/associations/builder/association.rb +2 -5
  16. data/lib/active_record/associations/builder/belongs_to.rb +7 -12
  17. data/lib/active_record/associations/builder/collection_association.rb +1 -1
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -1
  19. data/lib/active_record/associations/builder/has_many.rb +2 -0
  20. data/lib/active_record/associations/builder/has_one.rb +2 -0
  21. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  22. data/lib/active_record/associations/collection_association.rb +41 -33
  23. data/lib/active_record/associations/collection_proxy.rb +11 -14
  24. data/lib/active_record/associations/foreign_association.rb +2 -0
  25. data/lib/active_record/associations/has_many_association.rb +4 -2
  26. data/lib/active_record/associations/has_many_through_association.rb +4 -2
  27. data/lib/active_record/associations/has_one_association.rb +3 -1
  28. data/lib/active_record/associations/has_one_through_association.rb +3 -1
  29. data/lib/active_record/associations/join_dependency.rb +22 -40
  30. data/lib/active_record/associations/join_dependency/join_association.rb +17 -56
  31. data/lib/active_record/associations/join_dependency/join_base.rb +9 -8
  32. data/lib/active_record/associations/join_dependency/join_part.rb +2 -9
  33. data/lib/active_record/associations/preloader.rb +17 -37
  34. data/lib/active_record/associations/preloader/association.rb +42 -58
  35. data/lib/active_record/associations/preloader/through_association.rb +71 -79
  36. data/lib/active_record/associations/singular_association.rb +14 -10
  37. data/lib/active_record/associations/through_association.rb +3 -1
  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.rb +47 -7
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -0
  42. data/lib/active_record/attribute_methods/dirty.rb +25 -214
  43. data/lib/active_record/attribute_methods/primary_key.rb +7 -6
  44. data/lib/active_record/attribute_methods/query.rb +2 -0
  45. data/lib/active_record/attribute_methods/read.rb +8 -2
  46. data/lib/active_record/attribute_methods/serialization.rb +23 -0
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +6 -8
  48. data/lib/active_record/attribute_methods/write.rb +21 -9
  49. data/lib/active_record/attributes.rb +7 -6
  50. data/lib/active_record/autosave_association.rb +5 -11
  51. data/lib/active_record/base.rb +2 -0
  52. data/lib/active_record/callbacks.rb +6 -8
  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 +10 -5
  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 +120 -28
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +7 -2
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +14 -33
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +2 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +13 -5
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +40 -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 +103 -63
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +45 -9
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +62 -90
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +75 -138
  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 +3 -1
  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 -6
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +30 -30
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +91 -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.rb +2 -1
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -11
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +2 -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 +3 -1
  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 +3 -5
  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/quoting.rb +10 -0
  108. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +19 -25
  109. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +2 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +11 -7
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +20 -13
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +79 -65
  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 +47 -82
  116. data/lib/active_record/connection_adapters/schema_cache.rb +2 -0
  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 +19 -2
  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 +34 -89
  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 +27 -57
  128. data/lib/active_record/counter_cache.rb +15 -12
  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 +15 -13
  132. data/lib/active_record/errors.rb +54 -21
  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 +40 -24
  138. data/lib/active_record/gem_version.rb +5 -3
  139. data/lib/active_record/inheritance.rb +6 -5
  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 +31 -20
  144. data/lib/active_record/locking/pessimistic.rb +10 -7
  145. data/lib/active_record/log_subscriber.rb +2 -0
  146. data/lib/active_record/migration.rb +47 -21
  147. data/lib/active_record/migration/command_recorder.rb +11 -9
  148. data/lib/active_record/migration/compatibility.rb +20 -2
  149. data/lib/active_record/migration/join_table.rb +2 -0
  150. data/lib/active_record/model_schema.rb +29 -38
  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 +184 -40
  155. data/lib/active_record/query_cache.rb +17 -12
  156. data/lib/active_record/querying.rb +3 -1
  157. data/lib/active_record/railtie.rb +54 -1
  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 +41 -28
  161. data/lib/active_record/readonly_attributes.rb +3 -2
  162. data/lib/active_record/reflection.rb +100 -182
  163. data/lib/active_record/relation.rb +61 -193
  164. data/lib/active_record/relation/batches.rb +20 -5
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +2 -0
  166. data/lib/active_record/relation/calculations.rb +40 -23
  167. data/lib/active_record/relation/delegation.rb +10 -27
  168. data/lib/active_record/relation/finder_methods.rb +53 -49
  169. data/lib/active_record/relation/from_clause.rb +2 -8
  170. data/lib/active_record/relation/merger.rb +22 -19
  171. data/lib/active_record/relation/predicate_builder.rb +42 -79
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -7
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +2 -2
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +12 -1
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +54 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -6
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +6 -0
  179. data/lib/active_record/relation/query_attribute.rb +9 -2
  180. data/lib/active_record/relation/query_methods.rb +80 -69
  181. data/lib/active_record/relation/record_fetch_warning.rb +2 -0
  182. data/lib/active_record/relation/spawn_methods.rb +2 -0
  183. data/lib/active_record/relation/where_clause.rb +50 -67
  184. data/lib/active_record/relation/where_clause_factory.rb +4 -46
  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 +15 -9
  188. data/lib/active_record/schema.rb +3 -1
  189. data/lib/active_record/schema_dumper.rb +24 -23
  190. data/lib/active_record/schema_migration.rb +2 -0
  191. data/lib/active_record/scoping.rb +9 -8
  192. data/lib/active_record/scoping/default.rb +6 -7
  193. data/lib/active_record/scoping/named.rb +15 -7
  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 +2 -0
  198. data/lib/active_record/suppressor.rb +2 -0
  199. data/lib/active_record/table_metadata.rb +3 -1
  200. data/lib/active_record/tasks/database_tasks.rb +23 -12
  201. data/lib/active_record/tasks/mysql_database_tasks.rb +9 -48
  202. data/lib/active_record/tasks/postgresql_database_tasks.rb +10 -2
  203. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -3
  204. data/lib/active_record/timestamp.rb +5 -12
  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.rb +4 -1
  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_caster.rb +2 -0
  222. data/lib/active_record/type_caster/connection.rb +2 -0
  223. data/lib/active_record/type_caster/map.rb +2 -0
  224. data/lib/active_record/validations.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 +36 -6
  230. data/lib/active_record/version.rb +2 -0
  231. data/lib/rails/generators/active_record.rb +3 -1
  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.rb +2 -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/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. metadata +25 -38
  242. data/lib/active_record/associations/preloader/belongs_to.rb +0 -15
  243. data/lib/active_record/associations/preloader/collection_association.rb +0 -17
  244. data/lib/active_record/associations/preloader/has_many.rb +0 -15
  245. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  246. data/lib/active_record/associations/preloader/has_one.rb +0 -15
  247. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  248. data/lib/active_record/associations/preloader/singular_association.rb +0 -18
  249. data/lib/active_record/attribute.rb +0 -240
  250. data/lib/active_record/attribute/user_provided_default.rb +0 -30
  251. data/lib/active_record/attribute_mutation_tracker.rb +0 -122
  252. data/lib/active_record/attribute_set.rb +0 -113
  253. data/lib/active_record/attribute_set/builder.rb +0 -126
  254. data/lib/active_record/attribute_set/yaml_encoder.rb +0 -41
  255. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -10
  256. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  257. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +0 -88
  258. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -59
  259. data/lib/active_record/type/internal/abstract_json.rb +0 -37
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_record/associations/join_dependency/join_part"
2
4
 
3
5
  module ActiveRecord
@@ -9,11 +11,12 @@ module ActiveRecord
9
11
 
10
12
  attr_accessor :tables
11
13
 
12
- def initialize(reflection, children)
14
+ def initialize(reflection, children, alias_tracker)
13
15
  super(reflection.klass, children)
14
16
 
15
- @reflection = reflection
16
- @tables = nil
17
+ @alias_tracker = alias_tracker
18
+ @reflection = reflection
19
+ @tables = nil
17
20
  end
18
21
 
19
22
  def match?(other)
@@ -21,11 +24,8 @@ module ActiveRecord
21
24
  super && reflection == other.reflection
22
25
  end
23
26
 
24
- JoinInformation = Struct.new :joins, :binds
25
-
26
27
  def join_constraints(foreign_table, foreign_klass, join_type, tables, chain)
27
28
  joins = []
28
- binds = []
29
29
  tables = tables.reverse
30
30
 
31
31
  # The chain starts with the target table, but we want to end with it here (makes
@@ -34,71 +34,32 @@ module ActiveRecord
34
34
  table = tables.shift
35
35
  klass = reflection.klass
36
36
 
37
- join_keys = reflection.join_keys
38
- key = join_keys.key
39
- foreign_key = join_keys.foreign_key
40
-
41
- constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
42
-
43
- rel = reflection.join_scope(table)
37
+ constraint = reflection.build_join_constraint(table, foreign_table)
44
38
 
45
- if rel && !rel.arel.constraints.empty?
46
- binds += rel.bound_attributes
47
- constraint = constraint.and rel.arel.constraints
48
- end
39
+ joins << table.create_join(table, table.create_on(constraint), join_type)
49
40
 
50
- if reflection.type
51
- value = foreign_klass.base_class.name
52
- column = klass.columns_hash[reflection.type.to_s]
41
+ join_scope = reflection.join_scope(table, foreign_klass)
42
+ arel = join_scope.arel(alias_tracker.aliases)
53
43
 
54
- binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name))
55
- constraint = constraint.and klass.arel_attribute(reflection.type, table).eq(Arel::Nodes::BindParam.new)
44
+ if arel.constraints.any?
45
+ joins.concat arel.join_sources
46
+ right = joins.last.right
47
+ right.expr = right.expr.and(arel.constraints)
56
48
  end
57
49
 
58
- joins << table.create_join(table, table.create_on(constraint), join_type)
59
-
60
50
  # The current table in this iteration becomes the foreign table in the next
61
51
  foreign_table, foreign_klass = table, klass
62
52
  end
63
53
 
64
- JoinInformation.new joins, binds
65
- end
66
-
67
- # Builds equality condition.
68
- #
69
- # Example:
70
- #
71
- # class Physician < ActiveRecord::Base
72
- # has_many :appointments
73
- # end
74
- #
75
- # If I execute `Physician.joins(:appointments).to_a` then
76
- # klass # => Physician
77
- # table # => #<Arel::Table @name="appointments" ...>
78
- # key # => physician_id
79
- # foreign_table # => #<Arel::Table @name="physicians" ...>
80
- # foreign_key # => id
81
- #
82
- def build_constraint(klass, table, key, foreign_table, foreign_key)
83
- constraint = table[key].eq(foreign_table[foreign_key])
84
-
85
- if klass.finder_needs_type_condition?
86
- constraint = table.create_and([
87
- constraint,
88
- klass.send(:type_condition, table)
89
- ])
90
- end
91
-
92
- constraint
54
+ joins
93
55
  end
94
56
 
95
57
  def table
96
58
  tables.first
97
59
  end
98
60
 
99
- def aliased_table_name
100
- table.table_alias || table.name
101
- end
61
+ protected
62
+ attr_reader :alias_tracker
102
63
  end
103
64
  end
104
65
  end
@@ -1,20 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_record/associations/join_dependency/join_part"
2
4
 
3
5
  module ActiveRecord
4
6
  module Associations
5
7
  class JoinDependency # :nodoc:
6
8
  class JoinBase < JoinPart # :nodoc:
7
- def match?(other)
8
- return true if self == other
9
- super && base_klass == other.base_klass
10
- end
9
+ attr_reader :table
11
10
 
12
- def table
13
- base_klass.arel_table
11
+ def initialize(base_klass, table, children)
12
+ super(base_klass, children)
13
+ @table = table
14
14
  end
15
15
 
16
- def aliased_table_name
17
- base_klass.table_name
16
+ def match?(other)
17
+ return true if self == other
18
+ super && base_klass == other.base_klass
18
19
  end
19
20
  end
20
21
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  class JoinDependency # :nodoc:
@@ -22,10 +24,6 @@ module ActiveRecord
22
24
  @children = children
23
25
  end
24
26
 
25
- def name
26
- reflection.name
27
- end
28
-
29
27
  def match?(other)
30
28
  self.class == other.class
31
29
  end
@@ -40,11 +38,6 @@ module ActiveRecord
40
38
  raise NotImplementedError
41
39
  end
42
40
 
43
- # The alias for the active_record's table
44
- def aliased_table_name
45
- raise NotImplementedError
46
- end
47
-
48
41
  def extract_record(row, column_names_with_alias)
49
42
  # This code is performance critical as it is called per row.
50
43
  # see: https://github.com/rails/rails/pull/12185
@@ -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 = 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,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
+ 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
@@ -119,15 +95,23 @@ 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
@@ -137,8 +121,8 @@ module ActiveRecord
137
121
  scope.where!(reflection.type => model.base_class.sti_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