activerecord 7.0.0 → 7.1.0

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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1607 -1040
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +17 -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 +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 -12
  15. data/lib/active_record/associations/collection_proxy.rb +22 -12
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +27 -17
  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 +20 -14
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +345 -219
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +40 -26
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +18 -5
  33. data/lib/active_record/attribute_methods/serialization.rb +172 -69
  34. data/lib/active_record/attribute_methods/write.rb +3 -3
  35. data/lib/active_record/attribute_methods.rb +110 -28
  36. data/lib/active_record/attributes.rb +3 -3
  37. data/lib/active_record/autosave_association.rb +56 -10
  38. data/lib/active_record/base.rb +10 -5
  39. data/lib/active_record/callbacks.rb +16 -32
  40. data/lib/active_record/coders/column_serializer.rb +61 -0
  41. data/lib/active_record/coders/json.rb +1 -1
  42. data/lib/active_record/coders/yaml_column.rb +70 -34
  43. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  46. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  47. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  48. data/lib/active_record/connection_adapters/abstract/database_statements.rb +128 -32
  49. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  50. data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
  51. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -129
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +504 -106
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +3 -2
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -45
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +3 -1
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  79. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  80. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  81. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +358 -57
  83. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +343 -181
  85. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +45 -39
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
  89. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
  92. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  93. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  94. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  95. data/lib/active_record/connection_adapters.rb +3 -1
  96. data/lib/active_record/connection_handling.rb +73 -96
  97. data/lib/active_record/core.rb +136 -148
  98. data/lib/active_record/counter_cache.rb +46 -25
  99. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  100. data/lib/active_record/database_configurations/database_config.rb +9 -3
  101. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  102. data/lib/active_record/database_configurations/url_config.rb +17 -11
  103. data/lib/active_record/database_configurations.rb +87 -34
  104. data/lib/active_record/delegated_type.rb +9 -4
  105. data/lib/active_record/deprecator.rb +7 -0
  106. data/lib/active_record/destroy_association_async_job.rb +2 -0
  107. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  108. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  109. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  110. data/lib/active_record/encryption/config.rb +25 -1
  111. data/lib/active_record/encryption/configurable.rb +13 -14
  112. data/lib/active_record/encryption/context.rb +10 -3
  113. data/lib/active_record/encryption/contexts.rb +8 -4
  114. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  115. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  116. data/lib/active_record/encryption/encryptable_record.rb +38 -22
  117. data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
  118. data/lib/active_record/encryption/encryptor.rb +7 -7
  119. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  120. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -71
  121. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  122. data/lib/active_record/encryption/key_generator.rb +12 -1
  123. data/lib/active_record/encryption/message.rb +1 -1
  124. data/lib/active_record/encryption/message_serializer.rb +2 -0
  125. data/lib/active_record/encryption/properties.rb +4 -4
  126. data/lib/active_record/encryption/scheme.rb +20 -23
  127. data/lib/active_record/encryption.rb +1 -0
  128. data/lib/active_record/enum.rb +114 -27
  129. data/lib/active_record/errors.rb +108 -15
  130. data/lib/active_record/explain.rb +23 -3
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  133. data/lib/active_record/fixture_set/render_context.rb +2 -0
  134. data/lib/active_record/fixture_set/table_row.rb +29 -8
  135. data/lib/active_record/fixtures.rb +121 -73
  136. data/lib/active_record/future_result.rb +30 -5
  137. data/lib/active_record/gem_version.rb +2 -2
  138. data/lib/active_record/inheritance.rb +30 -16
  139. data/lib/active_record/insert_all.rb +55 -8
  140. data/lib/active_record/integration.rb +10 -10
  141. data/lib/active_record/internal_metadata.rb +118 -30
  142. data/lib/active_record/locking/optimistic.rb +32 -18
  143. data/lib/active_record/locking/pessimistic.rb +8 -5
  144. data/lib/active_record/log_subscriber.rb +39 -17
  145. data/lib/active_record/marshalling.rb +56 -0
  146. data/lib/active_record/message_pack.rb +124 -0
  147. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  148. data/lib/active_record/middleware/database_selector.rb +18 -13
  149. data/lib/active_record/middleware/shard_selector.rb +7 -5
  150. data/lib/active_record/migration/command_recorder.rb +104 -9
  151. data/lib/active_record/migration/compatibility.rb +158 -64
  152. data/lib/active_record/migration/default_strategy.rb +23 -0
  153. data/lib/active_record/migration/execution_strategy.rb +19 -0
  154. data/lib/active_record/migration.rb +271 -117
  155. data/lib/active_record/model_schema.rb +82 -50
  156. data/lib/active_record/nested_attributes.rb +23 -3
  157. data/lib/active_record/normalization.rb +159 -0
  158. data/lib/active_record/persistence.rb +200 -47
  159. data/lib/active_record/promise.rb +84 -0
  160. data/lib/active_record/query_cache.rb +3 -21
  161. data/lib/active_record/query_logs.rb +87 -51
  162. data/lib/active_record/query_logs_formatter.rb +41 -0
  163. data/lib/active_record/querying.rb +16 -3
  164. data/lib/active_record/railtie.rb +127 -61
  165. data/lib/active_record/railties/controller_runtime.rb +12 -8
  166. data/lib/active_record/railties/databases.rake +142 -143
  167. data/lib/active_record/railties/job_runtime.rb +23 -0
  168. data/lib/active_record/readonly_attributes.rb +32 -5
  169. data/lib/active_record/reflection.rb +177 -45
  170. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  171. data/lib/active_record/relation/batches.rb +190 -61
  172. data/lib/active_record/relation/calculations.rb +200 -83
  173. data/lib/active_record/relation/delegation.rb +23 -9
  174. data/lib/active_record/relation/finder_methods.rb +77 -16
  175. data/lib/active_record/relation/merger.rb +2 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  177. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  179. data/lib/active_record/relation/predicate_builder.rb +26 -14
  180. data/lib/active_record/relation/query_attribute.rb +25 -1
  181. data/lib/active_record/relation/query_methods.rb +429 -76
  182. data/lib/active_record/relation/spawn_methods.rb +18 -1
  183. data/lib/active_record/relation.rb +98 -41
  184. data/lib/active_record/result.rb +25 -9
  185. data/lib/active_record/runtime_registry.rb +10 -1
  186. data/lib/active_record/sanitization.rb +57 -16
  187. data/lib/active_record/schema.rb +36 -22
  188. data/lib/active_record/schema_dumper.rb +65 -23
  189. data/lib/active_record/schema_migration.rb +68 -33
  190. data/lib/active_record/scoping/default.rb +20 -12
  191. data/lib/active_record/scoping/named.rb +2 -2
  192. data/lib/active_record/scoping.rb +2 -1
  193. data/lib/active_record/secure_password.rb +60 -0
  194. data/lib/active_record/secure_token.rb +21 -3
  195. data/lib/active_record/serialization.rb +5 -0
  196. data/lib/active_record/signed_id.rb +9 -7
  197. data/lib/active_record/store.rb +16 -11
  198. data/lib/active_record/suppressor.rb +3 -1
  199. data/lib/active_record/table_metadata.rb +16 -3
  200. data/lib/active_record/tasks/database_tasks.rb +138 -107
  201. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  202. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  203. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  204. data/lib/active_record/test_fixtures.rb +123 -99
  205. data/lib/active_record/timestamp.rb +26 -14
  206. data/lib/active_record/token_for.rb +113 -0
  207. data/lib/active_record/touch_later.rb +11 -6
  208. data/lib/active_record/transactions.rb +39 -13
  209. data/lib/active_record/translation.rb +1 -1
  210. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  211. data/lib/active_record/type/internal/timezone.rb +7 -2
  212. data/lib/active_record/type/serialized.rb +8 -4
  213. data/lib/active_record/type/time.rb +4 -0
  214. data/lib/active_record/validations/absence.rb +1 -1
  215. data/lib/active_record/validations/associated.rb +3 -3
  216. data/lib/active_record/validations/numericality.rb +5 -4
  217. data/lib/active_record/validations/presence.rb +5 -28
  218. data/lib/active_record/validations/uniqueness.rb +50 -5
  219. data/lib/active_record/validations.rb +8 -4
  220. data/lib/active_record/version.rb +1 -1
  221. data/lib/active_record.rb +143 -16
  222. data/lib/arel/errors.rb +10 -0
  223. data/lib/arel/factory_methods.rb +4 -0
  224. data/lib/arel/filter_predications.rb +1 -1
  225. data/lib/arel/nodes/and.rb +4 -0
  226. data/lib/arel/nodes/binary.rb +6 -1
  227. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  228. data/lib/arel/nodes/cte.rb +36 -0
  229. data/lib/arel/nodes/filter.rb +1 -1
  230. data/lib/arel/nodes/fragments.rb +35 -0
  231. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  232. data/lib/arel/nodes/leading_join.rb +8 -0
  233. data/lib/arel/nodes/node.rb +111 -2
  234. data/lib/arel/nodes/sql_literal.rb +6 -0
  235. data/lib/arel/nodes/table_alias.rb +4 -0
  236. data/lib/arel/nodes.rb +4 -0
  237. data/lib/arel/predications.rb +2 -0
  238. data/lib/arel/table.rb +9 -5
  239. data/lib/arel/visitors/mysql.rb +8 -1
  240. data/lib/arel/visitors/to_sql.rb +81 -17
  241. data/lib/arel/visitors/visitor.rb +2 -2
  242. data/lib/arel.rb +16 -2
  243. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  244. data/lib/rails/generators/active_record/migration.rb +3 -1
  245. data/lib/rails/generators/active_record/model/USAGE +113 -0
  246. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  247. metadata +50 -15
  248. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  249. data/lib/active_record/null_relation.rb +0 -63
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/runtime_registry"
4
+
5
+ module ActiveRecord
6
+ module Railties # :nodoc:
7
+ module JobRuntime # :nodoc:
8
+ private
9
+ def instrument(operation, payload = {}, &block)
10
+ if operation == :perform && block
11
+ super(operation, payload) do
12
+ db_runtime_before_perform = ActiveRecord::RuntimeRegistry.sql_runtime
13
+ result = block.call
14
+ payload[:db_runtime] = ActiveRecord::RuntimeRegistry.sql_runtime - db_runtime_before_perform
15
+ result
16
+ end
17
+ else
18
+ super
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -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
 
