activerecord 7.0.8 → 7.1.3.4

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 (231) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1554 -1452
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +16 -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 +20 -4
  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 +15 -9
  15. data/lib/active_record/associations/collection_proxy.rb +15 -10
  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 +31 -7
  22. data/lib/active_record/associations/preloader.rb +13 -10
  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 +313 -217
  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 +52 -34
  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 +74 -51
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  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 -124
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -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 +18 -13
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -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 +14 -3
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
  71. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  72. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  73. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
  75. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  76. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  77. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  78. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +361 -60
  80. data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
  81. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  82. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  83. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  84. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  85. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  86. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  87. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +209 -79
  88. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  89. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  90. data/lib/active_record/connection_adapters/trilogy_adapter.rb +262 -0
  91. data/lib/active_record/connection_adapters.rb +3 -1
  92. data/lib/active_record/connection_handling.rb +72 -95
  93. data/lib/active_record/core.rb +175 -153
  94. data/lib/active_record/counter_cache.rb +46 -25
  95. data/lib/active_record/database_configurations/database_config.rb +9 -3
  96. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  97. data/lib/active_record/database_configurations/url_config.rb +17 -11
  98. data/lib/active_record/database_configurations.rb +86 -33
  99. data/lib/active_record/delegated_type.rb +9 -4
  100. data/lib/active_record/deprecator.rb +7 -0
  101. data/lib/active_record/destroy_association_async_job.rb +2 -0
  102. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  103. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  104. data/lib/active_record/encryption/config.rb +25 -1
  105. data/lib/active_record/encryption/configurable.rb +12 -19
  106. data/lib/active_record/encryption/context.rb +10 -3
  107. data/lib/active_record/encryption/contexts.rb +5 -1
  108. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  109. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +21 -6
  111. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  112. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  113. data/lib/active_record/encryption/key_generator.rb +12 -1
  114. data/lib/active_record/encryption/message_serializer.rb +2 -0
  115. data/lib/active_record/encryption/properties.rb +3 -3
  116. data/lib/active_record/encryption/scheme.rb +19 -22
  117. data/lib/active_record/encryption.rb +1 -0
  118. data/lib/active_record/enum.rb +112 -28
  119. data/lib/active_record/errors.rb +112 -18
  120. data/lib/active_record/explain.rb +23 -3
  121. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  122. data/lib/active_record/fixture_set/render_context.rb +2 -0
  123. data/lib/active_record/fixture_set/table_row.rb +29 -8
  124. data/lib/active_record/fixtures.rb +135 -71
  125. data/lib/active_record/future_result.rb +31 -5
  126. data/lib/active_record/gem_version.rb +4 -4
  127. data/lib/active_record/inheritance.rb +30 -16
  128. data/lib/active_record/insert_all.rb +57 -10
  129. data/lib/active_record/integration.rb +8 -8
  130. data/lib/active_record/internal_metadata.rb +120 -30
  131. data/lib/active_record/locking/pessimistic.rb +5 -2
  132. data/lib/active_record/log_subscriber.rb +29 -12
  133. data/lib/active_record/marshalling.rb +56 -0
  134. data/lib/active_record/message_pack.rb +124 -0
  135. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  136. data/lib/active_record/middleware/database_selector.rb +6 -8
  137. data/lib/active_record/middleware/shard_selector.rb +3 -1
  138. data/lib/active_record/migration/command_recorder.rb +104 -5
  139. data/lib/active_record/migration/compatibility.rb +139 -5
  140. data/lib/active_record/migration/default_strategy.rb +23 -0
  141. data/lib/active_record/migration/execution_strategy.rb +19 -0
  142. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  143. data/lib/active_record/migration.rb +219 -111
  144. data/lib/active_record/model_schema.rb +64 -44
  145. data/lib/active_record/nested_attributes.rb +24 -6
  146. data/lib/active_record/normalization.rb +167 -0
  147. data/lib/active_record/persistence.rb +188 -37
  148. data/lib/active_record/promise.rb +84 -0
  149. data/lib/active_record/query_cache.rb +3 -21
  150. data/lib/active_record/query_logs.rb +77 -52
  151. data/lib/active_record/query_logs_formatter.rb +41 -0
  152. data/lib/active_record/querying.rb +15 -2
  153. data/lib/active_record/railtie.rb +109 -47
  154. data/lib/active_record/railties/controller_runtime.rb +12 -6
  155. data/lib/active_record/railties/databases.rake +142 -148
  156. data/lib/active_record/railties/job_runtime.rb +23 -0
  157. data/lib/active_record/readonly_attributes.rb +32 -5
  158. data/lib/active_record/reflection.rb +174 -44
  159. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  160. data/lib/active_record/relation/batches.rb +190 -61
  161. data/lib/active_record/relation/calculations.rb +187 -63
  162. data/lib/active_record/relation/delegation.rb +23 -9
  163. data/lib/active_record/relation/finder_methods.rb +77 -16
  164. data/lib/active_record/relation/merger.rb +2 -0
  165. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  166. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  167. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  168. data/lib/active_record/relation/predicate_builder.rb +26 -14
  169. data/lib/active_record/relation/query_attribute.rb +2 -1
  170. data/lib/active_record/relation/query_methods.rb +352 -63
  171. data/lib/active_record/relation/spawn_methods.rb +18 -1
  172. data/lib/active_record/relation.rb +91 -35
  173. data/lib/active_record/result.rb +19 -5
  174. data/lib/active_record/runtime_registry.rb +24 -1
  175. data/lib/active_record/sanitization.rb +51 -11
  176. data/lib/active_record/schema.rb +2 -3
  177. data/lib/active_record/schema_dumper.rb +46 -7
  178. data/lib/active_record/schema_migration.rb +68 -33
  179. data/lib/active_record/scoping/default.rb +15 -5
  180. data/lib/active_record/scoping/named.rb +2 -2
  181. data/lib/active_record/scoping.rb +2 -1
  182. data/lib/active_record/secure_password.rb +60 -0
  183. data/lib/active_record/secure_token.rb +21 -3
  184. data/lib/active_record/signed_id.rb +7 -5
  185. data/lib/active_record/store.rb +8 -8
  186. data/lib/active_record/suppressor.rb +3 -1
  187. data/lib/active_record/table_metadata.rb +10 -1
  188. data/lib/active_record/tasks/database_tasks.rb +127 -105
  189. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  190. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  191. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  192. data/lib/active_record/test_fixtures.rb +113 -96
  193. data/lib/active_record/timestamp.rb +27 -15
  194. data/lib/active_record/token_for.rb +113 -0
  195. data/lib/active_record/touch_later.rb +11 -6
  196. data/lib/active_record/transactions.rb +36 -10
  197. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  198. data/lib/active_record/type/internal/timezone.rb +7 -2
  199. data/lib/active_record/type/time.rb +4 -0
  200. data/lib/active_record/validations/absence.rb +1 -1
  201. data/lib/active_record/validations/numericality.rb +5 -4
  202. data/lib/active_record/validations/presence.rb +5 -28
  203. data/lib/active_record/validations/uniqueness.rb +47 -2
  204. data/lib/active_record/validations.rb +8 -4
  205. data/lib/active_record/version.rb +1 -1
  206. data/lib/active_record.rb +121 -16
  207. data/lib/arel/errors.rb +10 -0
  208. data/lib/arel/factory_methods.rb +4 -0
  209. data/lib/arel/nodes/binary.rb +6 -1
  210. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  211. data/lib/arel/nodes/cte.rb +36 -0
  212. data/lib/arel/nodes/fragments.rb +35 -0
  213. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  214. data/lib/arel/nodes/leading_join.rb +8 -0
  215. data/lib/arel/nodes/node.rb +111 -2
  216. data/lib/arel/nodes/sql_literal.rb +6 -0
  217. data/lib/arel/nodes/table_alias.rb +4 -0
  218. data/lib/arel/nodes.rb +4 -0
  219. data/lib/arel/predications.rb +2 -0
  220. data/lib/arel/table.rb +9 -5
  221. data/lib/arel/visitors/mysql.rb +8 -1
  222. data/lib/arel/visitors/to_sql.rb +81 -17
  223. data/lib/arel/visitors/visitor.rb +2 -2
  224. data/lib/arel.rb +16 -2
  225. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  226. data/lib/rails/generators/active_record/migration.rb +3 -1
  227. data/lib/rails/generators/active_record/model/USAGE +113 -0
  228. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  229. metadata +48 -12
  230. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  231. 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
