activerecord 6.1.3.2 → 7.0.0.alpha2

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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +734 -1058
  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 +35 -7
  8. data/lib/active_record/associations/association_scope.rb +1 -3
  9. data/lib/active_record/associations/belongs_to_association.rb +16 -6
  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 +1 -1
  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 +24 -25
  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/preloader/association.rb +161 -49
  25. data/lib/active_record/associations/preloader/batch.rb +51 -0
  26. data/lib/active_record/associations/preloader/branch.rb +147 -0
  27. data/lib/active_record/associations/preloader/through_association.rb +37 -11
  28. data/lib/active_record/associations/preloader.rb +46 -110
  29. data/lib/active_record/associations/singular_association.rb +8 -2
  30. data/lib/active_record/associations/through_association.rb +1 -1
  31. data/lib/active_record/associations.rb +76 -81
  32. data/lib/active_record/asynchronous_queries_tracker.rb +57 -0
  33. data/lib/active_record/attribute_assignment.rb +1 -1
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  35. data/lib/active_record/attribute_methods/dirty.rb +41 -16
  36. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  37. data/lib/active_record/attribute_methods/query.rb +2 -2
  38. data/lib/active_record/attribute_methods/read.rb +7 -5
  39. data/lib/active_record/attribute_methods/serialization.rb +66 -12
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +6 -9
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +3 -18
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +2 -2
  47. data/lib/active_record/coders/yaml_column.rb +11 -1
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +312 -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 +31 -558
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +45 -21
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +14 -7
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -18
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +30 -9
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +60 -16
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -69
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +96 -81
  61. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +6 -2
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +33 -21
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +3 -0
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  66. data/lib/active_record/connection_adapters/pool_config.rb +1 -3
  67. data/lib/active_record/connection_adapters/pool_manager.rb +5 -1
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -12
  69. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  72. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  73. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +28 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  76. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +6 -6
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +5 -1
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -12
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +157 -100
  82. data/lib/active_record/connection_adapters/schema_cache.rb +35 -4
  83. data/lib/active_record/connection_adapters/sql_type_metadata.rb +0 -2
  84. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +23 -17
  85. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -2
  86. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -30
  87. data/lib/active_record/connection_adapters.rb +8 -5
  88. data/lib/active_record/connection_handling.rb +20 -38
  89. data/lib/active_record/core.rb +129 -117
  90. data/lib/active_record/database_configurations/database_config.rb +12 -0
  91. data/lib/active_record/database_configurations/hash_config.rb +27 -1
  92. data/lib/active_record/database_configurations/url_config.rb +2 -2
  93. data/lib/active_record/database_configurations.rb +18 -9
  94. data/lib/active_record/delegated_type.rb +33 -11
  95. data/lib/active_record/destroy_association_async_job.rb +1 -1
  96. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  97. data/lib/active_record/dynamic_matchers.rb +1 -1
  98. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  99. data/lib/active_record/encryption/cipher.rb +53 -0
  100. data/lib/active_record/encryption/config.rb +44 -0
  101. data/lib/active_record/encryption/configurable.rb +61 -0
  102. data/lib/active_record/encryption/context.rb +35 -0
  103. data/lib/active_record/encryption/contexts.rb +72 -0
  104. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  105. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  106. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  107. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  108. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  109. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  110. data/lib/active_record/encryption/encryptor.rb +155 -0
  111. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  112. data/lib/active_record/encryption/errors.rb +15 -0
  113. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  114. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +29 -0
  115. data/lib/active_record/encryption/key.rb +28 -0
  116. data/lib/active_record/encryption/key_generator.rb +42 -0
  117. data/lib/active_record/encryption/key_provider.rb +46 -0
  118. data/lib/active_record/encryption/message.rb +33 -0
  119. data/lib/active_record/encryption/message_serializer.rb +80 -0
  120. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  121. data/lib/active_record/encryption/properties.rb +76 -0
  122. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  123. data/lib/active_record/encryption/scheme.rb +99 -0
  124. data/lib/active_record/encryption.rb +55 -0
  125. data/lib/active_record/enum.rb +44 -46
  126. data/lib/active_record/errors.rb +66 -3
  127. data/lib/active_record/fixture_set/file.rb +15 -1
  128. data/lib/active_record/fixture_set/table_row.rb +40 -5
  129. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  130. data/lib/active_record/fixtures.rb +16 -11
  131. data/lib/active_record/future_result.rb +139 -0
  132. data/lib/active_record/gem_version.rb +4 -4
  133. data/lib/active_record/inheritance.rb +55 -17
  134. data/lib/active_record/insert_all.rb +39 -6
  135. data/lib/active_record/integration.rb +1 -1
  136. data/lib/active_record/internal_metadata.rb +3 -5
  137. data/lib/active_record/legacy_yaml_adapter.rb +1 -1
  138. data/lib/active_record/locking/optimistic.rb +10 -9
  139. data/lib/active_record/log_subscriber.rb +6 -2
  140. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  141. data/lib/active_record/middleware/database_selector.rb +8 -3
  142. data/lib/active_record/migration/command_recorder.rb +4 -4
  143. data/lib/active_record/migration/compatibility.rb +83 -1
  144. data/lib/active_record/migration/join_table.rb +1 -1
  145. data/lib/active_record/migration.rb +109 -79
  146. data/lib/active_record/model_schema.rb +46 -32
  147. data/lib/active_record/nested_attributes.rb +3 -3
  148. data/lib/active_record/no_touching.rb +2 -2
  149. data/lib/active_record/null_relation.rb +2 -6
  150. data/lib/active_record/persistence.rb +134 -45
  151. data/lib/active_record/query_cache.rb +2 -2
  152. data/lib/active_record/query_logs.rb +203 -0
  153. data/lib/active_record/querying.rb +15 -5
  154. data/lib/active_record/railtie.rb +117 -17
  155. data/lib/active_record/railties/controller_runtime.rb +1 -1
  156. data/lib/active_record/railties/databases.rake +83 -58
  157. data/lib/active_record/readonly_attributes.rb +11 -0
  158. data/lib/active_record/reflection.rb +45 -44
  159. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  160. data/lib/active_record/relation/batches.rb +3 -3
  161. data/lib/active_record/relation/calculations.rb +42 -25
  162. data/lib/active_record/relation/delegation.rb +6 -6
  163. data/lib/active_record/relation/finder_methods.rb +32 -23
  164. data/lib/active_record/relation/merger.rb +20 -13
  165. data/lib/active_record/relation/predicate_builder.rb +1 -6
  166. data/lib/active_record/relation/query_attribute.rb +5 -11
  167. data/lib/active_record/relation/query_methods.rb +233 -50
  168. data/lib/active_record/relation/record_fetch_warning.rb +2 -2
  169. data/lib/active_record/relation/spawn_methods.rb +2 -2
  170. data/lib/active_record/relation/where_clause.rb +22 -15
  171. data/lib/active_record/relation.rb +170 -87
  172. data/lib/active_record/result.rb +17 -2
  173. data/lib/active_record/runtime_registry.rb +2 -4
  174. data/lib/active_record/sanitization.rb +11 -7
  175. data/lib/active_record/schema_dumper.rb +3 -3
  176. data/lib/active_record/schema_migration.rb +0 -4
  177. data/lib/active_record/scoping/default.rb +62 -15
  178. data/lib/active_record/scoping/named.rb +3 -11
  179. data/lib/active_record/scoping.rb +40 -22
  180. data/lib/active_record/serialization.rb +1 -1
  181. data/lib/active_record/signed_id.rb +1 -1
  182. data/lib/active_record/statement_cache.rb +2 -2
  183. data/lib/active_record/tasks/database_tasks.rb +107 -23
  184. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  185. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -11
  186. data/lib/active_record/test_databases.rb +1 -1
  187. data/lib/active_record/test_fixtures.rb +45 -4
  188. data/lib/active_record/timestamp.rb +3 -4
  189. data/lib/active_record/transactions.rb +9 -14
  190. data/lib/active_record/translation.rb +2 -2
  191. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  192. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  193. data/lib/active_record/type/internal/timezone.rb +2 -2
  194. data/lib/active_record/type/serialized.rb +1 -1
  195. data/lib/active_record/type/type_map.rb +17 -20
  196. data/lib/active_record/type.rb +1 -2
  197. data/lib/active_record/validations/associated.rb +1 -1
  198. data/lib/active_record/validations/numericality.rb +1 -1
  199. data/lib/active_record.rb +170 -2
  200. data/lib/arel/attributes/attribute.rb +0 -8
  201. data/lib/arel/collectors/bind.rb +2 -2
  202. data/lib/arel/collectors/composite.rb +3 -3
  203. data/lib/arel/collectors/sql_string.rb +1 -1
  204. data/lib/arel/collectors/substitute_binds.rb +1 -1
  205. data/lib/arel/crud.rb +18 -22
  206. data/lib/arel/delete_manager.rb +2 -4
  207. data/lib/arel/insert_manager.rb +2 -3
  208. data/lib/arel/nodes/casted.rb +1 -1
  209. data/lib/arel/nodes/delete_statement.rb +8 -13
  210. data/lib/arel/nodes/homogeneous_in.rb +4 -0
  211. data/lib/arel/nodes/insert_statement.rb +2 -2
  212. data/lib/arel/nodes/select_core.rb +2 -2
  213. data/lib/arel/nodes/select_statement.rb +2 -2
  214. data/lib/arel/nodes/update_statement.rb +3 -2
  215. data/lib/arel/predications.rb +3 -3
  216. data/lib/arel/select_manager.rb +10 -4
  217. data/lib/arel/table.rb +0 -1
  218. data/lib/arel/tree_manager.rb +0 -12
  219. data/lib/arel/update_manager.rb +2 -4
  220. data/lib/arel/visitors/dot.rb +80 -90
  221. data/lib/arel/visitors/mysql.rb +6 -1
  222. data/lib/arel/visitors/postgresql.rb +0 -10
  223. data/lib/arel/visitors/to_sql.rb +44 -3
  224. data/lib/arel.rb +1 -1
  225. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  226. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  227. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  228. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  229. metadata +55 -16