@@ -297,6 +325,12 @@ module ActiveRecord
297
325
  options[:strict_loading]
298
326
  end
299
327
 
328
+ def strict_loading_violation_message(owner)
329
+ message = +"`#{owner}` is marked for strict_loading."
330
+ message << " The #{polymorphic? ? "polymorphic association" : "#{klass} association"}"
331
+ message << " named `:#{name}` cannot be lazily loaded."
332
+ end
333
+
300
334
  protected
301
335
  def actual_source_reflection # FIXME: this is a horrible name
302
336
  self
@@ -340,6 +374,7 @@ module ActiveRecord
340
374
  attr_reader :plural_name # :nodoc:
341
375
 
342
376
  def initialize(name, scope, options, active_record)
377
+ super()
343
378
  @name = name
344
379
  @scope = scope
345
380
  @options = options
@@ -417,23 +452,23 @@ module ActiveRecord
417
452
  raise ArgumentError, "Polymorphic associations do not support computing the class."
418
453
  end
419
454
 
420
- msg = <<-MSG.squish
421
- Rails couldn't find a valid model for #{name} association.
422
- Please provide the :class_name option on the association declaration.
423
- If :class_name is already provided, make sure it's an ActiveRecord::Base subclass.
424
- MSG
425
-
426
455
  begin
