activerecord 4.2.0 → 5.2.8.1

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 (274) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +640 -928
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +10 -11
  5. data/examples/performance.rb +32 -31
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +264 -247
  8. data/lib/active_record/association_relation.rb +24 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +87 -41
  11. data/lib/active_record/associations/association_scope.rb +106 -132
  12. data/lib/active_record/associations/belongs_to_association.rb +55 -36
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
  14. data/lib/active_record/associations/builder/association.rb +29 -38
  15. data/lib/active_record/associations/builder/belongs_to.rb +77 -30
  16. data/lib/active_record/associations/builder/collection_association.rb +14 -23
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +50 -39
  18. data/lib/active_record/associations/builder/has_many.rb +6 -4
  19. data/lib/active_record/associations/builder/has_one.rb +13 -6
  20. data/lib/active_record/associations/builder/singular_association.rb +15 -11
  21. data/lib/active_record/associations/collection_association.rb +145 -266
  22. data/lib/active_record/associations/collection_proxy.rb +242 -138
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +35 -75
  25. data/lib/active_record/associations/has_many_through_association.rb +51 -69
  26. data/lib/active_record/associations/has_one_association.rb +39 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +40 -81
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
  31. data/lib/active_record/associations/join_dependency.rb +134 -154
  32. data/lib/active_record/associations/preloader/association.rb +85 -116
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +83 -93
  35. data/lib/active_record/associations/singular_association.rb +27 -40
  36. data/lib/active_record/associations/through_association.rb +48 -23
  37. data/lib/active_record/associations.rb +1732 -1596
  38. data/lib/active_record/attribute_assignment.rb +58 -182
  39. data/lib/active_record/attribute_decorators.rb +39 -15
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +12 -5
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -125
  42. data/lib/active_record/attribute_methods/primary_key.rb +86 -71
  43. data/lib/active_record/attribute_methods/query.rb +4 -2
  44. data/lib/active_record/attribute_methods/read.rb +45 -63
  45. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -36
  47. data/lib/active_record/attribute_methods/write.rb +31 -46
  48. data/lib/active_record/attribute_methods.rb +170 -117
  49. data/lib/active_record/attributes.rb +201 -74
  50. data/lib/active_record/autosave_association.rb +118 -45
  51. data/lib/active_record/base.rb +60 -48
  52. data/lib/active_record/callbacks.rb +97 -57
  53. data/lib/active_record/coders/json.rb +3 -1
  54. data/lib/active_record/coders/yaml_column.rb +37 -13
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +254 -87
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +72 -22
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -217
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +617 -212
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +139 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +332 -191
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +567 -563
  69. data/lib/active_record/connection_adapters/column.rb +50 -41
  70. data/lib/active_record/connection_adapters/connection_specification.rb +147 -135
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +42 -195
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -115
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -57
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +10 -6
  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 +5 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -13
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  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 -11
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  99. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +65 -51
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +107 -47
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +144 -90
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +466 -280
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +439 -330
  117. data/lib/active_record/connection_adapters/schema_cache.rb +48 -24
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +269 -324
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +40 -27
  128. data/lib/active_record/core.rb +205 -202
  129. data/lib/active_record/counter_cache.rb +80 -37
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +87 -105
  132. data/lib/active_record/enum.rb +136 -90
  133. data/lib/active_record/errors.rb +180 -52
  134. data/lib/active_record/explain.rb +23 -11
  135. data/lib/active_record/explain_registry.rb +4 -2
  136. data/lib/active_record/explain_subscriber.rb +11 -6
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +193 -135
  139. data/lib/active_record/gem_version.rb +5 -3
  140. data/lib/active_record/inheritance.rb +148 -112
  141. data/lib/active_record/integration.rb +70 -28
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  144. data/lib/active_record/locale/en.yml +3 -2
  145. data/lib/active_record/locking/optimistic.rb +92 -98
  146. data/lib/active_record/locking/pessimistic.rb +15 -3
  147. data/lib/active_record/log_subscriber.rb +95 -33
  148. data/lib/active_record/migration/command_recorder.rb +133 -90
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +8 -6
  151. data/lib/active_record/migration.rb +594 -267
  152. data/lib/active_record/model_schema.rb +292 -111
  153. data/lib/active_record/nested_attributes.rb +266 -214
  154. data/lib/active_record/no_touching.rb +8 -2
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +350 -119
  157. data/lib/active_record/query_cache.rb +13 -24
  158. data/lib/active_record/querying.rb +19 -17
  159. data/lib/active_record/railtie.rb +117 -35
  160. data/lib/active_record/railties/console_sandbox.rb +2 -0
  161. data/lib/active_record/railties/controller_runtime.rb +9 -3
  162. data/lib/active_record/railties/databases.rake +160 -174
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +447 -288
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +204 -55
  167. data/lib/active_record/relation/calculations.rb +259 -244
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +290 -253
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +91 -68
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -23
  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 +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  179. data/lib/active_record/relation/predicate_builder.rb +118 -92
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +446 -389
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +18 -16
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +287 -339
  187. data/lib/active_record/result.rb +54 -36
  188. data/lib/active_record/runtime_registry.rb +6 -4
  189. data/lib/active_record/sanitization.rb +155 -124
  190. data/lib/active_record/schema.rb +30 -24
  191. data/lib/active_record/schema_dumper.rb +91 -87
  192. data/lib/active_record/schema_migration.rb +19 -19
  193. data/lib/active_record/scoping/default.rb +102 -84
  194. data/lib/active_record/scoping/named.rb +81 -32
  195. data/lib/active_record/scoping.rb +45 -26
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +5 -5
  198. data/lib/active_record/statement_cache.rb +45 -35
  199. data/lib/active_record/store.rb +42 -36
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +136 -95
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +59 -89
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -31
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -16
  206. data/lib/active_record/timestamp.rb +70 -38
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +208 -123
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +4 -41
  212. data/lib/active_record/type/date_time.rb +4 -38
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +30 -15
  218. data/lib/active_record/type/text.rb +2 -2
  219. data/lib/active_record/type/time.rb +11 -16
  220. data/lib/active_record/type/type_map.rb +15 -17
  221. data/lib/active_record/type/unsigned_integer.rb +9 -7
  222. data/lib/active_record/type.rb +79 -23
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +13 -4
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +14 -13
  230. data/lib/active_record/validations/uniqueness.rb +41 -32
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +36 -21
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -35
  237. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +8 -6
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -7
  239. data/lib/rails/generators/active_record/migration.rb +18 -1
  240. data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
  241. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +3 -0
  242. data/lib/rails/generators/active_record.rb +7 -5
  243. metadata +77 -53
  244. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  245. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  246. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  247. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  248. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  249. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  250. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  251. data/lib/active_record/attribute.rb +0 -149
  252. data/lib/active_record/attribute_set/builder.rb +0 -86
  253. data/lib/active_record/attribute_set.rb +0 -77
  254. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  255. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  256. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  257. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  258. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  259. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  260. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  261. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  262. data/lib/active_record/type/big_integer.rb +0 -13
  263. data/lib/active_record/type/binary.rb +0 -50
  264. data/lib/active_record/type/boolean.rb +0 -30
  265. data/lib/active_record/type/decimal.rb +0 -40
  266. data/lib/active_record/type/decorator.rb +0 -14
  267. data/lib/active_record/type/float.rb +0 -19
  268. data/lib/active_record/type/integer.rb +0 -55
  269. data/lib/active_record/type/mutable.rb +0 -16
  270. data/lib/active_record/type/numeric.rb +0 -36
  271. data/lib/active_record/type/string.rb +0 -36
  272. data/lib/active_record/type/time_value.rb +0 -38
  273. data/lib/active_record/type/value.rb +0 -101
  274. /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -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,151 +12,119 @@ module ActiveRecord
