activerecord 7.1.5.1 → 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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +645 -2329
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +14 -7
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +7 -1
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/errors.rb +265 -0
  17. data/lib/active_record/associations/has_many_association.rb +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +7 -1
  19. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  20. data/lib/active_record/associations/nested_error.rb +47 -0
  21. data/lib/active_record/associations/preloader/association.rb +2 -1
  22. data/lib/active_record/associations/preloader/branch.rb +7 -1
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  24. data/lib/active_record/associations/singular_association.rb +6 -0
  25. data/lib/active_record/associations/through_association.rb +1 -1
  26. data/lib/active_record/associations.rb +59 -292
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +1 -13
  31. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  33. data/lib/active_record/attribute_methods.rb +54 -63
  34. data/lib/active_record/attributes.rb +61 -47
  35. data/lib/active_record/autosave_association.rb +12 -29
  36. data/lib/active_record/base.rb +2 -3
  37. data/lib/active_record/callbacks.rb +1 -1
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +270 -65
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +189 -74
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +15 -6
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -44
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +40 -10
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  58. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  59. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  60. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -11
  61. data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
  62. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  63. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  64. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  65. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  66. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  67. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  70. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +125 -75
  71. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  72. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  73. data/lib/active_record/connection_adapters.rb +121 -0
  74. data/lib/active_record/connection_handling.rb +56 -41
  75. data/lib/active_record/core.rb +85 -37
  76. data/lib/active_record/counter_cache.rb +18 -9
  77. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  78. data/lib/active_record/database_configurations/database_config.rb +19 -4
  79. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  80. data/lib/active_record/database_configurations/url_config.rb +20 -1
  81. data/lib/active_record/database_configurations.rb +1 -1
  82. data/lib/active_record/delegated_type.rb +24 -0
  83. data/lib/active_record/dynamic_matchers.rb +2 -2
  84. data/lib/active_record/encryption/encryptable_record.rb +3 -3
  85. data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
  86. data/lib/active_record/encryption/encryptor.rb +18 -3
  87. data/lib/active_record/encryption/key_provider.rb +1 -1
  88. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  89. data/lib/active_record/encryption/message_serializer.rb +4 -0
  90. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  91. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  92. data/lib/active_record/enum.rb +18 -1
  93. data/lib/active_record/errors.rb +46 -20
  94. data/lib/active_record/explain.rb +13 -24
  95. data/lib/active_record/fixtures.rb +37 -31
  96. data/lib/active_record/future_result.rb +8 -4
  97. data/lib/active_record/gem_version.rb +2 -2
  98. data/lib/active_record/inheritance.rb +4 -2
  99. data/lib/active_record/insert_all.rb +18 -15
  100. data/lib/active_record/integration.rb +4 -1
  101. data/lib/active_record/internal_metadata.rb +48 -34
  102. data/lib/active_record/locking/optimistic.rb +7 -6
  103. data/lib/active_record/log_subscriber.rb +0 -21
  104. data/lib/active_record/message_pack.rb +1 -1
  105. data/lib/active_record/migration/command_recorder.rb +2 -3
  106. data/lib/active_record/migration/compatibility.rb +5 -3
  107. data/lib/active_record/migration/default_strategy.rb +4 -5
  108. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  109. data/lib/active_record/migration.rb +85 -76
  110. data/lib/active_record/model_schema.rb +31 -68
  111. data/lib/active_record/nested_attributes.rb +11 -3
  112. data/lib/active_record/normalization.rb +3 -7
  113. data/lib/active_record/persistence.rb +30 -352
  114. data/lib/active_record/query_cache.rb +19 -8
  115. data/lib/active_record/query_logs.rb +15 -0
  116. data/lib/active_record/querying.rb +21 -9
  117. data/lib/active_record/railtie.rb +37 -55
  118. data/lib/active_record/railties/controller_runtime.rb +13 -4
  119. data/lib/active_record/railties/databases.rake +40 -43
  120. data/lib/active_record/reflection.rb +98 -36
  121. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  122. data/lib/active_record/relation/batches.rb +14 -8
  123. data/lib/active_record/relation/calculations.rb +96 -63
  124. data/lib/active_record/relation/delegation.rb +8 -11
  125. data/lib/active_record/relation/finder_methods.rb +16 -2
  126. data/lib/active_record/relation/merger.rb +4 -6
  127. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  128. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
  129. data/lib/active_record/relation/predicate_builder.rb +3 -3
  130. data/lib/active_record/relation/query_methods.rb +224 -58
  131. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  132. data/lib/active_record/relation/spawn_methods.rb +2 -18
  133. data/lib/active_record/relation/where_clause.rb +7 -19
  134. data/lib/active_record/relation.rb +496 -72
  135. data/lib/active_record/result.rb +31 -44
  136. data/lib/active_record/runtime_registry.rb +39 -0
  137. data/lib/active_record/sanitization.rb +24 -19
  138. data/lib/active_record/schema.rb +8 -6
  139. data/lib/active_record/schema_dumper.rb +19 -9
  140. data/lib/active_record/schema_migration.rb +30 -14
  141. data/lib/active_record/scoping/named.rb +1 -0
  142. data/lib/active_record/signed_id.rb +20 -1
  143. data/lib/active_record/statement_cache.rb +7 -7
  144. data/lib/active_record/table_metadata.rb +1 -10
  145. data/lib/active_record/tasks/database_tasks.rb +69 -41
  146. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  147. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  148. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  149. data/lib/active_record/test_fixtures.rb +86 -89
  150. data/lib/active_record/testing/query_assertions.rb +121 -0
  151. data/lib/active_record/timestamp.rb +2 -2
  152. data/lib/active_record/token_for.rb +22 -12
  153. data/lib/active_record/touch_later.rb +1 -1
  154. data/lib/active_record/transaction.rb +132 -0
  155. data/lib/active_record/transactions.rb +70 -14
  156. data/lib/active_record/translation.rb +0 -2
  157. data/lib/active_record/type/serialized.rb +1 -3
  158. data/lib/active_record/type_caster/connection.rb +4 -4
  159. data/lib/active_record/validations/associated.rb +9 -3
  160. data/lib/active_record/validations/uniqueness.rb +15 -10
  161. data/lib/active_record/validations.rb +4 -1
  162. data/lib/active_record.rb +148 -39
  163. data/lib/arel/alias_predication.rb +1 -1
  164. data/lib/arel/collectors/bind.rb +2 -0
  165. data/lib/arel/collectors/composite.rb +7 -0
  166. data/lib/arel/collectors/sql_string.rb +1 -1
  167. data/lib/arel/collectors/substitute_binds.rb +1 -1
  168. data/lib/arel/nodes/binary.rb +0 -6
  169. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  170. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  171. data/lib/arel/nodes/node.rb +4 -3
  172. data/lib/arel/nodes/sql_literal.rb +7 -0
  173. data/lib/arel/nodes.rb +2 -2
  174. data/lib/arel/predications.rb +1 -1
  175. data/lib/arel/select_manager.rb +1 -1
  176. data/lib/arel/tree_manager.rb +3 -2
  177. data/lib/arel/update_manager.rb +2 -1
  178. data/lib/arel/visitors/dot.rb +1 -0
  179. data/lib/arel/visitors/mysql.rb +9 -4
  180. data/lib/arel/visitors/postgresql.rb +1 -12
  181. data/lib/arel/visitors/sqlite.rb +25 -0
  182. data/lib/arel/visitors/to_sql.rb +29 -16
  183. data/lib/arel.rb +7 -3
  184. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  185. metadata +16 -10
