activerecord 3.2.22.5 → 5.2.8

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 (275) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -621
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +41 -46
  5. data/examples/performance.rb +55 -42
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +264 -236
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +127 -75
  11. data/lib/active_record/associations/association_scope.rb +126 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +78 -27
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
  14. data/lib/active_record/associations/builder/association.rb +117 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +135 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -54
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
  18. data/lib/active_record/associations/builder/has_many.rb +10 -64
  19. data/lib/active_record/associations/builder/has_one.rb +19 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +28 -18
  21. data/lib/active_record/associations/collection_association.rb +226 -293
  22. data/lib/active_record/associations/collection_proxy.rb +1067 -69
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +83 -47
  25. data/lib/active_record/associations/has_many_through_association.rb +98 -65
  26. data/lib/active_record/associations/has_one_association.rb +57 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +212 -164
  32. data/lib/active_record/associations/preloader/association.rb +95 -89
  33. data/lib/active_record/associations/preloader/through_association.rb +84 -44
  34. data/lib/active_record/associations/preloader.rb +123 -111
  35. data/lib/active_record/associations/singular_association.rb +33 -24
  36. data/lib/active_record/associations/through_association.rb +60 -26
  37. data/lib/active_record/associations.rb +1759 -1506
  38. data/lib/active_record/attribute_assignment.rb +60 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +113 -74
  42. data/lib/active_record/attribute_methods/primary_key.rb +106 -77
  43. data/lib/active_record/attribute_methods/query.rb +8 -5
  44. data/lib/active_record/attribute_methods/read.rb +63 -114
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -90
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
  47. data/lib/active_record/attribute_methods/write.rb +43 -45
  48. data/lib/active_record/attribute_methods.rb +366 -149
  49. data/lib/active_record/attributes.rb +266 -0
  50. data/lib/active_record/autosave_association.rb +312 -225
  51. data/lib/active_record/base.rb +114 -505
  52. data/lib/active_record/callbacks.rb +145 -67
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
  69. data/lib/active_record/connection_adapters/column.rb +50 -255
  70. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  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 +59 -210
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  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 +92 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -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 +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  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 +41 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
  117. data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
  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 +545 -27
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +145 -0
  128. data/lib/active_record/core.rb +559 -0
  129. data/lib/active_record/counter_cache.rb +200 -105
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +107 -69
  132. data/lib/active_record/enum.rb +244 -0
  133. data/lib/active_record/errors.rb +245 -60
  134. data/lib/active_record/explain.rb +35 -71
  135. data/lib/active_record/explain_registry.rb +32 -0
  136. data/lib/active_record/explain_subscriber.rb +18 -9
  137. data/lib/active_record/fixture_set/file.rb +82 -0
  138. data/lib/active_record/fixtures.rb +418 -275
  139. data/lib/active_record/gem_version.rb +17 -0
  140. data/lib/active_record/inheritance.rb +209 -100
  141. data/lib/active_record/integration.rb +116 -21
  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 +9 -1
  145. data/lib/active_record/locking/optimistic.rb +107 -94
  146. data/lib/active_record/locking/pessimistic.rb +20 -8
  147. data/lib/active_record/log_subscriber.rb +99 -34
  148. data/lib/active_record/migration/command_recorder.rb +199 -64
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +17 -0
  151. data/lib/active_record/migration.rb +893 -296
  152. data/lib/active_record/model_schema.rb +328 -175
  153. data/lib/active_record/nested_attributes.rb +338 -242
  154. data/lib/active_record/no_touching.rb +58 -0
  155. data/lib/active_record/null_relation.rb +68 -0
  156. data/lib/active_record/persistence.rb +557 -170
  157. data/lib/active_record/query_cache.rb +14 -43
  158. data/lib/active_record/querying.rb +36 -24
  159. data/lib/active_record/railtie.rb +147 -52
  160. data/lib/active_record/railties/console_sandbox.rb +5 -4
  161. data/lib/active_record/railties/controller_runtime.rb +13 -6
  162. data/lib/active_record/railties/databases.rake +206 -488
  163. data/lib/active_record/readonly_attributes.rb +4 -6
  164. data/lib/active_record/reflection.rb +734 -228
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +249 -52
  167. data/lib/active_record/relation/calculations.rb +330 -284
  168. data/lib/active_record/relation/delegation.rb +135 -37
  169. data/lib/active_record/relation/finder_methods.rb +450 -287
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +193 -0
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  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 +19 -0
  179. data/lib/active_record/relation/predicate_builder.rb +132 -43
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +1037 -221
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +48 -151
  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 +451 -359
  187. data/lib/active_record/result.rb +129 -20
  188. data/lib/active_record/runtime_registry.rb +24 -0
  189. data/lib/active_record/sanitization.rb +164 -136
  190. data/lib/active_record/schema.rb +31 -19
  191. data/lib/active_record/schema_dumper.rb +154 -107
  192. data/lib/active_record/schema_migration.rb +56 -0
  193. data/lib/active_record/scoping/default.rb +108 -98
  194. data/lib/active_record/scoping/named.rb +125 -112
  195. data/lib/active_record/scoping.rb +77 -123
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +10 -6
  198. data/lib/active_record/statement_cache.rb +121 -0
  199. data/lib/active_record/store.rb +175 -16
  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 +337 -0
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  206. data/lib/active_record/timestamp.rb +80 -41
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +240 -119
  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 +9 -0
  212. data/lib/active_record/type/date_time.rb +9 -0
  213. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  214. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  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 +71 -0
  218. data/lib/active_record/type/text.rb +11 -0
  219. data/lib/active_record/type/time.rb +21 -0
  220. data/lib/active_record/type/type_map.rb +62 -0
  221. data/lib/active_record/type/unsigned_integer.rb +17 -0
  222. data/lib/active_record/type.rb +79 -0
  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 +35 -18
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +68 -0
  230. data/lib/active_record/validations/uniqueness.rb +133 -75
  231. data/lib/active_record/validations.rb +53 -43
  232. data/lib/active_record/version.rb +7 -7
  233. data/lib/active_record.rb +89 -57
  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 +61 -8
  237. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  238. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  239. data/lib/rails/generators/active_record/migration.rb +28 -8
  240. data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
  241. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  243. data/lib/rails/generators/active_record.rb +10 -16
  244. metadata +141 -62
  245. data/examples/associations.png +0 -0
  246. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  247. data/lib/active_record/associations/join_helper.rb +0 -55
  248. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  249. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  250. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  251. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  252. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  253. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  254. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  255. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  256. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  257. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  258. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  259. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  260. data/lib/active_record/dynamic_finder_match.rb +0 -68
  261. data/lib/active_record/dynamic_scope_match.rb +0 -23
  262. data/lib/active_record/fixtures/file.rb +0 -65
  263. data/lib/active_record/identity_map.rb +0 -162
  264. data/lib/active_record/observer.rb +0 -121
  265. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  266. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  267. data/lib/active_record/session_store.rb +0 -360
  268. data/lib/active_record/test_case.rb +0 -73
  269. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  270. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  271. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  272. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  273. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  274. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  275. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,66 +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
