activerecord 7.0.0 → 7.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 (289) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -1268
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  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 +28 -17
  20. data/lib/active_record/associations/collection_proxy.rb +36 -13
  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 +28 -18
  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 +18 -14
  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 +2 -4
  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 +378 -491
  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 +153 -70
  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 +153 -40
  47. data/lib/active_record/attributes.rb +63 -48
  48. data/lib/active_record/autosave_association.rb +70 -38
  49. data/lib/active_record/base.rb +12 -8
  50. data/lib/active_record/callbacks.rb +16 -32
  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 -34
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +124 -132
  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 +297 -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 +215 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
  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 +163 -29
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
  70. data/lib/active_record/connection_adapters/column.rb +9 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +27 -140
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
  100. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +61 -46
  104. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  106. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  107. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
  108. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
  109. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  110. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  111. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  112. data/lib/active_record/connection_adapters.rb +124 -1
  113. data/lib/active_record/connection_handling.rb +98 -106
  114. data/lib/active_record/core.rb +220 -177
  115. data/lib/active_record/counter_cache.rb +68 -34
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
  117. data/lib/active_record/database_configurations/database_config.rb +26 -5
  118. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  119. data/lib/active_record/database_configurations/url_config.rb +37 -12
  120. data/lib/active_record/database_configurations.rb +88 -35
  121. data/lib/active_record/delegated_type.rb +40 -11
  122. data/lib/active_record/deprecator.rb +7 -0
  123. data/lib/active_record/destroy_association_async_job.rb +3 -1
  124. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  125. data/lib/active_record/dynamic_matchers.rb +2 -2
  126. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  127. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  128. data/lib/active_record/encryption/config.rb +25 -1
  129. data/lib/active_record/encryption/configurable.rb +13 -14
  130. data/lib/active_record/encryption/context.rb +10 -3
  131. data/lib/active_record/encryption/contexts.rb +8 -4
  132. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  133. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  134. data/lib/active_record/encryption/encryptable_record.rb +47 -25
  135. data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
  136. data/lib/active_record/encryption/encryptor.rb +25 -10
  137. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  138. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  139. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  140. data/lib/active_record/encryption/key_generator.rb +12 -1
  141. data/lib/active_record/encryption/message.rb +1 -1
  142. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  143. data/lib/active_record/encryption/message_serializer.rb +6 -0
  144. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  145. data/lib/active_record/encryption/properties.rb +4 -4
  146. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  147. data/lib/active_record/encryption/scheme.rb +23 -22
  148. data/lib/active_record/encryption.rb +1 -0
  149. data/lib/active_record/enum.rb +131 -27
  150. data/lib/active_record/errors.rb +151 -31
  151. data/lib/active_record/explain.rb +21 -12
  152. data/lib/active_record/explain_subscriber.rb +1 -1
  153. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  154. data/lib/active_record/fixture_set/render_context.rb +2 -0
  155. data/lib/active_record/fixture_set/table_row.rb +29 -8
  156. data/lib/active_record/fixtures.rb +169 -99
  157. data/lib/active_record/future_result.rb +47 -8
  158. data/lib/active_record/gem_version.rb +3 -3
  159. data/lib/active_record/inheritance.rb +34 -18
  160. data/lib/active_record/insert_all.rb +72 -22
  161. data/lib/active_record/integration.rb +13 -10
  162. data/lib/active_record/internal_metadata.rb +124 -20
  163. data/lib/active_record/locking/optimistic.rb +39 -24
  164. data/lib/active_record/locking/pessimistic.rb +8 -5
  165. data/lib/active_record/log_subscriber.rb +28 -27
  166. data/lib/active_record/marshalling.rb +56 -0
  167. data/lib/active_record/message_pack.rb +124 -0
  168. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  169. data/lib/active_record/middleware/database_selector.rb +18 -13
  170. data/lib/active_record/middleware/shard_selector.rb +7 -5
  171. data/lib/active_record/migration/command_recorder.rb +110 -13
  172. data/lib/active_record/migration/compatibility.rb +174 -64
  173. data/lib/active_record/migration/default_strategy.rb +22 -0
  174. data/lib/active_record/migration/execution_strategy.rb +19 -0
  175. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  176. data/lib/active_record/migration.rb +292 -125
  177. data/lib/active_record/model_schema.rb +113 -112
  178. data/lib/active_record/nested_attributes.rb +35 -9
  179. data/lib/active_record/normalization.rb +163 -0
  180. data/lib/active_record/persistence.rb +177 -345
  181. data/lib/active_record/promise.rb +84 -0
  182. data/lib/active_record/query_cache.rb +19 -25
  183. data/lib/active_record/query_logs.rb +102 -51
  184. data/lib/active_record/query_logs_formatter.rb +41 -0
  185. data/lib/active_record/querying.rb +34 -9
  186. data/lib/active_record/railtie.rb +153 -100
  187. data/lib/active_record/railties/controller_runtime.rb +24 -10
  188. data/lib/active_record/railties/databases.rake +148 -152
  189. data/lib/active_record/railties/job_runtime.rb +23 -0
  190. data/lib/active_record/readonly_attributes.rb +32 -5
  191. data/lib/active_record/reflection.rb +278 -69
  192. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  193. data/lib/active_record/relation/batches.rb +198 -63
  194. data/lib/active_record/relation/calculations.rb +293 -108
  195. data/lib/active_record/relation/delegation.rb +31 -20
  196. data/lib/active_record/relation/finder_methods.rb +93 -18
  197. data/lib/active_record/relation/merger.rb +6 -6
  198. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  199. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  200. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  201. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  202. data/lib/active_record/relation/predicate_builder.rb +28 -16
  203. data/lib/active_record/relation/query_attribute.rb +25 -1
  204. data/lib/active_record/relation/query_methods.rb +625 -107
  205. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  206. data/lib/active_record/relation/spawn_methods.rb +5 -4
  207. data/lib/active_record/relation/where_clause.rb +7 -19
  208. data/lib/active_record/relation.rb +602 -96
  209. data/lib/active_record/result.rb +55 -52
  210. data/lib/active_record/runtime_registry.rb +63 -1
  211. data/lib/active_record/sanitization.rb +76 -30
  212. data/lib/active_record/schema.rb +39 -23
  213. data/lib/active_record/schema_dumper.rb +82 -30
  214. data/lib/active_record/schema_migration.rb +75 -24
  215. data/lib/active_record/scoping/default.rb +20 -12
  216. data/lib/active_record/scoping/named.rb +3 -2
  217. data/lib/active_record/scoping.rb +2 -1
  218. data/lib/active_record/secure_password.rb +60 -0
  219. data/lib/active_record/secure_token.rb +21 -3
  220. data/lib/active_record/serialization.rb +5 -0
  221. data/lib/active_record/signed_id.rb +29 -8
  222. data/lib/active_record/statement_cache.rb +7 -7
  223. data/lib/active_record/store.rb +16 -11
  224. data/lib/active_record/suppressor.rb +3 -1
  225. data/lib/active_record/table_metadata.rb +7 -3
  226. data/lib/active_record/tasks/database_tasks.rb +191 -121
  227. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  228. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  229. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  230. data/lib/active_record/test_fixtures.rb +174 -152
  231. data/lib/active_record/testing/query_assertions.rb +121 -0
  232. data/lib/active_record/timestamp.rb +31 -17
  233. data/lib/active_record/token_for.rb +123 -0
  234. data/lib/active_record/touch_later.rb +12 -7
  235. data/lib/active_record/transaction.rb +132 -0
  236. data/lib/active_record/transactions.rb +109 -27
  237. data/lib/active_record/translation.rb +1 -3
  238. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  239. data/lib/active_record/type/internal/timezone.rb +7 -2
  240. data/lib/active_record/type/serialized.rb +9 -7
  241. data/lib/active_record/type/time.rb +4 -0
  242. data/lib/active_record/type_caster/connection.rb +4 -4
  243. data/lib/active_record/validations/absence.rb +1 -1
  244. data/lib/active_record/validations/associated.rb +12 -6
  245. data/lib/active_record/validations/numericality.rb +5 -4
  246. data/lib/active_record/validations/presence.rb +5 -28
  247. data/lib/active_record/validations/uniqueness.rb +63 -14
  248. data/lib/active_record/validations.rb +12 -5
  249. data/lib/active_record/version.rb +1 -1
  250. data/lib/active_record.rb +266 -30
  251. data/lib/arel/alias_predication.rb +1 -1
  252. data/lib/arel/collectors/bind.rb +2 -0
  253. data/lib/arel/collectors/composite.rb +7 -0
  254. data/lib/arel/collectors/sql_string.rb +1 -1
  255. data/lib/arel/collectors/substitute_binds.rb +1 -1
  256. data/lib/arel/errors.rb +10 -0
  257. data/lib/arel/factory_methods.rb +4 -0
  258. data/lib/arel/filter_predications.rb +1 -1
  259. data/lib/arel/nodes/binary.rb +6 -7
  260. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  261. data/lib/arel/nodes/cte.rb +36 -0
  262. data/lib/arel/nodes/filter.rb +1 -1
  263. data/lib/arel/nodes/fragments.rb +35 -0
  264. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  265. data/lib/arel/nodes/leading_join.rb +8 -0
  266. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  267. data/lib/arel/nodes/node.rb +115 -5
  268. data/lib/arel/nodes/sql_literal.rb +13 -0
  269. data/lib/arel/nodes/table_alias.rb +4 -0
  270. data/lib/arel/nodes.rb +6 -2
  271. data/lib/arel/predications.rb +3 -1
  272. data/lib/arel/select_manager.rb +1 -1
  273. data/lib/arel/table.rb +9 -5
  274. data/lib/arel/tree_manager.rb +8 -3
  275. data/lib/arel/update_manager.rb +2 -1
  276. data/lib/arel/visitors/dot.rb +1 -0
  277. data/lib/arel/visitors/mysql.rb +17 -5
  278. data/lib/arel/visitors/postgresql.rb +1 -12
  279. data/lib/arel/visitors/to_sql.rb +112 -34
  280. data/lib/arel/visitors/visitor.rb +2 -2
  281. data/lib/arel.rb +21 -3
  282. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  283. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  284. data/lib/rails/generators/active_record/migration.rb +3 -1
  285. data/lib/rails/generators/active_record/model/USAGE +113 -0
  286. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  287. metadata +59 -17
  288. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  289. data/lib/active_record/null_relation.rb +0 -63
