activerecord 7.0.8 → 7.2.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 (277) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +530 -2004
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  27. data/lib/active_record/associations/join_dependency.rb +5 -5
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +328 -471
  36. data/lib/active_record/attribute_assignment.rb +1 -13
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +58 -45
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +10 -24
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +317 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +188 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +306 -128
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +274 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +368 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +364 -198
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +217 -174
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +44 -20
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +45 -10
  130. data/lib/active_record/encryption/encryptor.rb +17 -2
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  135. data/lib/active_record/encryption/message_serializer.rb +6 -0
  136. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  137. data/lib/active_record/encryption/properties.rb +3 -3
  138. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  139. data/lib/active_record/encryption/scheme.rb +22 -21
  140. data/lib/active_record/encryption.rb +1 -0
  141. data/lib/active_record/enum.rb +122 -29
  142. data/lib/active_record/errors.rb +151 -31
  143. data/lib/active_record/explain.rb +21 -12
  144. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  145. data/lib/active_record/fixture_set/render_context.rb +2 -0
  146. data/lib/active_record/fixture_set/table_row.rb +29 -8
  147. data/lib/active_record/fixtures.rb +167 -97
  148. data/lib/active_record/future_result.rb +47 -8
  149. data/lib/active_record/gem_version.rb +3 -3
  150. data/lib/active_record/inheritance.rb +34 -18
  151. data/lib/active_record/insert_all.rb +72 -22
  152. data/lib/active_record/integration.rb +11 -8
  153. data/lib/active_record/internal_metadata.rb +124 -20
  154. data/lib/active_record/locking/optimistic.rb +8 -7
  155. data/lib/active_record/locking/pessimistic.rb +5 -2
  156. data/lib/active_record/log_subscriber.rb +18 -22
  157. data/lib/active_record/marshalling.rb +56 -0
  158. data/lib/active_record/message_pack.rb +124 -0
  159. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  160. data/lib/active_record/middleware/database_selector.rb +6 -8
  161. data/lib/active_record/middleware/shard_selector.rb +3 -1
  162. data/lib/active_record/migration/command_recorder.rb +106 -8
  163. data/lib/active_record/migration/compatibility.rb +147 -5
  164. data/lib/active_record/migration/default_strategy.rb +22 -0
  165. data/lib/active_record/migration/execution_strategy.rb +19 -0
  166. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  167. data/lib/active_record/migration.rb +234 -117
  168. data/lib/active_record/model_schema.rb +88 -103
  169. data/lib/active_record/nested_attributes.rb +35 -9
  170. data/lib/active_record/normalization.rb +163 -0
  171. data/lib/active_record/persistence.rb +168 -339
  172. data/lib/active_record/promise.rb +84 -0
  173. data/lib/active_record/query_cache.rb +19 -25
  174. data/lib/active_record/query_logs.rb +92 -52
  175. data/lib/active_record/query_logs_formatter.rb +41 -0
  176. data/lib/active_record/querying.rb +33 -8
  177. data/lib/active_record/railtie.rb +135 -86
  178. data/lib/active_record/railties/controller_runtime.rb +22 -7
  179. data/lib/active_record/railties/databases.rake +145 -154
  180. data/lib/active_record/railties/job_runtime.rb +23 -0
  181. data/lib/active_record/readonly_attributes.rb +32 -5
  182. data/lib/active_record/reflection.rb +259 -68
  183. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  184. data/lib/active_record/relation/batches.rb +196 -61
  185. data/lib/active_record/relation/calculations.rb +249 -92
  186. data/lib/active_record/relation/delegation.rb +30 -19
  187. data/lib/active_record/relation/finder_methods.rb +93 -18
  188. data/lib/active_record/relation/merger.rb +6 -6
  189. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  190. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  191. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  192. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  193. data/lib/active_record/relation/predicate_builder.rb +28 -16
  194. data/lib/active_record/relation/query_attribute.rb +2 -1
  195. data/lib/active_record/relation/query_methods.rb +548 -94
  196. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  197. data/lib/active_record/relation/spawn_methods.rb +5 -4
  198. data/lib/active_record/relation/where_clause.rb +7 -19
  199. data/lib/active_record/relation.rb +580 -90
  200. data/lib/active_record/result.rb +49 -48
  201. data/lib/active_record/runtime_registry.rb +63 -1
  202. data/lib/active_record/sanitization.rb +70 -25
  203. data/lib/active_record/schema.rb +8 -7
  204. data/lib/active_record/schema_dumper.rb +63 -14
  205. data/lib/active_record/schema_migration.rb +75 -24
  206. data/lib/active_record/scoping/default.rb +15 -5
  207. data/lib/active_record/scoping/named.rb +2 -2
  208. data/lib/active_record/scoping.rb +2 -1
  209. data/lib/active_record/secure_password.rb +60 -0
  210. data/lib/active_record/secure_token.rb +21 -3
  211. data/lib/active_record/signed_id.rb +27 -6
  212. data/lib/active_record/statement_cache.rb +7 -7
  213. data/lib/active_record/store.rb +8 -8
  214. data/lib/active_record/suppressor.rb +3 -1
  215. data/lib/active_record/table_metadata.rb +1 -1
  216. data/lib/active_record/tasks/database_tasks.rb +180 -119
  217. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  218. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  219. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  220. data/lib/active_record/test_fixtures.rb +170 -155
  221. data/lib/active_record/testing/query_assertions.rb +121 -0
  222. data/lib/active_record/timestamp.rb +31 -17
  223. data/lib/active_record/token_for.rb +123 -0
  224. data/lib/active_record/touch_later.rb +12 -7
  225. data/lib/active_record/transaction.rb +132 -0
  226. data/lib/active_record/transactions.rb +106 -24
  227. data/lib/active_record/translation.rb +0 -2
  228. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  229. data/lib/active_record/type/internal/timezone.rb +7 -2
  230. data/lib/active_record/type/serialized.rb +1 -3
  231. data/lib/active_record/type/time.rb +4 -0
  232. data/lib/active_record/type_caster/connection.rb +4 -4
  233. data/lib/active_record/validations/absence.rb +1 -1
  234. data/lib/active_record/validations/associated.rb +9 -3
  235. data/lib/active_record/validations/numericality.rb +5 -4
  236. data/lib/active_record/validations/presence.rb +5 -28
  237. data/lib/active_record/validations/uniqueness.rb +60 -11
  238. data/lib/active_record/validations.rb +12 -5
  239. data/lib/active_record/version.rb +1 -1
  240. data/lib/active_record.rb +247 -33
  241. data/lib/arel/alias_predication.rb +1 -1
  242. data/lib/arel/collectors/bind.rb +2 -0
  243. data/lib/arel/collectors/composite.rb +7 -0
  244. data/lib/arel/collectors/sql_string.rb +1 -1
  245. data/lib/arel/collectors/substitute_binds.rb +1 -1
  246. data/lib/arel/errors.rb +10 -0
  247. data/lib/arel/factory_methods.rb +4 -0
  248. data/lib/arel/nodes/binary.rb +6 -7
  249. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  250. data/lib/arel/nodes/cte.rb +36 -0
  251. data/lib/arel/nodes/fragments.rb +35 -0
  252. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  253. data/lib/arel/nodes/leading_join.rb +8 -0
  254. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  255. data/lib/arel/nodes/node.rb +115 -5
  256. data/lib/arel/nodes/sql_literal.rb +13 -0
  257. data/lib/arel/nodes/table_alias.rb +4 -0
  258. data/lib/arel/nodes.rb +6 -2
  259. data/lib/arel/predications.rb +3 -1
  260. data/lib/arel/select_manager.rb +1 -1
  261. data/lib/arel/table.rb +9 -5
  262. data/lib/arel/tree_manager.rb +8 -3
  263. data/lib/arel/update_manager.rb +2 -1
  264. data/lib/arel/visitors/dot.rb +1 -0
  265. data/lib/arel/visitors/mysql.rb +17 -5
  266. data/lib/arel/visitors/postgresql.rb +1 -12
  267. data/lib/arel/visitors/to_sql.rb +112 -34
  268. data/lib/arel/visitors/visitor.rb +2 -2
  269. data/lib/arel.rb +21 -3
  270. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  271. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  272. data/lib/rails/generators/active_record/migration.rb +3 -1
  273. data/lib/rails/generators/active_record/model/USAGE +113 -0
  274. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  275. metadata +56 -14
  276. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  277. 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