@@ -27,7 +27,7 @@ module ActiveRecord
27
27
  # is computed directly through SQL and does not trigger by itself the
28
28
  # instantiation of the actual post records.
29
29
  class CollectionProxy < Relation
30
- def initialize(klass, association, **) #:nodoc:
30
+ def initialize(klass, association, **) # :nodoc:
31
31
  @association = association
32
32
  super klass
33
33
 
@@ -46,7 +46,7 @@ module ActiveRecord
46
46
  # Returns +true+ if the association has been loaded, otherwise +false+.
47
47
  #
48
48
  # person.pets.loaded? # => false
49
- # person.pets
49
+ # person.pets.records
50
50
  # person.pets.loaded? # => true
51
51
  def loaded?
52
52
  @association.loaded?
@@ -813,7 +813,7 @@ module ActiveRecord
813
813
  # to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
814
814
  # it is equivalent to <tt>!collection.exists?</tt>. If the collection has
815
815
  # not already been loaded and you are going to fetch the records anyway it
816
- # is better to check <tt>collection.length.zero?</tt>.
816
+ # is better to check <tt>collection.load.empty?</tt>.
817
817
  #
818
818
  # class Person < ActiveRecord::Base
819
819
  # has_many :pets
@@ -849,6 +849,11 @@ module ActiveRecord
849
849
  # person.pets.count # => 1
