activerecord 6.1.4.6 → 7.0.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 (240) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1188 -932
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/association_relation.rb +0 -10
  7. data/lib/active_record/associations/association.rb +33 -17
  8. data/lib/active_record/associations/association_scope.rb +1 -3
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -4
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +8 -2
  12. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_many.rb +3 -2
  15. data/lib/active_record/associations/builder/has_one.rb +2 -1
  16. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  17. data/lib/active_record/associations/collection_association.rb +34 -27
  18. data/lib/active_record/associations/collection_proxy.rb +8 -3
  19. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  20. data/lib/active_record/associations/has_many_association.rb +1 -1
  21. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  22. data/lib/active_record/associations/has_one_association.rb +10 -7
  23. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  24. data/lib/active_record/associations/join_dependency.rb +6 -2
  25. data/lib/active_record/associations/preloader/association.rb +187 -55
  26. data/lib/active_record/associations/preloader/batch.rb +48 -0
  27. data/lib/active_record/associations/preloader/branch.rb +147 -0
  28. data/lib/active_record/associations/preloader/through_association.rb +49 -13
  29. data/lib/active_record/associations/preloader.rb +39 -113
  30. data/lib/active_record/associations/singular_association.rb +8 -2
  31. data/lib/active_record/associations/through_association.rb +3 -3
  32. data/lib/active_record/associations.rb +119 -90
  33. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  34. data/lib/active_record/attribute_assignment.rb +1 -1
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  36. data/lib/active_record/attribute_methods/dirty.rb +49 -16
  37. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  38. data/lib/active_record/attribute_methods/query.rb +2 -2
  39. data/lib/active_record/attribute_methods/read.rb +7 -5
  40. data/lib/active_record/attribute_methods/serialization.rb +66 -12
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  42. data/lib/active_record/attribute_methods/write.rb +7 -10
  43. data/lib/active_record/attribute_methods.rb +13 -14
  44. data/lib/active_record/attributes.rb +24 -35
  45. data/lib/active_record/autosave_association.rb +8 -23
  46. data/lib/active_record/base.rb +19 -1
  47. data/lib/active_record/callbacks.rb +2 -2
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +47 -561
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +46 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +42 -72
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +38 -13
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +78 -22
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +149 -74
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +97 -81
  63. data/lib/active_record/connection_adapters/column.rb +4 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +38 -24
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +35 -21
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +5 -1
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  69. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  70. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +21 -12
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  80. data/lib/active_record/connection_adapters/postgresql/quoting.rb +50 -50
  81. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  83. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  84. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  85. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +35 -19
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +207 -107
  87. data/lib/active_record/connection_adapters/schema_cache.rb +29 -4
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +27 -19
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +28 -16
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +16 -14
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +89 -30
  92. data/lib/active_record/connection_adapters.rb +6 -5
  93. data/lib/active_record/connection_handling.rb +47 -53
  94. data/lib/active_record/core.rb +122 -132
  95. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  96. data/lib/active_record/database_configurations/database_config.rb +12 -9
  97. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  98. data/lib/active_record/database_configurations/url_config.rb +2 -2
  99. data/lib/active_record/database_configurations.rb +16 -32
  100. data/lib/active_record/delegated_type.rb +52 -11
  101. data/lib/active_record/destroy_association_async_job.rb +1 -1
  102. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  103. data/lib/active_record/dynamic_matchers.rb +1 -1
  104. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  105. data/lib/active_record/encryption/cipher.rb +53 -0
  106. data/lib/active_record/encryption/config.rb +44 -0
  107. data/lib/active_record/encryption/configurable.rb +61 -0
  108. data/lib/active_record/encryption/context.rb +35 -0
  109. data/lib/active_record/encryption/contexts.rb +72 -0
  110. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  111. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  112. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  113. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  114. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  115. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  116. data/lib/active_record/encryption/encryptor.rb +155 -0
  117. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  118. data/lib/active_record/encryption/errors.rb +15 -0
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  121. data/lib/active_record/encryption/key.rb +28 -0
  122. data/lib/active_record/encryption/key_generator.rb +42 -0
  123. data/lib/active_record/encryption/key_provider.rb +46 -0
  124. data/lib/active_record/encryption/message.rb +33 -0
  125. data/lib/active_record/encryption/message_serializer.rb +90 -0
  126. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  127. data/lib/active_record/encryption/properties.rb +76 -0
  128. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  129. data/lib/active_record/encryption/scheme.rb +99 -0
  130. data/lib/active_record/encryption.rb +55 -0
  131. data/lib/active_record/enum.rb +49 -42
  132. data/lib/active_record/errors.rb +67 -4
  133. data/lib/active_record/explain_registry.rb +11 -6
  134. data/lib/active_record/fixture_set/file.rb +15 -1
  135. data/lib/active_record/fixture_set/table_row.rb +41 -6
  136. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  137. data/lib/active_record/fixtures.rb +17 -20
  138. data/lib/active_record/future_result.rb +139 -0
  139. data/lib/active_record/gem_version.rb +4 -4
  140. data/lib/active_record/inheritance.rb +55 -17
  141. data/lib/active_record/insert_all.rb +80 -14
  142. data/lib/active_record/integration.rb +4 -3
  143. data/lib/active_record/internal_metadata.rb +3 -5
  144. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  145. data/lib/active_record/locking/optimistic.rb +10 -9
  146. data/lib/active_record/locking/pessimistic.rb +9 -3
  147. data/lib/active_record/log_subscriber.rb +14 -3
  148. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  149. data/lib/active_record/middleware/database_selector.rb +8 -3
  150. data/lib/active_record/middleware/shard_selector.rb +60 -0
  151. data/lib/active_record/migration/command_recorder.rb +4 -4
  152. data/lib/active_record/migration/compatibility.rb +107 -3
  153. data/lib/active_record/migration/join_table.rb +1 -1
  154. data/lib/active_record/migration.rb +109 -79
  155. data/lib/active_record/model_schema.rb +45 -58
  156. data/lib/active_record/nested_attributes.rb +13 -12
  157. data/lib/active_record/no_touching.rb +3 -3
  158. data/lib/active_record/null_relation.rb +2 -6
  159. data/lib/active_record/persistence.rb +219 -52
  160. data/lib/active_record/query_cache.rb +2 -2
  161. data/lib/active_record/query_logs.rb +138 -0
  162. data/lib/active_record/querying.rb +15 -5
  163. data/lib/active_record/railtie.rb +127 -17
  164. data/lib/active_record/railties/controller_runtime.rb +1 -1
  165. data/lib/active_record/railties/databases.rake +66 -129
  166. data/lib/active_record/readonly_attributes.rb +11 -0
  167. data/lib/active_record/reflection.rb +67 -50
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  169. data/lib/active_record/relation/batches.rb +3 -3
  170. data/lib/active_record/relation/calculations.rb +43 -38
  171. data/lib/active_record/relation/delegation.rb +7 -7
  172. data/lib/active_record/relation/finder_methods.rb +31 -35
  173. data/lib/active_record/relation/merger.rb +20 -13
  174. data/lib/active_record/relation/predicate_builder.rb +1 -6
  175. data/lib/active_record/relation/query_attribute.rb +5 -11
  176. data/lib/active_record/relation/query_methods.rb +249 -61
  177. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  178. data/lib/active_record/relation/spawn_methods.rb +2 -2
  179. data/lib/active_record/relation/where_clause.rb +10 -19
  180. data/lib/active_record/relation.rb +184 -84
  181. data/lib/active_record/result.rb +17 -7
  182. data/lib/active_record/runtime_registry.rb +9 -13
  183. data/lib/active_record/sanitization.rb +11 -7
  184. data/lib/active_record/schema.rb +38 -23
  185. data/lib/active_record/schema_dumper.rb +25 -19
  186. data/lib/active_record/schema_migration.rb +4 -4
  187. data/lib/active_record/scoping/default.rb +61 -12
  188. data/lib/active_record/scoping/named.rb +3 -11
  189. data/lib/active_record/scoping.rb +64 -34
  190. data/lib/active_record/serialization.rb +1 -1
  191. data/lib/active_record/signed_id.rb +1 -1
  192. data/lib/active_record/suppressor.rb +11 -15
  193. data/lib/active_record/tasks/database_tasks.rb +120 -58
  194. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  195. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -12
  196. data/lib/active_record/test_databases.rb +1 -1
  197. data/lib/active_record/test_fixtures.rb +4 -4
  198. data/lib/active_record/timestamp.rb +3 -4
  199. data/lib/active_record/transactions.rb +9 -14
  200. data/lib/active_record/translation.rb +2 -2
  201. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  202. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  203. data/lib/active_record/type/internal/timezone.rb +2 -2
  204. data/lib/active_record/type/serialized.rb +1 -1
  205. data/lib/active_record/type/type_map.rb +17 -20
  206. data/lib/active_record/type.rb +1 -2
  207. data/lib/active_record/validations/associated.rb +1 -1
  208. data/lib/active_record/validations/uniqueness.rb +1 -1
  209. data/lib/active_record.rb +204 -28
  210. data/lib/arel/attributes/attribute.rb +0 -8
  211. data/lib/arel/crud.rb +28 -22
  212. data/lib/arel/delete_manager.rb +18 -4
  213. data/lib/arel/filter_predications.rb +9 -0
  214. data/lib/arel/insert_manager.rb +2 -3
  215. data/lib/arel/nodes/casted.rb +1 -1
  216. data/lib/arel/nodes/delete_statement.rb +12 -13
  217. data/lib/arel/nodes/filter.rb +10 -0
  218. data/lib/arel/nodes/function.rb +1 -0
  219. data/lib/arel/nodes/insert_statement.rb +2 -2
  220. data/lib/arel/nodes/select_core.rb +2 -2
  221. data/lib/arel/nodes/select_statement.rb +2 -2
  222. data/lib/arel/nodes/update_statement.rb +8 -3
  223. data/lib/arel/nodes.rb +1 -0
  224. data/lib/arel/predications.rb +11 -3
  225. data/lib/arel/select_manager.rb +10 -4
  226. data/lib/arel/table.rb +0 -1
  227. data/lib/arel/tree_manager.rb +0 -12
  228. data/lib/arel/update_manager.rb +18 -4
  229. data/lib/arel/visitors/dot.rb +80 -90
  230. data/lib/arel/visitors/mysql.rb +8 -2
  231. data/lib/arel/visitors/postgresql.rb +0 -10
  232. data/lib/arel/visitors/to_sql.rb +58 -2
  233. data/lib/arel.rb +2 -1
  234. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  235. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  236. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  237. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  238. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  239. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  240. metadata +56 -11
