activerecord 6.1.4 → 7.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (234) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1049 -977
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/association_relation.rb +0 -10
  7. data/lib/active_record/associations/association.rb +33 -17
  8. data/lib/active_record/associations/association_scope.rb +1 -3
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -4
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +8 -2
  12. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_many.rb +3 -2
  15. data/lib/active_record/associations/builder/has_one.rb +2 -1
  16. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  17. data/lib/active_record/associations/collection_association.rb +34 -27
  18. data/lib/active_record/associations/collection_proxy.rb +8 -3
  19. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  20. data/lib/active_record/associations/has_many_association.rb +1 -1
  21. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  22. data/lib/active_record/associations/has_one_association.rb +10 -7
  23. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  24. data/lib/active_record/associations/preloader/association.rb +187 -55
  25. data/lib/active_record/associations/preloader/batch.rb +48 -0
  26. data/lib/active_record/associations/preloader/branch.rb +147 -0
  27. data/lib/active_record/associations/preloader/through_association.rb +49 -13
  28. data/lib/active_record/associations/preloader.rb +39 -113
  29. data/lib/active_record/associations/singular_association.rb +8 -2
  30. data/lib/active_record/associations/through_association.rb +3 -3
  31. data/lib/active_record/associations.rb +90 -82
  32. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  33. data/lib/active_record/attribute_assignment.rb +1 -1
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  35. data/lib/active_record/attribute_methods/dirty.rb +49 -16
  36. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  37. data/lib/active_record/attribute_methods/query.rb +2 -2
  38. data/lib/active_record/attribute_methods/read.rb +7 -5
  39. data/lib/active_record/attribute_methods/serialization.rb +66 -12
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +13 -14
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +6 -21
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +2 -2
  47. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  48. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +47 -561
  51. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +46 -22
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +42 -72
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +34 -9
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +69 -18
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +149 -74
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +97 -81
  61. data/lib/active_record/connection_adapters/column.rb +4 -0
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +35 -23
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +35 -21
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +4 -1
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  66. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  67. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -12
  69. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  72. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  73. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +28 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  76. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +50 -50
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  80. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  81. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +27 -16
  83. data/lib/active_record/connection_adapters/postgresql_adapter.rb +205 -105
  84. data/lib/active_record/connection_adapters/schema_cache.rb +29 -4
  85. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +25 -19
  86. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +15 -16
  87. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -2
  88. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -30
  89. data/lib/active_record/connection_adapters.rb +6 -5
  90. data/lib/active_record/connection_handling.rb +47 -53
  91. data/lib/active_record/core.rb +122 -132
  92. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  93. data/lib/active_record/database_configurations/database_config.rb +12 -9
  94. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  95. data/lib/active_record/database_configurations/url_config.rb +2 -2
  96. data/lib/active_record/database_configurations.rb +16 -32
  97. data/lib/active_record/delegated_type.rb +52 -11
  98. data/lib/active_record/destroy_association_async_job.rb +1 -1
  99. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  100. data/lib/active_record/dynamic_matchers.rb +1 -1
  101. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  102. data/lib/active_record/encryption/cipher.rb +53 -0
  103. data/lib/active_record/encryption/config.rb +44 -0
  104. data/lib/active_record/encryption/configurable.rb +61 -0
  105. data/lib/active_record/encryption/context.rb +35 -0
  106. data/lib/active_record/encryption/contexts.rb +72 -0
  107. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  108. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  109. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  111. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  112. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  113. data/lib/active_record/encryption/encryptor.rb +155 -0
  114. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  115. data/lib/active_record/encryption/errors.rb +15 -0
  116. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  117. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  118. data/lib/active_record/encryption/key.rb +28 -0
  119. data/lib/active_record/encryption/key_generator.rb +42 -0
  120. data/lib/active_record/encryption/key_provider.rb +46 -0
  121. data/lib/active_record/encryption/message.rb +33 -0
  122. data/lib/active_record/encryption/message_serializer.rb +90 -0
  123. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  124. data/lib/active_record/encryption/properties.rb +76 -0
  125. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  126. data/lib/active_record/encryption/scheme.rb +99 -0
  127. data/lib/active_record/encryption.rb +55 -0
  128. data/lib/active_record/enum.rb +49 -42
  129. data/lib/active_record/errors.rb +67 -4
  130. data/lib/active_record/explain_registry.rb +11 -6
  131. data/lib/active_record/fixture_set/file.rb +15 -1
  132. data/lib/active_record/fixture_set/table_row.rb +41 -6
  133. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  134. data/lib/active_record/fixtures.rb +17 -20
  135. data/lib/active_record/future_result.rb +139 -0
  136. data/lib/active_record/gem_version.rb +4 -4
  137. data/lib/active_record/inheritance.rb +55 -17
  138. data/lib/active_record/insert_all.rb +80 -14
  139. data/lib/active_record/integration.rb +4 -3
  140. data/lib/active_record/internal_metadata.rb +3 -5
  141. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  142. data/lib/active_record/locking/optimistic.rb +10 -9
  143. data/lib/active_record/locking/pessimistic.rb +9 -3
  144. data/lib/active_record/log_subscriber.rb +14 -3
  145. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  146. data/lib/active_record/middleware/database_selector.rb +8 -3
  147. data/lib/active_record/middleware/shard_selector.rb +60 -0
  148. data/lib/active_record/migration/command_recorder.rb +4 -4
  149. data/lib/active_record/migration/compatibility.rb +83 -1
  150. data/lib/active_record/migration/join_table.rb +1 -1
  151. data/lib/active_record/migration.rb +109 -79
  152. data/lib/active_record/model_schema.rb +45 -58
  153. data/lib/active_record/nested_attributes.rb +13 -12
  154. data/lib/active_record/no_touching.rb +3 -3
  155. data/lib/active_record/null_relation.rb +2 -6
  156. data/lib/active_record/persistence.rb +219 -52
  157. data/lib/active_record/query_cache.rb +2 -2
  158. data/lib/active_record/query_logs.rb +138 -0
  159. data/lib/active_record/querying.rb +15 -5
  160. data/lib/active_record/railtie.rb +127 -17
  161. data/lib/active_record/railties/controller_runtime.rb +1 -1
  162. data/lib/active_record/railties/databases.rake +66 -129
  163. data/lib/active_record/readonly_attributes.rb +11 -0
  164. data/lib/active_record/reflection.rb +67 -50
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  166. data/lib/active_record/relation/batches.rb +3 -3
  167. data/lib/active_record/relation/calculations.rb +40 -36
  168. data/lib/active_record/relation/delegation.rb +6 -6
  169. data/lib/active_record/relation/finder_methods.rb +31 -35
  170. data/lib/active_record/relation/merger.rb +20 -13
  171. data/lib/active_record/relation/predicate_builder.rb +1 -6
  172. data/lib/active_record/relation/query_attribute.rb +5 -11
  173. data/lib/active_record/relation/query_methods.rb +235 -61
  174. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  175. data/lib/active_record/relation/spawn_methods.rb +2 -2
  176. data/lib/active_record/relation/where_clause.rb +10 -19
  177. data/lib/active_record/relation.rb +171 -84
  178. data/lib/active_record/result.rb +17 -7
  179. data/lib/active_record/runtime_registry.rb +9 -13
  180. data/lib/active_record/sanitization.rb +11 -7
  181. data/lib/active_record/schema_dumper.rb +10 -3
  182. data/lib/active_record/schema_migration.rb +0 -4
  183. data/lib/active_record/scoping/default.rb +61 -12
  184. data/lib/active_record/scoping/named.rb +3 -11
  185. data/lib/active_record/scoping.rb +64 -34
  186. data/lib/active_record/serialization.rb +1 -1
  187. data/lib/active_record/signed_id.rb +1 -1
  188. data/lib/active_record/suppressor.rb +11 -15
  189. data/lib/active_record/tasks/database_tasks.rb +116 -58
  190. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  191. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -12
  192. data/lib/active_record/test_databases.rb +1 -1
  193. data/lib/active_record/test_fixtures.rb +4 -4
  194. data/lib/active_record/timestamp.rb +3 -4
  195. data/lib/active_record/transactions.rb +9 -14
  196. data/lib/active_record/translation.rb +2 -2
  197. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  198. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  199. data/lib/active_record/type/internal/timezone.rb +2 -2
  200. data/lib/active_record/type/serialized.rb +1 -1
  201. data/lib/active_record/type/type_map.rb +17 -20
  202. data/lib/active_record/type.rb +1 -2
  203. data/lib/active_record/validations/associated.rb +1 -1
  204. data/lib/active_record/validations/uniqueness.rb +1 -1
  205. data/lib/active_record.rb +204 -28
  206. data/lib/arel/attributes/attribute.rb +0 -8
  207. data/lib/arel/crud.rb +28 -22
  208. data/lib/arel/delete_manager.rb +18 -4
  209. data/lib/arel/filter_predications.rb +9 -0
  210. data/lib/arel/insert_manager.rb +2 -3
  211. data/lib/arel/nodes/casted.rb +1 -1
  212. data/lib/arel/nodes/delete_statement.rb +12 -13
  213. data/lib/arel/nodes/filter.rb +10 -0
  214. data/lib/arel/nodes/function.rb +1 -0
  215. data/lib/arel/nodes/insert_statement.rb +2 -2
  216. data/lib/arel/nodes/select_core.rb +2 -2
  217. data/lib/arel/nodes/select_statement.rb +2 -2
  218. data/lib/arel/nodes/update_statement.rb +8 -3
  219. data/lib/arel/nodes.rb +1 -0
  220. data/lib/arel/predications.rb +11 -3
  221. data/lib/arel/select_manager.rb +10 -4
  222. data/lib/arel/table.rb +0 -1
  223. data/lib/arel/tree_manager.rb +0 -12
  224. data/lib/arel/update_manager.rb +18 -4
  225. data/lib/arel/visitors/dot.rb +80 -90
  226. data/lib/arel/visitors/mysql.rb +8 -2
  227. data/lib/arel/visitors/postgresql.rb +0 -10
  228. data/lib/arel/visitors/to_sql.rb +58 -2
  229. data/lib/arel.rb +2 -1
  230. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  231. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  232. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  233. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  234. metadata +56 -13
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/enumerable"
4
- require "active_support/core_ext/string/conversions"
5
-
6
3
  module ActiveRecord
