activerecord 7.0.0 → 7.1.2

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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1701 -1039
  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 +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 +362 -236
  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 +52 -34
  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 +129 -31
  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 -131
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +513 -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 +151 -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 +16 -3
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -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/money.rb +3 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +372 -63
  84. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  85. data/lib/active_record/connection_adapters/postgresql_adapter.rb +359 -197
  86. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  87. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
  93. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  94. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  95. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  96. data/lib/active_record/connection_adapters.rb +3 -1
  97. data/lib/active_record/connection_handling.rb +73 -96
  98. data/lib/active_record/core.rb +142 -153
  99. data/lib/active_record/counter_cache.rb +46 -25
  100. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  101. data/lib/active_record/database_configurations/database_config.rb +9 -3
  102. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  103. data/lib/active_record/database_configurations/url_config.rb +17 -11
  104. data/lib/active_record/database_configurations.rb +87 -34
  105. data/lib/active_record/delegated_type.rb +9 -4
  106. data/lib/active_record/deprecator.rb +7 -0
  107. data/lib/active_record/destroy_association_async_job.rb +2 -0
  108. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  109. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  110. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  111. data/lib/active_record/encryption/config.rb +25 -1
  112. data/lib/active_record/encryption/configurable.rb +13 -14
  113. data/lib/active_record/encryption/context.rb +10 -3
  114. data/lib/active_record/encryption/contexts.rb +8 -4
  115. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  116. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  117. data/lib/active_record/encryption/encryptable_record.rb +38 -22
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
  119. data/lib/active_record/encryption/encryptor.rb +7 -7
  120. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  121. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  122. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  123. data/lib/active_record/encryption/key_generator.rb +12 -1
  124. data/lib/active_record/encryption/message.rb +1 -1
  125. data/lib/active_record/encryption/message_serializer.rb +2 -0
  126. data/lib/active_record/encryption/properties.rb +4 -4
  127. data/lib/active_record/encryption/scheme.rb +20 -23
  128. data/lib/active_record/encryption.rb +1 -0
  129. data/lib/active_record/enum.rb +113 -29
  130. data/lib/active_record/errors.rb +108 -15
  131. data/lib/active_record/explain.rb +23 -3
  132. data/lib/active_record/explain_subscriber.rb +1 -1
  133. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  134. data/lib/active_record/fixture_set/render_context.rb +2 -0
  135. data/lib/active_record/fixture_set/table_row.rb +29 -8
  136. data/lib/active_record/fixtures.rb +121 -73
  137. data/lib/active_record/future_result.rb +30 -5
  138. data/lib/active_record/gem_version.rb +3 -3
  139. data/lib/active_record/inheritance.rb +30 -16
  140. data/lib/active_record/insert_all.rb +57 -10
  141. data/lib/active_record/integration.rb +10 -10
  142. data/lib/active_record/internal_metadata.rb +120 -30
  143. data/lib/active_record/locking/optimistic.rb +32 -18
  144. data/lib/active_record/locking/pessimistic.rb +8 -5
  145. data/lib/active_record/log_subscriber.rb +39 -17
  146. data/lib/active_record/marshalling.rb +56 -0
  147. data/lib/active_record/message_pack.rb +124 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  149. data/lib/active_record/middleware/database_selector.rb +18 -13
  150. data/lib/active_record/middleware/shard_selector.rb +7 -5
  151. data/lib/active_record/migration/command_recorder.rb +108 -10
  152. data/lib/active_record/migration/compatibility.rb +158 -64
  153. data/lib/active_record/migration/default_strategy.rb +23 -0
  154. data/lib/active_record/migration/execution_strategy.rb +19 -0
  155. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  156. data/lib/active_record/migration.rb +274 -117
  157. data/lib/active_record/model_schema.rb +86 -54
  158. data/lib/active_record/nested_attributes.rb +24 -6
  159. data/lib/active_record/normalization.rb +167 -0
  160. data/lib/active_record/persistence.rb +200 -47
  161. data/lib/active_record/promise.rb +84 -0
  162. data/lib/active_record/query_cache.rb +3 -21
  163. data/lib/active_record/query_logs.rb +87 -51
  164. data/lib/active_record/query_logs_formatter.rb +41 -0
  165. data/lib/active_record/querying.rb +16 -3
  166. data/lib/active_record/railtie.rb +128 -62
  167. data/lib/active_record/railties/controller_runtime.rb +12 -8
  168. data/lib/active_record/railties/databases.rake +145 -146
  169. data/lib/active_record/railties/job_runtime.rb +23 -0
  170. data/lib/active_record/readonly_attributes.rb +32 -5
  171. data/lib/active_record/reflection.rb +189 -45
  172. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  173. data/lib/active_record/relation/batches.rb +190 -61
  174. data/lib/active_record/relation/calculations.rb +208 -83
  175. data/lib/active_record/relation/delegation.rb +23 -9
  176. data/lib/active_record/relation/finder_methods.rb +77 -16
  177. data/lib/active_record/relation/merger.rb +2 -0
  178. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  180. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  181. data/lib/active_record/relation/predicate_builder.rb +26 -14
  182. data/lib/active_record/relation/query_attribute.rb +25 -1
  183. data/lib/active_record/relation/query_methods.rb +430 -77
  184. data/lib/active_record/relation/spawn_methods.rb +18 -1
  185. data/lib/active_record/relation.rb +98 -41
  186. data/lib/active_record/result.rb +25 -9
  187. data/lib/active_record/runtime_registry.rb +10 -1
  188. data/lib/active_record/sanitization.rb +57 -16
  189. data/lib/active_record/schema.rb +36 -22
  190. data/lib/active_record/schema_dumper.rb +65 -23
  191. data/lib/active_record/schema_migration.rb +68 -33
  192. data/lib/active_record/scoping/default.rb +20 -12
  193. data/lib/active_record/scoping/named.rb +2 -2
  194. data/lib/active_record/scoping.rb +2 -1
  195. data/lib/active_record/secure_password.rb +60 -0
  196. data/lib/active_record/secure_token.rb +21 -3
  197. data/lib/active_record/serialization.rb +5 -0
  198. data/lib/active_record/signed_id.rb +9 -7
  199. data/lib/active_record/store.rb +16 -11
  200. data/lib/active_record/suppressor.rb +3 -1
  201. data/lib/active_record/table_metadata.rb +16 -3
  202. data/lib/active_record/tasks/database_tasks.rb +138 -107
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  206. data/lib/active_record/test_fixtures.rb +123 -99
  207. data/lib/active_record/timestamp.rb +27 -15
  208. data/lib/active_record/token_for.rb +113 -0
  209. data/lib/active_record/touch_later.rb +11 -6
  210. data/lib/active_record/transactions.rb +39 -13
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  213. data/lib/active_record/type/internal/timezone.rb +7 -2
  214. data/lib/active_record/type/serialized.rb +8 -4
  215. data/lib/active_record/type/time.rb +4 -0
  216. data/lib/active_record/validations/absence.rb +1 -1
  217. data/lib/active_record/validations/associated.rb +3 -3
  218. data/lib/active_record/validations/numericality.rb +5 -4
  219. data/lib/active_record/validations/presence.rb +5 -28
  220. data/lib/active_record/validations/uniqueness.rb +50 -5
  221. data/lib/active_record/validations.rb +8 -4
  222. data/lib/active_record/version.rb +1 -1
  223. data/lib/active_record.rb +143 -16
  224. data/lib/arel/errors.rb +10 -0
  225. data/lib/arel/factory_methods.rb +4 -0
  226. data/lib/arel/filter_predications.rb +1 -1
  227. data/lib/arel/nodes/and.rb +4 -0
  228. data/lib/arel/nodes/binary.rb +6 -1
  229. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  230. data/lib/arel/nodes/cte.rb +36 -0
  231. data/lib/arel/nodes/filter.rb +1 -1
  232. data/lib/arel/nodes/fragments.rb +35 -0
  233. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  234. data/lib/arel/nodes/leading_join.rb +8 -0
  235. data/lib/arel/nodes/node.rb +111 -2
  236. data/lib/arel/nodes/sql_literal.rb +6 -0
  237. data/lib/arel/nodes/table_alias.rb +4 -0
  238. data/lib/arel/nodes.rb +4 -0
  239. data/lib/arel/predications.rb +2 -0
  240. data/lib/arel/table.rb +9 -5
  241. data/lib/arel/visitors/mysql.rb +8 -1
  242. data/lib/arel/visitors/to_sql.rb +81 -17
  243. data/lib/arel/visitors/visitor.rb +2 -2
  244. data/lib/arel.rb +16 -2
  245. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  246. data/lib/rails/generators/active_record/migration.rb +3 -1
  247. data/lib/rails/generators/active_record/model/USAGE +113 -0
  248. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  249. metadata +51 -15
  250. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  251. 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)
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
 