@@ -353,6 +382,7 @@ module ActiveRecord
353
382
  @klass = options[:anonymous_class]
354
383
  @plural_name = active_record.pluralize_table_names ?
355
384
  name.to_s.pluralize : name.to_s
385
+ validate_reflection!
356
386
  end
357
387
 
358
388
  def autosave=(autosave)
@@ -404,6 +434,17 @@ module ActiveRecord
404
434
  def derive_class_name
405
435
  name.to_s.camelize
406
436
  end
437
+
438
+ def validate_reflection!
439
+ return unless options[:foreign_key].is_a?(Array)
440
+
441
+ message = <<~MSG.squish
442
+ Passing #{options[:foreign_key]} array to :foreign_key option
443
+ on the #{active_record}##{name} association is not supported.
444
+ Use the query_constraints: #{options[:foreign_key]} option instead to represent a composite foreign key.
445
+ MSG
446
+ raise ArgumentError, message
447
+ end
407
448
  end
408
449
 
409
450
  # Holds all the metadata about an aggregation as it was specified in the
@@ -423,23 +464,23 @@ module ActiveRecord
423
464
  raise ArgumentError, "Polymorphic associations do not support computing the class."
424
465
  end
425
466
 
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
467
  begin
433
468
  klass = active_record.send(:compute_type, name)