@@ -4,26 +4,22 @@ module ActiveRecord
4
4
  module Associations
5
5
  class Preloader
6
6
  class ThroughAssociation < Association # :nodoc:
7
- PRELOADER = ActiveRecord::Associations::Preloader.new(associate_by_default: false)
8
-
9
- def initialize(*)
10
- super
11
- @already_loaded = owners.first.association(through_reflection.name).loaded?
12
- end
13
-
14
7
  def preloaded_records
15
8
  @preloaded_records ||= source_preloaders.flat_map(&:preloaded_records)
16
9
  end
17
10
 
18
11
  def records_by_owner
19
12
  return @records_by_owner if defined?(@records_by_owner)
20
- source_records_by_owner = source_preloaders.map(&:records_by_owner).reduce(:merge)
21
- through_records_by_owner = through_preloaders.map(&:records_by_owner).reduce(:merge)
22
13
 
23
14
  @records_by_owner = owners.each_with_object({}) do |owner, result|
15
+ if loaded?(owner)
16
+ result[owner] = target_for(owner)
17
+ next
18
+ end
19
+
24
20
  through_records = through_records_by_owner[owner] || []
25
21
 
26
- if @already_loaded
22
+ if owners.first.association(through_reflection.name).loaded?
27
23
  if source_type = reflection.options[:source_type]