-
6
- def through_reflection
7
- reflection.through_reflection
8
- 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)
9
15
 
10
- def source_reflection
11
- 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
12
40
  end
13
41
 
14
- def associated_records_by_owner
15
- through_records = through_records_by_owner
42
+ private
43
+ def through_reflection
44
+ reflection.through_reflection
45
+ end
16
46
 
17
- ActiveRecord::Associations::Preloader.new(
18
- through_records.values.flatten,
19
- source_reflection.name, options
20
- ).run
47
+ def source_reflection
48
+ reflection.source_reflection
49
+ end
21
50
 
22
- through_records.each do |owner, records|
23
- records.map! { |r| r.send(source_reflection.name) }.flatten!
24
- records.compact!
51
+ def preload_index
52
+ @preload_index ||= @preloaded_records.each_with_object({}).with_index do |(id, result), index|
53
+ result[id] = index
54
+ end
25
55
  end
26
- end
27
56
 
28
- private
57
+ def through_scope
58
+ scope = through_reflection.klass.unscoped
59
+ options = reflection.options
29
60
 
30
- def through_records_by_owner
31
- ActiveRecord::Associations::Preloader.new(
32
- owners, through_reflection.name,
33
- through_options
34
- ).run
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
35
66
 
36
- Hash[owners.map do |owner|
37
- through_records = Array.wrap(owner.send(through_reflection.name))
67
+ if includes = values[:includes]
68
+ scope.includes!(source_reflection.name => includes)
69
+ else
70
+ scope.includes!(source_reflection.name)
71
+ end
38
72
 
39
- # Dont cache the association - we would only be caching a subset
40
- if (preload_options != through_options) ||
41
- (reflection.options[:source_type] && through_reflection.collection?)
42
- owner.association(through_reflection.name).reset
43
- end
73
+ if values[:references] && !values[:references].empty?
74
+ scope.references!(values[:references])
75
+ else
76
+ scope.references!(source_reflection.table_name)
77
+ end
44
78
 
45
- [owner, through_records]
46
- end]
47
- end
79
+ if joins = values[:joins]
80
+ scope.joins!(source_reflection.name => joins)
81
+ end
48
82
 