@@ -0,0 +1,265 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class AssociationNotFoundError < ConfigurationError # :nodoc:
5
+ attr_reader :record, :association_name
6
+
7
+ def initialize(record = nil, association_name = nil)
8
+ @record = record
9
+ @association_name = association_name
10
+ if record && association_name
11
+ super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
12
+ else
13
+ super("Association was not found.")
14
+ end
15
+ end
16
+
17
+ if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
18
+ include DidYouMean::Correctable
19
+
20
+ def corrections
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
26
+ else
27
+ []
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ class InverseOfAssociationNotFoundError < ActiveRecordError # :nodoc:
34
+ attr_reader :reflection, :associated_class
35
+
36
+ def initialize(reflection = nil, associated_class = nil)
37
+ if reflection
38
+ @reflection = reflection
39
+ @associated_class = associated_class.nil? ? reflection.klass : associated_class
40
+ super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
41
+ else
42
+ super("Could not find the inverse association.")
43
+ end
44
+ end
45
+
46
+ if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
47
+ include DidYouMean::Correctable
48
+
49
+ def corrections
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
55
+ else
56
+ []
57
+ end
58
+ end
59
+ end
60
+ end
61
+
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
71
+ end
72
+ end
73
+
74
+ class HasManyThroughAssociationNotFoundError < ActiveRecordError # :nodoc:
75
+ attr_reader :owner_class, :reflection
76
+
77
+ def initialize(owner_class = nil, reflection = nil)
78
+ if owner_class && reflection
79
+ @owner_class = owner_class
80
+ @reflection = reflection
81
+ super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class.name}")
82
+ else
83
+ super("Could not find the association.")
84
+ end
85
+ end
86
+
87
+ if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
88
+ include DidYouMean::Correctable
89
+
90
+ def corrections
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
97
+ else
98
+ []
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError # :nodoc:
105
+ def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil)
106
+ if owner_class_name && reflection && source_reflection
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.")
108
+ else
109
+ super("Cannot have a has_many :through association.")
110
+ end
111
+ end
112
+ end
113
+
114
+ class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError # :nodoc:
115
+ def initialize(owner_class_name = nil, reflection = nil)
116
+ if owner_class_name && reflection
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}'.")
118
+ else
119
+ super("Cannot have a has_many :through association.")
120
+ end
121
+ end
122
+ end
123
+
124
+ class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError # :nodoc:
125
+ def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil)
126
+ if owner_class_name && reflection && source_reflection
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.")
128
+ else
129
+ super("Cannot have a has_many :through association.")
130
+ end
131
+ end
132
+ end
133
+
134
+ class HasOneThroughCantAssociateThroughCollection < ActiveRecordError # :nodoc:
135
+ def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil)
136
+ if owner_class_name && reflection && through_reflection
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.")
138
+ else
139
+ super("Cannot have a has_one :through association.")
140
+ end
141
+ end
142
+ end
143
+
144
+ class HasOneAssociationPolymorphicThroughError < ActiveRecordError # :nodoc:
145
+ def initialize(owner_class_name = nil, reflection = nil)
146
+ if owner_class_name && reflection
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}'.")
148
+ else
149
+ super("Cannot have a has_one :through association.")
150
+ end
151
+ end
152
+ end
153
+
154
+ class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError # :nodoc:
155
+ def initialize(reflection = nil)
156
+ if reflection
157
+ through_reflection = reflection.through_reflection
158
+ source_reflection_names = reflection.source_reflection_names
159
+ source_associations = reflection.through_reflection.klass._reflections.keys
160
+ super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')}?")
161
+ else
162
+ super("Could not find the source association(s).")
163
+ end
164
+ end
165
+ end
166
+
167
+ class HasManyThroughOrderError < ActiveRecordError # :nodoc:
168
+ def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil)
169
+ if owner_class_name && reflection && through_reflection
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.")
171
+ else
172
+ super("Cannot have a has_many :through association before the through association is defined.")
173
+ end
174
+ end
175
+ end
176
+
177
+ class ThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError # :nodoc:
178
+ def initialize(owner = nil, reflection = nil)
179
+ if owner && reflection
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}.")
181
+ else
182
+ super("Cannot modify association.")
183
+ end
184
+ end
185
+ end
186
+
187
+ class CompositePrimaryKeyMismatchError < ActiveRecordError # :nodoc:
188
+ attr_reader :reflection
189
+
190
+ def initialize(reflection = nil)
191
+ if reflection
192
+ if reflection.has_one? || reflection.collection?
193
+ super("Association #{reflection.active_record}##{reflection.name} primary key #{reflection.active_record_primary_key} doesn't match with foreign key #{reflection.foreign_key}. Please specify query_constraints, or primary_key and foreign_key values.")
194
+ else
195
+ super("Association #{reflection.active_record}##{reflection.name} primary key #{reflection.association_primary_key} doesn't match with foreign key #{reflection.foreign_key}. Please specify query_constraints, or primary_key and foreign_key values.")
196
+ end
197
+ else
198
+ super("Association primary key doesn't match with foreign key.")
199
+ end
200
+ end
201
+ end
202
+
203
+ class AmbiguousSourceReflectionForThroughAssociation < ActiveRecordError # :nodoc:
204
+ def initialize(klass, macro, association_name, options, possible_sources)
205
+ example_options = options.dup
206
+ example_options[:source] = possible_sources.first
207
+
208
+ super("Ambiguous source reflection for through association. Please " \
209
+ "specify a :source directive on your declaration like:\n" \
210
+ "\n" \
211
+ " class #{klass} < ActiveRecord::Base\n" \
212
+ " #{macro} :#{association_name}, #{example_options}\n" \
213
+ " end"
214
+ )
215
+ end
216
+ end
217
+
218
+ class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection # :nodoc:
219
+ end
220
+
221
+ class HasOneThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection # :nodoc:
222
+ end
223
+
224
+ class ThroughNestedAssociationsAreReadonly < ActiveRecordError # :nodoc:
225
+ def initialize(owner = nil, reflection = nil)
226
+ if owner && reflection
227
+ super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
228
+ else
229
+ super("Through nested associations are read-only.")
230
+ end
231
+ end
232
+ end
233
+
234
+ class HasManyThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly # :nodoc:
235
+ end
236
+
237
+ class HasOneThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly # :nodoc:
238
+ end
239
+
240
+ # This error is raised when trying to eager load a polymorphic association using a JOIN.
241
+ # Eager loading polymorphic associations is only possible with
242
+ # {ActiveRecord::Relation#preload}[rdoc-ref:QueryMethods#preload].
243
+ class EagerLoadPolymorphicError < ActiveRecordError
244
+ def initialize(reflection = nil)
245
+ if reflection
246
+ super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}")
247
+ else
248
+ super("Eager load polymorphic error.")
249
+ end
250
+ end
251
+ end
252
+
253
+ # This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations
254
+ # (has_many, has_one) when there is at least 1 child associated instance.
255
+ # ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
256
+ class DeleteRestrictionError < ActiveRecordError # :nodoc:
257
+ def initialize(name = nil)
258
+ if name
259
+ super("Cannot delete record because of dependent #{name}")
260
+ else
261
+ super("Delete restriction error.")
262
+ end
263
+ end
264
+ end
265
+ end
@@ -78,7 +78,7 @@ module ActiveRecord
78
78
  # If the collection is empty the target is set to an empty array and