@@ -11,6 +11,7 @@ module ActiveRecord
11
11
  class_attribute :_reflections, instance_writer: false, default: {}
12
12
  class_attribute :aggregate_reflections, instance_writer: false, default: {}
13
13
  class_attribute :automatic_scope_inversing, instance_writer: false, default: false
14
+ class_attribute :automatically_invert_plural_associations, instance_writer: false, default: false
14
15
  end
15
16
 
16
17
  class << self
@@ -21,12 +22,12 @@ module ActiveRecord
21
22
 
22
23
  def add_reflection(ar, name, reflection)
23
24
  ar.clear_reflections_cache
24
- name = -name.to_s
25
+ name = name.to_sym
25
26
  ar._reflections = ar._reflections.except(name).merge!(name => reflection)
26
27
  end
27
28
 
28
29
  def add_aggregate_reflection(ar, name, reflection)
29
- ar.aggregate_reflections = ar.aggregate_reflections.merge(-name.to_s => reflection)
30
+ ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_sym => reflection)
30
31
  end
31
32
 
32
33
  private
@@ -46,6 +47,8 @@ module ActiveRecord
46
47
  end
47
48
  end
48
49
 
50
+ # = Active Record Reflection
51
+ #
49
52
  # \Reflection enables the ability to examine the associations and aggregations of