49
- def through_options
50
- through_options = {}
83
+ if left_outer_joins = values[:left_outer_joins]
84
+ scope.left_outer_joins!(source_reflection.name => left_outer_joins)
85
+ end
51
86
 
52
- if options[:source_type]
53
- through_options[:conditions] = { reflection.foreign_type => options[:source_type] }
54
- else
55
- if options[:conditions]
56
- through_options[:include] = options[:include] || options[:source]
57
- through_options[:conditions] = options[:conditions]
87
+ if scope.eager_loading? && order_values = values[:order]
88
+ scope = scope.order(order_values)
89
+ end
58
90
  end
59
- through_options[:order] = options[:order] if options.has_key?(:order)
91
+
92
+ scope unless scope.empty_scope?
60
93
  end
61
94
 
62
- through_options
63
- end
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
103
+ end
64
104
  end
65
105
  end
66
106
  end
@@ -1,53 +1,53 @@
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.
4
6
  #
5
- # Note that 'eager loading' and 'preloading' are actually the same thing.
6
- # However, there are two different eager loading strategies.
7
+ # Suppose that you have the following two Active Record models:
8
+ #
9
+ # class Author < ActiveRecord::Base
10
+ # # columns: name, age
11
+ # has_many :books
12
+ # end
13
+ #
14
+ # class Book < ActiveRecord::Base
15
+ # # columns: title, sales, author_id
16
+ # end
17
+ #
18
+ # When you load an author with all associated books Active Record will make
19
+ # multiple queries like this:
7
20
  #
8
- # The first one is by using table joins. This was only strategy available
9
- # prior to Rails 2.1. Suppose that you have an Author model with columns
10
- # 'name' and 'age', and a Book model with columns 'name' and 'sales'. Using
11
- # this strategy, Active Record would try to retrieve all data for an author
12
- # and all of its books via a single query:
21
+ # Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a
13
22
  #
14
- # SELECT * FROM authors
15
- # LEFT OUTER JOIN books ON authors.id = books.id
16
- # WHERE authors.name = 'Ken Akamatsu'
23
+ # => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
24
+ # => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
17
25
  #
18
- # However, this could result in many rows that contain redundant data. After
19
- # having received the first row, we already have enough data to instantiate
20
- # the Author object. In all subsequent rows, only the data for the joined
21
- # 'books' table is useful; the joined 'authors' data is just redundant, and
22
- # processing this redundant data takes memory and CPU time. The problem
23
- # quickly becomes worse and worse as the level of eager loading increases
24
- # (i.e. if Active Record is to eager load the associations' associations as
25
- # well).
26
+ # Active Record saves the ids of the records from the first query to use in
27
+ # the second. Depending on the number of associations involved there can be
28
+ # arbitrarily many SQL queries made.
29
+ #
30
+ # However, if there is a WHERE clause that spans across tables Active
31
+ # Record will fall back to a slightly more resource-intensive single query:
32
+ #
33
+ # Author.includes(:books).where(books: {title: 'Illiad'}).to_a
34
+ # => SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
35
+ # `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2
36
+ # FROM `authors`
37
+ # LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id`
38
+ # WHERE `books`.`title` = 'Illiad'
39
+ #
40
+ # This could result in many rows that contain redundant data and it performs poorly at scale
41
+ # and is therefore only used when necessary.
26
42
  #
27
- # The second strategy is to use multiple database queries, one for each
28
- # level of association. Since Rails 2.1, this is the default strategy. In
29
- # situations where a table join is necessary (e.g. when the +:conditions+
30
- # option references an association's column), it will fallback to the table
31
- # join strategy.
32
43
  class Preloader #:nodoc:
33
44
  extend ActiveSupport::Autoload
34
45
 
35
46
  eager_autoload do