11
12
  @reflection = reflection
12
13
  @preload_scope = preload_scope
13
14
  @model = owners.first && owners.first.class
14
- @scope = nil
15
- @owners_by_key = nil
16
15
  @preloaded_records = []
17
16
  end
18
17
 
19
18
  def run(preloader)
20
- preload(preloader)
21
- end
22
-
23
- def preload(preloader)
24
- raise NotImplementedError
25
- end
26
-
27
- def scope
28
- @scope ||= build_scope
29
- end
30
-
31
- def records_for(ids)
32
- query_scope(ids)
33
- end
34
-
35
- def query_scope(ids)
36
- scope.where(association_key.in(ids))
37
- end
38
-
39
- def table
40
- klass.arel_table
41
- end
42
-
43
- # The name of the key on the associated records
44
- def association_key_name
45
- raise NotImplementedError
46
- end
47
-
48
- # This is overridden by HABTM as the condition should be on the foreign_key column in
49
- # the join table
50
- def association_key
51
- table[association_key_name]
52
- end
53
-
54
- # The name of the key on the model which declares the association
55
- def owner_key_name
56
- raise NotImplementedError
57
- 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
58
24
 
59
- def owners_by_key
60
- @owners_by_key ||= if key_conversion_required?
61
- owners.group_by do |owner|
62
- owner[owner_key_name].to_s
63
- end
64
- else
65
- owners.group_by do |owner|
66
- owner[owner_key_name]
67
- end
68
- end
25
+ owners.each do |owner|
26
+ associate_records_to_owner(owner, records[convert_key(owner[owner_key_name])] || [])
27
+ end
69
28
  end