434
-
435
- unless klass < ActiveRecord::Base
436
- raise ArgumentError, msg
469
+ rescue NameError => error
470
+ if error.name.match?(/(?:\A|::)#{name}\z/)
471
+ message = "Missing model class #{name} for the #{active_record}##{self.name} association."
472
+ message += " You can specify a different model class with the :class_name option." unless options[:class_name]
473
+ raise NameError.new(message, name)
474
+ else
475
+ raise
437
476
  end
477
+ end
438
478
 
439
- klass
440
- rescue NameError
441
- raise NameError, msg
479
+ unless klass < ActiveRecord::Base
480
+ raise ArgumentError, "The #{name} model class for the #{active_record}##{self.name} association is not an ActiveRecord::Base subclass."
442
481
  end
482
+
483
+ klass
443
484
  end
444
485
 
445
486
  attr_reader :type, :foreign_type
@@ -449,6 +490,10 @@ module ActiveRecord
449
490
  super
450
491
  @type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
451
492
  @foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
493
+ @join_table = nil
494
+ @foreign_key = nil
495
+ @association_foreign_key = nil
496
+ @association_primary_key = nil
452
497
 
453
498
  ensure_option_not_given_as_class!(:class_name)
454
499
  end
@@ -465,8 +510,20 @@ module ActiveRecord
465
510
  @join_table ||= -(options[:join_table]&.to_s || derive_join_table)
466
511
  end
467
512
 
468
- def foreign_key
469
- @foreign_key ||= -(options[:foreign_key]&.to_s || derive_foreign_key)
513
+ def foreign_key(infer_from_inverse_of: true)
514
+ @foreign_key ||= if options[:query_constraints]
515
+ options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze
516
+ elsif options[:foreign_key]
517
+ options[:foreign_key].to_s
518
+ else
519
+ derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)
520
+
521
+ if active_record.has_query_constraints?
522
+ derived_fk = derive_fk_query_constraints(derived_fk)
523
+ end
524
+
525
+ derived_fk
526
+ end
470
527
  end
471
528
 
472
529
  def association_foreign_key
@@ -478,7 +535,22 @@ module ActiveRecord
478
535
  end
479
536
 
480
537
  def active_record_primary_key
481
- @active_record_primary_key ||= -(options[:primary_key]&.to_s || primary_key(active_record))
538
+ custom_primary_key = options[:primary_key]
539
+ @active_record_primary_key ||= if custom_primary_key
540
+ if custom_primary_key.is_a?(Array)
541
+ custom_primary_key.map { |pk| pk.to_s.freeze }.freeze
542
+ else
543
+ custom_primary_key.to_s.freeze
544
+ end
545
+ elsif active_record.has_query_constraints? || options[:query_constraints]
546
+ active_record.query_constraints_list
547
+ elsif active_record.composite_primary_key?
548
+ # If active_record has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
549
+ primary_key = primary_key(active_record)
550
+ primary_key.include?("id") ? "id" : primary_key.freeze
551
+ else
552
+ primary_key(active_record).freeze
553
+ end
482
554
  end
483
555
 
484
556
  def join_primary_key(klass = nil)
@@ -495,6 +567,14 @@ module ActiveRecord
495
567
 
496
568
  def check_validity!
497
569
  check_validity_of_inverse!
570
+
571
+ if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
572
+ if (has_one? || collection?) && Array(active_record_primary_key).length != Array(foreign_key).length
573
+ raise CompositePrimaryKeyMismatchError.new(self)
574
+ elsif belongs_to? && Array(association_primary_key).length != Array(foreign_key).length
575
+ raise CompositePrimaryKeyMismatchError.new(self)
576
+ end
577
+ end
498
578
  end
499
579
 
500
580
  def check_eager_loadable!
@@ -510,7 +590,7 @@ module ActiveRecord
510
590
  end
511
591
 
512
592
  def join_id_for(owner) # :nodoc:
513
- owner[join_foreign_key]
593
+ Array(join_foreign_key).map { |key| owner._read_attribute(key) }
514
594
  end
515
595
 
516
596
  def through_reflection
@@ -631,7 +711,9 @@ module ActiveRecord
631
711
 
632
712
  begin
633
713
  reflection = klass._reflect_on_association(inverse_name)
634
- rescue NameError
714
+ rescue NameError => error
715
+ raise unless error.name.to_s == class_name
716
+
635
717
  # Give up: we couldn't compute the klass type so we won't be able
636
718
  # to find any associations either.
637
719
  reflection = false