50
53
  # Active Record classes and objects. This information, for example,
51
54
  # can be used in a form builder that takes an Active Record object
@@ -65,7 +68,7 @@ module ActiveRecord
65
68
  # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
66
69
  #
67
70
  def reflect_on_aggregation(aggregation)
68
- aggregate_reflections[aggregation.to_s]
71
+ aggregate_reflections[aggregation.to_sym]
69
72
  end
70
73
 
71
74
  # Returns a Hash of name of the reflection as the key and an AssociationReflection as the value.
@@ -73,6 +76,10 @@ module ActiveRecord
73
76
  # Account.reflections # => {"balance" => AggregateReflection}
74
77
  #
75
78
  def reflections
79
+ normalized_reflections.stringify_keys
80
+ end
81
+
82
+ def normalized_reflections # :nodoc
76
83
  @__reflections ||= begin
77
84
  ref = {}
78
85
 
@@ -81,13 +88,13 @@ module ActiveRecord
81
88
 
82
89
  if parent_reflection
83
90
  parent_name = parent_reflection.name
84
- ref[parent_name.to_s] = parent_reflection
91
+ ref[parent_name] = parent_reflection
85
92
  else
86
93
  ref[name] = reflection
87
94
  end
88
95
  end
89
96
 
90
- ref
97
+ ref.freeze
91
98
  end
92
99
  end
93
100
 
@@ -102,7 +109,7 @@ module ActiveRecord
102
109
  # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
103
110
  #
104
111
  def reflect_on_all_associations(macro = nil)
105
- association_reflections = reflections.values
112
+ association_reflections = normalized_reflections.values
106
113
  association_reflections.select! { |reflection| reflection.macro == macro } if macro
107
114
  association_reflections
108
115
  end
@@ -113,21 +120,31 @@ module ActiveRecord
113
120
  # Invoice.reflect_on_association(:line_items).macro # returns :has_many
114
121
  #
115
122
  def reflect_on_association(association)
116
- reflections[association.to_s]
123
+ normalized_reflections[association.to_sym]
117
124
  end
118
125
 
119
126
  def _reflect_on_association(association) # :nodoc:
120
- _reflections[association.to_s]
127
+ _reflections[association.to_sym]
121
128
  end
122
129
 
123
130
  # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
124
131
  def reflect_on_all_autosave_associations
125
- reflections.values.select { |reflection| reflection.options[:autosave] }
132
+ reflections = normalized_reflections.values
133
+ reflections.select! { |reflection| reflection.options[:autosave] }
134
+ reflections
126
135
  end