70
29
 
71
- def options
72
- reflection.options
73
- end
30
+ protected
31
+ attr_reader :owners, :reflection, :preload_scope, :model, :klass
74
32
 
75
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
76
38
 
77
- def associated_records_by_owner(preloader)
78
- owners_map = owners_by_key
79
- owner_keys = owners_map.keys.compact
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
80
43
 
81
- # Each record may have multiple owners, and vice-versa
82
- records_by_owner = owners.each_with_object({}) do |owner,h|
83
- h[owner] = []
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?
51
+ end
84
52
  end
85
53
 
86
- if owner_keys.any?
87
- # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
88
- # Make several smaller queries if necessary or make one query if the adapter supports it
89
- sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
54
+ def owner_keys
55
+ @owner_keys ||= owners_by_key.keys
56
+ end
90
57
 
91
- records = load_slices sliced
92
- records.each do |record, owner_key|
93
- owners_map[owner_key].each do |owner|
94
- records_by_owner[owner] << record
58
+ def owners_by_key
59
+ unless defined?(@owners_by_key)
60
+ @owners_by_key = owners.each_with_object({}) do |owner, h|
61
+ key = convert_key(owner[owner_key_name])
62
+ h[key] = owner if key
95
63
  end
96
64
  end
65
+ @owners_by_key
97
66
  end
98
67
 
99
- records_by_owner
100
- end
101
-
102
- def key_conversion_required?
103
- association_key_type != owner_key_type
104
- end
105
-
106
- def association_key_type
107
- @klass.type_for_attribute(association_key_name.to_s).type
108
- end
68
+ def key_conversion_required?
69
+ unless defined?(@key_conversion_required)
70
+ @key_conversion_required = (association_key_type != owner_key_type)
71
+ end
109
72
 
110
- def owner_key_type
111
- @model.type_for_attribute(owner_key_name.to_s).type
112
- end
73
+ @key_conversion_required
74
+ end
113
75
 
114
- def load_slices(slices)
115
- @preloaded_records = slices.flat_map { |slice|
116
- records_for(slice)
117
- }
76
+ def convert_key(key)
77
+ if key_conversion_required?
78
+ key.to_s
79
+ else
80
+ key
81
+ end
82
+ end
118
83
 
119
- @preloaded_records.map { |record|
120
- key = record[association_key_name]
121
- key = key.to_s if key_conversion_required?
84
+ def association_key_type
85
+ @klass.type_for_attribute(association_key_name).type
86
+ end
122
87
 
123
- [record, key]
124
- }
125
- end
88
+ def owner_key_type
89
+ @model.type_for_attribute(owner_key_name).type
90
+ end
126
91
 
127
- def reflection_scope
128
- @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped
129
- end
92
+ def load_records(&block)
93
+ return {} if owner_keys.empty?
94
+ # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
95
+ # Make several smaller queries if necessary or make one query if the adapter supports it
96
+ slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
97
+ @preloaded_records = slices.flat_map do |slice|
98
+ records_for(slice, &block)
99
+ end
100
+ @preloaded_records.group_by do |record|
101
+ convert_key(record[association_key_name])
102
+ end
103
+ end
130
104
 
131
- def build_scope
132
- scope = klass.unscoped
105
+ def records_for(ids, &block)
106
+ scope.where(association_key_name => ids).load(&block)
107
+ end
133
108
 
