activerecord 7.2.3 → 8.0.0.beta1
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.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +192 -1261
- data/README.rdoc +2 -2
- data/lib/active_record/associations/alias_tracker.rb +4 -6
- data/lib/active_record/associations/association.rb +25 -5
- data/lib/active_record/associations/belongs_to_association.rb +2 -18
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +4 -4
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +4 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +50 -32
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/serialization.rb +1 -1
- data/lib/active_record/attribute_methods.rb +19 -24
- data/lib/active_record/attributes.rb +26 -37
- data/lib/active_record/autosave_association.rb +81 -49
- data/lib/active_record/base.rb +2 -2
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +31 -75
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +14 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +2 -6
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +27 -9
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +27 -57
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +28 -58
- data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -15
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +43 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -16
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +12 -14
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +51 -9
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +44 -101
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -13
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +60 -22
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
- data/lib/active_record/connection_handling.rb +29 -11
- data/lib/active_record/core.rb +15 -60
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -3
- data/lib/active_record/delegated_type.rb +18 -18
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encryptor.rb +35 -29
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/enum.rb +12 -13
- data/lib/active_record/errors.rb +16 -8
- data/lib/active_record/fixture_set/table_row.rb +2 -19
- data/lib/active_record/fixtures.rb +0 -1
- data/lib/active_record/future_result.rb +14 -10
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/insert_all.rb +1 -1
- data/lib/active_record/marshalling.rb +1 -4
- data/lib/active_record/migration/command_recorder.rb +22 -5
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +36 -35
- data/lib/active_record/model_schema.rb +1 -1
- data/lib/active_record/nested_attributes.rb +4 -6
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_cache.rb +5 -4
- data/lib/active_record/query_logs.rb +98 -44
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +10 -10
- data/lib/active_record/railtie.rb +5 -6
- data/lib/active_record/railties/databases.rake +1 -2
- data/lib/active_record/reflection.rb +9 -7
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +132 -72
- data/lib/active_record/relation/calculations.rb +55 -55
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +31 -32
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +0 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +5 -0
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +90 -91
- data/lib/active_record/relation/record_fetch_warning.rb +2 -2
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +2 -8
- data/lib/active_record/relation.rb +77 -76
- data/lib/active_record/result.rb +68 -7
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +16 -29
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +6 -7
- data/lib/active_record/statement_cache.rb +12 -12
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +24 -15
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/postgresql_database_tasks.rb +0 -7
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/testing/query_assertions.rb +2 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transactions.rb +1 -3
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +16 -1
- data/lib/arel/collectors/bind.rb +1 -1
- data/lib/arel/crud.rb +0 -2
- data/lib/arel/delete_manager.rb +0 -5
- data/lib/arel/nodes/delete_statement.rb +2 -4
- data/lib/arel/nodes/update_statement.rb +2 -4
- data/lib/arel/select_manager.rb +2 -6
- data/lib/arel/update_manager.rb +0 -5
- data/lib/arel/visitors/dot.rb +0 -2
- data/lib/arel/visitors/sqlite.rb +0 -25
- data/lib/arel/visitors/to_sql.rb +1 -3
- metadata +14 -11
|
@@ -84,7 +84,7 @@ module ActiveRecord
|
|
|
84
84
|
attribute_method_patterns_cache.clear
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
-
def alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
|
|
87
|
+
def alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
|
|
88
88
|
old_name = old_name.to_s
|
|
89
89
|
|
|
90
90
|
if !abstract_class? && !has_attribute?(old_name)
|
|
@@ -113,14 +113,13 @@ module ActiveRecord
|
|
|
113
113
|
unless abstract_class?
|
|
114
114
|
load_schema
|
|
115
115
|
super(attribute_names)
|
|
116
|
-
alias_attribute :id_value, :id if _has_attribute?("id")
|
|
116
|
+
alias_attribute :id_value, :id if _has_attribute?("id")
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
-
generate_alias_attributes
|
|
120
|
-
|
|
121
119
|
@attribute_methods_generated = true
|
|
122
|
-
end
|
|
123
120
|
|
|
121
|
+
generate_alias_attributes
|
|
122
|
+
end
|
|
124
123
|
true
|
|
125
124
|
end
|
|
126
125
|
|
|
@@ -473,27 +472,23 @@ module ActiveRecord
|
|
|
473
472
|
end
|
|
474
473
|
|
|
475
474
|
def method_missing(name, ...)
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
475
|
+
unless self.class.attribute_methods_generated?
|
|
476
|
+
if self.class.method_defined?(name)
|
|
477
|
+
# The method is explicitly defined in the model, but calls a generated
|
|
478
|
+
# method with super. So we must resume the call chain at the right step.
|
|
479
|
+
last_method = method(name)
|
|
480
|
+
last_method = last_method.super_method while last_method.super_method
|
|
481
|
+
self.class.define_attribute_methods
|
|
482
|
+
if last_method.super_method
|
|
483
|
+
return last_method.super_method.call(...)
|
|
484
|
+
end
|
|
485
|
+
elsif self.class.define_attribute_methods
|
|
486
|
+
# Some attribute methods weren't generated yet, we retry the call
|
|
487
|
+
return public_send(name, ...)
|
|
488
|
+
end
|
|
487
489
|
end
|
|
488
490
|
|
|
489
|
-
|
|
490
|
-
# method with super. So we must resume the call chain at the right step.
|
|
491
|
-
method = method.super_method while method && !method.owner.is_a?(GeneratedAttributeMethods)
|
|
492
|
-
if method
|
|
493
|
-
method.bind_call(self, ...)
|
|
494
|
-
else
|
|
495
|
-
super
|
|
496
|
-
end
|
|
491
|
+
super
|
|
497
492
|
end
|
|
498
493
|
|
|
499
494
|
def attribute_method?(attr_name)
|
|
@@ -21,34 +21,28 @@ module ActiveRecord
|
|
|
21
21
|
# your domain objects across much of Active Record, without having to
|
|
22
22
|
# rely on implementation details or monkey patching.
|
|
23
23
|
#
|
|
24
|
-
#
|
|
24
|
+
# +name+ The name of the methods to define attribute methods for, and the
|
|
25
|
+
# column which this will persist to.
|
|
25
26
|
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# A symbol such as +:string+ or +:integer+, or a type object to be used
|
|
32
|
-
# for this attribute. If this parameter is not passed, the previously
|
|
33
|
-
# defined type (if any) will be used. Otherwise, the type will be
|
|
34
|
-
# ActiveModel::Type::Value. See the examples below for more information
|
|
35
|
-
# about providing custom type objects.
|
|
27
|
+
# +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
|
|
28
|
+
# to be used for this attribute. If this parameter is not passed, the previously
|
|
29
|
+
# defined type (if any) will be used.
|
|
30
|
+
# Otherwise, the type will be ActiveModel::Type::Value.
|
|
31
|
+
# See the examples below for more information about providing custom type objects.
|
|
36
32
|
#
|
|
37
33
|
# ==== Options
|
|
38
34
|
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
35
|
+
# The following options are accepted:
|
|
36
|
+
#
|
|
37
|
+
# +default+ The default value to use when no value is provided. If this option
|
|
38
|
+
# is not passed, the previously defined default value (if any) on the superclass or in the schema will be used.
|
|
39
|
+
# Otherwise, the default will be +nil+.
|
|
44
40
|
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# examples below.
|
|
41
|
+
# +array+ (PostgreSQL only) specifies that the type should be an array (see the
|
|
42
|
+
# examples below).
|
|
48
43
|
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
# examples below.
|
|
44
|
+
# +range+ (PostgreSQL only) specifies that the type should be a range (see the
|
|
45
|
+
# examples below).
|
|
52
46
|
#
|
|
53
47
|
# When using a symbol for +cast_type+, extra options are forwarded to the
|
|
54
48
|
# constructor of the type object.
|
|
@@ -184,8 +178,8 @@ module ActiveRecord
|
|
|
184
178
|
# @currency_converter = currency_converter
|
|
185
179
|
# end
|
|
186
180
|
#
|
|
187
|
-
# # value will be the result of
|
|
188
|
-
# #
|
|
181
|
+
# # value will be the result of +deserialize+ or
|
|
182
|
+
# # +cast+. Assumed to be an instance of +Money+ in
|
|
189
183
|
# # this case.
|
|
190
184
|
# def serialize(value)
|
|
191
185
|
# value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
|
|
@@ -223,22 +217,17 @@ module ActiveRecord
|
|
|
223
217
|
# is provided so it can be used by plugin authors, application code
|
|
224
218
|
# should probably use ClassMethods#attribute.
|
|
225
219
|
#
|
|
226
|
-
#
|
|
227
|
-
#
|
|
228
|
-
# [+name+]
|
|
229
|
-
# The name of the attribute being defined. Expected to be a +String+.
|
|
220
|
+
# +name+ The name of the attribute being defined. Expected to be a +String+.
|
|
230
221
|
#
|
|
231
|
-
#
|
|
232
|
-
# The type object to use for this attribute.
|
|
222
|
+
# +cast_type+ The type object to use for this attribute.
|
|
233
223
|
#
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
# will be called once each time a new value is needed.
|
|
224
|
+
# +default+ The default value to use when no value is provided. If this option
|
|
225
|
+
# is not passed, the previous default value (if any) will be used.
|
|
226
|
+
# Otherwise, the default will be +nil+. A proc can also be passed, and
|
|
227
|
+
# will be called once each time a new value is needed.
|
|
239
228
|
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
229
|
+
# +user_provided_default+ Whether the default value should be cast using
|
|
230
|
+
# +cast+ or +deserialize+.
|
|
242
231
|
def define_attribute(
|
|
243
232
|
name,
|
|
244
233
|
cast_type,
|
|
@@ -221,8 +221,10 @@ module ActiveRecord
|
|
|
221
221
|
if reflection.validate? && !method_defined?(validation_method)
|
|
222
222
|
if reflection.collection?
|
|
223
223
|
method = :validate_collection_association
|
|
224
|
+
elsif reflection.has_one?
|
|
225
|
+
method = :validate_has_one_association
|
|
224
226
|
else
|
|
225
|
-
method = :
|
|
227
|
+
method = :validate_belongs_to_association
|
|
226
228
|
end
|
|
227
229
|
|
|
228
230
|
define_non_cyclic_method(validation_method) { send(method, reflection) }
|
|
@@ -274,6 +276,16 @@ module ActiveRecord
|
|
|
274
276
|
new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave?
|
|
275
277
|
end
|
|
276
278
|
|
|
279
|
+
def validating_belongs_to_for?(association)
|
|
280
|
+
@validating_belongs_to_for ||= {}
|
|
281
|
+
@validating_belongs_to_for[association]
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def autosaving_belongs_to_for?(association)
|
|
285
|
+
@autosaving_belongs_to_for ||= {}
|
|
286
|
+
@autosaving_belongs_to_for[association]
|
|
287
|
+
end
|
|
288
|
+
|
|
277
289
|
private
|
|
278
290
|
def init_internals
|
|
279
291
|
super
|
|
@@ -313,11 +325,33 @@ module ActiveRecord
|
|
|
313
325
|
end
|
|
314
326
|
|
|
315
327
|
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
|
|
316
|
-
# turned on for the association.
|
|
317
|
-
def
|
|
328
|
+
# turned on for the has_one association.
|
|
329
|
+
def validate_has_one_association(reflection)
|
|
330
|
+
association = association_instance_get(reflection.name)
|
|
331
|
+
record = association && association.reader
|
|
332
|
+
return unless record && (record.changed_for_autosave? || custom_validation_context?)
|
|
333
|
+
|
|
334
|
+
inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name)
|
|
335
|
+
return if inverse_association && (record.validating_belongs_to_for?(inverse_association) ||
|
|
336
|
+
record.autosaving_belongs_to_for?(inverse_association))
|
|
337
|
+
|
|
338
|
+
association_valid?(association, record)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
|
|
342
|
+
# turned on for the belongs_to association.
|
|
343
|
+
def validate_belongs_to_association(reflection)
|
|
318
344
|
association = association_instance_get(reflection.name)
|
|
319
345
|
record = association && association.reader
|
|
320
|
-
|
|
346
|
+
return unless record && (record.changed_for_autosave? || custom_validation_context?)
|
|
347
|
+
|
|
348
|
+
begin
|
|
349
|
+
@validating_belongs_to_for ||= {}
|
|
350
|
+
@validating_belongs_to_for[association] = true
|
|
351
|
+
association_valid?(association, record)
|
|
352
|
+
ensure
|
|
353
|
+
@validating_belongs_to_for[association] = false
|
|
354
|
+
end
|
|
321
355
|
end
|
|
322
356
|
|
|
323
357
|
# Validate the associated records if <tt>:validate</tt> or
|
|
@@ -338,29 +372,19 @@ module ActiveRecord
|
|
|
338
372
|
return true if record.destroyed? || (association.options[:autosave] && record.marked_for_destruction?)
|
|
339
373
|
|
|
340
374
|
context = validation_context if custom_validation_context?
|
|
341
|
-
return true if record.valid?(context)
|
|
342
375
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
associated_errors.each { |error|
|
|
355
|
-
errors.objects.append(
|
|
356
|
-
Associations::NestedError.new(association, error)
|
|
357
|
-
)
|
|
358
|
-
}
|
|
359
|
-
elsif associated_errors.any?
|
|
360
|
-
errors.add(association.reflection.name)
|
|
376
|
+
unless valid = record.valid?(context)
|
|
377
|
+
if association.options[:autosave]
|
|
378
|
+
record.errors.each { |error|
|
|
379
|
+
self.errors.objects.append(
|
|
380
|
+
Associations::NestedError.new(association, error)
|
|
381
|
+
)
|
|
382
|
+
}
|
|
383
|
+
else
|
|
384
|
+
errors.add(association.reflection.name)
|
|
385
|
+
end
|
|
361
386
|
end
|
|
362
|
-
|
|
363
|
-
errors.any?
|
|
387
|
+
valid
|
|
364
388
|
end
|
|
365
389
|
|
|
366
390
|
# Is used as an around_save callback to check while saving a collection
|
|
@@ -441,33 +465,34 @@ module ActiveRecord
|
|
|
441
465
|
return unless association && association.loaded?
|
|
442
466
|
|
|
443
467
|
record = association.load_target
|
|
468
|
+
return unless record && !record.destroyed?
|
|
444
469
|
|
|
445
|
-
|
|
446
|
-
autosave = reflection.options[:autosave]
|
|
447
|
-
|
|
448
|
-
if autosave && record.marked_for_destruction?
|
|
449
|
-
record.destroy
|
|
450
|
-
elsif autosave != false
|
|
451
|
-
primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
|
|
452
|
-
primary_key_value = primary_key.map { |key| _read_attribute(key) }
|
|
470
|
+
autosave = reflection.options[:autosave]
|
|
453
471
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
472
|
+
if autosave && record.marked_for_destruction?
|
|
473
|
+
record.destroy
|
|
474
|
+
elsif autosave != false
|
|
475
|
+
primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
|
|
476
|
+
primary_key_value = primary_key.map { |key| _read_attribute(key) }
|
|
477
|
+
return unless (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
|
|
458
478
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
end
|
|
463
|
-
association.set_inverse_instance(record)
|
|
464
|
-
end
|
|
479
|
+
unless reflection.through_reflection
|
|
480
|
+
foreign_key = Array(reflection.foreign_key)
|
|
481
|
+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
|
465
482
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
483
|
+
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
|
|
484
|
+
association_id = _read_attribute(primary_key)
|
|
485
|
+
record[foreign_key] = association_id unless record[foreign_key] == association_id
|
|
469
486
|
end
|
|
487
|
+
association.set_inverse_instance(record)
|
|
470
488
|
end
|
|
489
|
+
|
|
490
|
+
inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name)
|
|
491
|
+
return if inverse_association && record.autosaving_belongs_to_for?(inverse_association)
|
|
492
|
+
|
|
493
|
+
saved = record.save(validate: !autosave)
|
|
494
|
+
raise ActiveRecord::Rollback if !saved && autosave
|
|
495
|
+
saved
|
|
471
496
|
end
|
|
472
497
|
end
|
|
473
498
|
|
|
@@ -492,8 +517,7 @@ module ActiveRecord
|
|
|
492
517
|
return false unless reflection.inverse_of&.polymorphic?
|
|
493
518
|
|
|
494
519
|
class_name = record._read_attribute(reflection.inverse_of.foreign_type)
|
|
495
|
-
|
|
496
|
-
reflection.active_record.polymorphic_name != class_name
|
|
520
|
+
reflection.active_record != record.class.polymorphic_class_for(class_name)
|
|
497
521
|
end
|
|
498
522
|
|
|
499
523
|
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
|
|
@@ -512,7 +536,15 @@ module ActiveRecord
|
|
|
512
536
|
foreign_key.each { |key| self[key] = nil }
|
|
513
537
|
record.destroy
|
|
514
538
|
elsif autosave != false
|
|
515
|
-
saved =
|
|
539
|
+
saved = if record.new_record? || (autosave && record.changed_for_autosave?)
|
|
540
|
+
begin
|
|
541
|
+
@autosaving_belongs_to_for ||= {}
|
|
542
|
+
@autosaving_belongs_to_for[association] = true
|
|
543
|
+
record.save(validate: !autosave)
|
|
544
|
+
ensure
|
|
545
|
+
@autosaving_belongs_to_for[association] = false
|
|
546
|
+
end
|
|
547
|
+
end
|
|
516
548
|
|
|
517
549
|
if association.updated?
|
|
518
550
|
primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
|
data/lib/active_record/base.rb
CHANGED
|
@@ -256,13 +256,13 @@ module ActiveRecord # :nodoc:
|
|
|
256
256
|
# * AssociationTypeMismatch - The object assigned to the association wasn't of the type
|
|
257
257
|
# specified in the association definition.
|
|
258
258
|
# * AttributeAssignmentError - An error occurred while doing a mass assignment through the
|
|
259
|
-
# {ActiveRecord::Base#attributes=}[rdoc-ref:
|
|
259
|
+
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
|
|
260
260
|
# You can inspect the +attribute+ property of the exception object to determine which attribute
|
|
261
261
|
# triggered the error.
|
|
262
262
|
# * ConnectionNotEstablished - No connection has been established.
|
|
263
263
|
# Use {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] before querying.
|
|
264
264
|
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
|
|
265
|
-
# {ActiveRecord::Base#attributes=}[rdoc-ref:
|
|
265
|
+
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
|
|
266
266
|
# The +errors+ property of this exception contains an array of
|
|
267
267
|
# AttributeAssignmentError
|
|
268
268
|
# objects that should be inspected to determine which attributes triggered the errors.
|
|
@@ -418,7 +418,7 @@ module ActiveRecord
|
|
|
418
418
|
|
|
419
419
|
def destroy # :nodoc:
|
|
420
420
|
@_destroy_callback_already_called ||= false
|
|
421
|
-
return
|
|
421
|
+
return if @_destroy_callback_already_called
|
|
422
422
|
@_destroy_callback_already_called = true
|
|
423
423
|
_run_destroy_callbacks { super }
|
|
424
424
|
rescue RecordNotDestroyed => e
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "thread"
|
|
4
3
|
require "concurrent/map"
|
|
5
4
|
|
|
6
5
|
module ActiveRecord
|
|
@@ -210,18 +209,25 @@ module ActiveRecord
|
|
|
210
209
|
# This makes retrieving the connection pool O(1) once the process is warm.
|
|
211
210
|
# When a connection is established or removed, we invalidate the cache.
|
|
212
211
|
def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false)
|
|
213
|
-
|
|
212
|
+
pool_manager = get_pool_manager(connection_name)
|
|
213
|
+
pool = pool_manager&.get_pool_config(role, shard)&.pool
|
|
214
214
|
|
|
215
215
|
if strict && !pool
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
216
|
+
selector = [
|
|
217
|
+
("'#{shard}' shard" unless shard == ActiveRecord::Base.default_shard),
|
|
218
|
+
("'#{role}' role" unless role == ActiveRecord::Base.default_role),
|
|
219
|
+
].compact.join(" and ")
|
|
220
|
+
|
|
221
|
+
selector = [
|
|
222
|
+
(connection_name unless connection_name == "ActiveRecord::Base"),
|
|
223
|
+
selector.presence,
|
|
224
|
+
].compact.join(" with ")
|
|
225
|
+
|
|
226
|
+
selector = " for #{selector}" if selector.present?
|
|
227
|
+
|
|
228
|
+
message = "No database connection defined#{selector}."
|
|
223
229
|
|
|
224
|
-
raise
|
|
230
|
+
raise ConnectionNotDefined.new(message, connection_name: connection_name, shard: shard, role: role)
|
|
225
231
|
end
|
|
226
232
|
|
|
227
233
|
pool
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "thread"
|
|
4
3
|
require "concurrent/map"
|
|
5
4
|
require "monitor"
|
|
6
5
|
|
|
@@ -38,7 +37,6 @@ module ActiveRecord
|
|
|
38
37
|
|
|
39
38
|
def schema_cache; end
|
|
40
39
|
def connection_class; end
|
|
41
|
-
def query_cache; end
|
|
42
40
|
def checkin(_); end
|
|
43
41
|
def remove(_); end
|
|
44
42
|
def async_executor; end
|
|
@@ -119,30 +117,24 @@ module ActiveRecord
|
|
|
119
117
|
# * private methods that require being called in a +synchronize+ blocks
|
|
120
118
|
# are now explicitly documented
|
|
121
119
|
class ConnectionPool
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
# FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
|
|
129
|
-
# but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
|
|
130
|
-
def initialize
|
|
131
|
-
@map = {}
|
|
132
|
-
end
|
|
120
|
+
class WeakThreadKeyMap # :nodoc:
|
|
121
|
+
# FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
|
|
122
|
+
# but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
|
|
123
|
+
def initialize
|
|
124
|
+
@map = {}
|
|
125
|
+
end
|
|
133
126
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
127
|
+
def clear
|
|
128
|
+
@map.clear
|
|
129
|
+
end
|
|
137
130
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
131
|
+
def [](key)
|
|
132
|
+
@map[key]
|
|
133
|
+
end
|
|
141
134
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
end
|
|
135
|
+
def []=(key, value)
|
|
136
|
+
@map.select! { |c, _| c.alive? }
|
|
137
|
+
@map[key] = value
|
|
146
138
|
end
|
|
147
139
|
end
|
|
148
140
|
|
|
@@ -191,31 +183,6 @@ module ActiveRecord
|
|
|
191
183
|
end
|
|
192
184
|
end
|
|
193
185
|
|
|
194
|
-
module ExecutorHooks # :nodoc:
|
|
195
|
-
class << self
|
|
196
|
-
def run
|
|
197
|
-
# noop
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
def complete(_)
|
|
201
|
-
ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
|
|
202
|
-
if (connection = pool.active_connection?)
|
|
203
|
-
transaction = connection.current_transaction
|
|
204
|
-
if transaction.closed? || !transaction.joinable?
|
|
205
|
-
pool.release_connection
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
class << self
|
|
214
|
-
def install_executor_hooks(executor = ActiveSupport::Executor)
|
|
215
|
-
executor.register_hook(ExecutorHooks)
|
|
216
|
-
end
|
|
217
|
-
end
|
|
218
|
-
|
|
219
186
|
include MonitorMixin
|
|
220
187
|
prepend QueryCache::ConnectionPoolConfiguration
|
|
221
188
|
include ConnectionAdapters::AbstractPool
|
|
@@ -316,9 +283,8 @@ module ActiveRecord
|
|
|
316
283
|
# held in a cache keyed by a thread.
|
|
317
284
|
def lease_connection
|
|
318
285
|
lease = connection_lease
|
|
319
|
-
lease.connection ||= checkout
|
|
320
286
|
lease.sticky = true
|
|
321
|
-
lease.connection
|
|
287
|
+
lease.connection ||= checkout
|
|
322
288
|
end
|
|
323
289
|
|
|
324
290
|
def permanent_lease? # :nodoc:
|
|
@@ -344,7 +310,6 @@ module ActiveRecord
|
|
|
344
310
|
end
|
|
345
311
|
|
|
346
312
|
@pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
|
|
347
|
-
@pinned_connection.pinned = true
|
|
348
313
|
@pinned_connection.verify! # eagerly validate the connection
|
|
349
314
|
@pinned_connection.begin_transaction joinable: false, _lazy: false
|
|
350
315
|
end
|
|
@@ -367,8 +332,6 @@ module ActiveRecord
|
|
|
367
332
|
end
|
|
368
333
|
|
|
369
334
|
if @pinned_connection.nil?
|
|
370
|
-
connection.pinned = false
|
|
371
|
-
connection.steal!
|
|
372
335
|
connection.lock_thread = nil
|
|
373
336
|
checkin(connection)
|
|
374
337
|
end
|
|
@@ -558,25 +521,20 @@ module ActiveRecord
|
|
|
558
521
|
# Raises:
|
|
559
522
|
# - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
|
|
560
523
|
def checkout(checkout_timeout = @checkout_timeout)
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
synchronize do
|
|
565
|
-
# The pinned connection may have been cleaned up before we synchronized, so check if it is still present
|
|
566
|
-
if @pinned_connection
|
|
524
|
+
if @pinned_connection
|
|
525
|
+
@pinned_connection.lock.synchronize do
|
|
526
|
+
synchronize do
|
|
567
527
|
@pinned_connection.verify!
|
|
568
|
-
|
|
569
528
|
# Any leased connection must be in @connections otherwise
|
|
570
529
|
# some methods like #connected? won't behave correctly
|
|
571
530
|
unless @connections.include?(@pinned_connection)
|
|
572
531
|
@connections << @pinned_connection
|
|
573
532
|
end
|
|
574
|
-
|
|
575
|
-
@pinned_connection
|
|
576
|
-
else
|
|
577
|
-
checkout_and_verify(acquire_connection(checkout_timeout))
|
|
578
533
|
end
|
|
579
534
|
end
|
|
535
|
+
@pinned_connection
|
|
536
|
+
else
|
|
537
|
+
checkout_and_verify(acquire_connection(checkout_timeout))
|
|
580
538
|
end
|
|
581
539
|
end
|
|
582
540
|
|
|
@@ -711,14 +669,6 @@ module ActiveRecord
|
|
|
711
669
|
Thread.pass
|
|
712
670
|
end
|
|
713
671
|
|
|
714
|
-
def new_connection # :nodoc:
|
|
715
|
-
connection = db_config.new_connection
|
|
716
|
-
connection.pool = self
|
|
717
|
-
connection
|
|
718
|
-
rescue ConnectionNotEstablished => ex
|
|
719
|
-
raise ex.set_pool(self)
|
|
720
|
-
end
|
|
721
|
-
|
|
722
672
|
private
|
|
723
673
|
def connection_lease
|
|
724
674
|
@leases[ActiveSupport::IsolatedExecutionState.context]
|
|
@@ -898,12 +848,18 @@ module ActiveRecord
|
|
|
898
848
|
#--
|
|
899
849
|
# if owner_thread param is omitted, this must be called in synchronize block
|
|
900
850
|
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
|
|
901
|
-
|
|
902
|
-
@leases[owner_thread].clear(conn)
|
|
903
|
-
end
|
|
851
|
+
@leases[owner_thread].clear(conn)
|
|
904
852
|
end
|
|
905
853
|
alias_method :release, :remove_connection_from_thread_cache
|
|
906
854
|
|
|
855
|
+
def new_connection
|
|
856
|
+
connection = db_config.new_connection
|
|
857
|
+
connection.pool = self
|
|
858
|
+
connection
|
|
859
|
+
rescue ConnectionNotEstablished => ex
|
|
860
|
+
raise ex.set_pool(self)
|
|
861
|
+
end
|
|
862
|
+
|
|
907
863
|
# If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
|
|
908
864
|
# to the DB is done outside main synchronized section.
|
|
909
865
|
#--
|