activerecord 7.0.4 → 7.1.5.1

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 (246) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1971 -1243
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  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 +20 -14
  15. data/lib/active_record/associations/collection_proxy.rb +20 -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/join_association.rb +3 -2
  21. data/lib/active_record/associations/join_dependency.rb +10 -10
  22. data/lib/active_record/associations/preloader/association.rb +31 -7
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  24. data/lib/active_record/associations/preloader.rb +13 -10
  25. data/lib/active_record/associations/singular_association.rb +1 -1
  26. data/lib/active_record/associations/through_association.rb +22 -11
  27. data/lib/active_record/associations.rb +333 -222
  28. data/lib/active_record/attribute_assignment.rb +0 -2
  29. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  30. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  31. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  32. data/lib/active_record/attribute_methods/query.rb +28 -16
  33. data/lib/active_record/attribute_methods/read.rb +21 -8
  34. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -4
  36. data/lib/active_record/attribute_methods/write.rb +6 -6
  37. data/lib/active_record/attribute_methods.rb +148 -26
  38. data/lib/active_record/attributes.rb +3 -3
  39. data/lib/active_record/autosave_association.rb +59 -10
  40. data/lib/active_record/base.rb +7 -2
  41. data/lib/active_record/callbacks.rb +16 -32
  42. data/lib/active_record/coders/column_serializer.rb +61 -0
  43. data/lib/active_record/coders/json.rb +1 -1
  44. data/lib/active_record/coders/yaml_column.rb +70 -42
  45. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  46. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  47. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  48. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
  49. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -7
  53. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +155 -25
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +297 -127
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +509 -103
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +254 -125
  60. data/lib/active_record/connection_adapters/column.rb +9 -0
  61. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -14
  64. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  65. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  71. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  72. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
  74. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  78. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  81. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  82. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  83. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  84. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +365 -61
  85. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +354 -193
  87. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  88. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  89. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  90. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
  91. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +213 -85
  94. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  97. data/lib/active_record/connection_adapters.rb +3 -1
  98. data/lib/active_record/connection_handling.rb +72 -95
  99. data/lib/active_record/core.rb +181 -154
  100. data/lib/active_record/counter_cache.rb +52 -27
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  102. data/lib/active_record/database_configurations/database_config.rb +9 -3
  103. data/lib/active_record/database_configurations/hash_config.rb +28 -14
  104. data/lib/active_record/database_configurations/url_config.rb +17 -11
  105. data/lib/active_record/database_configurations.rb +86 -33
  106. data/lib/active_record/delegated_type.rb +15 -10
  107. data/lib/active_record/deprecator.rb +7 -0
  108. data/lib/active_record/destroy_association_async_job.rb +3 -1
  109. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  110. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  111. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  112. data/lib/active_record/encryption/config.rb +25 -1
  113. data/lib/active_record/encryption/configurable.rb +12 -19
  114. data/lib/active_record/encryption/context.rb +10 -3
  115. data/lib/active_record/encryption/contexts.rb +5 -1
  116. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  117. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  121. data/lib/active_record/encryption/key_generator.rb +12 -1
  122. data/lib/active_record/encryption/message_serializer.rb +2 -0
  123. data/lib/active_record/encryption/properties.rb +3 -3
  124. data/lib/active_record/encryption/scheme.rb +22 -21
  125. data/lib/active_record/encryption.rb +3 -0
  126. data/lib/active_record/enum.rb +112 -28
  127. data/lib/active_record/errors.rb +112 -18
  128. data/lib/active_record/explain.rb +23 -3
  129. data/lib/active_record/explain_subscriber.rb +1 -1
  130. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  131. data/lib/active_record/fixture_set/render_context.rb +2 -0
  132. data/lib/active_record/fixture_set/table_row.rb +29 -8
  133. data/lib/active_record/fixtures.rb +135 -71
  134. data/lib/active_record/future_result.rb +40 -5
  135. data/lib/active_record/gem_version.rb +4 -4
  136. data/lib/active_record/inheritance.rb +30 -16
  137. data/lib/active_record/insert_all.rb +57 -10
  138. data/lib/active_record/integration.rb +8 -8
  139. data/lib/active_record/internal_metadata.rb +120 -30
  140. data/lib/active_record/locking/optimistic.rb +33 -19
  141. data/lib/active_record/locking/pessimistic.rb +5 -2
  142. data/lib/active_record/log_subscriber.rb +29 -12
  143. data/lib/active_record/marshalling.rb +59 -0
  144. data/lib/active_record/message_pack.rb +124 -0
  145. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  146. data/lib/active_record/middleware/database_selector.rb +9 -11
  147. data/lib/active_record/middleware/shard_selector.rb +3 -1
  148. data/lib/active_record/migration/command_recorder.rb +105 -7
  149. data/lib/active_record/migration/compatibility.rb +163 -58
  150. data/lib/active_record/migration/default_strategy.rb +23 -0
  151. data/lib/active_record/migration/execution_strategy.rb +19 -0
  152. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  153. data/lib/active_record/migration.rb +271 -114
  154. data/lib/active_record/model_schema.rb +69 -44
  155. data/lib/active_record/nested_attributes.rb +37 -8
  156. data/lib/active_record/normalization.rb +167 -0
  157. data/lib/active_record/persistence.rb +195 -42
  158. data/lib/active_record/promise.rb +84 -0
  159. data/lib/active_record/query_cache.rb +4 -22
  160. data/lib/active_record/query_logs.rb +87 -51
  161. data/lib/active_record/query_logs_formatter.rb +41 -0
  162. data/lib/active_record/querying.rb +15 -2
  163. data/lib/active_record/railtie.rb +107 -45
  164. data/lib/active_record/railties/controller_runtime.rb +14 -9
  165. data/lib/active_record/railties/databases.rake +144 -150
  166. data/lib/active_record/railties/job_runtime.rb +23 -0
  167. data/lib/active_record/readonly_attributes.rb +32 -5
  168. data/lib/active_record/reflection.rb +189 -45
  169. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  170. data/lib/active_record/relation/batches.rb +190 -61
  171. data/lib/active_record/relation/calculations.rb +232 -81
  172. data/lib/active_record/relation/delegation.rb +23 -9
  173. data/lib/active_record/relation/finder_methods.rb +77 -16
  174. data/lib/active_record/relation/merger.rb +2 -0
  175. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  177. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  178. data/lib/active_record/relation/predicate_builder.rb +26 -14
  179. data/lib/active_record/relation/query_attribute.rb +25 -1
  180. data/lib/active_record/relation/query_methods.rb +408 -76
  181. data/lib/active_record/relation/spawn_methods.rb +18 -1
  182. data/lib/active_record/relation.rb +103 -37
  183. data/lib/active_record/result.rb +25 -9
  184. data/lib/active_record/runtime_registry.rb +24 -1
  185. data/lib/active_record/sanitization.rb +51 -11
  186. data/lib/active_record/schema.rb +2 -3
  187. data/lib/active_record/schema_dumper.rb +50 -7
  188. data/lib/active_record/schema_migration.rb +68 -33
  189. data/lib/active_record/scoping/default.rb +15 -5
  190. data/lib/active_record/scoping/named.rb +2 -2
  191. data/lib/active_record/scoping.rb +2 -1
  192. data/lib/active_record/secure_password.rb +60 -0
  193. data/lib/active_record/secure_token.rb +21 -3
  194. data/lib/active_record/signed_id.rb +7 -5
  195. data/lib/active_record/store.rb +9 -9
  196. data/lib/active_record/suppressor.rb +3 -1
  197. data/lib/active_record/table_metadata.rb +16 -3
  198. data/lib/active_record/tasks/database_tasks.rb +152 -108
  199. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  200. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  201. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  202. data/lib/active_record/test_fixtures.rb +114 -96
  203. data/lib/active_record/timestamp.rb +30 -16
  204. data/lib/active_record/token_for.rb +113 -0
  205. data/lib/active_record/touch_later.rb +11 -6
  206. data/lib/active_record/transactions.rb +39 -13
  207. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  208. data/lib/active_record/type/internal/timezone.rb +7 -2
  209. data/lib/active_record/type/serialized.rb +8 -4
  210. data/lib/active_record/type/time.rb +4 -0
  211. data/lib/active_record/validations/absence.rb +1 -1
  212. data/lib/active_record/validations/numericality.rb +5 -4
  213. data/lib/active_record/validations/presence.rb +5 -28
  214. data/lib/active_record/validations/uniqueness.rb +47 -2
  215. data/lib/active_record/validations.rb +8 -4
  216. data/lib/active_record/version.rb +1 -1
  217. data/lib/active_record.rb +130 -17
  218. data/lib/arel/errors.rb +10 -0
  219. data/lib/arel/factory_methods.rb +4 -0
  220. data/lib/arel/filter_predications.rb +1 -1
  221. data/lib/arel/nodes/and.rb +4 -0
  222. data/lib/arel/nodes/binary.rb +6 -1
  223. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  224. data/lib/arel/nodes/cte.rb +36 -0
  225. data/lib/arel/nodes/filter.rb +1 -1
  226. data/lib/arel/nodes/fragments.rb +35 -0
  227. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  228. data/lib/arel/nodes/leading_join.rb +8 -0
  229. data/lib/arel/nodes/node.rb +111 -2
  230. data/lib/arel/nodes/sql_literal.rb +6 -0
  231. data/lib/arel/nodes/table_alias.rb +4 -0
  232. data/lib/arel/nodes.rb +4 -0
  233. data/lib/arel/predications.rb +2 -0
  234. data/lib/arel/table.rb +9 -5
  235. data/lib/arel/tree_manager.rb +5 -1
  236. data/lib/arel/visitors/mysql.rb +8 -1
  237. data/lib/arel/visitors/to_sql.rb +83 -18
  238. data/lib/arel/visitors/visitor.rb +2 -2
  239. data/lib/arel.rb +16 -2
  240. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  241. data/lib/rails/generators/active_record/migration.rb +3 -1
  242. data/lib/rails/generators/active_record/model/USAGE +113 -0
  243. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  244. metadata +51 -15
  245. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  246. 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)