134
- values = reflection_scope.values
135
- reflection_binds = reflection_scope.bind_values
136
- preload_values = preload_scope.values
137
- preload_binds = preload_scope.bind_values
109
+ def scope
110
+ @scope ||= build_scope
111
+ end
138
112
 
139
- scope.where_values = Array(values[:where]) + Array(preload_values[:where])
140
- scope.references_values = Array(values[:references]) + Array(preload_values[:references])
141
- scope.bind_values = (reflection_binds + preload_binds)
113
+ def reflection_scope
114
+ @reflection_scope ||= reflection.scope ? reflection.scope_for(klass.unscoped) : klass.unscoped
115
+ end
142
116
 
143
- scope._select! preload_values[:select] || values[:select] || table[Arel.star]
144
- scope.includes! preload_values[:includes] || values[:includes]
145
- scope.joins! preload_values[:joins] || values[:joins]
146
- scope.order! preload_values[:order] || values[:order]
117
+ def build_scope
118
+ scope = klass.scope_for_association
147
119
 
148
- if preload_values[:readonly] || values[:readonly]
149
- scope.readonly!
150
- end
120
+ if reflection.type
121
+ scope.where!(reflection.type => model.polymorphic_name)
122
+ end
151
123
 
152
- if options[:as]
153
- scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
124
+ scope.merge!(reflection_scope) if reflection.scope
125
+ scope.merge!(preload_scope) if preload_scope
126
+ scope
154
127
  end
155
-
156
- scope.unscope_values = Array(values[:unscope])
157
- klass.default_scoped.merge(scope)
158
- end
159
128
  end
160
129
  end
161
130
  end
@@ -1,95 +1,106 @@
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
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)
14
+ @preloaded_records = preloaders.flat_map(&:preloaded_records)
8
15
 
9
- def source_reflection
10
- reflection.source_reflection
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
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
37
+ end
38
+ associate_records_to_owner(owner, result)
39
+ end
11
40
  end
12
41
 
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
- [owner, Array(association.reader)]
42
+ private
43
+ def through_reflection
44
+ reflection.through_reflection
22
45
  end
23
46
 
24
- reset_association owners, through_reflection.name
25
-
26
- middle_records = through_records.flat_map { |(_,rec)| rec }
27
-
28
- preloaders = preloader.preload(middle_records,
29
- source_reflection.name,
30
- reflection_scope)
31
-
32
- @preloaded_records = preloaders.flat_map(&:preloaded_records)
33
-
34
- middle_to_pl = preloaders.each_with_object({}) do |pl,h|
35
- pl.owners.each { |middle|
36
- h[middle] = pl
37
- }
47
+ def source_reflection
48
+ reflection.source_reflection
38
49
  end
39
50
 
40
- record_offset = {}
41
- @preloaded_records.each_with_index do |record,i|
42
- record_offset[record] = i
51
+ def preload_index
52
+ @preload_index ||= @preloaded_records.each_with_object({}).with_index do |(id, result), index|
53
+ result[id] = index
54
+ end
43
55
  end
44
56
 
45
- through_records.each_with_object({}) { |(lhs,center),records_by_owner|
46
- pl_to_middle = center.group_by { |record| middle_to_pl[record] }
47
-
48
- records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
49
- rhs_records = middles.flat_map { |r|
50
- association = r.association source_reflection.name
51
-
52
- association.reader
53
- }.compact
54
-
55
- rhs_records.sort_by { |rhs| record_offset[rhs] }
57
+ def through_scope
58
+ scope = through_reflection.klass.unscoped
59
+ options = reflection.options
60
+
61
+ if options[:source_type]
62
+ scope.where! reflection.foreign_type => options[:source_type]
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)
85
+ end
86
+
87
+ if scope.eager_loading? && order_values = values[:order]
88
+ scope = scope.order(order_values)
89
+ end
56
90
  end
57
- }
58
- end
59
-
60
- private
61
91
 
62
- def reset_association(owners, association_name)
63
- should_reset = (through_scope != through_reflection.klass.unscoped) ||
64
- (reflection.options[:source_type] && through_reflection.collection?)
65
-
66
- # Don't cache the association - we would only be caching a subset
67
- if should_reset
68
- owners.each { |owner|
69
- owner.association(association_name).reset
70
- }
92
+ scope unless scope.empty_scope?
71
93
  end