79
79
  # the loaded flag is set to true as well.
80
80
  def count_records
81
- count = if reflection.has_cached_counter?
81
+ count = if reflection.has_active_cached_counter?
82
82
  owner.read_attribute(reflection.counter_cache_column).to_i
83
83
  else
84
84
  scope.count(:all)
@@ -93,7 +93,13 @@ module ActiveRecord
93
93
  @through_scope = scope
94
94
  record = super
95
95
 
96
- inverse = source_reflection.inverse_of
96
+ inverse =
97
+ if source_reflection.polymorphic?
98
+ source_reflection.polymorphic_inverse_of(record.class)
99
+ else
100
+ source_reflection.inverse_of
101
+ end
102
+
97
103
  if inverse
98
104
  if inverse.collection?
99
105
  record.send(inverse.name) << build_through_record(record)
@@ -38,39 +38,41 @@ module ActiveRecord
38
38
  chain << [reflection, table]
39
39
  end
40
40
 
41
- # The chain starts with the target table, but we want to end with it here (makes
42
- # more sense in this context), so we reverse
43
- chain.reverse_each do |reflection, table|
44
- klass = reflection.klass
41
+ base_klass.with_connection do |connection|
42
+ # The chain starts with the target table, but we want to end with it here (makes
43
+ # more sense in this context), so we reverse
44
+ chain.reverse_each do |reflection, table|
45
+ klass = reflection.klass
45
46
 
