activerecord 7.0.8.7 → 7.1.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1339 -1572
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +15 -16
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +17 -9
  15. data/lib/active_record/associations/collection_proxy.rb +16 -11
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader.rb +12 -9
  23. data/lib/active_record/associations/singular_association.rb +1 -1
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +193 -97
  26. data/lib/active_record/attribute_assignment.rb +0 -2
  27. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +40 -26
  29. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  30. data/lib/active_record/attribute_methods/query.rb +28 -16
  31. data/lib/active_record/attribute_methods/read.rb +18 -5
  32. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +105 -21
  35. data/lib/active_record/attributes.rb +3 -3
  36. data/lib/active_record/autosave_association.rb +55 -9
  37. data/lib/active_record/base.rb +7 -2
  38. data/lib/active_record/callbacks.rb +10 -24
  39. data/lib/active_record/coders/column_serializer.rb +61 -0
  40. data/lib/active_record/coders/json.rb +1 -1
  41. data/lib/active_record/coders/yaml_column.rb +70 -42
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  45. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +109 -32
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  49. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  50. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  51. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  52. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  53. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -122
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +280 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +502 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +200 -108
  57. data/lib/active_record/connection_adapters/column.rb +9 -0
  58. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  59. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  61. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  62. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  63. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -12
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  67. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  68. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  69. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -29
  71. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  72. data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -6
  73. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  74. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  75. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  76. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +42 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +351 -54
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -168
  79. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  80. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  81. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +42 -36
  82. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  83. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  84. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  85. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +162 -77
  86. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  87. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  88. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  89. data/lib/active_record/connection_adapters.rb +3 -1
  90. data/lib/active_record/connection_handling.rb +71 -94
  91. data/lib/active_record/core.rb +128 -138
  92. data/lib/active_record/counter_cache.rb +46 -25
  93. data/lib/active_record/database_configurations/database_config.rb +9 -3
  94. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  95. data/lib/active_record/database_configurations/url_config.rb +17 -11
  96. data/lib/active_record/database_configurations.rb +86 -33
  97. data/lib/active_record/delegated_type.rb +8 -3
  98. data/lib/active_record/deprecator.rb +7 -0
  99. data/lib/active_record/destroy_association_async_job.rb +2 -0
  100. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  101. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  102. data/lib/active_record/encryption/config.rb +25 -1
  103. data/lib/active_record/encryption/configurable.rb +12 -19
  104. data/lib/active_record/encryption/context.rb +10 -3
  105. data/lib/active_record/encryption/contexts.rb +5 -1
  106. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  107. data/lib/active_record/encryption/encryptable_record.rb +36 -18
  108. data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
  109. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
  110. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +2 -2
  111. data/lib/active_record/encryption/key_generator.rb +12 -1
  112. data/lib/active_record/encryption/message_serializer.rb +2 -0
  113. data/lib/active_record/encryption/properties.rb +3 -3
  114. data/lib/active_record/encryption/scheme.rb +19 -22
  115. data/lib/active_record/encryption.rb +1 -0
  116. data/lib/active_record/enum.rb +113 -26
  117. data/lib/active_record/errors.rb +89 -15
  118. data/lib/active_record/explain.rb +23 -3
  119. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  120. data/lib/active_record/fixture_set/render_context.rb +2 -0
  121. data/lib/active_record/fixture_set/table_row.rb +29 -8
  122. data/lib/active_record/fixtures.rb +119 -71
  123. data/lib/active_record/future_result.rb +30 -5
  124. data/lib/active_record/gem_version.rb +4 -4
  125. data/lib/active_record/inheritance.rb +30 -16
  126. data/lib/active_record/insert_all.rb +55 -8
  127. data/lib/active_record/integration.rb +8 -8
  128. data/lib/active_record/internal_metadata.rb +118 -30
  129. data/lib/active_record/locking/pessimistic.rb +5 -2
  130. data/lib/active_record/log_subscriber.rb +29 -12
  131. data/lib/active_record/marshalling.rb +56 -0
  132. data/lib/active_record/message_pack.rb +124 -0
  133. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  134. data/lib/active_record/middleware/database_selector.rb +5 -7
  135. data/lib/active_record/middleware/shard_selector.rb +3 -1
  136. data/lib/active_record/migration/command_recorder.rb +100 -4
  137. data/lib/active_record/migration/compatibility.rb +131 -5
  138. data/lib/active_record/migration/default_strategy.rb +23 -0
  139. data/lib/active_record/migration/execution_strategy.rb +19 -0
  140. data/lib/active_record/migration.rb +213 -109
  141. data/lib/active_record/model_schema.rb +47 -27
  142. data/lib/active_record/nested_attributes.rb +28 -3
  143. data/lib/active_record/normalization.rb +158 -0
  144. data/lib/active_record/persistence.rb +183 -33
  145. data/lib/active_record/promise.rb +84 -0
  146. data/lib/active_record/query_cache.rb +3 -21
  147. data/lib/active_record/query_logs.rb +77 -52
  148. data/lib/active_record/query_logs_formatter.rb +41 -0
  149. data/lib/active_record/querying.rb +15 -2
  150. data/lib/active_record/railtie.rb +107 -45
  151. data/lib/active_record/railties/controller_runtime.rb +10 -5
  152. data/lib/active_record/railties/databases.rake +139 -145
  153. data/lib/active_record/railties/job_runtime.rb +23 -0
  154. data/lib/active_record/readonly_attributes.rb +32 -5
  155. data/lib/active_record/reflection.rb +169 -45
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  157. data/lib/active_record/relation/batches.rb +190 -61
  158. data/lib/active_record/relation/calculations.rb +152 -63
  159. data/lib/active_record/relation/delegation.rb +22 -8
  160. data/lib/active_record/relation/finder_methods.rb +85 -15
  161. data/lib/active_record/relation/merger.rb +2 -0
  162. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  164. data/lib/active_record/relation/predicate_builder.rb +26 -14
  165. data/lib/active_record/relation/query_attribute.rb +2 -1
  166. data/lib/active_record/relation/query_methods.rb +351 -62
  167. data/lib/active_record/relation/spawn_methods.rb +18 -1
  168. data/lib/active_record/relation.rb +76 -35
  169. data/lib/active_record/result.rb +19 -5
  170. data/lib/active_record/runtime_registry.rb +10 -1
  171. data/lib/active_record/sanitization.rb +51 -11
  172. data/lib/active_record/schema.rb +2 -3
  173. data/lib/active_record/schema_dumper.rb +41 -7
  174. data/lib/active_record/schema_migration.rb +68 -33
  175. data/lib/active_record/scoping/default.rb +15 -5
  176. data/lib/active_record/scoping/named.rb +2 -2
  177. data/lib/active_record/scoping.rb +2 -1
  178. data/lib/active_record/secure_password.rb +60 -0
  179. data/lib/active_record/secure_token.rb +21 -3
  180. data/lib/active_record/signed_id.rb +7 -5
  181. data/lib/active_record/store.rb +8 -8
  182. data/lib/active_record/suppressor.rb +3 -1
  183. data/lib/active_record/table_metadata.rb +10 -1
  184. data/lib/active_record/tasks/database_tasks.rb +127 -105
  185. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  186. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  187. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -7
  188. data/lib/active_record/test_fixtures.rb +113 -96
  189. data/lib/active_record/timestamp.rb +26 -14
  190. data/lib/active_record/token_for.rb +113 -0
  191. data/lib/active_record/touch_later.rb +11 -6
  192. data/lib/active_record/transactions.rb +36 -10
  193. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  194. data/lib/active_record/type/internal/timezone.rb +7 -2
  195. data/lib/active_record/type/time.rb +4 -0
  196. data/lib/active_record/validations/absence.rb +1 -1
  197. data/lib/active_record/validations/numericality.rb +5 -4
  198. data/lib/active_record/validations/presence.rb +5 -28
  199. data/lib/active_record/validations/uniqueness.rb +47 -2
  200. data/lib/active_record/validations.rb +8 -4
  201. data/lib/active_record/version.rb +1 -1
  202. data/lib/active_record.rb +121 -16
  203. data/lib/arel/errors.rb +10 -0
  204. data/lib/arel/factory_methods.rb +4 -0
  205. data/lib/arel/nodes/binary.rb +6 -1
  206. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  207. data/lib/arel/nodes/cte.rb +36 -0
  208. data/lib/arel/nodes/fragments.rb +35 -0
  209. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  210. data/lib/arel/nodes/leading_join.rb +8 -0
  211. data/lib/arel/nodes/node.rb +111 -2
  212. data/lib/arel/nodes/sql_literal.rb +6 -0
  213. data/lib/arel/nodes/table_alias.rb +4 -0
  214. data/lib/arel/nodes.rb +4 -0
  215. data/lib/arel/predications.rb +2 -0
  216. data/lib/arel/table.rb +9 -5
  217. data/lib/arel/visitors/mysql.rb +8 -1
  218. data/lib/arel/visitors/to_sql.rb +81 -17
  219. data/lib/arel/visitors/visitor.rb +2 -2
  220. data/lib/arel.rb +16 -2
  221. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  222. data/lib/rails/generators/active_record/migration.rb +3 -1
  223. data/lib/rails/generators/active_record/model/USAGE +113 -0
  224. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  225. metadata +52 -17
  226. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  227. data/lib/active_record/null_relation.rb +0 -63
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
+ class ReadonlyAttributeError < ActiveRecordError
5
+ end
6
+
4
7
  module ReadonlyAttributes
