activerecord 7.0.8.7 → 7.2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +631 -1944
  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 +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  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 +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  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 +11 -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 +64 -50
  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 +11 -25
  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 +323 -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 +217 -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 +307 -129
  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 +278 -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 +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  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 +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  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 +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  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/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +29 -8
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +4 -4
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +234 -117
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +63 -14
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +27 -6
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +106 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +2 -0
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +54 -12
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. 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,7 +428,15 @@ 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 ||= _klass(class_name)
432
+ end
433
+
434
+ def _klass(class_name) # :nodoc:
435
+ if active_record.name.demodulize == class_name
436
+ return compute_class("::#{class_name}") rescue NameError
437
+ end
438
+
439
+ compute_class(class_name)
383
440
  end
384
441
 
385
442
  def compute_class(name)
@@ -404,6 +461,26 @@ module ActiveRecord
404
461
  def derive_class_name
405
462
  name.to_s.camelize
406
463
  end
464
+
465
+ def normalize_options(options)
466
+ counter_cache = options.delete(:counter_cache)
467
+
468
+ if counter_cache
469
+ active = true
470
+
471
+ case counter_cache
472
+ when String, Symbol
473
+ column = -counter_cache.to_s
474
+ when Hash
475
+ active = counter_cache.fetch(:active, true)
476
+ column = counter_cache[:column]&.to_s
477
+ end
478
+
479
+ options[:counter_cache] = { active: active, column: column }
480
+ end
481
+
482
+ options
483
+ end
407
484
  end
408
485
 
409
486
  # Holds all the metadata about an aggregation as it was specified in the
@@ -423,23 +500,23 @@ module ActiveRecord
423
500
  raise ArgumentError, "Polymorphic associations do not support computing the class."
424
501
  end
425
502
 
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
503
  begin
433
504
  klass = active_record.send(:compute_type, name)
434
-
435
- unless klass < ActiveRecord::Base
436
- raise ArgumentError, msg
505
+ rescue NameError => error
506
+ if error.name.match?(/(?:\A|::)#{name}\z/)
507
+ message = "Missing model class #{name} for the #{active_record}##{self.name} association."
508
+ message += " You can specify a different model class with the :class_name option." unless options[:class_name]
509
+ raise NameError.new(message, name)
510
+ else
511
+ raise
437
512
  end
513
+ end
438
514
 
439
- klass
440
- rescue NameError
441
- raise NameError, msg
515
+ unless klass < ActiveRecord::Base
516
+ raise ArgumentError, "The #{name} model class for the #{active_record}##{self.name} association is not an ActiveRecord::Base subclass."
442
517
  end
518
+
519
+ klass
443
520
  end
444
521
 
445
522
  attr_reader :type, :foreign_type
@@ -449,6 +526,21 @@ module ActiveRecord
449
526
  super
450
527
  @type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
451
528
  @foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
529
+ @join_table = nil
530
+ @foreign_key = nil
531
+ @association_foreign_key = nil
532
+ @association_primary_key = nil
533
+ if options[:query_constraints]
534
+ ActiveRecord.deprecator.warn <<~MSG.squish
535
+ Setting `query_constraints:` option on `#{active_record}.#{macro} :#{name}` is deprecated.
536
+ To maintain current behavior, use the `foreign_key` option instead.
537
+ MSG
538
+ end
539
+
540
+ # If the foreign key is an array, set query constraints options and don't use the foreign key
541
+ if options[:foreign_key].is_a?(Array)
542
+ options[:query_constraints] = options.delete(:foreign_key)
543
+ end
452
544
 
453
545
  ensure_option_not_given_as_class!(:class_name)
454
546
  end
@@ -458,15 +550,33 @@ module ActiveRecord
458
550
  if polymorphic?
459
551
  key = [key, owner._read_attribute(@foreign_type)]
460
552
  end
461
- klass.cached_find_by_statement(key, &block)
553
+ klass.with_connection do |connection|
554
+ klass.cached_find_by_statement(connection, key, &block)
555
+ end
462
556
  end
463
557
 
464
558
  def join_table
465
559
  @join_table ||= -(options[:join_table]&.to_s || derive_join_table)
466
560
  end
467
561
 
468
- def foreign_key
469
- @foreign_key ||= -(options[:foreign_key]&.to_s || derive_foreign_key)
562
+ def foreign_key(infer_from_inverse_of: true)
563
+ @foreign_key ||= if options[:foreign_key]
564
+ if options[:foreign_key].is_a?(Array)
565
+ options[:foreign_key].map { |fk| fk.to_s.freeze }.freeze
566
+ else
567
+ options[:foreign_key].to_s.freeze
568
+ end
569
+ elsif options[:query_constraints]
570
+ options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze
571
+ else
572
+ derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)
573
+
574
+ if active_record.has_query_constraints?
575
+ derived_fk = derive_fk_query_constraints(derived_fk)
576
+ end
577
+
578
+ derived_fk
579
+ end
470
580
  end