28
24
  through_records = through_records.select do |record|
29
25
  record[reflection.foreign_type] == source_type
@@ -42,9 +38,39 @@ module ActiveRecord
42
38
  end
43
39
  end
44
40
 
41
+ def runnable_loaders
42
+ if data_available?
43
+ [self]
44
+ elsif through_preloaders.all?(&:run?)
45
+ source_preloaders.flat_map(&:runnable_loaders)
46
+ else
47
+ through_preloaders.flat_map(&:runnable_loaders)
48
+ end
49
+ end
50
+
51
+ def future_classes
52
+ if run?
53
+ []
54
+ elsif through_preloaders.all?(&:run?)
55
+ source_preloaders.flat_map(&:future_classes).uniq
56
+ else
57
+ through_classes = through_preloaders.flat_map(&:future_classes)
58
+ source_classes = source_reflection.
59
+ chain.
60
+ reject { |reflection| reflection.respond_to?(:polymorphic?) && reflection.polymorphic? }.
61
+ map(&:klass)
62
+ (through_classes + source_classes).uniq
63
+ end
64
+ end
65
+
45
66
  private
67
+ def data_available?
68
+ owners.all? { |owner| loaded?(owner) } ||
69
+ through_preloaders.all?(&:run?) && source_preloaders.all?(&:run?)
70
+ end
71
+
46
72
  def source_preloaders