850
850
  # person.pets.any? # => true
851
851
  #
852
+ # Calling it without a block when the collection is not yet
853
+ # loaded is equivalent to <tt>collection.exists?</tt>.
854
+ # If you're going to load the collection anyway, it is better
855
+ # to call <tt>collection.load.any?</tt> to avoid an extra query.
856
+ #
852
857
  # You can also pass a +block+ to define criteria. The behavior
853
858
  # is the same, it returns true if the collection based on the
854
859
  # criteria is not empty.
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ class DisableJoinsAssociationScope < AssociationScope # :nodoc:
6
+ def scope(association)
7
+ source_reflection = association.reflection
8
+ owner = association.owner
9
+ unscoped = association.klass.unscoped
10
+ reverse_chain = get_chain(source_reflection, association, unscoped.alias_tracker).reverse
11
+
12
+ last_reflection, last_ordered, last_join_ids = last_scope_chain(reverse_chain, owner)
13
+
14
+ add_constraints(last_reflection, last_reflection.join_primary_key, last_join_ids, owner, last_ordered)
15
+ end
16
+
17
+ private
18
+ def last_scope_chain(reverse_chain, owner)
19
+ first_item = reverse_chain.shift
20
+ first_scope = [first_item, false, [owner._read_attribute(first_item.join_foreign_key)]]
21
+
22
+ reverse_chain.inject(first_scope) do |(reflection, ordered, join_ids), next_reflection|
23
+ key = reflection.join_primary_key
24
+ records = add_constraints(reflection, key, join_ids, owner, ordered)
25
+ foreign_key = next_reflection.join_foreign_key
26
+ record_ids = records.pluck(foreign_key)
27
+ records_ordered = records && records.order_values.any?
28
+
29
+ [next_reflection, records_ordered, record_ids]
30
+ end
31
+ end
32
+
33
+ def add_constraints(reflection, key, join_ids, owner, ordered)
34
+ scope = reflection.build_scope(reflection.aliased_table).where(key => join_ids)
35
+
36
+ relation = reflection.klass.scope_for_association
37
+ scope.merge!(
38
+ relation.except(:select, :create_with, :includes, :preload, :eager_load, :joins, :left_outer_joins)
39
+ )
40
+
41
+ scope = reflection.constraints.inject(scope) do |memo, scope_chain_item|
42
+ item = eval_scope(reflection, scope_chain_item, owner)
43
+ scope.unscope!(*item.unscope_values)
44
+ scope.where_clause += item.where_clause
45
+ scope.order_values = item.order_values | scope.order_values
46
+ scope
47
+ end
48
+
49
+ if scope.order_values.empty? && ordered
50
+ split_scope = DisableJoinsAssociationRelation.create(scope.klass, key, join_ids)
51
+ split_scope.where_clause += scope.where_clause
52
+ split_scope
53
+ else
54
+ scope
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  #
8
8
  # If the association has a <tt>:through</tt> option further specialization