72
- end
73
-
74
-
75
- def through_scope
76
- scope = through_reflection.klass.unscoped
77
94
 
78
- if options[:source_type]
79
- scope.where! reflection.foreign_type => options[:source_type]
80
- else
81
- unless reflection_scope.where_values.empty?
82
- scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
83
- scope.where_values = reflection_scope.values[:where]
84
- scope.bind_values = reflection_scope.bind_values
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
85
102
  end
86
-
87
- scope.references! reflection_scope.values[:references]
88
- scope = scope.order reflection_scope.values[:order] if scope.eager_loading?
89
103
  end
90
-
91
- scope
92
- end
93
104
  end
94
105
  end
95
106
  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.
@@ -10,13 +12,13 @@ module ActiveRecord
10
12
  # end
11
13
  #
12
14
  # class Book < ActiveRecord::Base
13
- # # columns: title, sales
15
+ # # columns: title, sales, author_id
14
16
  # end
15
17
  #
16
18
  # When you load an author with all associated books Active Record will make
17
19
  # multiple queries like this:
18
20
  #
19
- # Author.includes(:books).where(:name => ['bell hooks', 'Homer').to_a
21
+ # Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a
20
22
  #
21
23
  # => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
22
24
  # => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
@@ -42,16 +44,8 @@ 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
51
  # Eager loads the named associations for the given Active Record record(s).
@@ -88,18 +82,14 @@ module ActiveRecord
88
82
  # [ :books, :author ]
89
83
  # { author: :avatar }
90
84
  # [ :books, { author: :avatar } ]
91
-
92
- NULL_RELATION = Struct.new(:values, :bind_values).new({}, [])
93
-
94
85
  def preload(records, associations, preload_scope = nil)
95
- records = Array.wrap(records).compact.uniq
96
- associations = Array.wrap(associations)
97
- preload_scope = preload_scope || NULL_RELATION
86
+ records = Array.wrap(records).compact
98
87
 
99
88
  if records.empty?
100
89
  []
101
90
  else