127
136
 
128
137
  def clear_reflections_cache # :nodoc:
129
138
  @__reflections = nil
130
139
  end
140
+
141
+ private
142
+ def inherited(subclass)
143
+ super
144
+ subclass.class_eval do
145
+ @__reflections = nil
146
+ end
147
+ end
131
148
  end
132
149
 
133
150
  # Holds all the methods that are shared between MacroReflection and ThroughReflection.
@@ -144,6 +161,14 @@ module ActiveRecord
144
161
  # PolymorphicReflection
145
162
  # RuntimeReflection
146
163
  class AbstractReflection # :nodoc:
164
+ def initialize
165
+ @class_name = nil
166
+ @counter_cache_column = nil
167
+ @inverse_of = nil
168
+ @inverse_which_updates_counter_cache_defined = false
169
+ @inverse_which_updates_counter_cache = nil
170
+ end
171
+
147
172
  def through_reflection?
148
173
  false
149
174
  end
@@ -183,10 +208,14 @@ module ActiveRecord
183
208
 
184
209
  scope_chain_items.inject(klass_scope, &:merge!)
185
210
 
186
- primary_key = join_primary_key
187
- foreign_key = join_foreign_key
211
+ primary_key_column_names = Array(join_primary_key)
212
+ foreign_key_column_names = Array(join_foreign_key)
188
213
 
189
- klass_scope.where!(table[primary_key].eq(foreign_table[foreign_key]))
214
+ primary_foreign_key_pairs = primary_key_column_names.zip(foreign_key_column_names)
215
+
216
+ primary_foreign_key_pairs.each do |primary_key_column_name, foreign_key_column_name|
217
+ klass_scope.where!(table[primary_key_column_name].eq(foreign_table[foreign_key_column_name]))
218
+ end
190
219
 
191
220
  if klass.finder_needs_type_condition?
192
221
  klass_scope.where!(klass.send(:type_condition, table))
@@ -213,14 +242,16 @@ module ActiveRecord
213
242
  end
214
243
 
215
244
  def counter_cache_column
216
- @counter_cache_column ||= if belongs_to?
217
- if options[:counter_cache] == true
218
- -"#{active_record.name.demodulize.underscore.pluralize}_count"
219
- elsif options[:counter_cache]
220
- -options[:counter_cache].to_s
245
+ @counter_cache_column ||= begin
246
+ counter_cache = options[:counter_cache]
247
+
248
+ if belongs_to?
249
+ if counter_cache
250
+ counter_cache[:column] || -"#{active_record.name.demodulize.underscore.pluralize}_count"
251
+ end
252
+ else
253
+ -((counter_cache && -counter_cache[:column]) || "#{name}_count")
221
254
  end
222
- else
223
- -(options[:counter_cache]&.to_s || "#{name}_count")
224
255
  end
225
256
  end
226
257
 
@@ -231,11 +262,11 @@ module ActiveRecord
231
262
  end
232
263
 
233
264
  def check_validity_of_inverse!
234
- unless polymorphic?
235
- if has_inverse? && inverse_of.nil?
265
+ if !polymorphic? && has_inverse?
266
+ if inverse_of.nil?
236
267
  raise InverseOfAssociationNotFoundError.new(self)
237
268
  end
238
- if has_inverse? && inverse_of == self
269
+ if inverse_of == self
239
270
  raise InverseOfAssociationRecursiveError.new(self)
240
271
  end
241
272
  end
@@ -252,10 +283,16 @@ module ActiveRecord
252
283
  #
253
284
  # Hence this method.
254
285
  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
286
+ unless @inverse_which_updates_counter_cache_defined
287
+ if counter_cache_column
288
+ inverse_candidates = inverse_of ? [inverse_of] : klass.reflect_on_all_associations(:belongs_to)
289
+ @inverse_which_updates_counter_cache = inverse_candidates.find do |inverse|
290
+ inverse.counter_cache_column == counter_cache_column && (inverse.polymorphic? || inverse.klass == active_record)
291
+ end
292
+ end
293
+ @inverse_which_updates_counter_cache_defined = true
258
294
  end