427
456
  klass = active_record.send(:compute_type, name)
428
-
429
- unless klass < ActiveRecord::Base
430
- 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
431
464
  end
465
+ end
432
466
 
433
- klass
434
- rescue NameError
435
- 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."
436
469
  end
470
+
471
+ klass
437
472
  end
438
473
 
439
474
  attr_reader :type, :foreign_type
@@ -443,6 +478,10 @@ module ActiveRecord
443
478
  super
444
479
  @type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
445
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
446
485
 
447
486
  ensure_option_not_given_as_class!(:class_name)
448
487
  end
@@ -459,8 +498,20 @@ module ActiveRecord
459
498
  @join_table ||= -(options[:join_table]&.to_s || derive_join_table)
460
499
  end
461
500
 
462
- def foreign_key
463
- @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(derived_fk)
511
+ end
512
+
513
+ derived_fk
514
+ end
464
515
  end
465
516
 
466
517
  def association_foreign_key
@@ -472,19 +523,46 @@ module ActiveRecord
472
523
  end
473
524
 
474
525
  def active_record_primary_key
475
- @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
476
542
  end
477
543
 
478
544
  def join_primary_key(klass = nil)
479
545
  foreign_key
480
546
  end
481
547
 
548
+ def join_primary_type
549
+ type
550
+ end
551
+
482
552
  def join_foreign_key