471
581
 
472
582
  def association_foreign_key
@@ -478,7 +588,22 @@ module ActiveRecord
478
588
  end
479
589
 
480
590
  def active_record_primary_key
481
- @active_record_primary_key ||= -(options[:primary_key]&.to_s || primary_key(active_record))
591
+ custom_primary_key = options[:primary_key]
592
+ @active_record_primary_key ||= if custom_primary_key
593
+ if custom_primary_key.is_a?(Array)
594
+ custom_primary_key.map { |pk| pk.to_s.freeze }.freeze
595
+ else
596
+ custom_primary_key.to_s.freeze
597
+ end
598
+ elsif active_record.has_query_constraints? || options[:query_constraints]
599
+ active_record.query_constraints_list
600
+ elsif active_record.composite_primary_key?
601
+ # If active_record has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
602
+ primary_key = primary_key(active_record)
603
+ primary_key.include?("id") ? "id" : primary_key.freeze
604
+ else
605
+ primary_key(active_record).freeze
606
+ end
482
607
  end
483
608
 
484
609
  def join_primary_key(klass = nil)
@@ -495,6 +620,14 @@ module ActiveRecord
495
620
 
496
621
  def check_validity!
497
622
  check_validity_of_inverse!
623
+
624
+ if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
625
+ if (has_one? || collection?) && Array(active_record_primary_key).length != Array(foreign_key).length
626
+ raise CompositePrimaryKeyMismatchError.new(self)
627
+ elsif belongs_to? && Array(association_primary_key).length != Array(foreign_key).length
628
+ raise CompositePrimaryKeyMismatchError.new(self)
629
+ end
630
+ end
498
631
  end
499
632
 
500
633
  def check_eager_loadable!
@@ -510,7 +643,7 @@ module ActiveRecord
510
643
  end
511
644
 
512
645
  def join_id_for(owner) # :nodoc:
513
- owner[join_foreign_key]
646
+ Array(join_foreign_key).map { |key| owner._read_attribute(key) }
514
647
  end
515
648
 
516
649
  def through_reflection
@@ -631,14 +764,20 @@ module ActiveRecord
631
764
 
632
765
  begin
633
766
  reflection = klass._reflect_on_association(inverse_name)
634
- rescue NameError
767
+ if !reflection && active_record.automatically_invert_plural_associations
768
+ plural_inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
769
+ reflection = klass._reflect_on_association(plural_inverse_name)
770
+ end
771
+ rescue NameError => error
772
+ raise unless error.name.to_s == class_name
773
+
635
774
  # Give up: we couldn't compute the klass type so we won't be able
636
775
  # to find any associations either.
637
776
  reflection = false
638
777
  end
639
778
 
640
779
  if valid_inverse_reflection?(reflection)
641
- inverse_name
780
+ reflection.name
642
781
  end
643
782
  end
644
783
  end
@@ -688,16 +827,58 @@ module ActiveRecord
688
827
  class_name.camelize
689
828
  end
690
829
 
691
- def derive_foreign_key
830
+ def derive_foreign_key(infer_from_inverse_of: true)
692
831
  if belongs_to?
693
832
  "#{name}_id"
694
833
  elsif options[:as]
695
834
  "#{options[:as]}_id"
835
+ elsif options[:inverse_of] && infer_from_inverse_of
836
+ inverse_of.foreign_key(infer_from_inverse_of: false)
696
837
  else
697
838
  active_record.model_name.to_s.foreign_key
698
839
  end
699
840
  end
700
841
 
842
+ def derive_fk_query_constraints(foreign_key)
843
+ primary_query_constraints = active_record.query_constraints_list
844
+ owner_pk = active_record.primary_key
845
+
846
+ if primary_query_constraints.size > 2
847
+ raise ArgumentError, <<~MSG.squish
848
+ The query constraints list on the `#{active_record}` model has more than 2
849
+ attributes. Active Record is unable to derive the query constraints
850
+ for the association. You need to explicitly define the query constraints
851
+ for this association.
852
+ MSG
853
+ end
854
+
855
+ if !primary_query_constraints.include?(owner_pk)
856
+ raise ArgumentError, <<~MSG.squish
857
+ The query constraints on the `#{active_record}` model does not include the primary
858
+ key so Active Record is unable to derive the foreign key constraints for
859
+ the association. You need to explicitly define the query constraints for this
860
+ association.
861
+ MSG
862
+ end
863
+
864
+ return foreign_key if primary_query_constraints.include?(foreign_key)
865
+
866
+ first_key, last_key = primary_query_constraints
867
+
868
+ if first_key == owner_pk
869
+ [foreign_key, last_key.to_s]
870
+ elsif last_key == owner_pk
871
+ [first_key.to_s, foreign_key]
872
+ else
873
+ raise ArgumentError, <<~MSG.squish
874
+ Active Record couldn't correctly interpret the query constraints
875
+ for the `#{active_record}` model. The query constraints on `#{active_record}` are
876
+ `#{primary_query_constraints}` and the foreign key is `#{foreign_key}`.
877
+ You need to explicitly set the query constraints for this association.
878
+ MSG
879
+ end
880
+ end
881
+
701
882
  def derive_join_table