295
+ @inverse_which_updates_counter_cache
259
296
  end
260
297
  alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
261
298
 
@@ -263,7 +300,7 @@ module ActiveRecord
263
300
  inverse_of && inverse_which_updates_counter_cache == inverse_of
264
301
  end
265
302
 
266
- # Returns whether a counter cache should be used for this association.
303
+ # Returns whether this association has a counter cache.
267
304
  #
268
305
  # The counter_cache option must be given on either the owner or inverse
269
306
  # association, and the column must be present on the owner.
@@ -273,6 +310,17 @@ module ActiveRecord
273
310
  active_record.has_attribute?(counter_cache_column)
274
311
  end
275
312
 
313
+ # Returns whether this association has a counter cache and its column values were backfilled
314
+ # (and so it is used internally by methods like +size+/+any?+/etc).
315
+ def has_active_cached_counter?
316
+ return false unless has_cached_counter?
317
+
318
+ counter_cache = options[:counter_cache] ||
319
+ (inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache])
320
+
321
+ counter_cache[:active] != false
322
+ end
323
+
276
324
  def counter_must_be_updated_by_has_many?
277
325
  !inverse_updates_counter_in_memory? && has_cached_counter?
278
326
  end
@@ -346,9 +394,10 @@ module ActiveRecord
346
394
  attr_reader :plural_name # :nodoc:
347
395
 
348
396
  def initialize(name, scope, options, active_record)
397
+ super()
349
398
  @name = name
350
399
  @scope = scope
351
- @options = options
400
+ @options = normalize_options(options)
352
401
  @active_record = active_record
353
402
  @klass = options[:anonymous_class]
354
403
  @plural_name = active_record.pluralize_table_names ?
@@ -379,13 +428,17 @@ module ActiveRecord
379
428
  # a new association object. Use +build_association+ or +create_association+
380
429
  # instead. This allows plugins to hook into association object creation.
381
430
  def klass
382
- @klass ||= compute_class(class_name)
431
+ @klass ||= compute_class(compute_name(class_name))
383
432
  end
384
433
 
385
434
  def compute_class(name)
386
435
  name.constantize
387
436
  end
388
437
 
438
+ def compute_name(name) # :nodoc:
439
+ active_record.name.demodulize == name ? "::#{name}" : name
440
+ end
441
+
389
442
  # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
390
443
  # and +other_aggregation+ has an options hash assigned to it.
391
444
  def ==(other_aggregation)
@@ -404,6 +457,26 @@ module ActiveRecord
404
457
  def derive_class_name
405
458
  name.to_s.camelize
406
459
  end
460
+
461
+ def normalize_options(options)
462
+ counter_cache = options.delete(:counter_cache)
463
+
464
+ if counter_cache
465
+ active = true
466
+
467
+ case counter_cache
468
+ when String, Symbol
469
+ column = -counter_cache.to_s
470
+ when Hash
471
+ active = counter_cache.fetch(:active, true)
472
+ column = counter_cache[:column]&.to_s
473
+ end
474
+
475
+ options[:counter_cache] = { active: active, column: column }
476
+ end
477
+
478
+ options
479
+ end
407
480
  end
408
481
 
409
482
  # Holds all the metadata about an aggregation as it was specified in the
@@ -423,23 +496,23 @@ module ActiveRecord
423
496
  raise ArgumentError, "Polymorphic associations do not support computing the class."
424
497
  end
425
498
 
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
499
  begin
433
500
  klass = active_record.send(:compute_type, name)
