activerecord 5.2.3

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 (244) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +937 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +217 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record.rb +188 -0
  8. data/lib/active_record/aggregations.rb +283 -0
  9. data/lib/active_record/association_relation.rb +40 -0
  10. data/lib/active_record/associations.rb +1860 -0
  11. data/lib/active_record/associations/alias_tracker.rb +81 -0
  12. data/lib/active_record/associations/association.rb +299 -0
  13. data/lib/active_record/associations/association_scope.rb +168 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +130 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +140 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +163 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +82 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
  20. data/lib/active_record/associations/builder/has_many.rb +17 -0
  21. data/lib/active_record/associations/builder/has_one.rb +30 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +42 -0
  23. data/lib/active_record/associations/collection_association.rb +513 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1131 -0
  25. data/lib/active_record/associations/foreign_association.rb +13 -0
  26. data/lib/active_record/associations/has_many_association.rb +144 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +227 -0
  28. data/lib/active_record/associations/has_one_association.rb +120 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency.rb +262 -0
  31. data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
  32. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  33. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  34. data/lib/active_record/associations/preloader.rb +193 -0
  35. data/lib/active_record/associations/preloader/association.rb +131 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +107 -0
  37. data/lib/active_record/associations/singular_association.rb +73 -0
  38. data/lib/active_record/associations/through_association.rb +121 -0
  39. data/lib/active_record/attribute_assignment.rb +88 -0
  40. data/lib/active_record/attribute_decorators.rb +90 -0
  41. data/lib/active_record/attribute_methods.rb +492 -0
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +150 -0
  44. data/lib/active_record/attribute_methods/primary_key.rb +143 -0
  45. data/lib/active_record/attribute_methods/query.rb +42 -0
  46. data/lib/active_record/attribute_methods/read.rb +85 -0
  47. data/lib/active_record/attribute_methods/serialization.rb +90 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
  49. data/lib/active_record/attribute_methods/write.rb +68 -0
  50. data/lib/active_record/attributes.rb +266 -0
  51. data/lib/active_record/autosave_association.rb +498 -0
  52. data/lib/active_record/base.rb +329 -0
  53. data/lib/active_record/callbacks.rb +353 -0
  54. data/lib/active_record/coders/json.rb +15 -0
  55. data/lib/active_record/coders/yaml_column.rb +50 -0
  56. data/lib/active_record/collection_cache_key.rb +53 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
  70. data/lib/active_record/connection_adapters/column.rb +91 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  73. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
  118. data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
  127. data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
  128. data/lib/active_record/connection_handling.rb +145 -0
  129. data/lib/active_record/core.rb +559 -0
  130. data/lib/active_record/counter_cache.rb +218 -0
  131. data/lib/active_record/define_callbacks.rb +22 -0
  132. data/lib/active_record/dynamic_matchers.rb +122 -0
  133. data/lib/active_record/enum.rb +244 -0
  134. data/lib/active_record/errors.rb +380 -0
  135. data/lib/active_record/explain.rb +50 -0
  136. data/lib/active_record/explain_registry.rb +32 -0
  137. data/lib/active_record/explain_subscriber.rb +34 -0
  138. data/lib/active_record/fixture_set/file.rb +82 -0
  139. data/lib/active_record/fixtures.rb +1065 -0
  140. data/lib/active_record/gem_version.rb +17 -0
  141. data/lib/active_record/inheritance.rb +283 -0
  142. data/lib/active_record/integration.rb +155 -0
  143. data/lib/active_record/internal_metadata.rb +45 -0
  144. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  145. data/lib/active_record/locale/en.yml +48 -0
  146. data/lib/active_record/locking/optimistic.rb +198 -0
  147. data/lib/active_record/locking/pessimistic.rb +89 -0
  148. data/lib/active_record/log_subscriber.rb +137 -0
  149. data/lib/active_record/migration.rb +1378 -0
  150. data/lib/active_record/migration/command_recorder.rb +240 -0
  151. data/lib/active_record/migration/compatibility.rb +217 -0
  152. data/lib/active_record/migration/join_table.rb +17 -0
  153. data/lib/active_record/model_schema.rb +521 -0
  154. data/lib/active_record/nested_attributes.rb +600 -0
  155. data/lib/active_record/no_touching.rb +58 -0
  156. data/lib/active_record/null_relation.rb +68 -0
  157. data/lib/active_record/persistence.rb +763 -0
  158. data/lib/active_record/query_cache.rb +45 -0
  159. data/lib/active_record/querying.rb +70 -0
  160. data/lib/active_record/railtie.rb +226 -0
  161. data/lib/active_record/railties/console_sandbox.rb +7 -0
  162. data/lib/active_record/railties/controller_runtime.rb +56 -0
  163. data/lib/active_record/railties/databases.rake +377 -0
  164. data/lib/active_record/readonly_attributes.rb +24 -0
  165. data/lib/active_record/reflection.rb +1044 -0
  166. data/lib/active_record/relation.rb +629 -0
  167. data/lib/active_record/relation/batches.rb +287 -0
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  169. data/lib/active_record/relation/calculations.rb +417 -0
  170. data/lib/active_record/relation/delegation.rb +147 -0
  171. data/lib/active_record/relation/finder_methods.rb +565 -0
  172. data/lib/active_record/relation/from_clause.rb +26 -0
  173. data/lib/active_record/relation/merger.rb +193 -0
  174. data/lib/active_record/relation/predicate_builder.rb +152 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  180. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  181. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  182. data/lib/active_record/relation/query_attribute.rb +45 -0
  183. data/lib/active_record/relation/query_methods.rb +1231 -0
  184. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  185. data/lib/active_record/relation/spawn_methods.rb +77 -0
  186. data/lib/active_record/relation/where_clause.rb +186 -0
  187. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  188. data/lib/active_record/result.rb +149 -0
  189. data/lib/active_record/runtime_registry.rb +24 -0
  190. data/lib/active_record/sanitization.rb +222 -0
  191. data/lib/active_record/schema.rb +70 -0
  192. data/lib/active_record/schema_dumper.rb +255 -0
  193. data/lib/active_record/schema_migration.rb +56 -0
  194. data/lib/active_record/scoping.rb +106 -0
  195. data/lib/active_record/scoping/default.rb +152 -0
  196. data/lib/active_record/scoping/named.rb +213 -0
  197. data/lib/active_record/secure_token.rb +40 -0
  198. data/lib/active_record/serialization.rb +22 -0
  199. data/lib/active_record/statement_cache.rb +121 -0
  200. data/lib/active_record/store.rb +211 -0
  201. data/lib/active_record/suppressor.rb +61 -0
  202. data/lib/active_record/table_metadata.rb +82 -0
  203. data/lib/active_record/tasks/database_tasks.rb +337 -0
  204. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  205. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  206. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  207. data/lib/active_record/timestamp.rb +153 -0
  208. data/lib/active_record/touch_later.rb +64 -0
  209. data/lib/active_record/transactions.rb +502 -0
  210. data/lib/active_record/translation.rb +24 -0
  211. data/lib/active_record/type.rb +79 -0
  212. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  213. data/lib/active_record/type/date.rb +9 -0
  214. data/lib/active_record/type/date_time.rb +9 -0
  215. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  217. data/lib/active_record/type/internal/timezone.rb +17 -0
  218. data/lib/active_record/type/json.rb +30 -0
  219. data/lib/active_record/type/serialized.rb +71 -0
  220. data/lib/active_record/type/text.rb +11 -0
  221. data/lib/active_record/type/time.rb +21 -0
  222. data/lib/active_record/type/type_map.rb +62 -0
  223. data/lib/active_record/type/unsigned_integer.rb +17 -0
  224. data/lib/active_record/type_caster.rb +9 -0
  225. data/lib/active_record/type_caster/connection.rb +33 -0
  226. data/lib/active_record/type_caster/map.rb +23 -0
  227. data/lib/active_record/validations.rb +93 -0
  228. data/lib/active_record/validations/absence.rb +25 -0
  229. data/lib/active_record/validations/associated.rb +60 -0
  230. data/lib/active_record/validations/length.rb +26 -0
  231. data/lib/active_record/validations/presence.rb +68 -0
  232. data/lib/active_record/validations/uniqueness.rb +238 -0
  233. data/lib/active_record/version.rb +10 -0
  234. data/lib/rails/generators/active_record.rb +19 -0
  235. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  236. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  237. data/lib/rails/generators/active_record/migration.rb +35 -0
  238. data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
  239. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  240. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  241. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  242. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  243. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  244. metadata +333 -0
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ # Implements the details of eager loading of Active Record associations.
6
+ #
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:
20
+ #
21
+ # Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a
22
+ #
23
+ # => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
24
+ # => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
25
+ #
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.
42
+ #
43
+ class Preloader #:nodoc:
44
+ extend ActiveSupport::Autoload
45
+
46
+ eager_autoload do
47
+ autoload :Association, "active_record/associations/preloader/association"
48
+ autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
49
+ end
50
+
51
+ # Eager loads the named associations for the given Active Record record(s).
52
+ #
53
+ # In this description, 'association name' shall refer to the name passed
54
+ # to an association creation method. For example, a model that specifies
55
+ # <tt>belongs_to :author</tt>, <tt>has_many :buyers</tt> has association
56
+ # names +:author+ and +:buyers+.
57
+ #
58
+ # == Parameters
59
+ # +records+ is an array of ActiveRecord::Base. This array needs not be flat,
60
+ # i.e. +records+ itself may also contain arrays of records. In any case,
61
+ # +preload_associations+ will preload the all associations records by
62
+ # flattening +records+.
63
+ #
64
+ # +associations+ specifies one or more associations that you want to
65
+ # preload. It may be:
66
+ # - a Symbol or a String which specifies a single association name. For
67
+ # example, specifying +:books+ allows this method to preload all books
68
+ # for an Author.
69
+ # - an Array which specifies multiple association names. This array
70
+ # is processed recursively. For example, specifying <tt>[:avatar, :books]</tt>
71
+ # allows this method to preload an author's avatar as well as all of his
72
+ # books.
73
+ # - a Hash which specifies multiple association names, as well as
74
+ # association names for the to-be-preloaded association objects. For
75
+ # example, specifying <tt>{ author: :avatar }</tt> will preload a
76
+ # book's author, as well as that author's avatar.
77
+ #
78
+ # +:associations+ has the same format as the +:include+ option for
79
+ # <tt>ActiveRecord::Base.find</tt>. So +associations+ could look like this:
80
+ #
81
+ # :books
82
+ # [ :books, :author ]
83
+ # { author: :avatar }
84
+ # [ :books, { author: :avatar } ]
85
+ def preload(records, associations, preload_scope = nil)
86
+ records = Array.wrap(records).compact
87
+
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
+ }
95
+ end
96
+ end
97
+
98
+ private
99
+
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
+ end
113
+
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
+ }
124
+ end
125
+
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
145
+ end
146
+ end
147
+
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
164
+ end
165
+
166
+ def run(preloader); end
167
+
168
+ def preloaded_records
169
+ owners.flat_map { |owner| owner.association(reflection.name).target }
170
+ end
171
+
172
+ protected
173
+ attr_reader :owners, :reflection
174
+ end
175
+
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.first.association(reflection.name).loaded?
181
+ return AlreadyLoaded
182
+ end
183
+ reflection.check_preloadable!
184
+
185
+ if reflection.options[:through]
186
+ ThroughAssociation
187
+ else
188
+ Association
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class Preloader
6
+ class Association #:nodoc:
7
+ attr_reader :preloaded_records
8
+
9
+ def initialize(klass, owners, reflection, preload_scope)
10
+ @klass = klass
11
+ @owners = owners
12
+ @reflection = reflection
13
+ @preload_scope = preload_scope
14
+ @model = owners.first && owners.first.class
15
+ @preloaded_records = []
16
+ end
17
+
18
+ def run(preloader)
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
24
+
25
+ owners.each do |owner|
26
+ associate_records_to_owner(owner, records[convert_key(owner[owner_key_name])] || [])
27
+ end
28
+ end
29
+
30
+ protected
31
+ attr_reader :owners, :reflection, :preload_scope, :model, :klass
32
+
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
38
+
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
43
+
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
52
+ end
53
+
54
+ def owner_keys
55
+ @owner_keys ||= owners_by_key.keys
56
+ end
57
+
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
63
+ end
64
+ end
65
+ @owners_by_key
66
+ end
67
+
68
+ def key_conversion_required?
69
+ unless defined?(@key_conversion_required)
70
+ @key_conversion_required = (association_key_type != owner_key_type)
71
+ end
72
+
73
+ @key_conversion_required
74
+ end
75
+
76
+ def convert_key(key)
77
+ if key_conversion_required?
78
+ key.to_s
79
+ else
80
+ key
81
+ end
82
+ end
83
+
84
+ def association_key_type
85
+ @klass.type_for_attribute(association_key_name).type
86
+ end
87
+
88
+ def owner_key_type
89
+ @model.type_for_attribute(owner_key_name).type
90
+ end
91
+
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
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
+
113
+ def reflection_scope
114
+ @reflection_scope ||= reflection.scope ? reflection.scope_for(klass.unscoped) : klass.unscoped
115
+ end
116
+
117
+ def build_scope
118
+ scope = klass.scope_for_association
119
+
120
+ if reflection.type
121
+ scope.where!(reflection.type => model.polymorphic_name)
122
+ end
123
+
124
+ scope.merge!(reflection_scope) if reflection.scope
125
+ scope.merge!(preload_scope) if preload_scope
126
+ scope
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class Preloader
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)
15
+
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
40
+ end
41
+
42
+ private
43
+ def through_reflection
44
+ reflection.through_reflection
45
+ end
46
+
47
+ def source_reflection
48
+ reflection.source_reflection
49
+ end
50
+
51
+ def preload_index
52
+ @preload_index ||= @preloaded_records.each_with_object({}).with_index do |(id, result), index|
53
+ result[id] = index
54
+ end
55
+ end
56
+
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
90
+ end
91
+
92
+ scope unless scope.empty_scope?
93
+ end
94
+
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
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class SingularAssociation < Association #:nodoc:
6
+ # Implements the reader method, e.g. foo.bar for Foo.has_one :bar
7
+ def reader
8
+ if !loaded? || stale_target?
9
+ reload
10
+ end
11
+
12
+ target
13
+ end
14
+
15
+ # Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
16
+ def writer(record)
17
+ replace(record)
18
+ end
19
+
20
+ def build(attributes = {}, &block)
21
+ record = build_record(attributes, &block)
22
+ set_new_record(record)
23
+ record
24
+ end
25
+
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
32
+
33
+ private
34
+ def scope_for_create
35
+ super.except!(klass.primary_key)
36
+ end
37
+
38
+ def find_target
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
54
+ end
55
+
56
+ def replace(record)
57
+ raise NotImplementedError, "Subclasses must implement a replace(record) method"
58
+ end
59
+
60
+ def set_new_record(record)
61
+ replace(record)
62
+ end
63
+
64
+ def _create_record(attributes, raise_error = false, &block)
65
+ record = build_record(attributes, &block)
66
+ saved = record.save
67
+ set_new_record(record)
68
+ raise RecordInvalid.new(record) if !saved && raise_error
69
+ record
70
+ end
71
+ end
72
+ end
73
+ end