7
- class AssociationNotFoundError < ConfigurationError #:nodoc:
4
+ class AssociationNotFoundError < ConfigurationError # :nodoc:
8
5
  attr_reader :record, :association_name
6
+
9
7
  def initialize(record = nil, association_name = nil)
10
8
  @record = record
11
9
  @association_name = association_name
@@ -16,32 +14,25 @@ module ActiveRecord
16
14
  end
17
15
  end
18
16
 
19
- class Correction
20
- def initialize(error)
21
- @error = error
22
- end
17
+ if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
18
+ include DidYouMean::Correctable
23
19
 
24
20
  def corrections
25
- if @error.association_name
26
- maybe_these = @error.record.class.reflections.keys
27
-
28
- maybe_these.sort_by { |n|
29
- DidYouMean::Jaro.distance(@error.association_name.to_s, n)
30
- }.reverse.first(4)
21
+ if record && association_name
22
+ @corrections ||= begin
23
+ maybe_these = record.class.reflections.keys
24
+ DidYouMean::SpellChecker.new(dictionary: maybe_these).correct(association_name)
25
+ end
31
26
  else
32
27
  []
33
28
  end
34
29
  end
35
30
  end
36
-
37
- # We may not have DYM, and DYM might not let us register error handlers
38
- if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
39
- DidYouMean.correct_error(self, Correction)
40
- end
41
31
  end