5
8
  extend ActiveSupport::Concern
6
9
 
@@ -9,10 +12,11 @@ module ActiveRecord
9
12
  end
10
13
 
11
14
  module ClassMethods
12
- # Attributes listed as readonly will be used to create a new record but update operations will
13
- # ignore these fields.
15
+ # Attributes listed as readonly will be used to create a new record.
16
+ # Assigning a new value to a readonly attribute on a persisted record raises an error.
14
17
  #
15
- # You can assign a new value to a readonly attribute, but it will be ignored when the record is updated.
18
+ # By setting +config.active_record.raise_on_assign_to_attr_readonly+ to +false+, it will
19
+ # not raise. The value will change in memory, but will not be persisted on +save+.
16
20
  #
17
21
  # ==== Examples
18
22
  #
@@ -21,9 +25,14 @@ module ActiveRecord
21
25
  # end
22
26
  #
23
27
  # post = Post.create!(title: "Introducing Ruby on Rails!")
24
- # post.update(title: "a different title") # change to title will be ignored
28
+ # post.title = "a different title" # raises ActiveRecord::ReadonlyAttributeError
29
+ # post.update(title: "a different title") # raises ActiveRecord::ReadonlyAttributeError
25
30
  def attr_readonly(*attributes)
26
- self._attr_readonly = Set.new(attributes.map(&:to_s)) + (_attr_readonly || [])
31
+ self._attr_readonly |= attributes.map(&:to_s)
32
+
33
+ if ActiveRecord.raise_on_assign_to_attr_readonly
34
+ include(HasReadonlyAttributes)
35
+ end
27
36
  end