483
553
  active_record_primary_key
484
554
  end
485
555
 
486
556
  def check_validity!
487
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
488
566
  end
489
567
 
490
568
  def check_eager_loadable!
@@ -500,7 +578,7 @@ module ActiveRecord
500
578
  end
501
579
 
502
580
  def join_id_for(owner) # :nodoc:
503
- owner[join_foreign_key]
581
+ Array(join_foreign_key).map { |key| owner._read_attribute(key) }
504
582
  end
505
583
 
506
584
  def through_reflection
@@ -582,6 +660,10 @@ module ActiveRecord
582
660
  options[:polymorphic]
583
661
  end
584
662
 
663
+ def polymorphic_name
664
+ active_record.polymorphic_name
665
+ end
666
+
585
667
  def add_as_source(seed)
586
668
  seed
587
669
  end
@@ -617,7 +699,9 @@ module ActiveRecord
617
699
 
618
700
  begin
619
701
  reflection = klass._reflect_on_association(inverse_name)
620
- rescue NameError
702
+ rescue NameError => error
703
+ raise unless error.name.to_s == class_name
704
+
621
705
  # Give up: we couldn't compute the klass type so we won't be able
622
706
  # to find any associations either.
623
707
  reflection = false
@@ -674,16 +758,56 @@ module ActiveRecord
674
758
  class_name.camelize
675
759
  end
676
760
 
677
- def derive_foreign_key
761
+ def derive_foreign_key(infer_from_inverse_of: true)
678
762
  if belongs_to?
679
763
  "#{name}_id"
680
764
  elsif options[:as]
681
765
  "#{options[:as]}_id"
766
+ elsif options[:inverse_of] && infer_from_inverse_of
767
+ inverse_of.foreign_key(infer_from_inverse_of: false)
682
768
  else
683
769
  active_record.model_name.to_s.foreign_key
684
770
  end
685
771
  end
686
772
 
773
+ def derive_fk_query_constraints(foreign_key)
774
+ primary_query_constraints = active_record.query_constraints_list
775
+ owner_pk = active_record.primary_key
776
+
777
+ if primary_query_constraints.size != 2
778
+ raise ArgumentError, <<~MSG.squish
779
+ The query constraints list on the `#{active_record}` 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 `#{active_record}` 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
+ first_key, last_key = primary_query_constraints
796
+
797
+ if first_key == owner_pk
798
+ [foreign_key, last_key.to_s]
799
+ elsif last_key == owner_pk
800
+ [first_key.to_s, foreign_key]
801
+ else
802
+ raise ArgumentError, <<~MSG.squish
803
+ Active Record couldn't correctly interpret the query constraints
804
+ for the `#{active_record}` model. The query constraints on `#{active_record}` are
805
+ `#{primary_query_constraints}` and the foreign key is `#{foreign_key}`.
806
+ You need to explicitly set the query constraints for this association.
807
+ MSG
808
+ end
809
+ end
810
+
687
811
  def derive_join_table