42
32
 
43
- class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
33
+ class InverseOfAssociationNotFoundError < ActiveRecordError # :nodoc:
44
34
  attr_reader :reflection, :associated_class
35
+
45
36
  def initialize(reflection = nil, associated_class = nil)
46
37
  if reflection
47
38
  @reflection = reflection
@@ -52,31 +43,35 @@ module ActiveRecord
52
43
  end
53
44
  end
54
45
 
55
- class Correction
56
- def initialize(error)
57
- @error = error
58
- end
46
+ if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
47
+ include DidYouMean::Correctable
59
48
 
60
49
  def corrections
61
- if @error.reflection && @error.associated_class
62
- maybe_these = @error.associated_class.reflections.keys
63
-
64
- maybe_these.sort_by { |n|
65
- DidYouMean::Jaro.distance(@error.reflection.options[:inverse_of].to_s, n)
66
- }.reverse.first(4)
50
+ if reflection && associated_class
51
+ @corrections ||= begin
52
+ maybe_these = associated_class.reflections.keys
53
+ DidYouMean::SpellChecker.new(dictionary: maybe_these).correct(reflection.options[:inverse_of].to_s)
54
+ end
67
55
  else
68
56
  []
69
57
  end
70
58
  end
71
59
  end
60
+ end
72
61
 
73
- # We may not have DYM, and DYM might not let us register error handlers
74
- if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
75
- DidYouMean.correct_error(self, Correction)
62
+ class InverseOfAssociationRecursiveError < ActiveRecordError # :nodoc:
63
+ attr_reader :reflection
64
+ def initialize(reflection = nil)
65
+ if reflection
66
+ @reflection = reflection
67
+ super("Inverse association #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{reflection.class_name}) is recursive.")
68
+ else
69
+ super("Inverse association is recursive.")
70
+ end
76
71
  end
77
72
  end
78
73
 