@@ -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
@@ -297,6 +345,12 @@ module ActiveRecord
297
345
  options[:strict_loading]
298
346
  end
299
347
 
348
+ def strict_loading_violation_message(owner)
349
+ message = +"`#{owner}` is marked for strict_loading."
350
+ message << " The #{polymorphic? ? "polymorphic association" : "#{klass} association"}"
351
+ message << " named `:#{name}` cannot be lazily loaded."
352
+ end
353
+
300
354
  protected
301
355
  def actual_source_reflection # FIXME: this is a horrible name
302
356
  self
@@ -340,9 +394,10 @@ module ActiveRecord
340
394
  attr_reader :plural_name # :nodoc:
341
395
 
342
396
  def initialize(name, scope, options, active_record)
397
+ super()
343
398
  @name = name
344
399
  @scope = scope
345
- @options = options
400
+ @options = normalize_options(options)
346
401
  @active_record = active_record
347
402
  @klass = options[:anonymous_class]
348
403
  @plural_name = active_record.pluralize_table_names ?
@@ -373,7 +428,15 @@ module ActiveRecord
373
428
  # a new association object. Use +build_association+ or +create_association+
374
429
  # instead. This allows plugins to hook into association object creation.
375
430
  def klass
376
- @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)
377
440
  end
378
441
 