188
206
 
189
- klass_scope.where!(table[primary_key].eq(foreign_table[foreign_key]))
207
+ primary_foreign_key_pairs = primary_key_column_names.zip(foreign_key_column_names)
208
+
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,19 +535,46 @@ 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)
485
557
  foreign_key
486
558
  end
487
559
 
560
+ def join_primary_type
561
+ type
562
+ end
563
+
488
564
  def join_foreign_key
489
565
  active_record_primary_key
490
566
  end
491
567
 
492
568
  def check_validity!
493
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
494
578
  end
495
579
 
496
580
  def check_eager_loadable!
@@ -506,7 +590,7 @@ module ActiveRecord
506
590
  end
507
591
 
508
592
  def join_id_for(owner) # :nodoc:
509
- owner[join_foreign_key]
593
+ Array(join_foreign_key).map { |key| owner._read_attribute(key) }
510
594
  end
511
595
 
512
596
  def through_reflection
@@ -588,6 +672,10 @@ module ActiveRecord
588
672
  options[:polymorphic]
589
673
  end
590
674
 
675
+ def polymorphic_name
676
+ active_record.polymorphic_name
677
+ end
678
+
591
679
  def add_as_source(seed)
592
680
  seed
593
681
  end
@@ -623,7 +711,9 @@ module ActiveRecord
623
711
 