79
- class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
74
+ class HasManyThroughAssociationNotFoundError < ActiveRecordError # :nodoc:
80
75
  attr_reader :owner_class, :reflection
81
76
 
82
77
  def initialize(owner_class = nil, reflection = nil)
@@ -89,32 +84,24 @@ module ActiveRecord
89
84
  end
90
85
  end
91
86
 
92
- class Correction
93
- def initialize(error)
94
- @error = error
95
- end
87
+ if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
88
+ include DidYouMean::Correctable
96
89
 
97
90
  def corrections
98
- if @error.reflection && @error.owner_class
99
- maybe_these = @error.owner_class.reflections.keys
100
- maybe_these -= [@error.reflection.name.to_s] # remove failing reflection
101
-
102
- maybe_these.sort_by { |n|
103
- DidYouMean::Jaro.distance(@error.reflection.options[:through].to_s, n)
104
- }.reverse.first(4)
91
+ if owner_class && reflection
92
+ @corrections ||= begin
93
+ maybe_these = owner_class.reflections.keys
94
+ maybe_these -= [reflection.name.to_s] # remove failing reflection
95
+ DidYouMean::SpellChecker.new(dictionary: maybe_these).correct(reflection.options[:through].to_s)
96
+ end
105
97
  else
106
98
  []
107
99
  end
108
100
  end
109
101
  end
110
-
111
- # We may not have DYM, and DYM might not let us register error handlers
112
- if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
113
- DidYouMean.correct_error(self, Correction)
114
- end
115
102
  end
116
103
 
117
- class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc:
104
+ class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError # :nodoc:
118
105
  def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil)
119
106
  if owner_class_name && reflection && source_reflection
120
107
  super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}' without 'source_type'. Try adding 'source_type: \"#{reflection.name.to_s.classify}\"' to 'has_many :through' definition.")
@@ -124,7 +111,7 @@ module ActiveRecord
124
111
  end
125
112
  end
126
113
 
127
- class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError #:nodoc:
114
+ class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError # :nodoc:
128
115
  def initialize(owner_class_name = nil, reflection = nil)
129
116
  if owner_class_name && reflection
130
117
  super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
@@ -134,7 +121,7 @@ module ActiveRecord
134
121
  end
135
122
  end
136
123
 
137
- class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
124
+ class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError # :nodoc:
138
125
  def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil)
139
126
  if owner_class_name && reflection && source_reflection
140
127
  super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
@@ -144,7 +131,7 @@ module ActiveRecord
144
131
  end
145
132
  end
146
133
 
147
- class HasOneThroughCantAssociateThroughCollection < ActiveRecordError #:nodoc:
134
+ class HasOneThroughCantAssociateThroughCollection < ActiveRecordError # :nodoc:
148
135
  def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil)
149
136
  if owner_class_name && reflection && through_reflection
150
137
  super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.")
@@ -154,7 +141,7 @@ module ActiveRecord
154
141
  end
155
142
  end
156
143
 
157
- class HasOneAssociationPolymorphicThroughError < ActiveRecordError #:nodoc:
144
+ class HasOneAssociationPolymorphicThroughError < ActiveRecordError # :nodoc:
158
145
  def initialize(owner_class_name = nil, reflection = nil)
159
146
  if owner_class_name && reflection
160
147
  super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
@@ -164,7 +151,7 @@ module ActiveRecord
164
151
  end
165
152
  end
166
153
 
167
- class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
154
+ class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError # :nodoc:
168
155
  def initialize(reflection = nil)
169
156
  if reflection
170
157
  through_reflection = reflection.through_reflection
@@ -177,7 +164,7 @@ module ActiveRecord
177
164
  end
178
165
  end
179
166
 
180
- class HasManyThroughOrderError < ActiveRecordError #:nodoc:
167
+ class HasManyThroughOrderError < ActiveRecordError # :nodoc:
181
168
  def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil)
182
169
  if owner_class_name && reflection && through_reflection
183
170
  super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through '#{owner_class_name}##{through_reflection.name}' before the through association is defined.")
@@ -187,7 +174,7 @@ module ActiveRecord
187
174
  end
188
175
  end
189
176
 
190
- class ThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
177
+ class ThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError # :nodoc:
191
178
  def initialize(owner = nil, reflection = nil)
192
179
  if owner && reflection
193
180
  super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
@@ -212,13 +199,13 @@ module ActiveRecord
212
199
  end
213
200
  end
214
201
 
215
- class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc:
202
+ class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection # :nodoc:
216
203
  end
217
204
 