9
9
  # is provided by its child HasManyThroughAssociation.
10
- class HasManyAssociation < CollectionAssociation #:nodoc:
10
+ class HasManyAssociation < CollectionAssociation # :nodoc:
11
11
  include ForeignAssociation
12
12
 
13
13
  def handle_dependency
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  # = Active Record Has Many Through Association
6
- class HasManyThroughAssociation < HasManyAssociation #:nodoc:
6
+ class HasManyThroughAssociation < HasManyAssociation # :nodoc:
7
7
  include ThroughAssociation
8
8
 
9
9
  def initialize(owner, reflection)
@@ -214,6 +214,7 @@ module ActiveRecord
214
214
 
215
215
  def find_target
216
216
  return [] unless target_reflection_has_associated_record?
217
+ return scope.to_a if disable_joins
217
218
  super
218
219
  end
219
220
 
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  # = Active Record Has One Association
6
- class HasOneAssociation < SingularAssociation #:nodoc:
6
+ class HasOneAssociation < SingularAssociation # :nodoc:
7
7
  include ForeignAssociation
8
8
 
9
9
  def handle_dependency
@@ -70,7 +70,7 @@ module ActiveRecord
70
70
  if save && !record.save
71
71
  nullify_owner_attributes(record)
72
72
  set_owner_attributes(target) if target
73
- raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
73
+ raise RecordNotSaved.new("Failed to save the new associated #{reflection.name}.", record)
74
74
  end
75
75
  end
76
76
  end
@@ -102,8 +102,11 @@ module ActiveRecord
102
102
 
103
103
  if target.persisted? && owner.persisted? && !target.save
104
104
  set_owner_attributes(target)
105
- raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " \
106
- "The record failed to save after its foreign key was set to nil."
105
+ raise RecordNotSaved.new(
106
+ "Failed to remove the existing associated #{reflection.name}. " \
107
+ "The record failed to save after its foreign key was set to nil.",
108
+ target
109
+ )
107
110
  end
108
111
  end
109
112
  end
@@ -112,9 +115,9 @@ module ActiveRecord
112
115
  record[reflection.foreign_key] = nil
113
116
  end
114
117
 
115
- def transaction_if(value)
118
+ def transaction_if(value, &block)
116
119
  if value
117
- reflection.klass.transaction { yield }
120
+ reflection.klass.transaction(&block)
118
121
  else
119
122
  yield
120
123
  end
@@ -122,7 +125,7 @@ module ActiveRecord
122
125
 
123
126
  def _create_record(attributes, raise_error = false, &block)
124
127
  unless owner.persisted?
125
- raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
128
+ raise ActiveRecord::RecordNotSaved.new("You cannot call create unless the parent is saved", owner)
126
129
  end
127
130
 
128
131
  super
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  # = Active Record Has One Through Association
6
- class HasOneThroughAssociation < HasOneAssociation #:nodoc:
6
+ class HasOneThroughAssociation < HasOneAssociation # :nodoc:
7
7
  include ThroughAssociation
8
8
 
9
9
  private