@@ -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
@@ -347,6 +382,7 @@ module ActiveRecord
347
382
  @klass = options[:anonymous_class]
348
383
  @plural_name = active_record.pluralize_table_names ?
349
384
  name.to_s.pluralize : name.to_s
385
+ validate_reflection!
350
386
  end
351
387
 
352
388
  def autosave=(autosave)
@@ -398,6 +434,17 @@ module ActiveRecord
398
434
  def derive_class_name
399
435
  name.to_s.camelize
400
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
401
448
  end
402
449
 
403
450
  # Holds all the metadata about an aggregation as it was specified in the
@@ -417,23 +464,23 @@ module ActiveRecord
417
464
  raise ArgumentError, "Polymorphic associations do not support computing the class."
418
465
  end
419
466
 
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
467
  begin
427
468
  klass = active_record.send(:compute_type, name)
428
-
429
- unless klass < ActiveRecord::Base
430
- 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
431
476
  end
477
+ end
432
478
 
433
- klass
434
- rescue NameError
435
- 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."
436
481
  end
482
+
483
+ klass
437
484
  end
438
485
 
439
486
  attr_reader :type, :foreign_type
@@ -443,6 +490,10 @@ module ActiveRecord
443
490
  super
444
491
  @type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
445
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
446
497
 