47
- @source_preloaders ||= PRELOADER.preload(middle_records, source_reflection.name, scope)
73
+ @source_preloaders ||= ActiveRecord::Associations::Preloader.new(records: middle_records, associations: source_reflection.name, scope: scope, associate_by_default: false).loaders
48
74
  end
49
75
 
50
76
  def middle_records
@@ -52,7 +78,7 @@ module ActiveRecord
52
78
  end
53
79
 
54
80
  def through_preloaders
55
- @through_preloaders ||= PRELOADER.preload(owners, through_reflection.name, through_scope)
81
+ @through_preloaders ||= ActiveRecord::Associations::Preloader.new(records: owners, associations: through_reflection.name, scope: through_scope, associate_by_default: false).loaders
56
82
  end
57
83
 
58
84
  def through_reflection
@@ -63,6 +89,14 @@ module ActiveRecord
63
89
  reflection.source_reflection
64
90
  end
65
91
 
92
+ def source_records_by_owner
93
+ @source_records_by_owner ||= source_preloaders.map(&:records_by_owner).reduce(:merge)
94
+ end
95
+
96
+ def through_records_by_owner
97
+ @through_records_by_owner ||= through_preloaders.map(&:records_by_owner).reduce(:merge)
98
+ end
99
+
66
100
  def preload_index
67
101
  @preload_index ||= preloaded_records.each_with_object({}).with_index do |(record, result), index|
68
102
  result[record] = index
@@ -73,6 +107,8 @@ module ActiveRecord
73
107
  scope = through_reflection.klass.unscoped
74
108
  options = reflection.options
75
109
 
110
+ return scope if options[:disable_joins]
111
+
76
112
  values = reflection_scope.values
77
113
  if annotations = values[:annotate]
78
114
  scope.annotate!(*annotations)
@@ -108,7 +144,7 @@ module ActiveRecord
108
144
  end
109
145
  end
110
146
 
111
- scope
147
+ cascade_strict_loading(scope)
112
148
  end
113
149
  end
114
150
  end
@@ -41,15 +41,18 @@ module ActiveRecord
41
41
  #
42
42
  # This could result in many rows that contain redundant data and it performs poorly at scale