379
442
  def compute_class(name)
@@ -398,6 +461,26 @@ module ActiveRecord
398
461
  def derive_class_name
399
462
  name.to_s.camelize
400
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
401
484
  end
402
485
 
403
486
  # Holds all the metadata about an aggregation as it was specified in the
@@ -417,23 +500,23 @@ module ActiveRecord
417
500
  raise ArgumentError, "Polymorphic associations do not support computing the class."
418
501
  end
419
502
 
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
503
  begin
427
504
  klass = active_record.send(:compute_type, name)
428
-
429
- unless klass < ActiveRecord::Base
430
- 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
431
512
  end
513
+ end
432
514
 
433
- klass
434
- rescue NameError
435
- 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."
436
517
  end
518
+
519
+ klass
437
520
  end
438
521
 
439
522
  attr_reader :type, :foreign_type
@@ -443,6 +526,21 @@ module ActiveRecord
443
526
  super
444
527
  @type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
445
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
446
544
 
447
545
  ensure_option_not_given_as_class!(:class_name)
448
546
  end
@@ -452,15 +550,33 @@ module ActiveRecord
452
550
  if polymorphic?
453
551
  key = [key, owner._read_attribute(@foreign_type)]
454
552
  end
455
- klass.cached_find_by_statement(key, &block)
553
+ klass.with_connection do |connection|
554
+ klass.cached_find_by_statement(connection, key, &block)
555
+ end
456
556
  end
457
557
 
458
558
  def join_table
459
559
  @join_table ||= -(options[:join_table]&.to_s || derive_join_table)
460
560
  end
461
561
 
462
- def foreign_key
463
- @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
464
580
  end
465
581
 
466
582
  def association_foreign_key
@@ -472,19 +588,46 @@ module ActiveRecord
472
588
  end
473
589
 
474
590
  def active_record_primary_key
475
- @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
476
607
  end
477
608
 
478
609
  def join_primary_key(klass = nil)
479
610
  foreign_key
480
611
  end
481
612
 
613
+ def join_primary_type
614
+ type
615
+ end
616
+
482
617
  def join_foreign_key
483
618
  active_record_primary_key
484
619
  end
485
620
 
486
621
  def check_validity!
487
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
488
631
  end
489
632
 
490
633
  def check_eager_loadable!
@@ -500,7 +643,7 @@ module ActiveRecord
500
643
  end
501
644
 
502
645
  def join_id_for(owner) # :nodoc:
503
- owner[join_foreign_key]
646
+ Array(join_foreign_key).map { |key| owner._read_attribute(key) }
504
647
  end