434
-
435
- unless klass < ActiveRecord::Base
436
- raise ArgumentError, msg
501
+ rescue NameError => error
502
+ if error.name.match?(/(?:\A|::)#{name}\z/)
503
+ message = "Missing model class #{name} for the #{active_record}##{self.name} association."
504
+ message += " You can specify a different model class with the :class_name option." unless options[:class_name]
505
+ raise NameError.new(message, name)
506
+ else
507
+ raise
437
508
  end
509
+ end
438
510
 
439
- klass
440
- rescue NameError
441
- raise NameError, msg
511
+ unless klass < ActiveRecord::Base
512
+ raise ArgumentError, "The #{name} model class for the #{active_record}##{self.name} association is not an ActiveRecord::Base subclass."
442
513
  end
514
+
515
+ klass
443
516
  end
444
517
 
445
518
  attr_reader :type, :foreign_type
@@ -449,6 +522,21 @@ module ActiveRecord
449
522
  super
450
523
  @type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
451
524
  @foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
525
+ @join_table = nil
526
+ @foreign_key = nil
527
+ @association_foreign_key = nil
528
+ @association_primary_key = nil
529
+ if options[:query_constraints]
530
+ ActiveRecord.deprecator.warn <<~MSG.squish
531
+ Setting `query_constraints:` option on `#{active_record}.#{macro} :#{name}` is deprecated.
532
+ To maintain current behavior, use the `foreign_key` option instead.
533
+ MSG
534
+ end
535
+
536
+ # If the foreign key is an array, set query constraints options and don't use the foreign key
537
+ if options[:foreign_key].is_a?(Array)
538
+ options[:query_constraints] = options.delete(:foreign_key)
539
+ end
452
540
 
453
541
  ensure_option_not_given_as_class!(:class_name)
454
542
  end
@@ -458,15 +546,33 @@ module ActiveRecord
458
546
  if polymorphic?
459
547
  key = [key, owner._read_attribute(@foreign_type)]
460
548
  end
461
- klass.cached_find_by_statement(key, &block)
549
+ klass.with_connection do |connection|
550
+ klass.cached_find_by_statement(connection, key, &block)
551
+ end
462
552
  end
463
553
 
464
554
  def join_table
465
555
  @join_table ||= -(options[:join_table]&.to_s || derive_join_table)
466
556
  end
467
557
 
468
- def foreign_key
469
- @foreign_key ||= -(options[:foreign_key]&.to_s || derive_foreign_key)
558
+ def foreign_key(infer_from_inverse_of: true)
559
+ @foreign_key ||= if options[:foreign_key]
560
+ if options[:foreign_key].is_a?(Array)
561
+ options[:foreign_key].map { |fk| fk.to_s.freeze }.freeze
562
+ else
563
+ options[:foreign_key].to_s.freeze
564
+ end
565
+ elsif options[:query_constraints]
566
+ options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze
567
+ else
568
+ derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)
569
+
570
+ if active_record.has_query_constraints?
571
+ derived_fk = derive_fk_query_constraints(derived_fk)
572
+ end
573
+
574
+ derived_fk
575
+ end
470
576
  end
471
577
 
472
578
  def association_foreign_key
@@ -478,7 +584,22 @@ module ActiveRecord
478
584
  end
479
585
 
480
586
  def active_record_primary_key
481
- @active_record_primary_key ||= -(options[:primary_key]&.to_s || primary_key(active_record))
587
+ custom_primary_key = options[:primary_key]
588
+ @active_record_primary_key ||= if custom_primary_key
589
+ if custom_primary_key.is_a?(Array)
590
+ custom_primary_key.map { |pk| pk.to_s.freeze }.freeze
591
+ else
592
+ custom_primary_key.to_s.freeze
593
+ end
594
+ elsif active_record.has_query_constraints? || options[:query_constraints]
595
+ active_record.query_constraints_list
596
+ elsif active_record.composite_primary_key?
597
+ # If active_record has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
598
+ primary_key = primary_key(active_record)
599
+ primary_key.include?("id") ? "id" : primary_key.freeze
600
+ else
601
+ primary_key(active_record).freeze
602
+ end
482
603
  end
483
604
 
484
605
  def join_primary_key(klass = nil)
@@ -495,6 +616,14 @@ module ActiveRecord
495
616
 
496
617
  def check_validity!
497
618
  check_validity_of_inverse!
