activerecord 7.0.0.alpha2 → 7.0.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +539 -11
- data/lib/active_record/associations/association.rb +2 -8
- data/lib/active_record/associations/builder/collection_association.rb +9 -2
- data/lib/active_record/associations/collection_association.rb +10 -2
- data/lib/active_record/associations/join_dependency.rb +6 -2
- data/lib/active_record/associations/preloader/association.rb +68 -48
- data/lib/active_record/associations/preloader/batch.rb +3 -6
- data/lib/active_record/associations/preloader/through_association.rb +19 -9
- data/lib/active_record/associations/preloader.rb +14 -24
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/associations.rb +16 -3
- data/lib/active_record/asynchronous_queries_tracker.rb +3 -0
- data/lib/active_record/attribute_methods/dirty.rb +9 -1
- data/lib/active_record/attribute_methods.rb +7 -5
- data/lib/active_record/autosave_association.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +6 -26
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +18 -5
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +33 -70
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +9 -2
- data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -19
- data/lib/active_record/connection_adapters/abstract_adapter.rb +37 -8
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/column.rb +4 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +23 -24
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +1 -1
- data/lib/active_record/connection_adapters/pool_config.rb +7 -5
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -44
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +18 -1
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -4
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +48 -5
- data/lib/active_record/connection_adapters/schema_cache.rb +3 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +4 -2
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +15 -16
- data/lib/active_record/connection_handling.rb +31 -19
- data/lib/active_record/core.rb +13 -24
- data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
- data/lib/active_record/database_configurations/database_config.rb +0 -9
- data/lib/active_record/database_configurations/hash_config.rb +40 -8
- data/lib/active_record/database_configurations.rb +2 -27
- data/lib/active_record/delegated_type.rb +19 -0
- data/lib/active_record/encryption/encryptable_record.rb +1 -1
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +1 -2
- data/lib/active_record/encryption/message_serializer.rb +11 -1
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +8 -1
- data/lib/active_record/errors.rb +1 -1
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/fixture_set/table_row.rb +1 -1
- data/lib/active_record/fixtures.rb +1 -9
- data/lib/active_record/future_result.rb +2 -2
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/insert_all.rb +52 -15
- data/lib/active_record/integration.rb +3 -2
- data/lib/active_record/legacy_yaml_adapter.rb +2 -39
- data/lib/active_record/locking/pessimistic.rb +9 -3
- data/lib/active_record/log_subscriber.rb +8 -1
- data/lib/active_record/middleware/shard_selector.rb +60 -0
- data/lib/active_record/migration.rb +2 -2
- data/lib/active_record/model_schema.rb +1 -28
- data/lib/active_record/nested_attributes.rb +11 -10
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/persistence.rb +99 -21
- data/lib/active_record/query_logs.rb +18 -83
- data/lib/active_record/railtie.rb +11 -1
- data/lib/active_record/railties/databases.rake +4 -91
- data/lib/active_record/reflection.rb +22 -6
- data/lib/active_record/relation/calculations.rb +1 -10
- data/lib/active_record/relation/finder_methods.rb +0 -13
- data/lib/active_record/relation/query_methods.rb +5 -14
- data/lib/active_record/relation/record_fetch_warning.rb +5 -7
- data/lib/active_record/relation/where_clause.rb +2 -15
- data/lib/active_record/relation.rb +11 -15
- data/lib/active_record/result.rb +0 -5
- data/lib/active_record/runtime_registry.rb +10 -12
- data/lib/active_record/schema_dumper.rb +7 -0
- data/lib/active_record/schema_migration.rb +4 -0
- data/lib/active_record/scoping.rb +34 -22
- data/lib/active_record/suppressor.rb +11 -15
- data/lib/active_record/tasks/database_tasks.rb +18 -44
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -1
- data/lib/active_record/validations/uniqueness.rb +1 -1
- data/lib/active_record.rb +41 -33
- data/lib/arel/crud.rb +12 -2
- data/lib/arel/delete_manager.rb +16 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/nodes/delete_statement.rb +5 -1
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/update_statement.rb +5 -1
- data/lib/arel/nodes.rb +1 -0
- data/lib/arel/predications.rb +10 -2
- data/lib/arel/update_manager.rb +16 -0
- data/lib/arel/visitors/mysql.rb +2 -1
- data/lib/arel/visitors/to_sql.rb +15 -0
- data/lib/arel.rb +1 -0
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- metadata +18 -12
@@ -335,7 +335,7 @@ module ActiveRecord
|
|
335
335
|
end
|
336
336
|
end
|
337
337
|
|
338
|
-
persisted + memory
|
338
|
+
persisted + memory.reject(&:persisted?)
|
339
339
|
end
|
340
340
|
|
341
341
|
def _create_record(attributes, raise = false, &block)
|
@@ -456,6 +456,10 @@ module ActiveRecord
|
|
456
456
|
|
457
457
|
yield(record) if block_given?
|
458
458
|
|
459
|
+
if !index && @replaced_or_added_targets.include?(record)
|
460
|
+
index = @target.index(record)
|
461
|
+
end
|
462
|
+
|
459
463
|
@replaced_or_added_targets << record if inversing || index || record.new_record?
|
460
464
|
|
461
465
|
if index
|
@@ -480,7 +484,11 @@ module ActiveRecord
|
|
480
484
|
|
481
485
|
def callbacks_for(callback_name)
|
482
486
|
full_callback_name = "#{callback_name}_for_#{reflection.name}"
|
483
|
-
owner.class.
|
487
|
+
if owner.class.respond_to?(full_callback_name)
|
488
|
+
owner.class.send(full_callback_name)
|
489
|
+
else
|
490
|
+
[]
|
491
|
+
end
|
484
492
|
end
|
485
493
|
|
486
494
|
def include_in_memory?(record)
|
@@ -3,8 +3,12 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Associations
|
5
5
|
class JoinDependency # :nodoc:
|
6
|
-
|
7
|
-
|
6
|
+
extend ActiveSupport::Autoload
|
7
|
+
|
8
|
+
eager_autoload do
|
9
|
+
autoload :JoinBase
|
10
|
+
autoload :JoinAssociation
|
11
|
+
end
|
8
12
|
|
9
13
|
class Aliases # :nodoc:
|
10
14
|
def initialize(tables)
|
@@ -23,11 +23,7 @@ module ActiveRecord
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def records_for(loaders)
|
26
|
-
|
27
|
-
|
28
|
-
scope.where(association_key_name => ids).load do |record|
|
29
|
-
loaders.each { |l| l.set_inverse(record) }
|
30
|
-
end
|
26
|
+
LoaderRecords.new(loaders, self).records
|
31
27
|
end
|
32
28
|
|
33
29
|
def load_records_in_batch(loaders)
|
@@ -38,6 +34,52 @@ module ActiveRecord
|
|
38
34
|
loader.run
|
39
35
|
end
|
40
36
|
end
|
37
|
+
|
38
|
+
def load_records_for_keys(keys, &block)
|
39
|
+
scope.where(association_key_name => keys).load(&block)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class LoaderRecords
|
44
|
+
def initialize(loaders, loader_query)
|
45
|
+
@loader_query = loader_query
|
46
|
+
@loaders = loaders
|
47
|
+
@keys_to_load = Set.new
|
48
|
+
@already_loaded_records_by_key = {}
|
49
|
+
|
50
|
+
populate_keys_to_load_and_already_loaded_records
|
51
|
+
end
|
52
|
+
|
53
|
+
def records
|
54
|
+
load_records + already_loaded_records
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
attr_reader :loader_query, :loaders, :keys_to_load, :already_loaded_records_by_key
|
59
|
+
|
60
|
+
def populate_keys_to_load_and_already_loaded_records
|
61
|
+
loaders.each do |loader|
|
62
|
+
loader.owners_by_key.each do |key, owners|
|
63
|
+
if loaded_owner = owners.find { |owner| loader.loaded?(owner) }
|
64
|
+
already_loaded_records_by_key[key] = loader.target_for(loaded_owner)
|
65
|
+
else
|
66
|
+
keys_to_load << key
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
@keys_to_load.subtract(already_loaded_records_by_key.keys)
|
72
|
+
end
|
73
|
+
|
74
|
+
def load_records
|
75
|
+
loader_query.load_records_for_keys(keys_to_load) do |record|
|
76
|
+
loaders.each { |l| l.set_inverse(record) }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def already_loaded_records
|
81
|
+
already_loaded_records_by_key.values.flatten
|
82
|
+
end
|
41
83
|
end
|
42
84
|
|
43
85
|
attr_reader :klass
|
@@ -57,12 +99,8 @@ module ActiveRecord
|
|
57
99
|
@klass.table_name
|
58
100
|
end
|
59
101
|
|
60
|
-
def data_available?
|
61
|
-
already_loaded?
|
62
|
-
end
|
63
|
-
|
64
102
|
def future_classes
|
65
|
-
if run?
|
103
|
+
if run?
|
66
104
|
[]
|
67
105
|
else
|
68
106
|
[@klass]
|
@@ -81,11 +119,6 @@ module ActiveRecord
|
|
81
119
|
return self if run?
|
82
120
|
@run = true
|
83
121
|
|
84
|
-
if already_loaded?
|
85
|
-
fetch_from_preloaded_records
|
86
|
-
return self
|
87
|
-
end
|
88
|
-
|
89
122
|
records = records_by_owner
|
90
123
|
|
91
124
|
owners.each do |owner|
|
@@ -96,25 +129,17 @@ module ActiveRecord
|
|
96
129
|
end
|
97
130
|
|
98
131
|
def records_by_owner
|
99
|
-
|
132
|
+
load_records unless defined?(@records_by_owner)
|
100
133
|
|
101
134
|
@records_by_owner
|
102
135
|
end
|
103
136
|
|
104
137
|
def preloaded_records
|
105
|
-
|
138
|
+
load_records unless defined?(@preloaded_records)
|
106
139
|
|
107
140
|
@preloaded_records
|
108
141
|
end
|
109
142
|
|
110
|
-
def ensure_loaded
|
111
|
-
if already_loaded?
|
112
|
-
fetch_from_preloaded_records
|
113
|
-
else
|
114
|
-
load_records
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
143
|
# The name of the key on the associated records
|
119
144
|
def association_key_name
|
120
145
|
reflection.join_primary_key(klass)
|
@@ -124,8 +149,19 @@ module ActiveRecord
|
|
124
149
|
LoaderQuery.new(scope, association_key_name)
|
125
150
|
end
|
126
151
|
|
127
|
-
def
|
128
|
-
@
|
152
|
+
def owners_by_key
|
153
|
+
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
|
154
|
+
key = convert_key(owner[owner_key_name])
|
155
|
+
(result[key] ||= []) << owner if key
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def loaded?(owner)
|
160
|
+
owner.association(reflection.name).loaded?
|
161
|
+
end
|
162
|
+
|
163
|
+
def target_for(owner)
|
164
|
+
Array.wrap(owner.association(reflection.name).target)
|
129
165
|
end
|
130
166
|
|
131
167
|
def scope
|
@@ -169,7 +205,7 @@ module ActiveRecord
|
|
169
205
|
return if preload_scope && !preload_scope.empty_scope?
|
170
206
|
return if reflection.collection?
|
171
207
|
|
172
|
-
unscoped_records.each do |record|
|
208
|
+
unscoped_records.select { |r| r[association_key_name].present? }.each do |record|
|
173
209
|
owners = owners_by_key[convert_key(record[association_key_name])]
|
174
210
|
owners&.each_with_index do |owner, i|
|
175
211
|
association = owner.association(reflection.name)
|
@@ -185,25 +221,16 @@ module ActiveRecord
|
|
185
221
|
private
|
186
222
|
attr_reader :owners, :reflection, :preload_scope, :model
|
187
223
|
|
188
|
-
def already_loaded?
|
189
|
-
@already_loaded ||= owners.all? { |o| o.association(reflection.name).loaded? }
|
190
|
-
end
|
191
|
-
|
192
|
-
def fetch_from_preloaded_records
|
193
|
-
@records_by_owner = owners.index_with do |owner|
|
194
|
-
Array(owner.association(reflection.name).target)
|
195
|
-
end
|
196
|
-
|
197
|
-
@preloaded_records = records_by_owner.flat_map(&:last)
|
198
|
-
end
|
199
|
-
|
200
224
|
# The name of the key on the model which declares the association
|
201
225
|
def owner_key_name
|
202
226
|
reflection.join_foreign_key
|
203
227
|
end
|
204
228
|
|
205
229
|
def associate_records_to_owner(owner, records)
|
230
|
+
return if loaded?(owner)
|
231
|
+
|
206
232
|
association = owner.association(reflection.name)
|
233
|
+
|
207
234
|
if reflection.collection?
|
208
235
|
association.target = records
|
209
236
|
else
|
@@ -211,13 +238,6 @@ module ActiveRecord
|
|
211
238
|
end
|
212
239
|
end
|
213
240
|
|
214
|
-
def owners_by_key
|
215
|
-
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
|
216
|
-
key = convert_key(owner[owner_key_name])
|
217
|
-
(result[key] ||= []) << owner if key
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
241
|
def key_conversion_required?
|
222
242
|
unless defined?(@key_conversion_required)
|
223
243
|
@key_conversion_required = (association_key_type != owner_key_type)
|
@@ -243,7 +263,7 @@ module ActiveRecord
|
|
243
263
|
end
|
244
264
|
|
245
265
|
def reflection_scope
|
246
|
-
@reflection_scope ||= reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(&:merge!)
|
266
|
+
@reflection_scope ||= reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(klass.unscoped, &:merge!)
|
247
267
|
end
|
248
268
|
|
249
269
|
def build_scope
|
@@ -6,7 +6,7 @@ module ActiveRecord
|
|
6
6
|
class Batch # :nodoc:
|
7
7
|
def initialize(preloaders, available_records:)
|
8
8
|
@preloaders = preloaders.reject(&:empty?)
|
9
|
-
@available_records = available_records.flatten.group_by
|
9
|
+
@available_records = available_records.flatten.group_by { |r| r.class.base_class }
|
10
10
|
end
|
11
11
|
|
12
12
|
def call
|
@@ -14,12 +14,9 @@ module ActiveRecord
|
|
14
14
|
until branches.empty?
|
15
15
|
loaders = branches.flat_map(&:runnable_loaders)
|
16
16
|
|
17
|
-
loaders.each { |loader| loader.associate_records_from_unscoped(@available_records[loader.klass]) }
|
17
|
+
loaders.each { |loader| loader.associate_records_from_unscoped(@available_records[loader.klass.base_class]) }
|
18
18
|
|
19
|
-
|
20
|
-
if already_loaded.any?
|
21
|
-
already_loaded.each(&:run)
|
22
|
-
elsif loaders.any?
|
19
|
+
if loaders.any?
|
23
20
|
future_tables = branches.flat_map do |branch|
|
24
21
|
branch.future_classes - branch.runnable_loaders.map(&:klass)
|
25
22
|
end.map(&:table_name).uniq
|
@@ -10,10 +10,13 @@ module ActiveRecord
|
|
10
10
|
|
11
11
|
def records_by_owner
|
12
12
|
return @records_by_owner if defined?(@records_by_owner)
|
13
|
-
source_records_by_owner = source_preloaders.map(&:records_by_owner).reduce(:merge)
|
14
|
-
through_records_by_owner = through_preloaders.map(&:records_by_owner).reduce(:merge)
|
15
13
|
|
16
14
|
@records_by_owner = owners.each_with_object({}) do |owner, result|
|
15
|
+
if loaded?(owner)
|
16
|
+
result[owner] = target_for(owner)
|
17
|
+
next
|
18
|
+
end
|
19
|
+
|
17
20
|
through_records = through_records_by_owner[owner] || []
|
18
21
|
|
19
22
|
if owners.first.association(through_reflection.name).loaded?
|
@@ -35,12 +38,6 @@ module ActiveRecord
|
|
35
38
|
end
|
36
39
|
end
|
37
40
|
|
38
|
-
def data_available?
|
39
|
-
return true if super()
|
40
|
-
through_preloaders.all?(&:run?) &&
|
41
|
-
source_preloaders.all?(&:run?)
|
42
|
-
end
|
43
|
-
|
44
41
|
def runnable_loaders
|
45
42
|
if data_available?
|
46
43
|
[self]
|
@@ -52,7 +49,7 @@ module ActiveRecord
|
|
52
49
|
end
|
53
50
|
|
54
51
|
def future_classes
|
55
|
-
if run?
|
52
|
+
if run?
|
56
53
|
[]
|
57
54
|
elsif through_preloaders.all?(&:run?)
|
58
55
|
source_preloaders.flat_map(&:future_classes).uniq
|
@@ -67,6 +64,11 @@ module ActiveRecord
|
|
67
64
|
end
|
68
65
|
|
69
66
|
private
|
67
|
+
def data_available?
|
68
|
+
owners.all? { |owner| loaded?(owner) } ||
|
69
|
+
through_preloaders.all?(&:run?) && source_preloaders.all?(&:run?)
|
70
|
+
end
|
71
|
+
|
70
72
|
def source_preloaders
|
71
73
|
@source_preloaders ||= ActiveRecord::Associations::Preloader.new(records: middle_records, associations: source_reflection.name, scope: scope, associate_by_default: false).loaders
|
72
74
|
end
|
@@ -87,6 +89,14 @@ module ActiveRecord
|
|
87
89
|
reflection.source_reflection
|
88
90
|
end
|
89
91
|
|
92
|
+
def source_records_by_owner
|
93
|
+
@source_records_by_owner ||= source_preloaders.map(&:records_by_owner).reduce(:merge)
|
94
|
+
end
|
95
|
+
|
96
|
+
def through_records_by_owner
|
97
|
+
@through_records_by_owner ||= through_preloaders.map(&:records_by_owner).reduce(:merge)
|
98
|
+
end
|
99
|
+
|
90
100
|
def preload_index
|
91
101
|
@preload_index ||= preloaded_records.each_with_object({}).with_index do |(record, result), index|
|
92
102
|
result[record] = index
|
@@ -93,25 +93,21 @@ module ActiveRecord
|
|
93
93
|
# associations before querying the database. This can save database
|
94
94
|
# queries by reusing in-memory objects. The optimization is only applied
|
95
95
|
# to single associations (i.e. :belongs_to, :has_one) with no scopes.
|
96
|
-
def initialize(associate_by_default: true
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
@scope = kwargs[:scope]
|
103
|
-
@available_records = kwargs[:available_records] || []
|
104
|
-
@associate_by_default = associate_by_default
|
96
|
+
def initialize(records:, associations:, scope: nil, available_records: [], associate_by_default: true)
|
97
|
+
@records = records
|
98
|
+
@associations = associations
|
99
|
+
@scope = scope
|
100
|
+
@available_records = available_records || []
|
101
|
+
@associate_by_default = associate_by_default
|
105
102
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
end
|
103
|
+
@tree = Branch.new(
|
104
|
+
parent: nil,
|
105
|
+
association: nil,
|
106
|
+
children: @associations,
|
107
|
+
associate_by_default: @associate_by_default,
|
108
|
+
scope: @scope
|
109
|
+
)
|
110
|
+
@tree.preloaded_records = @records
|
115
111
|
end
|
116
112
|
|
117
113
|
def empty?
|
@@ -124,12 +120,6 @@ module ActiveRecord
|
|
124
120
|
loaders
|
125
121
|
end
|
126
122
|
|
127
|
-
def preload(records, associations, preload_scope = nil)
|
128
|
-
ActiveSupport::Deprecation.warn("`preload` is deprecated and will be removed in Rails 7.0. Call `Preloader.new(kwargs).call` instead.")
|
129
|
-
|
130
|
-
Preloader.new(records: records, associations: associations, scope: preload_scope).call
|
131
|
-
end
|
132
|
-
|
133
123
|
def branches
|
134
124
|
@tree.children
|
135
125
|
end
|
@@ -74,8 +74,8 @@ module ActiveRecord
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
|
-
# Note: this does not capture all cases, for example it would be
|
78
|
-
# properly support stale-checking for nested associations.
|
77
|
+
# Note: this does not capture all cases, for example it would be impractical
|
78
|
+
# to try to properly support stale-checking for nested associations.
|
79
79
|
def stale_state
|
80
80
|
if through_reflection.belongs_to?
|
81
81
|
owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
|
@@ -59,6 +59,18 @@ module ActiveRecord
|
|
59
59
|
end
|
60
60
|
end
|
61
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
|
+
|
62
74
|
class HasManyThroughAssociationNotFoundError < ActiveRecordError # :nodoc:
|
63
75
|
attr_reader :owner_class, :reflection
|
64
76
|
|
@@ -748,9 +760,10 @@ module ActiveRecord
|
|
748
760
|
# inverse detection only works on #has_many, #has_one, and
|
749
761
|
# #belongs_to associations.
|
750
762
|
#
|
751
|
-
# <tt>:foreign_key</tt> and <tt>:through</tt> options on the associations
|
752
|
-
#
|
753
|
-
#
|
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].
|
754
767
|
#
|
755
768
|
# The automatic guessing of the inverse association uses a heuristic based
|
756
769
|
# on the name of the class, so it may not work for all associations,
|
@@ -229,7 +229,15 @@ module ActiveRecord
|
|
229
229
|
end
|
230
230
|
|
231
231
|
def attribute_names_for_partial_inserts
|
232
|
-
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
|
233
241
|
end
|
234
242
|
end
|
235
243
|
end
|
@@ -387,20 +387,22 @@ module ActiveRecord
|
|
387
387
|
attribute_names.index_with { |name| @attributes[name] }
|
388
388
|
end
|
389
389
|
|
390
|
-
# Filters the primary keys
|
390
|
+
# Filters the primary keys, readonly attributes and virtual columns from the attribute names.
|
391
391
|
def attributes_for_update(attribute_names)
|
392
392
|
attribute_names &= self.class.column_names
|
393
393
|
attribute_names.delete_if do |name|
|
394
|
-
self.class.readonly_attribute?(name)
|
394
|
+
self.class.readonly_attribute?(name) ||
|
395
|
+
column_for_attribute(name).virtual?
|
395
396
|
end
|
396
397
|
end
|
397
398
|
|
398
|
-
# Filters out the primary keys, from the attribute names, when the primary
|
399
|
+
# Filters out the virtual columns and also primary keys, from the attribute names, when the primary
|
399
400
|
# key is to be generated (e.g. the id attribute has no value).
|
400
401
|
def attributes_for_create(attribute_names)
|
401
402
|
attribute_names &= self.class.column_names
|
402
403
|
attribute_names.delete_if do |name|
|
403
|
-
pk_attribute?(name) && id.nil?
|
404
|
+
(pk_attribute?(name) && id.nil?) ||
|
405
|
+
column_for_attribute(name).virtual?
|
404
406
|
end
|
405
407
|
end
|
406
408
|
|
@@ -411,7 +413,7 @@ module ActiveRecord
|
|
411
413
|
inspected_value = if value.is_a?(String) && value.length > 50
|
412
414
|
"#{value[0, 50]}...".inspect
|
413
415
|
elsif value.is_a?(Date) || value.is_a?(Time)
|
414
|
-
%("#{value.
|
416
|
+
%("#{value.to_formatted_s(:inspect)}")
|
415
417
|
else
|
416
418
|
value.inspect
|
417
419
|
end
|
@@ -404,6 +404,8 @@ module ActiveRecord
|
|
404
404
|
saved = true
|
405
405
|
|
406
406
|
if autosave != false && (new_record_before_save || record.new_record?)
|
407
|
+
association.set_inverse_instance(record)
|
408
|
+
|
407
409
|
if autosave
|
408
410
|
saved = association.insert_record(record, false)
|
409
411
|
elsif !reflection.nested?
|
@@ -447,9 +449,7 @@ module ActiveRecord
|
|
447
449
|
if (autosave && record.changed_for_autosave?) || record_changed?(reflection, record, key)
|
448
450
|
unless reflection.through_reflection
|
449
451
|
record[reflection.foreign_key] = key
|
450
|
-
|
451
|
-
record.association(inverse_reflection.name).inversed_from(self)
|
452
|
-
end
|
452
|
+
association.set_inverse_instance(record)
|
453
453
|
end
|
454
454
|
|
455
455
|
saved = record.save(validate: !autosave)
|
@@ -81,11 +81,11 @@ module ActiveRecord
|
|
81
81
|
end
|
82
82
|
|
83
83
|
def prevent_writes # :nodoc:
|
84
|
-
|
84
|
+
ActiveSupport::IsolatedExecutionState[:active_record_prevent_writes]
|
85
85
|
end
|
86
86
|
|
87
87
|
def prevent_writes=(prevent_writes) # :nodoc:
|
88
|
-
|
88
|
+
ActiveSupport::IsolatedExecutionState[:active_record_prevent_writes] = prevent_writes
|
89
89
|
end
|
90
90
|
|
91
91
|
# Prevent writing to the database regardless of role.
|
@@ -126,7 +126,7 @@ module ActiveRecord
|
|
126
126
|
def establish_connection(config, owner_name: Base, role: ActiveRecord::Base.current_role, shard: Base.current_shard)
|
127
127
|
owner_name = StringConnectionOwner.new(config.to_s) if config.is_a?(Symbol)
|
128
128
|
|
129
|
-
pool_config = resolve_pool_config(config, owner_name)
|
129
|
+
pool_config = resolve_pool_config(config, owner_name, role, shard)
|
130
130
|
db_config = pool_config.db_config
|
131
131
|
|
132
132
|
# Protects the connection named `ActiveRecord::Base` from being removed
|
@@ -218,15 +218,6 @@ module ActiveRecord
|
|
218
218
|
pool && pool.connected?
|
219
219
|
end
|
220
220
|
|
221
|
-
# Remove the connection for this class. This will close the active
|
222
|
-
# connection and the defined connection (if they exist). The result
|
223
|
-
# can be used as an argument for #establish_connection, for easily
|
224
|
-
# re-establishing the connection.
|
225
|
-
def remove_connection(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
|
226
|
-
remove_connection_pool(owner, role: role, shard: shard)&.configuration_hash
|
227
|
-
end
|
228
|
-
deprecate remove_connection: "Use #remove_connection_pool, which now returns a DatabaseConfig object instead of a Hash"
|
229
|
-
|
230
221
|
def remove_connection_pool(owner, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard)
|
231
222
|
if pool_manager = get_pool_manager(owner)
|
232
223
|
pool_config = pool_manager.remove_pool_config(role, shard)
|
@@ -250,19 +241,8 @@ module ActiveRecord
|
|
250
241
|
attr_reader :owner_to_pool_manager
|
251
242
|
|
252
243
|
# Returns the pool manager for an owner.
|
253
|
-
#
|
254
|
-
# Using `"primary"` to look up the pool manager for `ActiveRecord::Base` is
|
255
|
-
# deprecated in favor of looking it up by `"ActiveRecord::Base"`.
|
256
|
-
#
|
257
|
-
# During the deprecation period, if `"primary"` is passed, the pool manager
|
258
|
-
# for `ActiveRecord::Base` will still be returned.
|
259
244
|
def get_pool_manager(owner)
|
260
|
-
|
261
|
-
|
262
|
-
if owner == "primary"
|
263
|
-
ActiveSupport::Deprecation.warn("Using `\"primary\"` as a `connection_specification_name` is deprecated and will be removed in Rails 7.0.0. Please use `ActiveRecord::Base`.")
|
264
|
-
owner_to_pool_manager[Base.name]
|
265
|
-
end
|
245
|
+
owner_to_pool_manager[owner]
|
266
246
|
end
|
267
247
|
|
268
248
|
# Returns an instance of PoolConfig for a given adapter.
|
@@ -275,7 +255,7 @@ module ActiveRecord
|
|
275
255
|
# pool_config.db_config.configuration_hash
|
276
256
|
# # => { host: "localhost", database: "foo", adapter: "sqlite3" }
|
277
257
|
#
|
278
|
-
def resolve_pool_config(config, owner_name)
|
258
|
+
def resolve_pool_config(config, owner_name, role, shard)
|
279
259
|
db_config = Base.configurations.resolve(config)
|
280
260
|
|
281
261
|
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db_config.adapter
|
@@ -305,7 +285,7 @@ module ActiveRecord
|
|
305
285
|
raise AdapterNotFound, "database configuration specifies nonexistent #{db_config.adapter} adapter"
|
306
286
|
end
|
307
287
|
|
308
|
-
ConnectionAdapters::PoolConfig.new(owner_name, db_config)
|
288
|
+
ConnectionAdapters::PoolConfig.new(owner_name, db_config, role, shard)
|
309
289
|
end
|
310
290
|
end
|
311
291
|
end
|
@@ -110,7 +110,7 @@ module ActiveRecord
|
|
110
110
|
def wait_poll(timeout)
|
111
111
|
@num_waiting += 1
|
112
112
|
|
113
|
-
t0 =
|
113
|
+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
114
114
|
elapsed = 0
|
115
115
|
loop do
|
116
116
|
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
@@ -119,7 +119,7 @@ module ActiveRecord
|
|
119
119
|
|
120
120
|
return remove if any?
|
121
121
|
|
122
|
-
elapsed =
|
122
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0
|
123
123
|
if elapsed >= timeout
|
124
124
|
msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" %
|
125
125
|
[timeout, elapsed]
|