688
812
  ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
689
813
  end
@@ -734,6 +858,12 @@ module ActiveRecord
734
858
  def association_primary_key(klass = nil)
735
859
  if primary_key = options[:primary_key]
736
860
  @association_primary_key ||= -primary_key.to_s
861
+ elsif !polymorphic? && ((klass || self.klass).has_query_constraints? || options[:query_constraints])
862
+ (klass || self.klass).composite_query_constraints_list
863
+ elsif (klass || self.klass).composite_primary_key?
864
+ # If klass has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
865
+ primary_key = (klass || self.klass).primary_key
866
+ primary_key.include?("id") ? "id" : primary_key
737
867
  else
738
868
  primary_key(klass || self.klass)
739
869
  end
@@ -772,6 +902,7 @@ module ActiveRecord
772
902
  :active_record_primary_key, :join_foreign_key, to: :source_reflection
773
903
 
774
904
  def initialize(delegate_reflection)
905
+ super()
775
906
  @delegate_reflection = delegate_reflection
776
907
  @klass = delegate_reflection.options[:anonymous_class]
777
908
  @source_reflection_name = delegate_reflection.options[:source]
@@ -905,24 +1036,23 @@ module ActiveRecord
905
1036
  end
906
1037
 
907
1038
  def source_reflection_name # :nodoc:
908
- return @source_reflection_name if @source_reflection_name
909
-
910
- names = [name.to_s.singularize, name].collect(&:to_sym).uniq
911
- names = names.find_all { |n|
912
- through_reflection.klass._reflect_on_association(n)
913
- }
914
-
915
- if names.length > 1
916
- raise AmbiguousSourceReflectionForThroughAssociation.new(
917
- active_record.name,
918
- macro,
919
- name,
920
- options,
921
- source_reflection_names
922
- )
1039
+ @source_reflection_name ||= begin
1040
+ names = [name.to_s.singularize, name].collect(&:to_sym).uniq
1041
+ names = names.find_all { |n|
1042
+ through_reflection.klass._reflect_on_association(n)
1043
+ }
1044
+
1045
+ if names.length > 1
1046
+ raise AmbiguousSourceReflectionForThroughAssociation.new(
1047
+ active_record.name,
1048
+ macro,
1049
+ name,
1050
+ options,
1051
+ source_reflection_names
1052
+ )
1053
+ end
1054
+ names.first
923
1055
  end
924
-
925
- @source_reflection_name = names.first
926
1056
  end
927
1057
 
928
1058
  def source_options
@@ -1026,12 +1156,13 @@ module ActiveRecord
1026
1156
  :name, :scope_for, to: :@reflection
1027
1157
 
1028
1158
  def initialize(reflection, previous_reflection)
1159
+ super()
1029
1160
  @reflection = reflection
1030
1161
  @previous_reflection = previous_reflection
1031
1162
  end
1032
1163
 
1033
1164
  def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
1034
- scopes = @previous_reflection.join_scopes(table, predicate_builder, record) + super
1165
+ scopes = @previous_reflection.join_scopes(table, predicate_builder, klass, record) + super
1035
1166
  scopes << build_scope(table, predicate_builder, klass).instance_exec(record, &source_type_scope)
1036
1167
  end
1037
1168
 
@@ -1051,6 +1182,7 @@ module ActiveRecord
1051
1182
  delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection
1052
1183
 
1053
1184
  def initialize(reflection, association)
1185
+ super()
1054
1186
  @reflection = reflection
1055
1187
  @association = association
1056
1188
  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