702
883
  ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
703
884
  end
@@ -747,7 +928,17 @@ module ActiveRecord
747
928
  # klass option is necessary to support loading polymorphic associations
748
929
  def association_primary_key(klass = nil)
749
930
  if primary_key = options[:primary_key]
750
- @association_primary_key ||= -primary_key.to_s
931
+ @association_primary_key ||= if primary_key.is_a?(Array)
932
+ primary_key.map { |pk| pk.to_s.freeze }.freeze
933
+ else
934
+ -primary_key.to_s
935
+ end
936
+ elsif (klass || self.klass).has_query_constraints? || options[:query_constraints]
937
+ (klass || self.klass).composite_query_constraints_list
938
+ elsif (klass || self.klass).composite_primary_key?
939
+ # If klass has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
940
+ primary_key = (klass || self.klass).primary_key
941
+ primary_key.include?("id") ? "id" : primary_key
751
942
  else
752
943
  primary_key(klass || self.klass)
753
944
  end
@@ -786,6 +977,7 @@ module ActiveRecord
786
977
  :active_record_primary_key, :join_foreign_key, to: :source_reflection
787
978
 
788
979
  def initialize(delegate_reflection)
980
+ super()
789
981
  @delegate_reflection = delegate_reflection
790
982
  @klass = delegate_reflection.options[:anonymous_class]
791
983
  @source_reflection_name = delegate_reflection.options[:source]
@@ -798,7 +990,7 @@ module ActiveRecord
798
990
  end
799
991
 
800
992
  def klass
801
- @klass ||= delegate_reflection.compute_class(class_name)
993
+ @klass ||= delegate_reflection._klass(class_name)
802
994
  end
803
995
 
804
996
  # Returns the source of the through reflection. It checks both a singularized
@@ -819,6 +1011,8 @@ module ActiveRecord
819
1011
  # # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
820
1012
  #
821
1013
  def source_reflection
1014
+ return unless source_reflection_name
1015
+
822
1016
  through_reflection.klass._reflect_on_association(source_reflection_name)
823
1017
  end
824
1018
 
@@ -919,24 +1113,23 @@ module ActiveRecord
919
1113
  end
920
1114
 
921
1115
  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
- )
1116
+ @source_reflection_name ||= begin
1117
+ names = [name.to_s.singularize, name].collect(&:to_sym).uniq
1118
+ names = names.find_all { |n|
1119
+ through_reflection.klass._reflect_on_association(n)
1120
+ }
1121
+
1122
+ if names.length > 1
1123
+ raise AmbiguousSourceReflectionForThroughAssociation.new(
1124
+ active_record.name,
1125
+ macro,
1126
+ name,
1127
+ options,
1128
+ source_reflection_names
1129
+ )
1130
+ end
1131
+ names.first
937
1132
  end
938
-
939
- @source_reflection_name = names.first
940
1133
  end
941
1134
 
942
1135
  def source_options
@@ -977,7 +1170,7 @@ module ActiveRecord
977
1170
  end
978
1171
 
979
1172
  if parent_reflection.nil?
980
- reflections = active_record.reflections.keys.map(&:to_sym)
1173
+ reflections = active_record.normalized_reflections.keys
981
1174
 
982
1175
  if reflections.index(through_reflection.name) > reflections.index(name)
983
1176
  raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
@@ -1040,12 +1233,16 @@ module ActiveRecord
1040
1233
  :name, :scope_for, to: :@reflection
1041
1234
 
1042
1235
  def initialize(reflection, previous_reflection)
1236
+ super()
1043
1237
  @reflection = reflection
1044
1238
  @previous_reflection = previous_reflection
1045
1239
  end
1046
1240
 
1047
1241
  def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
1048
- scopes = @previous_reflection.join_scopes(table, predicate_builder, klass, record) + super
1242
+ scopes = super
1243
+ unless @previous_reflection.through_reflection?
1244
+ scopes += @previous_reflection.join_scopes(table, predicate_builder, klass, record)
1245
+ end
1049
1246
  scopes << build_scope(table, predicate_builder, klass).instance_exec(record, &source_type_scope)
1050
1247
  end
1051
1248
 
@@ -1065,6 +1262,7 @@ module ActiveRecord
1065
1262
  delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection
1066
1263
 
1067
1264
  def initialize(reflection, association)
1265
+ super()
1068
1266
  @reflection = reflection
1069
1267
  @association = association
1070
1268
  end