102
- associations.flat_map { |association|
91
+ records.uniq!
92
+ Array.wrap(associations).flat_map { |association|
103
93
  preloaders_on association, records, preload_scope
104
94
  }
105
95
  end
@@ -107,97 +97,97 @@ module ActiveRecord
107
97
 
108
98
  private
109
99
 
110
- def preloaders_on(association, records, scope)
111
- case association
112
- when Hash
113
- preloaders_for_hash(association, records, scope)
114
- when Symbol
115
- preloaders_for_one(association, records, scope)
116
- when String
117
- preloaders_for_one(association.to_sym, records, scope)
118
- else
119
- raise ArgumentError, "#{association.inspect} was not recognised for preload"
100
+ # Loads all the given data into +records+ for the +association+.
101
+ def preloaders_on(association, records, scope)
102
+ case association
103
+ when Hash
104
+ preloaders_for_hash(association, records, scope)
105
+ when Symbol
106
+ preloaders_for_one(association, records, scope)
107
+ when String
108
+ preloaders_for_one(association.to_sym, records, scope)
109
+ else
110
+ raise ArgumentError, "#{association.inspect} was not recognized for preload"
111
+ end
120
112
  end
121
- end
122
113
 
123
- def preloaders_for_hash(association, records, scope)
124
- association.flat_map { |parent, child|
125
- loaders = preloaders_for_one parent, records, scope
114
+ def preloaders_for_hash(association, records, scope)
115
+ association.flat_map { |parent, child|
116
+ loaders = preloaders_for_one parent, records, scope
126
117
 
127
- recs = loaders.flat_map(&:preloaded_records).uniq
128
- loaders.concat Array.wrap(child).flat_map { |assoc|
129
- preloaders_on assoc, recs, scope
118
+ recs = loaders.flat_map(&:preloaded_records).uniq
119
+ loaders.concat Array.wrap(child).flat_map { |assoc|
120
+ preloaders_on assoc, recs, scope
121
+ }
122
+ loaders
130
123
  }
131
- loaders
132
- }
133
- end
124
+ end
134
125
 
135
- # Not all records have the same class, so group then preload group on the reflection
136
- # itself so that if various subclass share the same association then we do not split
137
- # them unnecessarily
138
- #
139
- # Additionally, polymorphic belongs_to associations can have multiple associated
140
- # classes, depending on the polymorphic_type field. So we group by the classes as
141
- # well.
142
- def preloaders_for_one(association, records, scope)
143
- grouped_records(association, records).flat_map do |reflection, klasses|
144
- klasses.map do |rhs_klass, rs|
145
- loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope)
146
- loader.run self
147
- loader
126
+ # Loads all the given data into +records+ for a singular +association+.
127
+ #
128
+ # Functions by instantiating a preloader class such as Preloader::HasManyThrough and
129
+ # call the +run+ method for each passed in class in the +records+ argument.
130
+ #
131
+ # Not all records have the same class, so group then preload group on the reflection
132
+ # itself so that if various subclass share the same association then we do not split
133
+ # them unnecessarily
134
+ #
135
+ # Additionally, polymorphic belongs_to associations can have multiple associated
136
+ # classes, depending on the polymorphic_type field. So we group by the classes as
137
+ # well.
138
+ def preloaders_for_one(association, records, scope)
139
+ grouped_records(association, records).flat_map do |reflection, klasses|
140
+ klasses.map do |rhs_klass, rs|
141
+ loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
142
+ loader.run self
143
+ loader
144
+ end
148
145
  end
149
146
  end
150
- end
151
147
 
152
- def grouped_records(association, records)
153
- h = {}
154
- records.each do |record|
155
- next unless record
156
- assoc = record.association(association)
157
- klasses = h[assoc.reflection] ||= {}
158
- (klasses[assoc.klass] ||= []) << record
148
+ def grouped_records(association, records)
149
+ h = {}
150
+ records.each do |record|
151
+ next unless record
152
+ assoc = record.association(association)
153
+ next unless assoc.klass
154
+ klasses = h[assoc.reflection] ||= {}
155
+ (klasses[assoc.klass] ||= []) << record
156
+ end
157
+ h
159
158
  end
160
- h
161
- end
162
159
 
163
- class AlreadyLoaded
164
- attr_reader :owners, :reflection
160
+ class AlreadyLoaded # :nodoc:
161
+ def initialize(klass, owners, reflection, preload_scope)
162
+ @owners = owners
163
+ @reflection = reflection
164
+ end
165
165
 
166
- def initialize(klass, owners, reflection, preload_scope)
167
- @owners = owners
168
- @reflection = reflection
169
- end
166
+ def run(preloader); end
170
167
 
171
- def run(preloader); end
168
+ def preloaded_records
169
+ owners.flat_map { |owner| owner.association(reflection.name).target }
170
+ end
172
171
 
173
- def preloaded_records
174
- owners.flat_map { |owner| owner.association(reflection.name).target }
172
+ protected
173
+ attr_reader :owners, :reflection
175
174
  end
176
- end
177
175
 
178
- class NullPreloader
179
- def self.new(klass, owners, reflection, preload_scope); self; end
180
- def self.run(preloader); end
181
- def self.preloaded_records; []; end
182
- end
183
-
184
- def preloader_for(reflection, owners, rhs_klass)
185
- return NullPreloader unless rhs_klass
176
+ # Returns a class containing the logic needed to load preload the data
177
+ # and attach it to a relation. The class returned implements a `run` method
178
+ # that accepts a preloader.
179
+ def preloader_for(reflection, owners)
180
+ if owners.all? { |o| o.association(reflection.name).loaded? }
181
+ return AlreadyLoaded
182
+ end
183
+ reflection.check_preloadable!
186
184
 
187
- if owners.first.association(reflection.name).loaded?
188
- return AlreadyLoaded
189
- end
190
- reflection.check_preloadable!
191
-
192
- case reflection.macro
193
- when :has_many
194
- reflection.options[:through] ? HasManyThrough : HasMany
195
- when :has_one
196
- reflection.options[:through] ? HasOneThrough : HasOne
197
- when :belongs_to
198
- BelongsTo
185
+ if reflection.options[:through]
186
+ ThroughAssociation
187
+ else
188
+ Association
189
+ end
199
190
  end
200
- end
201
191
  end
202
192
  end
203
193
  end