28
37
 
29
38
  # Returns an array of all the attributes that have been specified as readonly.
@@ -35,5 +44,23 @@ module ActiveRecord
35
44
  _attr_readonly.include?(name)
36
45
  end
37
46
  end
47
+
48
+ module HasReadonlyAttributes # :nodoc:
49
+ def write_attribute(attr_name, value)
50
+ if !new_record? && self.class.readonly_attribute?(attr_name.to_s)
51
+ raise ReadonlyAttributeError.new(attr_name)
52
+ end
53
+
54
+ super
55
+ end
56
+
57
+ def _write_attribute(attr_name, value)
58
+ if !new_record? && self.class.readonly_attribute?(attr_name.to_s)
59
+ raise ReadonlyAttributeError.new(attr_name)
60
+ end
61
+
62
+ super
63
+ end
64
+ end
38
65
  end
39
66
  end
@@ -46,6 +46,8 @@ module ActiveRecord
46
46
  end
47
47
  end
48
48
 
49
+ # = Active Record Reflection
50
+ #
49
51
  # \Reflection enables the ability to examine the associations and aggregations of
50
52
  # Active Record classes and objects. This information, for example,
51
53
  # can be used in a form builder that takes an Active Record object
@@ -128,6 +130,14 @@ module ActiveRecord
128
130
  def clear_reflections_cache # :nodoc:
129
131
  @__reflections = nil
130
132
  end
133
+
134
+ private
135
+ def inherited(subclass)
136
+ super
137
+ subclass.class_eval do
138
+ @__reflections = nil
139
+ end
140
+ end
131
141
  end
132
142
 
133
143
  # Holds all the methods that are shared between MacroReflection and ThroughReflection.
@@ -144,6 +154,14 @@ module ActiveRecord
144
154
  # PolymorphicReflection
145
155
  # RuntimeReflection
146
156
  class AbstractReflection # :nodoc:
157
+ def initialize
158
+ @class_name = nil
159
+ @counter_cache_column = nil
160
+ @inverse_of = nil
161
+ @inverse_which_updates_counter_cache_defined = false
162
+ @inverse_which_updates_counter_cache = nil
163
+ end
164
+
147
165
  def through_reflection?
148
166
  false
149
167
  end
@@ -183,10 +201,14 @@ module ActiveRecord
183
201
 
184
202
  scope_chain_items.inject(klass_scope, &:merge!)
185
203
 
186
- primary_key = join_primary_key
187
- foreign_key = join_foreign_key
204
+ primary_key_column_names = Array(join_primary_key)
205
+ foreign_key_column_names = Array(join_foreign_key)
206
+
207
+ primary_foreign_key_pairs = primary_key_column_names.zip(foreign_key_column_names)
188
208
 
189
- klass_scope.where!(table[primary_key].eq(foreign_table[foreign_key]))
209
+ primary_foreign_key_pairs.each do |primary_key_column_name, foreign_key_column_name|
210
+ klass_scope.where!(table[primary_key_column_name].eq(foreign_table[foreign_key_column_name]))
211
+ end
190
212
 
191
213
  if klass.finder_needs_type_condition?
192
214
  klass_scope.where!(klass.send(:type_condition, table))
@@ -231,11 +253,11 @@ module ActiveRecord
231
253
  end
232
254
 
233
255
  def check_validity_of_inverse!
234
- unless polymorphic?
235
- if has_inverse? && inverse_of.nil?
256
+ if !polymorphic? && has_inverse?
257
+ if inverse_of.nil?
236
258
  raise InverseOfAssociationNotFoundError.new(self)
237
259
  end
238
- if has_inverse? && inverse_of == self
260
+ if inverse_of == self
239
261
  raise InverseOfAssociationRecursiveError.new(self)
240
262
  end
241
263
  end
@@ -252,10 +274,16 @@ module ActiveRecord
252
274
  #
253
275
  # Hence this method.
254
276
  def inverse_which_updates_counter_cache
255
- return @inverse_which_updates_counter_cache if defined?(@inverse_which_updates_counter_cache)
256
- @inverse_which_updates_counter_cache = klass.reflect_on_all_associations(:belongs_to).find do |inverse|
257
- inverse.counter_cache_column == counter_cache_column
277
+ unless @inverse_which_updates_counter_cache_defined
278
+ if counter_cache_column
279
+ inverse_candidates = inverse_of ? [inverse_of] : klass.reflect_on_all_associations(:belongs_to)
280
+ @inverse_which_updates_counter_cache = inverse_candidates.find do |inverse|
281
+ inverse.counter_cache_column == counter_cache_column && (inverse.polymorphic? || inverse.klass == active_record)
282
+ end
283
+ end
284
+ @inverse_which_updates_counter_cache_defined = true
258
285
  end
286
+ @inverse_which_updates_counter_cache
259
287
  end
260
288
  alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
261
289
 
@@ -346,6 +374,7 @@ module ActiveRecord
346
374
  attr_reader :plural_name # :nodoc:
347
375
 
348
376
  def initialize(name, scope, options, active_record)