36
- autoload :Association, 'active_record/associations/preloader/association'
37
- autoload :SingularAssociation, 'active_record/associations/preloader/singular_association'
38
- autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association'
39
- autoload :ThroughAssociation, 'active_record/associations/preloader/through_association'
40
-
41
- autoload :HasMany, 'active_record/associations/preloader/has_many'
42
- autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through'
43
- autoload :HasOne, 'active_record/associations/preloader/has_one'
44
- autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through'
45
- autoload :HasAndBelongsToMany, 'active_record/associations/preloader/has_and_belongs_to_many'
46
- 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"
47
49
  end
48
50
 
49
- attr_reader :records, :associations, :options, :model
50
-
51
51
  # Eager loads the named associations for the given Active Record record(s).
52
52
  #
53
53
  # In this description, 'association name' shall refer to the name passed
@@ -72,7 +72,7 @@ module ActiveRecord
72
72
  # books.
73
73
  # - a Hash which specifies multiple association names, as well as
74
74
  # association names for the to-be-preloaded association objects. For
75
- # example, specifying <tt>{ :author => :avatar }</tt> will preload a
75
+ # example, specifying <tt>{ author: :avatar }</tt> will preload a
76
76
  # book's author, as well as that author's avatar.
77
77
  #
78
78
  # +:associations+ has the same format as the +:include+ option for
@@ -80,102 +80,114 @@ module ActiveRecord
80
80
  #
81
81
  # :books
82
82
  # [ :books, :author ]
83
- # { :author => :avatar }
84
- # [ :books, { :author => :avatar } ]
85
- #
86
- # +options+ contains options that will be passed to ActiveRecord::Base#find
87
- # (which is called under the hood for preloading records). But it is passed
88
- # only one level deep in the +associations+ argument, i.e. it's not passed
89
- # to the child associations when +associations+ is a Hash.
90
- def initialize(records, associations, options = {})
91
- @records = Array.wrap(records).compact.uniq
92
- @associations = Array.wrap(associations)
93
- @options = options
94
- end
83
+ # { author: :avatar }
84
+ # [ :books, { author: :avatar } ]
85
+ def preload(records, associations, preload_scope = nil)
86
+ records = Array.wrap(records).compact
95
87
 
96
- def run
97
- unless records.empty?
98
- associations.each { |association| preload(association) }
88
+ if records.empty?
89
+ []
90
+ else
91
+ records.uniq!
92
+ Array.wrap(associations).flat_map { |association|
93
+ preloaders_on association, records, preload_scope
94
+ }
99
95
  end
100
96
  end
101
97
 
102
98
  private
103
99
 
104
- def preload(association)
105
- case association
106
- when Hash
107
- preload_hash(association)
108
- when String, Symbol
109
- preload_one(association.to_sym)
110
- else
111
- 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
112
112
  end
113
- end
114
113
 
115
- def preload_hash(association)
116
- association.each do |parent, child|
117
- Preloader.new(records, parent, options).run
118
- Preloader.new(records.map { |record| record.send(parent) }.flatten, child).run
114
+ def preloaders_for_hash(association, records, scope)
115
+ association.flat_map { |parent, child|
116
+ loaders = preloaders_for_one parent, records, scope
117
+
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
123
+ }
119
124
  end
120
- end
121
125
 
122
- # Not all records have the same class, so group then preload group on the reflection
123
- # itself so that if various subclass share the same association then we do not split
124
- # them unnecessarily
125
- #
126
- # Additionally, polymorphic belongs_to associations can have multiple associated
127
- # classes, depending on the polymorphic_type field. So we group by the classes as
128
- # well.
129
- def preload_one(association)
130
- grouped_records(association).each do |reflection, klasses|
131
- klasses.each do |klass, records|
132
- preloader_for(reflection).new(klass, records, reflection, options).run
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
133
145
  end
134
146
  end
135
- end
136
147
 