624
712
  begin
625
713
  reflection = klass._reflect_on_association(inverse_name)
626
- rescue NameError
714
+ rescue NameError => error
715
+ raise unless error.name.to_s == class_name
716
+
627
717
  # Give up: we couldn't compute the klass type so we won't be able
628
718
  # to find any associations either.
629
719
  reflection = false
@@ -680,16 +770,58 @@ module ActiveRecord
680
770
  class_name.camelize
681
771
  end
682
772
 
683
- def derive_foreign_key
773
+ def derive_foreign_key(infer_from_inverse_of: true)
684
774
  if belongs_to?
685
775
  "#{name}_id"
686
776
  elsif options[:as]
687
777
  "#{options[:as]}_id"
778
+ elsif options[:inverse_of] && infer_from_inverse_of
779
+ inverse_of.foreign_key(infer_from_inverse_of: false)
688
780
  else
689
781
  active_record.model_name.to_s.foreign_key
690
782
  end
691
783
  end
692
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
+ return foreign_key if primary_query_constraints.include?(foreign_key)
808
+
809
+ first_key, last_key = primary_query_constraints
810
+
811
+ if first_key == owner_pk
812
+ [foreign_key, last_key.to_s]
813
+ elsif last_key == owner_pk
814
+ [first_key.to_s, foreign_key]
815
+ else
816
+ raise ArgumentError, <<~MSG.squish
817
+ Active Record couldn't correctly interpret the query constraints
818
+ for the `#{active_record}` model. The query constraints on `#{active_record}` are
819
+ `#{primary_query_constraints}` and the foreign key is `#{foreign_key}`.
820
+ You need to explicitly set the query constraints for this association.
821
+ MSG
822
+ end
823
+ end
824
+
693
825
  def derive_join_table