505
648
 
506
649
  def through_reflection
@@ -582,6 +725,10 @@ module ActiveRecord
582
725
  options[:polymorphic]
583
726
  end
584
727
 
728
+ def polymorphic_name
729
+ active_record.polymorphic_name
730
+ end
731
+
585
732
  def add_as_source(seed)
586
733
  seed
587
734
  end
@@ -617,14 +764,20 @@ module ActiveRecord
617
764
 
618
765
  begin
619
766
  reflection = klass._reflect_on_association(inverse_name)
620
- 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
+
621
774
  # Give up: we couldn't compute the klass type so we won't be able
622
775
  # to find any associations either.
623
776
  reflection = false
624
777
  end
625
778
 
626
779
  if valid_inverse_reflection?(reflection)
627
- inverse_name
780
+ reflection.name
628
781
  end
629
782
  end
630
783
  end
@@ -674,16 +827,58 @@ module ActiveRecord
674
827
  class_name.camelize
675
828
  end
676
829
 
677
- def derive_foreign_key
830
+ def derive_foreign_key(infer_from_inverse_of: true)
678
831
  if belongs_to?
679
832
  "#{name}_id"
680
833
  elsif options[:as]
681
834
  "#{options[:as]}_id"
835
+ elsif options[:inverse_of] && infer_from_inverse_of
836
+ inverse_of.foreign_key(infer_from_inverse_of: false)
682
837
  else
683
838
  active_record.model_name.to_s.foreign_key
684
839
  end
685
840
  end
686
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
+
687
882
  def derive_join_table
688
883
  ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
689
884
  end
@@ -733,7 +928,17 @@ module ActiveRecord
733
928
  # klass option is necessary to support loading polymorphic associations
734
929
  def association_primary_key(klass = nil)
735
930
  if primary_key = options[:primary_key]
736
- @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
737
942
  else
738
943
  primary_key(klass || self.klass)
739
944
  end
@@ -772,6 +977,7 @@ module ActiveRecord
772
977
  :active_record_primary_key, :join_foreign_key, to: :source_reflection
773
978
 
774
979
  def initialize(delegate_reflection)
980
+ super()
775
981
  @delegate_reflection = delegate_reflection
776
982
  @klass = delegate_reflection.options[:anonymous_class]
777
983
  @source_reflection_name = delegate_reflection.options[:source]
@@ -784,7 +990,7 @@ module ActiveRecord
784
990
  end
785
991
 
786
992
  def klass
787
- @klass ||= delegate_reflection.compute_class(class_name)
993
+ @klass ||= delegate_reflection._klass(class_name)
788
994
  end
789
995
 
790
996
  # Returns the source of the through reflection. It checks both a singularized
@@ -805,6 +1011,8 @@ module ActiveRecord
805
1011
  # # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
806
1012
  #
807
1013
  def source_reflection
1014
+ return unless source_reflection_name
1015
+
808
1016
  through_reflection.klass._reflect_on_association(source_reflection_name)
809
1017
  end
810
1018
 
@@ -905,24 +1113,23 @@ module ActiveRecord
905
1113
  end
906
1114
 
907
1115
  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
- )
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
923
1132
  end
924
-
925
- @source_reflection_name = names.first
926
1133
  end
927
1134
 
928
1135
  def source_options
@@ -963,7 +1170,7 @@ module ActiveRecord
963
1170
  end
964
1171
 
965
1172
  if parent_reflection.nil?
966
- reflections = active_record.reflections.keys.map(&:to_sym)
1173
+ reflections = active_record.normalized_reflections.keys
967
1174
 
968
1175
  if reflections.index(through_reflection.name) > reflections.index(name)
969
1176
  raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
@@ -1026,12 +1233,13 @@ module ActiveRecord
1026
1233
  :name, :scope_for, to: :@reflection
1027
1234
 
1028
1235
  def initialize(reflection, previous_reflection)
1236
+ super()
1029
1237
  @reflection = reflection
1030
1238
  @previous_reflection = previous_reflection
1031
1239
  end
1032
1240
 
1033
1241
  def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
1034
- scopes = @previous_reflection.join_scopes(table, predicate_builder, record) + super
1242
+ scopes = @previous_reflection.join_scopes(table, predicate_builder, klass, record) + super
1035
1243
  scopes << build_scope(table, predicate_builder, klass).instance_exec(record, &source_type_scope)
1036
1244
  end
1037
1245
 
@@ -1051,6 +1259,7 @@ module ActiveRecord
1051
1259
  delegate :scope, :type, :constraints, :join_foreign_key, to: :@reflection
1052
1260
 
1053
1261
  def initialize(reflection, association)
1262
+ super()
1054
1263
  @reflection = reflection
1055
1264
  @association = association
1056
1265
  end