@@ -3,17 +3,89 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  class Preloader
6
- class Association #:nodoc:
7
- def initialize(klass, owners, reflection, preload_scope, associate_by_default = true)
6
+ class Association # :nodoc:
7
+ class LoaderQuery
8
+ attr_reader :scope, :association_key_name
9
+
10
+ def initialize(scope, association_key_name)
11
+ @scope = scope
12
+ @association_key_name = association_key_name
13
+ end
14
+
15
+ def eql?(other)
16
+ association_key_name == other.association_key_name &&
17
+ scope.table_name == other.scope.table_name &&
18
+ scope.values_for_queries == other.scope.values_for_queries
19
+ end
20
+
21
+ def hash
22
+ [association_key_name, scope.table_name, scope.values_for_queries].hash
23
+ end
24
+
25
+ def records_for(loaders)
26
+ ids = loaders.flat_map(&:owner_keys).uniq
27
+
28
+ scope.where(association_key_name => ids).load do |record|
29
+ loaders.each { |l| l.set_inverse(record) }
30
+ end
31
+ end
32
+
33
+ def load_records_in_batch(loaders)
34
+ raw_records = records_for(loaders)
35
+
36
+ loaders.each do |loader|
37
+ loader.load_records(raw_records)
38
+ loader.run
39
+ end
40
+ end
41
+ end
42
+
43
+ attr_reader :klass
44
+
45
+ def initialize(klass, owners, reflection, preload_scope, reflection_scope, associate_by_default)
8
46
  @klass = klass
9
47
  @owners = owners.uniq(&:__id__)
10
48
  @reflection = reflection
11
49
  @preload_scope = preload_scope
50
+ @reflection_scope = reflection_scope
12
51
  @associate = associate_by_default || !preload_scope || preload_scope.empty_scope?
13
52
  @model = owners.first && owners.first.class
53
+ @run = false
54
+ end
55
+
56
+ def table_name
57
+ @klass.table_name
58
+ end
59
+
60
+ def data_available?
61
+ already_loaded?
62
+ end
63
+
64
+ def future_classes
65
+ if run? || already_loaded?
66
+ []
67
+ else
68
+ [@klass]
69
+ end
70
+ end
71
+
72
+ def runnable_loaders
73
+ [self]
74
+ end
75
+
76
+ def run?
77
+ @run
14
78
  end
15
79
 
16
80
  def run
81
+ return self if run?
82
+ @run = true
83
+
84
+ if already_loaded?
85
+ fetch_from_preloaded_records
86
+ return self
87
+ end
88
+
17
89
  records = records_by_owner
18
90
 
19
91
  owners.each do |owner|
@@ -24,45 +96,105 @@ module ActiveRecord
24
96
  end
25
97
 
26
98
  def records_by_owner
27
- load_records unless defined?(@records_by_owner)
99
+ ensure_loaded unless defined?(@records_by_owner)
28
100
 
29
101
  @records_by_owner
30
102
  end
31
103
 
32
104
  def preloaded_records
33
- load_records unless defined?(@preloaded_records)
105
+ ensure_loaded unless defined?(@preloaded_records)
34
106
 
35
107
  @preloaded_records
36
108
  end
37
109
 
38
- private
39
- attr_reader :owners, :reflection, :preload_scope, :model, :klass
110
+ def ensure_loaded
111
+ if already_loaded?
112
+ fetch_from_preloaded_records
113
+ else
114
+ load_records
115
+ end
116
+ end
40
117
 
41
- def load_records
42
- # owners can be duplicated when a relation has a collection association join
43
- # #compare_by_identity makes such owners different hash keys
44
- @records_by_owner = {}.compare_by_identity
45
- raw_records = owner_keys.empty? ? [] : records_for(owner_keys)
118
+ # The name of the key on the associated records
119
+ def association_key_name
120
+ reflection.join_primary_key(klass)
121
+ end
46
122
 
47
- @preloaded_records = raw_records.select do |record|
48
- assignments = false
123
+ def loader_query
124
+ LoaderQuery.new(scope, association_key_name)
125
+ end
49
126
 
50
- owners_by_key[convert_key(record[association_key_name])].each do |owner|
51
- entries = (@records_by_owner[owner] ||= [])
127
+ def owner_keys
128
+ @owner_keys ||= owners_by_key.keys
129
+ end
52
130
 