377
+ super()
349
378
  @name = name
350
379
  @scope = scope
351
380
  @options = options
@@ -423,23 +452,23 @@ module ActiveRecord
423
452
  raise ArgumentError, "Polymorphic associations do not support computing the class."
424
453
  end
425
454
 
426
- msg = <<-MSG.squish
427
- Rails couldn't find a valid model for #{name} association.
428
- Please provide the :class_name option on the association declaration.
429
- If :class_name is already provided, make sure it's an ActiveRecord::Base subclass.
430
- MSG
431
-
432
455
  begin
433
456
  klass = active_record.send(:compute_type, name)
434
-
435
- unless klass < ActiveRecord::Base
436
- raise ArgumentError, msg
457
+ rescue NameError => error
458
+ if error.name.match?(/(?:\A|::)#{name}\z/)
459
+ message = "Missing model class #{name} for the #{active_record}##{self.name} association."
460
+ message += " You can specify a different model class with the :class_name option." unless options[:class_name]
461
+ raise NameError.new(message, name)
462
+ else
463
+ raise
437
464
  end
465
+ end
438
466
 
439
- klass
440
- rescue NameError
441
- raise NameError, msg
467
+ unless klass < ActiveRecord::Base
468
+ raise ArgumentError, "The #{name} model class for the #{active_record}##{self.name} association is not an ActiveRecord::Base subclass."
442
469
  end
470
+
471
+ klass
443
472
  end
444
473
 
445
474
  attr_reader :type, :foreign_type
@@ -449,6 +478,10 @@ module ActiveRecord
449
478
  super
450
479
  @type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
451
480
  @foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
481
+ @join_table = nil
482
+ @foreign_key = nil
483
+ @association_foreign_key = nil
484
+ @association_primary_key = nil
452
485
 
453
486
  ensure_option_not_given_as_class!(:class_name)
454
487
  end
@@ -465,8 +498,20 @@ module ActiveRecord
465
498
  @join_table ||= -(options[:join_table]&.to_s || derive_join_table)
466
499
  end
467
500
 
468
- def foreign_key
469
- @foreign_key ||= -(options[:foreign_key]&.to_s || derive_foreign_key)
501
+ def foreign_key(infer_from_inverse_of: true)
502
+ @foreign_key ||= if options[:query_constraints]
503
+ options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze
504
+ elsif options[:foreign_key]
505
+ options[:foreign_key].to_s
506
+ else
507
+ derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)
508
+
509
+ if active_record.has_query_constraints?
510
+ derived_fk = derive_fk_query_constraints(active_record, derived_fk)
511
+ end
512
+
513
+ derived_fk
514
+ end
470
515
  end
471
516
 
472
517
  def association_foreign_key
@@ -478,7 +523,22 @@ module ActiveRecord
478
523
  end
479
524
 
480
525
  def active_record_primary_key
481
- @active_record_primary_key ||= -(options[:primary_key]&.to_s || primary_key(active_record))
526
+ custom_primary_key = options[:primary_key]
527
+ @active_record_primary_key ||= if custom_primary_key
528
+ if custom_primary_key.is_a?(Array)
529
+ custom_primary_key.map { |pk| pk.to_s.freeze }.freeze
530
+ else
531
+ custom_primary_key.to_s.freeze
532
+ end
533
+ elsif active_record.has_query_constraints? || options[:query_constraints]
534
+ active_record.query_constraints_list
535
+ elsif active_record.composite_primary_key?
536
+ # If active_record has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
537
+ primary_key = primary_key(active_record)
538
+ primary_key.include?("id") ? "id" : primary_key.freeze
539
+ else
540
+ primary_key(active_record).freeze
541
+ end
482
542
  end
483
543
 
484
544
  def join_primary_key(klass = nil)
@@ -495,6 +555,14 @@ module ActiveRecord
495
555
 
496
556
  def check_validity!
497
557
  check_validity_of_inverse!