447
498
  ensure_option_not_given_as_class!(:class_name)
448
499
  end
@@ -459,8 +510,20 @@ module ActiveRecord
459
510
  @join_table ||= -(options[:join_table]&.to_s || derive_join_table)
460
511
  end
461
512
 
462
- def foreign_key
463
- @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
464
527
  end
465
528
 
466
529
  def association_foreign_key
@@ -472,19 +535,46 @@ module ActiveRecord
472
535
  end
473
536
 
474
537
  def active_record_primary_key
475
- @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
476
554
  end
477
555
 
478
556
  def join_primary_key(klass = nil)
479
557
  foreign_key
480
558
  end
481
559
 
560
+ def join_primary_type
561
+ type
562
+ end
563
+
482
564
  def join_foreign_key
483
565
  active_record_primary_key
484
566
  end
485
567
 
486
568
  def check_validity!
487
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
488
578
  end
489
579
 
490
580
  def check_eager_loadable!
@@ -500,7 +590,7 @@ module ActiveRecord
500
590
  end
501
591
 
502
592
  def join_id_for(owner) # :nodoc:
503
- owner[join_foreign_key]
593
+ Array(join_foreign_key).map { |key| owner._read_attribute(key) }
504
594
  end
505
595
 
506
596
  def through_reflection
@@ -582,6 +672,10 @@ module ActiveRecord
582
672
  options[:polymorphic]
583
673
  end
584
674
 
675
+ def polymorphic_name
676
+ active_record.polymorphic_name
677
+ end
678
+
585
679
  def add_as_source(seed)
586
680
  seed
587
681
  end
@@ -617,7 +711,9 @@ module ActiveRecord
617
711
 
618
712
  begin
619
713
  reflection = klass._reflect_on_association(inverse_name)
620
- rescue NameError
714
+ rescue NameError => error
715
+ raise unless error.name.to_s == class_name
716
+
621
717
  # Give up: we couldn't compute the klass type so we won't be able
622
718
  # to find any associations either.
623
719
  reflection = false
@@ -674,16 +770,56 @@ module ActiveRecord
674
770
  class_name.camelize
675
771
  end
676
772
 
677
- def derive_foreign_key
773
+ def derive_foreign_key(infer_from_inverse_of: true)
678
774
  if belongs_to?
679
775
  "#{name}_id"
680
776
  elsif options[:as]
681
777
  "#{options[:as]}_id"
778
+ elsif options[:inverse_of] && infer_from_inverse_of
779
+ inverse_of.foreign_key(infer_from_inverse_of: false)
682
780
  else
683
781
  active_record.model_name.to_s.foreign_key
684
782
  end
685
783
  end
686
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
+
687
823
  def derive_join_table
688
824
  ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
689
825
  end
@@ -734,6 +870,12 @@ module ActiveRecord
734
870
  def association_primary_key(klass = nil)
735
871
  if primary_key = options[:primary_key]
736
872
  @association_primary_key ||= -primary_key.to_s
873
+ elsif !polymorphic? && ((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
737
879
  else
738
880
  primary_key(klass || self.klass)
739
881
  end
@@ -772,6 +914,7 @@ module ActiveRecord
772
914
  :active_record_primary_key, :join_foreign_key, to: :source_reflection
773
915
 
774
916
  def initialize(delegate_reflection)
917
+ super()
775
918
  @delegate_reflection = delegate_reflection
776
919
  @klass = delegate_reflection.options[:anonymous_class]
777
920
  @source_reflection_name = delegate_reflection.options[:source]
@@ -905,24 +1048,23 @@ module ActiveRecord
905
1048
  end
906
1049
 
907
1050
  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
- )
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
923
1067
  end
924
-
925
- @source_reflection_name = names.first
926
1068
  end
927
1069
 
928
1070
  def source_options
@@ -1026,12 +1168,13 @@ module ActiveRecord
1026
1168
  :name, :scope_for, to: :@reflection
1027
1169
 
1028
1170
  def initialize(reflection, previous_reflection)
1171
+ super()
1029
1172
  @reflection = reflection
1030
1173
  @previous_reflection = previous_reflection
1031
1174
  end
1032
1175
 
1033
1176
  def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
1034
- scopes = @previous_reflection.join_scopes(table, predicate_builder, record) + super
1177
+ scopes = @previous_reflection.join_scopes(table, predicate_builder, klass, record) + super
1035
1178
  scopes << build_scope(table, predicate_builder, klass).instance_exec(record, &source_type_scope)
1036
1179
  end
1037
1180
 
@@ -1051,6 +1194,7 @@ module ActiveRecord
1051
1194
  delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection
1052
1195
 
1053
1196
  def initialize(reflection, association)
1197
+ super()
1054
1198
  @reflection = reflection
1055
1199
  @association = association
1056
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