619
+
620
+ if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
621
+ if (has_one? || collection?) && Array(active_record_primary_key).length != Array(foreign_key).length
622
+ raise CompositePrimaryKeyMismatchError.new(self)
623
+ elsif belongs_to? && Array(association_primary_key).length != Array(foreign_key).length
624
+ raise CompositePrimaryKeyMismatchError.new(self)
625
+ end
626
+ end
498
627
  end
499
628
 
500
629
  def check_eager_loadable!
@@ -510,7 +639,7 @@ module ActiveRecord
510
639
  end
511
640
 
512
641
  def join_id_for(owner) # :nodoc:
513
- owner[join_foreign_key]
642
+ Array(join_foreign_key).map { |key| owner._read_attribute(key) }
514
643
  end
515
644
 
516
645
  def through_reflection
@@ -631,14 +760,20 @@ module ActiveRecord
631
760
 
632
761
  begin
633
762
  reflection = klass._reflect_on_association(inverse_name)
634
- rescue NameError
763
+ if !reflection && active_record.automatically_invert_plural_associations
764
+ plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
765
+ reflection = klass._reflect_on_association(plural_inverse_name)
766
+ end
767
+ rescue NameError => error
768
+ raise unless error.name.to_s == class_name
769
+
635
770
  # Give up: we couldn't compute the klass type so we won't be able
636
771
  # to find any associations either.
637
772
  reflection = false
638
773
  end
639
774
 
640
775
  if valid_inverse_reflection?(reflection)
641
- inverse_name
776
+ reflection.name
642
777
  end
643
778
  end
644
779
  end
@@ -688,16 +823,58 @@ module ActiveRecord
688
823
  class_name.camelize
689
824
  end
690
825
 
691
- def derive_foreign_key
826
+ def derive_foreign_key(infer_from_inverse_of: true)
692
827
  if belongs_to?
693
828
  "#{name}_id"
694
829
  elsif options[:as]
695
830
  "#{options[:as]}_id"
831
+ elsif options[:inverse_of] && infer_from_inverse_of
832
+ inverse_of.foreign_key(infer_from_inverse_of: false)
696
833
  else
697
834
  active_record.model_name.to_s.foreign_key
698
835
  end
699
836
  end
700
837
 
838
+ def derive_fk_query_constraints(foreign_key)
839
+ primary_query_constraints = active_record.query_constraints_list
840
+ owner_pk = active_record.primary_key
841
+
842
+ if primary_query_constraints.size > 2
843
+ raise ArgumentError, <<~MSG.squish
844
+ The query constraints list on the `#{active_record}` model has more than 2
845
+ attributes. Active Record is unable to derive the query constraints
846
+ for the association. You need to explicitly define the query constraints
847
+ for this association.
848
+ MSG
849
+ end
850
+
851
+ if !primary_query_constraints.include?(owner_pk)
852
+ raise ArgumentError, <<~MSG.squish
853
+ The query constraints on the `#{active_record}` model does not include the primary
854
+ key so Active Record is unable to derive the foreign key constraints for
855
+ the association. You need to explicitly define the query constraints for this
856
+ association.
857
+ MSG
858
+ end
859
+
860
+ return foreign_key if primary_query_constraints.include?(foreign_key)
861
+
862
+ first_key, last_key = primary_query_constraints
863
+
864
+ if first_key == owner_pk
865
+ [foreign_key, last_key.to_s]
866
+ elsif last_key == owner_pk
867
+ [first_key.to_s, foreign_key]
868
+ else
869
+ raise ArgumentError, <<~MSG.squish
870
+ Active Record couldn't correctly interpret the query constraints
871
+ for the `#{active_record}` model. The query constraints on `#{active_record}` are
872
+ `#{primary_query_constraints}` and the foreign key is `#{foreign_key}`.
873
+ You need to explicitly set the query constraints for this association.
874
+ MSG
875
+ end
876
+ end
877
+
701
878
  def derive_join_table
702
879
  ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
703
880
  end
@@ -747,7 +924,17 @@ module ActiveRecord
747
924
  # klass option is necessary to support loading polymorphic associations
748
925
  def association_primary_key(klass = nil)
749
926
  if primary_key = options[:primary_key]