558
+
559
+ if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
560
+ if (has_one? || collection?) && Array(active_record_primary_key).length != Array(foreign_key).length
561
+ raise CompositePrimaryKeyMismatchError.new(self)
562
+ elsif belongs_to? && Array(association_primary_key).length != Array(foreign_key).length
563
+ raise CompositePrimaryKeyMismatchError.new(self)
564
+ end
565
+ end
498
566
  end
499
567
 
500
568
  def check_eager_loadable!
@@ -510,7 +578,7 @@ module ActiveRecord
510
578
  end
511
579
 
512
580
  def join_id_for(owner) # :nodoc:
513
- owner[join_foreign_key]
581
+ Array(join_foreign_key).map { |key| owner._read_attribute(key) }
514
582
  end
515
583
 
516
584
  def through_reflection
@@ -631,7 +699,9 @@ module ActiveRecord
631
699
 
632
700
  begin
633
701
  reflection = klass._reflect_on_association(inverse_name)
634
- rescue NameError
702
+ rescue NameError => error
703
+ raise unless error.name.to_s == class_name
704
+
635
705
  # Give up: we couldn't compute the klass type so we won't be able
636
706
  # to find any associations either.
637
707
  reflection = false
@@ -688,16 +758,62 @@ module ActiveRecord
688
758
  class_name.camelize
689
759
  end
690
760
 
691
- def derive_foreign_key
761
+ def derive_foreign_key(infer_from_inverse_of: true)
692
762
  if belongs_to?
693
763
  "#{name}_id"
694
764
  elsif options[:as]
695
765
  "#{options[:as]}_id"
766
+ elsif options[:inverse_of] && infer_from_inverse_of
767
+ inverse_of.foreign_key(infer_from_inverse_of: false)
696
768
  else
697
769
  active_record.model_name.to_s.foreign_key
698
770
  end
699
771
  end
700
772
 
773
+ def derive_fk_query_constraints(klass, foreign_key)
774
+ primary_query_constraints = klass.query_constraints_list
775
+ owner_pk = klass.primary_key
776
+
777
+ if primary_query_constraints.size != 2
778
+ raise ArgumentError, <<~MSG.squish
779
+ The query constraints list on the `#{klass}` model has more than 2
780
+ attributes. Active Record is unable to derive the query constraints
781
+ for the association. You need to explicitly define the query constraints
782
+ for this association.
783
+ MSG
784
+ end
785
+
786
+ if !primary_query_constraints.include?(owner_pk)
787
+ raise ArgumentError, <<~MSG.squish
788
+ The query constraints on the `#{klass}` model does not include the primary
789
+ key so Active Record is unable to derive the foreign key constraints for
790
+ the association. You need to explicitly define the query constraints for this
791
+ association.
792
+ MSG
793
+ end
794
+
795
+ # The primary key and foreign key are both already in the query constraints
796
+ # so we don't want to derive the key. In this case we want a single key.
797
+ if primary_query_constraints.include?(owner_pk) && primary_query_constraints.include?(foreign_key)
798
+ return foreign_key
799
+ end
800
+
801
+ first_key, last_key = primary_query_constraints
802
+
803
+ if first_key == owner_pk
804
+ [foreign_key, last_key.to_s]
805
+ elsif last_key == owner_pk
806
+ [first_key.to_s, foreign_key]
807
+ else
808
+ raise ArgumentError, <<~MSG.squish
809
+ Active Record couldn't correctly interpret the query constraints
810
+ for the `#{klass}` model. The query constraints on `#{klass}` are
811
+ `#{primary_query_constraints}` and the foreign key is `#{foreign_key}`.
812
+ You need to explicitly set the query constraints for this association.
813
+ MSG
814
+ end
815
+ end
816
+
701
817
  def derive_join_table
702
818
  ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
703
819
  end
@@ -746,8 +862,14 @@ module ActiveRecord
746
862
 
747
863
  # klass option is necessary to support loading polymorphic associations
748
864
  def association_primary_key(klass = nil)
749
- if primary_key = options[:primary_key]
865
+ if !polymorphic? && ((klass || self.klass).has_query_constraints? || options[:query_constraints])
866
+ (klass || self.klass).composite_query_constraints_list
867
+ elsif primary_key = options[:primary_key]
750
868
  @association_primary_key ||= -primary_key.to_s