46
- scope = reflection.join_scope(table, foreign_table, foreign_klass)
47
+ scope = reflection.join_scope(table, foreign_table, foreign_klass)
47
48
 
48
- unless scope.references_values.empty?
49
- associations = scope.eager_load_values | scope.includes_values
49
+ unless scope.references_values.empty?
50
+ associations = scope.eager_load_values | scope.includes_values
50
51
 
51
- unless associations.empty?
52
- scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
52
+ unless associations.empty?
53
+ scope.joins! scope.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
54
+ end
53
55
  end
54
- end
55
56
 
56
- arel = scope.arel(alias_tracker.aliases)
57
- nodes = arel.constraints.first
57
+ arel = scope.arel(alias_tracker.aliases)
58
+ nodes = arel.constraints.first
58
59
 
59
- if nodes.is_a?(Arel::Nodes::And)
60
- others = nodes.children.extract! do |node|
61
- !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
60
+ if nodes.is_a?(Arel::Nodes::And)
61
+ others = nodes.children.extract! do |node|
62
+ !Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
63
+ end
62
64
  end
63
- end
64
65
 
65
- joins << join_type.new(table, Arel::Nodes::On.new(nodes))
66
+ joins << join_type.new(table, Arel::Nodes::On.new(nodes))
66
67
 