53
- if reflection.collection? || entries.empty?
54
- entries << record
55
- assignments = true
56
- end
131
+ def scope
132
+ @scope ||= build_scope
133
+ end
134
+
135
+ def set_inverse(record)
136
+ if owners = owners_by_key[convert_key(record[association_key_name])]
137
+ # Processing only the first owner
138
+ # because the record is modified but not an owner
139
+ association = owners.first.association(reflection.name)
140
+ association.set_inverse_instance(record)
141
+ end
142
+ end
143
+
144
+ def load_records(raw_records = nil)
145
+ # owners can be duplicated when a relation has a collection association join
146
+ # #compare_by_identity makes such owners different hash keys
147
+ @records_by_owner = {}.compare_by_identity
148
+ raw_records ||= loader_query.records_for([self])
149
+
150
+ @preloaded_records = raw_records.select do |record|
151
+ assignments = false
152
+
153
+ owners_by_key[convert_key(record[association_key_name])]&.each do |owner|
154
+ entries = (@records_by_owner[owner] ||= [])
155
+
156
+ if reflection.collection? || entries.empty?
157
+ entries << record
158
+ assignments = true
57
159
  end
160
+ end
58
161
 
59
- assignments
162
+ assignments
163
+ end
164
+ end
165
+
166
+ def associate_records_from_unscoped(unscoped_records)
167
+ return if unscoped_records.nil? || unscoped_records.empty?
168
+ return if !reflection_scope.empty_scope?
169
+ return if preload_scope && !preload_scope.empty_scope?
170
+ return if reflection.collection?
171
+
172
+ unscoped_records.each do |record|
173
+ owners = owners_by_key[convert_key(record[association_key_name])]
174
+ owners&.each_with_index do |owner, i|
175
+ association = owner.association(reflection.name)
176
+ association.target = record
177
+
178
+ if i == 0 # Set inverse on first owner
179
+ association.set_inverse_instance(record)
180
+ end
60
181
  end
61
182
  end
183
+ end
184
+
185
+ private
186
+ attr_reader :owners, :reflection, :preload_scope, :model
62
187
 
63
- # The name of the key on the associated records
64
- def association_key_name
65
- reflection.join_primary_key(klass)
188
+ def already_loaded?
189
+ @already_loaded ||= owners.all? { |o| o.association(reflection.name).loaded? }
190
+ end
191
+
192
+ def fetch_from_preloaded_records
193
+ @records_by_owner = owners.index_with do |owner|
194
+ Array(owner.association(reflection.name).target)
195
+ end
196
+
197
+ @preloaded_records = records_by_owner.flat_map(&:last)
66
198
  end
67
199
 
68
200
  # The name of the key on the model which declares the association
@@ -79,10 +211,6 @@ module ActiveRecord
79
211
  end
80
212
  end
81
213
 
82
- def owner_keys
83
- @owner_keys ||= owners_by_key.keys
84
- end
85
-
86
214
  def owners_by_key
87
215
  @owners_by_key ||= owners.each_with_object({}) do |owner, result|
88
216
  key = convert_key(owner[owner_key_name])
@@ -114,24 +242,8 @@ module ActiveRecord
114
242
  @model.type_for_attribute(owner_key_name).type
115
243
  end
116
244
 
117
- def records_for(ids)
118
- scope.where(association_key_name => ids).load do |record|
119
- # Processing only the first owner
120
- # because the record is modified but not an owner
121
- owner = owners_by_key[convert_key(record[association_key_name])].first
122
- association = owner.association(reflection.name)
123
- association.set_inverse_instance(record)
124
- end
125
- end
126
-
127
- def scope
128
- @scope ||= build_scope
129
- end
130
-
131
245
  def reflection_scope
132
- @reflection_scope ||= begin
133
- reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(&:merge!) || klass.unscoped
134
- end
246
+ @reflection_scope ||= reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(&:merge!) || klass.unscoped
135
247
  end
136
248
 
137
249
  def build_scope
@@ -147,11 +259,11 @@ module ActiveRecord
147
259
  scope.merge!(preload_scope)
148
260
  end
149
261
 
150
- if preload_scope && preload_scope.strict_loading_value
151
- scope.strict_loading
152
- else
153
- scope
154
- end
262
+ cascade_strict_loading(scope)
263
+ end
264
+
265
+ def cascade_strict_loading(scope)
266
+ preload_scope&.strict_loading_value ? scope.strict_loading : scope
155
267
  end
156
268
  end
157
269
  end