694
826
  ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
695
827
  end
@@ -739,7 +871,17 @@ module ActiveRecord
739
871
  # klass option is necessary to support loading polymorphic associations
740
872
  def association_primary_key(klass = nil)
741
873
  if primary_key = options[:primary_key]
742
- @association_primary_key ||= -primary_key.to_s
874
+ @association_primary_key ||= if primary_key.is_a?(Array)
875
+ primary_key.map { |pk| pk.to_s.freeze }.freeze
876
+ else
877
+ -primary_key.to_s
878
+ end
879
+ elsif (klass || self.klass).has_query_constraints? || options[:query_constraints]
880
+ (klass || self.klass).composite_query_constraints_list
881
+ elsif (klass || self.klass).composite_primary_key?
882
+ # If klass has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
883
+ primary_key = (klass || self.klass).primary_key
884
+ primary_key.include?("id") ? "id" : primary_key
743
885
  else
744
886
  primary_key(klass || self.klass)
745
887
  end
@@ -778,6 +920,7 @@ module ActiveRecord
778
920
  :active_record_primary_key, :join_foreign_key, to: :source_reflection
779
921
 
780
922
  def initialize(delegate_reflection)
923
+ super()
781
924
  @delegate_reflection = delegate_reflection
782
925
  @klass = delegate_reflection.options[:anonymous_class]
783
926
  @source_reflection_name = delegate_reflection.options[:source]
@@ -911,24 +1054,23 @@ module ActiveRecord
911
1054
  end
912
1055
 
913
1056
  def source_reflection_name # :nodoc:
914
- return @source_reflection_name if @source_reflection_name
915
-
916
- names = [name.to_s.singularize, name].collect(&:to_sym).uniq
917
- names = names.find_all { |n|
918
- through_reflection.klass._reflect_on_association(n)
919
- }
920
-
921
- if names.length > 1
922
- raise AmbiguousSourceReflectionForThroughAssociation.new(
923
- active_record.name,
924
- macro,
925
- name,
926
- options,
927
- source_reflection_names
928
- )
1057
+ @source_reflection_name ||= begin
1058
+ names = [name.to_s.singularize, name].collect(&:to_sym).uniq
1059
+ names = names.find_all { |n|
1060
+ through_reflection.klass._reflect_on_association(n)
1061
+ }
1062
+
1063
+ if names.length > 1
1064
+ raise AmbiguousSourceReflectionForThroughAssociation.new(
1065
+ active_record.name,
1066
+ macro,
1067
+ name,
1068
+ options,
1069
+ source_reflection_names
1070
+ )
1071
+ end
1072
+ names.first
929
1073
  end
930
-
931
- @source_reflection_name = names.first
932
1074
  end
933
1075
 
934
1076
  def source_options
@@ -1032,6 +1174,7 @@ module ActiveRecord
1032
1174
  :name, :scope_for, to: :@reflection
1033
1175
 
1034
1176
  def initialize(reflection, previous_reflection)
1177
+ super()
1035
1178
  @reflection = reflection
1036
1179
  @previous_reflection = previous_reflection
1037
1180
  end
@@ -1057,6 +1200,7 @@ module ActiveRecord
1057
1200
  delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection
1058
1201
 
1059
1202
  def initialize(reflection, association)
1203
+ super()
1060
1204
  @reflection = reflection
1061
1205
  @association = association
1062
1206
  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