218
- class HasOneThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc:
205
+ class HasOneThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection # :nodoc:
219
206
  end
220
207
 
221
- class ThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc:
208
+ class ThroughNestedAssociationsAreReadonly < ActiveRecordError # :nodoc:
222
209
  def initialize(owner = nil, reflection = nil)
223
210
  if owner && reflection
224
211
  super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
@@ -228,10 +215,10 @@ module ActiveRecord
228
215
  end
229
216
  end
230
217
 
231
- class HasManyThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc:
218
+ class HasManyThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly # :nodoc:
232
219
  end
233
220
 
234
- class HasOneThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc:
221
+ class HasOneThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly # :nodoc:
235
222
  end
236
223
 
237
224
  # This error is raised when trying to eager load a polymorphic association using a JOIN.
@@ -250,7 +237,7 @@ module ActiveRecord
250
237
  # This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations
251
238
  # (has_many, has_one) when there is at least 1 child associated instance.
252
239
  # ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
253
- class DeleteRestrictionError < ActiveRecordError #:nodoc:
240
+ class DeleteRestrictionError < ActiveRecordError # :nodoc:
254
241
  def initialize(name = nil)
255
242
  if name
256
243
  super("Cannot delete record because of dependent #{name}")
@@ -274,7 +261,7 @@ module ActiveRecord
274
261
  autoload :CollectionProxy
275
262
  autoload :ThroughAssociation
276
263
 
277
- module Builder #:nodoc:
264
+ module Builder # :nodoc:
278
265
  autoload :Association, "active_record/associations/builder/association"
279
266
  autoload :SingularAssociation, "active_record/associations/builder/singular_association"
280
267
  autoload :CollectionAssociation, "active_record/associations/builder/collection_association"
@@ -296,6 +283,7 @@ module ActiveRecord
296
283
  autoload :Preloader
297
284
  autoload :JoinDependency
298
285
  autoload :AssociationScope
286
+ autoload :DisableJoinsAssociationScope
299
287
  autoload :AliasTracker
300
288
  end
301
289
 
@@ -305,7 +293,7 @@ module ActiveRecord
305
293
  end
306
294
 
307
295
  # Returns the association instance for the given name, instantiating it if it doesn't already exist
308
- def association(name) #:nodoc:
296
+ def association(name) # :nodoc:
309
297
  association = association_instance_get(name)
310
298
 
311
299
  if association.nil?
@@ -328,17 +316,7 @@ module ActiveRecord
328
316
  super
329
317
  end
330
318
 
331
- def reload(*) # :nodoc:
332
- clear_association_cache
333
- super
334
- end
335
-
336
319
  private
337
- # Clears out the association cache.
338
- def clear_association_cache
339
- @association_cache.clear if persisted?
340
- end
341
-
342
320
  def init_internals
343
321
  @association_cache = {}
344
322
  super
@@ -398,6 +376,8 @@ module ActiveRecord
398
376
  # create_other(attributes={}) | X | | X
399
377
  # create_other!(attributes={}) | X | | X
400
378
  # reload_other | X | X | X
379
+ # other_changed? | X | X |
380
+ # other_previously_changed? | X | X |
401
381
  #
402
382
  # === Collection associations (one-to-many / many-to-many)
403
383
  # | | | has_many
@@ -780,9 +760,10 @@ module ActiveRecord
780
760
  # inverse detection only works on #has_many, #has_one, and
781
761
  # #belongs_to associations.
782
762
  #
783
- # <tt>:foreign_key</tt> and <tt>:through</tt> options on the associations,
784
- # or a custom scope, will also prevent the association's inverse
785
- # from being found automatically.
763
+ # <tt>:foreign_key</tt> and <tt>:through</tt> options on the associations
764
+ # will also prevent the association's inverse from being found automatically,
765
+ # as will a custom scopes in some cases. See further details in the
766
+ # {Active Record Associations guide}[https://guides.rubyonrails.org/association_basics.html#bi-directional-associations].
786
767
  #
787
768
  # The automatic guessing of the inverse association uses a heuristic based
788
769
  # on the name of the class, so it may not work for all associations,
@@ -1410,6 +1391,11 @@ module ActiveRecord
1410
1391
  # join model. This allows associated records to be built which will automatically create
1411
1392
  # the appropriate join model records when they are saved. (See the 'Association Join Models'
1412
1393
  # section above.)
1394
+ # [:disable_joins]
1395
+ # Specifies whether joins should be skipped for an association. If set to true, two or more queries
1396
+ # will be generated. Note that in some cases, if order or limit is applied, it will be done in-memory
1397
+ # due to database limitations. This option is only applicable on `has_many :through` associations as
1398
+ # `has_many` alone do not perform a join.
1413
1399
  # [:source]