137
- def grouped_records(association)
138
- Hash[
139
- records_by_reflection(association).map do |reflection, records|
140
- [reflection, records.group_by { |record| association_klass(reflection, 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
158
+ end
159
+
160
+ class AlreadyLoaded # :nodoc:
161
+ def initialize(klass, owners, reflection, preload_scope)
162
+ @owners = owners
163
+ @reflection = reflection
141
164
  end
142
- ]
143
- end
144
165
 
145
- def records_by_reflection(association)
146
- records.group_by do |record|
147
- reflection = record.class.reflections[association]
166
+ def run(preloader); end
148
167
 
149
- unless reflection
150
- raise ActiveRecord::ConfigurationError, "Association named '#{association}' was not found; " \
151
- "perhaps you misspelled it?"
168
+ def preloaded_records
169
+ owners.flat_map { |owner| owner.association(reflection.name).target }
152
170
  end
153
171
 
154
- reflection
172
+ protected
173
+ attr_reader :owners, :reflection
155
174
  end
156
- end
157
175
 
158
- def association_klass(reflection, record)
159
- if reflection.macro == :belongs_to && reflection.options[:polymorphic]
160
- klass = record.send(reflection.foreign_type)
161
- klass && klass.constantize
162
- else
163
- reflection.klass
164
- end
165
- end
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!
166
184
 
167
- def preloader_for(reflection)
168
- case reflection.macro
169
- when :has_many
170
- reflection.options[:through] ? HasManyThrough : HasMany
171
- when :has_one
172
- reflection.options[:through] ? HasOneThrough : HasOne
173
- when :has_and_belongs_to_many
174
- HasAndBelongsToMany
175
- when :belongs_to
176
- BelongsTo
185
+ if reflection.options[:through]
186
+ ThroughAssociation
187
+ else
188
+ Association
189
+ end
177
190
  end
178
- end
179
191
  end
180
192
  end
181
193
  end
@@ -1,48 +1,58 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  class SingularAssociation < Association #:nodoc:
4
6
  # Implements the reader method, e.g. foo.bar for Foo.has_one :bar
5
- def reader(force_reload = false)
6
- if force_reload
7
- klass.uncached { reload }
8
- elsif !loaded? || stale_target?
7
+ def reader
8
+ if !loaded? || stale_target?
9
9
  reload
10
10
  end
11
11
 
12
12
  target
13
13
  end
14
14
 
15
- # Implements the writer method, e.g. foo.items= for Foo.has_many :items
15
+ # Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
16
16
  def writer(record)
17
17
  replace(record)
18
18
  end
19
19
 
20
- def create(attributes = {}, options = {}, &block)
21
- create_record(attributes, options, &block)
22
- end
23
-
24
- def create!(attributes = {}, options = {}, &block)
25
- create_record(attributes, options, true, &block)
26
- end
27
-
28
- def build(attributes = {}, options = {})
29
- record = build_record(attributes, options)
30
- yield(record) if block_given?
20
+ def build(attributes = {}, &block)
21
+ record = build_record(attributes, &block)
31
22
  set_new_record(record)
32
23
  record
33
24
  end
34
25
 
35
- private
26
+ # Implements the reload reader method, e.g. foo.reload_bar for
27
+ # Foo.has_one :bar
28
+ def force_reload_reader
29
+ klass.uncached { reload }
30
+ target
31
+ end
36
32
 
37
- def create_scope
38
- scoped.scope_for_create.stringify_keys.except(klass.primary_key)
33
+ private
34
+ def scope_for_create
35
+ super.except!(klass.primary_key)
39
36
  end
40
37
 
41
38
  def find_target
42
- scoped.first.tap { |record| set_inverse_instance(record) }
39
+ scope = self.scope
40
+ return scope.take if skip_statement_cache?(scope)
41
+
42
+ conn = klass.connection
43
+ sc = reflection.association_scope_cache(conn, owner) do |params|
44
+ as = AssociationScope.create { params.bind }
45
+ target_scope.merge!(as.scope(self)).limit(1)
46
+ end
47
+
48
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
49
+ sc.execute(binds, conn) do |record|
50
+ set_inverse_instance record
51
+ end.first
52
+ rescue ::RangeError
53
+ nil
43
54
  end
44
55
 
45
- # Implemented by subclasses
46
56
  def replace(record)
47
57
  raise NotImplementedError, "Subclasses must implement a replace(record) method"
48
58
  end
@@ -51,9 +61,8 @@ module ActiveRecord
51
61
  replace(record)
52
62
  end
53
63
 
54
- def create_record(attributes, options, raise_error = false)
55
- record = build_record(attributes, options)
56
- yield(record) if block_given?
64
+ def _create_record(attributes, raise_error = false, &block)
65
+ record = build_record(attributes, &block)
57
66
  saved = record.save
58
67
  set_new_record(record)
59
68
  raise RecordInvalid.new(record) if !saved && raise_error