43
43
  # and is therefore only used when necessary.
44
- #
45
- class Preloader #:nodoc:
44
+ class Preloader # :nodoc:
46
45
  extend ActiveSupport::Autoload
47
46
 
48
47
  eager_autoload do
49
48
  autoload :Association, "active_record/associations/preloader/association"
49
+ autoload :Batch, "active_record/associations/preloader/batch"
50
+ autoload :Branch, "active_record/associations/preloader/branch"
50
51
  autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
51
52
  end
52
53
 
54
+ attr_reader :records, :associations, :scope, :associate_by_default
55
+
53
56
  # Eager loads the named associations for the given Active Record record(s).
54
57
  #
55
58
  # In this description, 'association name' shall refer to the name passed
@@ -77,130 +80,53 @@ module ActiveRecord
77
80
  # example, specifying <tt>{ author: :avatar }</tt> will preload a
78
81
  # book's author, as well as that author's avatar.
79
82
  #
80
- # +:associations+ has the same format as the +:include+ option for
81
- # <tt>ActiveRecord::Base.find</tt>. So +associations+ could look like this:
83
+ # +:associations+ has the same format as the +:include+ method in
84
+ # <tt>ActiveRecord::QueryMethods</tt>. So +associations+ could look like this:
82
85
  #
83
86
  # :books
84
87
  # [ :books, :author ]
85
88
  # { author: :avatar }
86
89
  # [ :books, { author: :avatar } ]
87
- def preload(records, associations, preload_scope = nil)
88
- records = Array.wrap(records).compact
90
+ #
91
+ # +available_records+ is an array of ActiveRecord::Base. The Preloader
92
+ # will try to use the objects in this array to preload the requested
93
+ # associations before querying the database. This can save database
94
+ # queries by reusing in-memory objects. The optimization is only applied
95
+ # to single associations (i.e. :belongs_to, :has_one) with no scopes.
96
+ def initialize(records:, associations:, scope: nil, available_records: [], associate_by_default: true)
97
+ @records = records
98
+ @associations = associations
99
+ @scope = scope
100
+ @available_records = available_records || []
101
+ @associate_by_default = associate_by_default
89
102
 
90
- if records.empty?
91
- []
92
- else
93
- Array.wrap(associations).flat_map { |association|
94
- preloaders_on association, records, preload_scope
95
- }
96
- end
103
+ @tree = Branch.new(
104
+ parent: nil,
105
+ association: nil,
106
+ children: @associations,
107
+ associate_by_default: @associate_by_default,
108
+ scope: @scope
109
+ )
110
+ @tree.preloaded_records = @records
97
111
  end
98
112
 
99
- def initialize(associate_by_default: true)
100
- @associate_by_default = associate_by_default
113
+ def empty?
114
+ associations.nil? || records.length == 0
101
115
  end
102
116
 
103
- private
104
- # Loads all the given data into +records+ for the +association+.
105
- def preloaders_on(association, records, scope, polymorphic_parent = false)
106
- case association
107
- when Hash
108
- preloaders_for_hash(association, records, scope, polymorphic_parent)
109
- when Symbol, String
110
- preloaders_for_one(association, records, scope, polymorphic_parent)
111
- else
112
- raise ArgumentError, "#{association.inspect} was not recognized for preload"
113
- end
114
- end
115
-
116
- def preloaders_for_hash(association, records, scope, polymorphic_parent)
117
- association.flat_map { |parent, child|
118
- grouped_records(parent, records, polymorphic_parent).flat_map do |reflection, reflection_records|
119
- loaders = preloaders_for_reflection(reflection, reflection_records, scope)
120
- recs = loaders.flat_map(&:preloaded_records).uniq
121
- child_polymorphic_parent = reflection && reflection.options[:polymorphic]
122
- loaders.concat Array.wrap(child).flat_map { |assoc|
123
- preloaders_on assoc, recs, scope, child_polymorphic_parent
124
- }
125
- loaders
126
- end
127
- }
128
- end
129
-
130
- # Loads all the given data into +records+ for a singular +association+.
131
- #
132
- # Functions by instantiating a preloader class such as Preloader::Association and
133
- # call the +run+ method for each passed in class in the +records+ argument.
134
- #
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, polymorphic_parent)
143
- grouped_records(association, records, polymorphic_parent)
144
- .flat_map do |reflection, reflection_records|
145
- preloaders_for_reflection reflection, reflection_records, scope
146
- end
147
- end
148
-
149
- def preloaders_for_reflection(reflection, records, scope)
150
- records.group_by { |record| record.association(reflection.name).klass }.map do |rhs_klass, rs|
151
- preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope, @associate_by_default).run
152
- end
153
- end
117
+ def call
118
+ Batch.new([self], available_records: @available_records).call
154
119
 