1414
1400
  # Specifies the source association name used by #has_many <tt>:through</tt> queries.
1415
1401
  # Only use it if the name cannot be inferred from the association.
@@ -1439,7 +1425,8 @@ module ActiveRecord
1439
1425
  # Useful for defining methods on associations, especially when they should be shared between multiple
1440
1426
  # association objects.
1441
1427
  # [:strict_loading]
1442
- # Enforces strict loading every time the associated record is loaded through this association.
1428
+ # When set to +true+, enforces strict loading every time the associated record is loaded through this
1429
+ # association.
1443
1430
  # [:ensuring_owner_was]
1444
1431
  # Specifies an instance method to be called on the owner. The method must return true in order for the
1445
1432
  # associated records to be deleted in a background job.
@@ -1453,6 +1440,7 @@ module ActiveRecord
1453
1440
  # has_many :tags, as: :taggable
1454
1441
  # has_many :reports, -> { readonly }
1455
1442
  # has_many :subscribers, through: :subscriptions, source: :user
1443
+ # has_many :subscribers, through: :subscriptions, disable_joins: true
1456
1444
  # has_many :comments, strict_loading: true
1457
1445
  def has_many(name, scope = nil, **options, &extension)
1458
1446
  reflection = Builder::HasMany.build(self, name, scope, options, &extension)
@@ -1558,8 +1546,21 @@ module ActiveRecord
1558
1546
  # source reflection. You can only use a <tt>:through</tt> query through a #has_one
1559
1547
  # or #belongs_to association on the join model.
1560
1548
  #
1549
+ # If the association on the join model is a #belongs_to, the collection can be modified
1550
+ # and the records on the <tt>:through</tt> model will be automatically created and removed
1551
+ # as appropriate. Otherwise, the collection is read-only, so you should manipulate the
1552
+ # <tt>:through</tt> association directly.
1553
+ #
1561
1554
  # If you are going to modify the association (rather than just read from it), then it is
1562
- # a good idea to set the <tt>:inverse_of</tt> option.
1555
+ # a good idea to set the <tt>:inverse_of</tt> option on the source association on the
1556
+ # join model. This allows associated records to be built which will automatically create
1557
+ # the appropriate join model records when they are saved. (See the 'Association Join Models'
1558
+ # section above.)
1559
+ # [:disable_joins]
1560
+ # Specifies whether joins should be skipped for an association. If set to true, two or more queries
1561
+ # will be generated. Note that in some cases, if order or limit is applied, it will be done in-memory
1562
+ # due to database limitations. This option is only applicable on `has_one :through` associations as
1563
+ # `has_one` alone does not perform a join.
1563
1564
  # [:source]
1564
1565
  # Specifies the source association name used by #has_one <tt>:through</tt> queries.
1565
1566
  # Only use it if the name cannot be inferred from the association.
@@ -1601,6 +1602,7 @@ module ActiveRecord
1601
1602
  # has_one :attachment, as: :attachable
1602
1603
  # has_one :boss, -> { readonly }
1603
1604
  # has_one :club, through: :membership
1605
+ # has_one :club, through: :membership, disable_joins: true
1604
1606
  # has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable
1605
1607
  # has_one :credit_card, required: true
1606
1608
  # has_one :credit_card, strict_loading: true
@@ -1637,6 +1639,10 @@ module ActiveRecord
1637
1639
  # if the record is invalid.
1638
1640
  # [reload_association]
1639
1641
  # Returns the associated object, forcing a database read.
1642
+ # [association_changed?]
1643
+ # Returns true if a new associate object has been assigned and the next save will update the foreign key.
1644
+ # [association_previously_changed?]
1645
+ # Returns true if the previous save updated the association to reference a new associate object.
1640
1646
  #
1641
1647
  # === Example
1642
1648
  #
@@ -1647,6 +1653,8 @@ module ActiveRecord
1647
1653
  # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
1648
1654
  # * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>)
1649
1655
  # * <tt>Post#reload_author</tt>
1656
+ # * <tt>Post#author_changed?</tt>
1657
+ # * <tt>Post#author_previously_changed?</tt>
1650
1658
  # The declaration can also include an +options+ hash to specialize the behavior of the association.
1651
1659
  #
1652
1660
  # === Scopes
@@ -1780,7 +1788,7 @@ module ActiveRecord
1780
1788
  # The join table should not have a primary key or a model associated with it. You must manually generate the
1781
1789
  # join table with a migration such as this:
1782
1790
  #
1783
- # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[6.0]
1791
+ # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[7.0]
1784
1792
  # def change
1785
1793
  # create_join_table :developers, :projects
1786
1794
  # end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class AsynchronousQueriesTracker # :nodoc:
5
+ module NullSession # :nodoc:
6
+ class << self
7
+ def active?
8
+ true
9
+ end
10
+
11
+ def finalize
12
+ end
13
+ end
14
+ end
15
+
16
+ class Session # :nodoc:
17
+ def initialize
18
+ @active = true
19
+ end
20
+
21
+ def active?
22
+ @active
23
+ end
24
+
25
+ def finalize
26
+ @active = false
27
+ end
28
+ end
29
+
30
+ class << self
31
+ def install_executor_hooks(executor = ActiveSupport::Executor)
32
+ executor.register_hook(self)
33
+ end
34
+
35
+ def run
36
+ ActiveRecord::Base.asynchronous_queries_tracker.start_session
37
+ end
38
+
39
+ def complete(asynchronous_queries_tracker)
40
+ asynchronous_queries_tracker.finalize_session
41
+ end
42
+ end
43
+
44
+ attr_reader :current_session
45
+
46
+ def initialize
47
+ @current_session = NullSession
48
+ end
49
+
50
+ def start_session
51
+ @current_session = Session.new
52
+ self
53
+ end
54
+
55
+ def finalize_session
56
+ @current_session.finalize
57
+ @current_session = NullSession
58
+ end
59
+ end
60
+ end
@@ -46,7 +46,7 @@ module ActiveRecord
46
46
  def execute_callstack_for_multiparameter_attributes(callstack)
47
47
  errors = []
48
48
  callstack.each do |name, values_with_empty_parameters|
49
- if values_with_empty_parameters.each_value.all?(&:nil?)
49
+ if values_with_empty_parameters.each_value.all?(NilClass)
50
50
  values = nil
51
51
  else
52
52
  values = values_with_empty_parameters
@@ -29,8 +29,8 @@ module ActiveRecord
29
29
  extend ActiveSupport::Concern
30
30
 
31
31
  included do
32
- attribute_method_suffix "_before_type_cast", "_for_database"
33
- attribute_method_suffix "_came_from_user?"
32
+ attribute_method_suffix "_before_type_cast", "_for_database", parameters: false
33
+ attribute_method_suffix "_came_from_user?", parameters: false
34
34
  end
35
35
 
36
36
  # Returns the value of the attribute identified by +attr_name+ before
@@ -66,6 +66,11 @@ module ActiveRecord
66
66
  @attributes.values_before_type_cast
67
67
  end
68
68
 
69
+ # Returns a hash of attributes for assignment to the database.
70
+ def attributes_for_database
71
+ @attributes.values_for_database
72
+ end
73
+
69
74
  private
70
75
  # Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
71
76
  def attribute_before_type_cast(attr_name)
@@ -14,16 +14,43 @@ module ActiveRecord
14
14
  raise "You cannot include Dirty after Timestamp"
15
15
  end
16
16
 
17
- class_attribute :partial_writes, instance_writer: false, default: true
17
+ class_attribute :partial_updates, instance_writer: false, default: true
18
+ class_attribute :partial_inserts, instance_writer: false, default: true
18
19
 
19
20
  # Attribute methods for "changed in last call to save?"
20
- attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
21
- attribute_method_prefix("saved_change_to_")
22
- attribute_method_suffix("_before_last_save")
21
+ attribute_method_affix(prefix: "saved_change_to_", suffix: "?", parameters: "**options")
22
+ attribute_method_prefix("saved_change_to_", parameters: false)
23
+ attribute_method_suffix("_before_last_save", parameters: false)
23
24
 
24
25
  # Attribute methods for "will change if I call save?"
25
- attribute_method_affix(prefix: "will_save_change_to_", suffix: "?")
26
- attribute_method_suffix("_change_to_be_saved", "_in_database")
26
+ attribute_method_affix(prefix: "will_save_change_to_", suffix: "?", parameters: "**options")
27
+ attribute_method_suffix("_change_to_be_saved", "_in_database", parameters: false)
28
+ end
29
+
30
+ module ClassMethods
31
+ def partial_writes
32
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
33
+ ActiveRecord::Base.partial_writes is deprecated and will be removed in Rails 7.1.
34
+ Use `partial_updates` and `partial_inserts` instead.
35
+ MSG
36
+ partial_updates && partial_inserts
37
+ end
38
+
39
+ def partial_writes?
40
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
41
+ `ActiveRecord::Base.partial_writes?` is deprecated and will be removed in Rails 7.1.
42
+ Use `partial_updates?` and `partial_inserts?` instead.
43
+ MSG
44
+ partial_updates? && partial_inserts?
45
+ end
46
+
47
+ def partial_writes=(value)
48
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
49
+ `ActiveRecord::Base.partial_writes=` is deprecated and will be removed in Rails 7.1.
50
+ Use `partial_updates=` and `partial_inserts=` instead.
51
+ MSG
52
+ self.partial_updates = self.partial_inserts = value
53
+ end
27
54
  end