67
- if others && !others.empty?
68
- joins.concat arel.join_sources
69
- append_constraints(joins.last, others)
70
- end
68
+ if others && !others.empty?
69
+ joins.concat arel.join_sources
70
+ append_constraints(connection, joins.last, others)
71
+ end
71
72
 
72
- # The current table in this iteration becomes the foreign table in the next
73
- foreign_table, foreign_klass = table, klass
73
+ # The current table in this iteration becomes the foreign table in the next
74
+ foreign_table, foreign_klass = table, klass
75
+ end
74
76
  end
75
77
 
76
78
  joins
@@ -89,10 +91,10 @@ module ActiveRecord
89
91
  end
90
92
 
91
93
  private
92
- def append_constraints(join, constraints)
94
+ def append_constraints(connection, join, constraints)
93
95
  if join.is_a?(Arel::Nodes::StringJoin)
94
96
  join_string = Arel::Nodes::And.new(constraints.unshift join.left)
95
- join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
97
+ join.left = Arel.sql(connection.visitor.compile(join_string))
96
98
  else
97
99
  right = join.right
98
100
  right.expr = Arel::Nodes::And.new(constraints.unshift right.expr)
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Validation error class to wrap association records' errors,
4
+ # with index_errors support.
5
+ module ActiveRecord
6
+ module Associations
7
+ class NestedError < ::ActiveModel::NestedError
8
+ def initialize(association, inner_error)
9
+ @base = association.owner
10
+ @association = association
11
+ @inner_error = inner_error
12
+ super(@base, inner_error, { attribute: compute_attribute(inner_error) })
13
+ end
14
+
15
+ private
16
+ attr_reader :association
17
+
18
+ def compute_attribute(inner_error)
19
+ association_name = association.reflection.name
20
+
21
+ if association.collection? && index_errors_setting && index
22
+ "#{association_name}[#{index}].#{inner_error.attribute}".to_sym
23
+ else
24
+ "#{association_name}.#{inner_error.attribute}".to_sym
25
+ end
26
+ end
27
+
28
+ def index_errors_setting
29
+ @index_errors_setting ||=
30
+ association.options.fetch(:index_errors, ActiveRecord.index_nested_attribute_errors)
31
+ end
32
+
33
+ def index
34
+ @index ||= ordered_records&.find_index(inner_error.base)
35
+ end
36
+
37
+ def ordered_records
38
+ case index_errors_setting
39
+ when true # default is association order
40
+ association.target
41
+ when :nested_attributes_order
42
+ association.nested_attributes_target
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -248,7 +248,8 @@ module ActiveRecord
248
248
  association = owner.association(reflection.name)
249
249
 
250
250
  if reflection.collection?
251
- association.target = records
251
+ not_persisted_records = association.target.reject(&:persisted?)
252
+ association.target = records + not_persisted_records
252
253
  else
253
254
  association.target = records.first
254
255
  end
@@ -9,7 +9,13 @@ module ActiveRecord
9
9
  attr_writer :preloaded_records
10
10
 
11
11
  def initialize(association:, children:, parent:, associate_by_default:, scope:)
12
- @association = association
12
+ @association = if association
13
+ begin
14
+ @association = association.to_sym
15
+ rescue NoMethodError
16
+ raise ArgumentError, "Association names must be Symbol or String, got: #{association.class.name}"
17
+ end
18
+ end
13
19
  @parent = parent
14
20
  @scope = scope
15
21
  @associate_by_default = associate_by_default
@@ -9,9 +9,7 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  def records_by_owner
12
- return @records_by_owner if defined?(@records_by_owner)
13
-
14
- @records_by_owner = owners.each_with_object({}) do |owner, result|
12
+ @records_by_owner ||= owners.each_with_object({}) do |owner, result|
15
13
  if loaded?(owner)
16
14
  result[owner] = target_for(owner)
17
15
  next
@@ -14,6 +14,12 @@ module ActiveRecord
14
14
  target
15
15
  end
16
16
 
17
+ # Resets the \loaded flag to +false+ and sets the \target to +nil+.
18
+ def reset
19
+ super
20
+ @target = nil
21
+ end
22
+
17
23
  # Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
18
24
  def writer(record)
19
25
  replace(record)
@@ -82,7 +82,7 @@ module ActiveRecord
82
82
  def stale_state
83
83
  if through_reflection.belongs_to?
84
84
  Array(through_reflection.foreign_key).filter_map do |foreign_key_column|
85
- owner[foreign_key_column] && owner[foreign_key_column].to_s
85
+ owner[foreign_key_column]
86
86
  end.presence
87
87
  end
88
88
  end