750
- @association_primary_key ||= -primary_key.to_s
927
+ @association_primary_key ||= if primary_key.is_a?(Array)
928
+ primary_key.map { |pk| pk.to_s.freeze }.freeze
929
+ else
930
+ -primary_key.to_s
931
+ end
932
+ elsif (klass || self.klass).has_query_constraints? || options[:query_constraints]
933
+ (klass || self.klass).composite_query_constraints_list
934
+ elsif (klass || self.klass).composite_primary_key?
935
+ # If klass has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
936
+ primary_key = (klass || self.klass).primary_key
937
+ primary_key.include?("id") ? "id" : primary_key
751
938
  else
752
939
  primary_key(klass || self.klass)
753
940
  end
@@ -786,6 +973,7 @@ module ActiveRecord
786
973
  :active_record_primary_key, :join_foreign_key, to: :source_reflection
787
974
 
788
975
  def initialize(delegate_reflection)
976
+ super()
789
977
  @delegate_reflection = delegate_reflection
790
978
  @klass = delegate_reflection.options[:anonymous_class]
791
979
  @source_reflection_name = delegate_reflection.options[:source]
@@ -798,7 +986,7 @@ module ActiveRecord
798
986
  end
799
987
 
800
988
  def klass
801
- @klass ||= delegate_reflection.compute_class(class_name)
989
+ @klass ||= delegate_reflection.compute_class(compute_name(class_name))
802
990
  end
803
991
 
804
992
  # Returns the source of the through reflection. It checks both a singularized
@@ -819,6 +1007,8 @@ module ActiveRecord
819
1007
  # # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
820
1008
  #
821
1009
  def source_reflection
1010
+ return unless source_reflection_name
1011
+
822
1012
  through_reflection.klass._reflect_on_association(source_reflection_name)
823
1013
  end
824
1014
 
@@ -919,24 +1109,23 @@ module ActiveRecord
919
1109
  end
920
1110
 
921
1111
  def source_reflection_name # :nodoc:
922
- return @source_reflection_name if @source_reflection_name
923
-
924
- names = [name.to_s.singularize, name].collect(&:to_sym).uniq
925
- names = names.find_all { |n|
926
- through_reflection.klass._reflect_on_association(n)
927
- }
928
-
929
- if names.length > 1
930
- raise AmbiguousSourceReflectionForThroughAssociation.new(
931
- active_record.name,
932
- macro,
933
- name,
934
- options,
935
- source_reflection_names
936
- )
1112
+ @source_reflection_name ||= begin
1113
+ names = [name.to_s.singularize, name].collect(&:to_sym).uniq
1114
+ names = names.find_all { |n|
1115
+ through_reflection.klass._reflect_on_association(n)
1116
+ }
1117
+
1118
+ if names.length > 1
1119
+ raise AmbiguousSourceReflectionForThroughAssociation.new(
1120
+ active_record.name,
1121
+ macro,
1122
+ name,
1123
+ options,
1124
+ source_reflection_names
1125
+ )
1126
+ end
1127
+ names.first
937
1128
  end
938
-
939
- @source_reflection_name = names.first
940
1129
  end
941
1130
 
942
1131
  def source_options
@@ -977,7 +1166,7 @@ module ActiveRecord
977
1166
  end
978
1167
 
979
1168
  if parent_reflection.nil?
980
- reflections = active_record.reflections.keys.map(&:to_sym)
1169
+ reflections = active_record.normalized_reflections.keys
981
1170
 
982
1171
  if reflections.index(through_reflection.name) > reflections.index(name)
983
1172
  raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
@@ -1040,6 +1229,7 @@ module ActiveRecord
1040
1229
  :name, :scope_for, to: :@reflection
1041
1230
 
1042
1231
  def initialize(reflection, previous_reflection)
1232
+ super()
1043
1233
  @reflection = reflection
1044
1234
  @previous_reflection = previous_reflection
1045
1235
  end
@@ -1065,6 +1255,7 @@ module ActiveRecord
1065
1255
  delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection
1066
1256
 
1067
1257
  def initialize(reflection, association)
1258
+ super()
1068
1259
  @reflection = reflection
1069
1260
  @association = association
1070
1261
  end