155
- def grouped_records(association, records, polymorphic_parent)
156
- h = {}
157
- records.each do |record|
158
- reflection = record.class._reflect_on_association(association)
159
- next if polymorphic_parent && !reflection || !record.association(association).klass
160
- (h[reflection] ||= []) << record
161
- end
162
- h
163
- end
164
-
165
- class AlreadyLoaded # :nodoc:
166
- def initialize(klass, owners, reflection, preload_scope, associate_by_default = true)
167
- @owners = owners
168
- @reflection = reflection
169
- end
170
-
171
- def run
172
- self
173
- end
174
-
175
- def preloaded_records
176
- @preloaded_records ||= records_by_owner.flat_map(&:last)
177
- end
178
-
179
- def records_by_owner
180
- @records_by_owner ||= owners.index_with do |owner|
181
- Array(owner.association(reflection.name).target)
182
- end
183
- end
184
-
185
- private
186
- attr_reader :owners, :reflection
187
- end
120
+ loaders
121
+ end
188
122
 
189
- # Returns a class containing the logic needed to load preload the data
190
- # and attach it to a relation. The class returned implements a `run` method
191
- # that accepts a preloader.
192
- def preloader_for(reflection, owners)
193
- if owners.all? { |o| o.association(reflection.name).loaded? }
194
- return AlreadyLoaded
195
- end
196
- reflection.check_preloadable!
123
+ def branches
124
+ @tree.children
125
+ end
197
126
 
198
- if reflection.options[:through]
199
- ThroughAssociation
200
- else
201
- Association
202
- end
203
- end
127
+ def loaders
128
+ branches.flat_map(&:loaders)
129
+ end
204
130
  end
205
131
  end
206
132
  end
@@ -2,9 +2,11 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Associations
5
- class SingularAssociation < Association #:nodoc:
5
+ class SingularAssociation < Association # :nodoc:
6
6
  # Implements the reader method, e.g. foo.bar for Foo.has_one :bar
7
7
  def reader
8
+ ensure_klass_exists!
9
+
8
10
  if !loaded? || stale_target?
9
11
  reload
10
12
  end
@@ -36,7 +38,11 @@ module ActiveRecord
36
38
  end
37
39
 
38
40
  def find_target
39
- super.first
41
+ if disable_joins
42
+ scope.first
43
+ else
44
+ super.first
45
+ end
40
46
  end
41
47
 
42
48
  def replace(record)
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  # = Active Record Through Association
6
- module ThroughAssociation #:nodoc:
6
+ module ThroughAssociation # :nodoc:
7
7
  delegate :source_reflection, to: :reflection
8
8
 
9
9
  private
@@ -74,8 +74,8 @@ module ActiveRecord
74
74
  end
75
75
  end
76
76
 
77
- # Note: this does not capture all cases, for example it would be crazy to try to
78
- # properly support stale-checking for nested associations.
77
+ # Note: this does not capture all cases, for example it would be impractical
78
+ # to try to properly support stale-checking for nested associations.
79
79
  def stale_state
80
80
  if through_reflection.belongs_to?
81
81
  owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s