28
55
 
29
56
  # <tt>reload</tt> the record and clears changed attributes.
@@ -156,12 +183,6 @@ module ActiveRecord
156
183
  end
157
184
 
158
185
  private
159
- def write_attribute_without_type_cast(attr_name, value)
160
- result = super
161
- clear_attribute_change(attr_name)
162
- result
163
- end
164
-
165
186
  def _touch_row(attribute_names, time)
166
187
  @_touch_attr_names = Set.new(attribute_names)
167
188
 
@@ -191,20 +212,32 @@ module ActiveRecord
191
212
  @_touch_attr_names, @_skip_dirty_tracking = nil, nil
192
213
  end
193
214
 
194
- def _update_record(attribute_names = attribute_names_for_partial_writes)
215
+ def _update_record(attribute_names = attribute_names_for_partial_updates)
195
216
  affected_rows = super
196
217
  changes_applied
197
218
  affected_rows
198
219
  end
199
220
 
200
- def _create_record(attribute_names = attribute_names_for_partial_writes)
221
+ def _create_record(attribute_names = attribute_names_for_partial_inserts)
201
222
  id = super
202
223
  changes_applied
203
224
  id
204
225
  end
205
226
 
206
- def attribute_names_for_partial_writes
207
- partial_writes? ? changed_attribute_names_to_save : attribute_names
227
+ def attribute_names_for_partial_updates
228
+ partial_updates? ? changed_attribute_names_to_save : attribute_names
229
+ end
230
+
231
+ def attribute_names_for_partial_inserts
232
+ if partial_inserts?
233
+ changed_attribute_names_to_save
234
+ else
235
+ attribute_names.reject do |attr_name|
236
+ if column_for_attribute(attr_name).default_function
237
+ !attribute_changed?(attr_name)
238
+ end
239
+ end
240
+ end
208
241
  end
209
242
  end
210
243
  end
@@ -78,7 +78,7 @@ module ActiveRecord
78
78
  @quoted_primary_key ||= connection.quote_column_name(primary_key)
79
79
  end
80
80
 
81
- def reset_primary_key #:nodoc:
81
+ def reset_primary_key # :nodoc:
82
82
  if base_class?
83
83
  self.primary_key = get_primary_key(base_class.name)
84
84
  else
@@ -86,7 +86,7 @@ module ActiveRecord
86
86
  end
87
87
  end
88
88
 
89
- def get_primary_key(base_name) #:nodoc:
89
+ def get_primary_key(base_name) # :nodoc:
90
90
  if base_name && primary_key_prefix_type == :table_name
91
91
  base_name.foreign_key(false)
92
92
  elsif base_name && primary_key_prefix_type == :table_name_with_underscore
@@ -6,11 +6,11 @@ module ActiveRecord
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
9
- attribute_method_suffix "?"
9
+ attribute_method_suffix "?", parameters: false
10
10
  end
11
11
 
12
12
  def query_attribute(attr_name)
13
- value = self[attr_name]
13
+ value = self.public_send(attr_name)
14
14
 
15
15
  case value
16
16
  when true then true
@@ -11,10 +11,12 @@ module ActiveRecord
11
11
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
12
12
  owner, name
13
13
  ) do |temp_method_name, attr_name_expr|
14
- owner <<
15
- "def #{temp_method_name}" <<
16
- " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
17
- "end"
14
+ owner.define_cached_method(name, as: temp_method_name, namespace: :active_record) do |batch|
15
+ batch <<
16
+ "def #{temp_method_name}" <<
17
+ " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
18
+ "end"
19
+ end
18
20
  end
19
21
  end
20
22
  end
@@ -32,7 +34,7 @@ module ActiveRecord
32
34
 
33
35
  # This method exists to avoid the expensive primary_key check internally, without
34
36
  # breaking compatibility with the read_attribute API
35
- def _read_attribute(attr_name, &block) # :nodoc
37
+ def _read_attribute(attr_name, &block) # :nodoc:
36
38
  @attributes.fetch_value(attr_name, &block)
37
39
  end
38
40