869
+ elsif (klass || self.klass).composite_primary_key?
870
+ # If klass has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
871
+ primary_key = (klass || self.klass).primary_key
872
+ primary_key.include?("id") ? "id" : primary_key
751
873
  else
752
874
  primary_key(klass || self.klass)
753
875
  end
@@ -786,6 +908,7 @@ module ActiveRecord
786
908
  :active_record_primary_key, :join_foreign_key, to: :source_reflection
787
909
 
788
910
  def initialize(delegate_reflection)
911
+ super()
789
912
  @delegate_reflection = delegate_reflection
790
913
  @klass = delegate_reflection.options[:anonymous_class]
791
914
  @source_reflection_name = delegate_reflection.options[:source]
@@ -919,24 +1042,23 @@ module ActiveRecord
919
1042
  end
920
1043
 
921
1044
  def source_reflection_name # :nodoc:
922
- return @source_reflection_name if @source_reflection_name
923
-
924
- names = [name.to_s.singularize, name].collect(&:to_sym).uniq
925
- names = names.find_all { |n|
926
- through_reflection.klass._reflect_on_association(n)
927
- }
928
-
929
- if names.length > 1
930
- raise AmbiguousSourceReflectionForThroughAssociation.new(
931
- active_record.name,
932
- macro,
933
- name,
934
- options,
935
- source_reflection_names
936
- )
1045
+ @source_reflection_name ||= begin
1046
+ names = [name.to_s.singularize, name].collect(&:to_sym).uniq
1047
+ names = names.find_all { |n|
1048
+ through_reflection.klass._reflect_on_association(n)
1049
+ }
1050
+
1051
+ if names.length > 1
1052
+ raise AmbiguousSourceReflectionForThroughAssociation.new(
1053
+ active_record.name,
1054
+ macro,
1055
+ name,
1056
+ options,
1057
+ source_reflection_names
1058
+ )
1059
+ end
1060
+ names.first
937
1061
  end
938
-
939
- @source_reflection_name = names.first
940
1062
  end
941
1063
 
942
1064
  def source_options
@@ -1040,6 +1162,7 @@ module ActiveRecord
1040
1162
  :name, :scope_for, to: :@reflection
1041
1163
 
1042
1164
  def initialize(reflection, previous_reflection)
1165
+ super()
1043
1166
  @reflection = reflection
1044
1167
  @previous_reflection = previous_reflection
1045
1168
  end
@@ -1065,6 +1188,7 @@ module ActiveRecord
1065
1188
  delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection
1066
1189
 
1067
1190
  def initialize(reflection, association)
1191
+ super()
1068
1192
  @reflection = reflection
1069
1193
  @association = association
1070
1194
  end
@@ -5,11 +5,13 @@ module ActiveRecord
5
5
  class BatchEnumerator
6
6
  include Enumerable
7
7
 
8
- def initialize(of: 1000, start: nil, finish: nil, relation:) # :nodoc:
8
+ def initialize(of: 1000, start: nil, finish: nil, relation:, order: :asc, use_ranges: nil) # :nodoc:
9
9
  @of = of
10
10
  @relation = relation
11
11
  @start = start
12
12
  @finish = finish
13
+ @order = order
14
+ @use_ranges = use_ranges
13
15
  end
14
16
 
15
17
  # The primary key value from which the BatchEnumerator starts, inclusive of the value.
@@ -50,7 +52,7 @@ module ActiveRecord
50
52
  def each_record(&block)
51
53
  return to_enum(:each_record) unless block_given?
52
54
 
53
- @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
55
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true, order: @order).each do |relation|
54
56
  relation.records.each(&block)
55
57
  end
56
58
  end
@@ -90,7 +92,7 @@ module ActiveRecord
90
92
  # relation.update_all(awesome: true)
91
93
  # end
92
94
  def each(&block)
93
- enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false)
95
+ enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false, order: @order, use_ranges: @use_ranges)
94
96
  return enum.each(&block) if block_given?
95
97
  enum
96
98
  end