@@ -688,16 +770,56 @@ module ActiveRecord
688
770
  class_name.camelize
689
771
  end
690
772
 
691
- def derive_foreign_key
773
+ def derive_foreign_key(infer_from_inverse_of: true)
692
774
  if belongs_to?
693
775
  "#{name}_id"
694
776
  elsif options[:as]
695
777
  "#{options[:as]}_id"
778
+ elsif options[:inverse_of] && infer_from_inverse_of
779
+ inverse_of.foreign_key(infer_from_inverse_of: false)
696
780
  else
697
781
  active_record.model_name.to_s.foreign_key
698
782
  end
699
783
  end
700
784
 
785
+ def derive_fk_query_constraints(foreign_key)
786
+ primary_query_constraints = active_record.query_constraints_list
787
+ owner_pk = active_record.primary_key
788
+
789
+ if primary_query_constraints.size != 2
790
+ raise ArgumentError, <<~MSG.squish
791
+ The query constraints list on the `#{active_record}` model has more than 2
792
+ attributes. Active Record is unable to derive the query constraints
793
+ for the association. You need to explicitly define the query constraints
794
+ for this association.
795
+ MSG
796
+ end
797
+
798
+ if !primary_query_constraints.include?(owner_pk)
799
+ raise ArgumentError, <<~MSG.squish
800
+ The query constraints on the `#{active_record}` model does not include the primary
801
+ key so Active Record is unable to derive the foreign key constraints for
802
+ the association. You need to explicitly define the query constraints for this
803
+ association.
804
+ MSG
805
+ end
806
+
807
+ first_key, last_key = primary_query_constraints
808
+
809
+ if first_key == owner_pk
810
+ [foreign_key, last_key.to_s]
811
+ elsif last_key == owner_pk
812
+ [first_key.to_s, foreign_key]
813
+ else
814
+ raise ArgumentError, <<~MSG.squish
815
+ Active Record couldn't correctly interpret the query constraints
816
+ for the `#{active_record}` model. The query constraints on `#{active_record}` are
817
+ `#{primary_query_constraints}` and the foreign key is `#{foreign_key}`.
818
+ You need to explicitly set the query constraints for this association.
819
+ MSG
820
+ end
821
+ end
822
+
701
823
  def derive_join_table
702
824
  ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
703
825
  end
@@ -748,6 +870,12 @@ module ActiveRecord
748
870
  def association_primary_key(klass = nil)
749
871
  if primary_key = options[:primary_key]
750
872
  @association_primary_key ||= -primary_key.to_s
873
+ elsif (klass || self.klass).has_query_constraints? || options[:query_constraints]
874
+ (klass || self.klass).composite_query_constraints_list
875
+ elsif (klass || self.klass).composite_primary_key?
876
+ # If klass has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
877
+ primary_key = (klass || self.klass).primary_key
878
+ primary_key.include?("id") ? "id" : primary_key
751
879
  else
752
880
  primary_key(klass || self.klass)
753
881
  end
@@ -786,6 +914,7 @@ module ActiveRecord
786
914
  :active_record_primary_key, :join_foreign_key, to: :source_reflection
787
915
 
788
916
  def initialize(delegate_reflection)
917
+ super()
789
918
  @delegate_reflection = delegate_reflection
790
919
  @klass = delegate_reflection.options[:anonymous_class]
791
920
  @source_reflection_name = delegate_reflection.options[:source]
@@ -919,24 +1048,23 @@ module ActiveRecord
919
1048
  end
920
1049
 
921
1050
  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
- )
1051
+ @source_reflection_name ||= begin
1052
+ names = [name.to_s.singularize, name].collect(&:to_sym).uniq
1053
+ names = names.find_all { |n|
1054
+ through_reflection.klass._reflect_on_association(n)
1055
+ }
1056
+
1057
+ if names.length > 1
1058
+ raise AmbiguousSourceReflectionForThroughAssociation.new(
1059
+ active_record.name,
1060
+ macro,
1061
+ name,
1062
+ options,
1063
+ source_reflection_names
1064
+ )
1065
+ end
1066
+ names.first
937
1067
  end
938
-
939
- @source_reflection_name = names.first
940
1068
  end
941
1069
 
942
1070
  def source_options
@@ -1040,6 +1168,7 @@ module ActiveRecord
1040
1168
  :name, :scope_for, to: :@reflection
1041
1169
 
1042
1170
  def initialize(reflection, previous_reflection)
1171
+ super()
1043
1172
  @reflection = reflection
1044
1173
  @previous_reflection = previous_reflection
1045
1174
  end
@@ -1065,6 +1194,7 @@ module ActiveRecord
1065
1194
  delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection
1066
1195
 
1067
1196
  def initialize(reflection, association)
1197
+ super()
1068
1198
  @reflection = reflection
1069
1199
  @association = association
1070
1200
  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