activerecord 7.2.2.1 → 8.0.5
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +523 -677
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +1 -0
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +34 -10
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +10 -8
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +34 -4
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +1 -1
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
- data/lib/active_record/attribute_methods.rb +24 -19
- data/lib/active_record/attributes.rb +37 -26
- data/lib/active_record/autosave_association.rb +91 -39
- data/lib/active_record/base.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
- 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 +55 -47
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +91 -44
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -8
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +40 -10
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +92 -70
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +35 -46
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -9
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -24
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +60 -17
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +61 -99
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +10 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +63 -17
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- 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_adapters.rb +0 -56
- data/lib/active_record/connection_handling.rb +35 -9
- data/lib/active_record/core.rb +57 -22
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/database_configurations/database_config.rb +4 -0
- data/lib/active_record/database_configurations/hash_config.rb +16 -2
- 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 +37 -28
- 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 +22 -34
- data/lib/active_record/errors.rb +16 -8
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +0 -2
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/insert_all.rb +3 -3
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +5 -11
- data/lib/active_record/migration/command_recorder.rb +32 -12
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +40 -43
- data/lib/active_record/model_schema.rb +3 -4
- data/lib/active_record/nested_attributes.rb +4 -6
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_logs.rb +106 -50
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +12 -12
- data/lib/active_record/railtie.rb +4 -28
- data/lib/active_record/railties/databases.rake +11 -34
- data/lib/active_record/reflection.rb +18 -21
- 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 +71 -65
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +32 -31
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
- 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 +13 -0
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +121 -70
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +9 -3
- data/lib/active_record/relation.rb +95 -67
- data/lib/active_record/result.rb +66 -4
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +34 -11
- 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 +7 -6
- data/lib/active_record/statement_cache.rb +14 -14
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +1 -3
- data/lib/active_record/tasks/database_tasks.rb +69 -60
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/postgresql_database_tasks.rb +9 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transactions.rb +8 -7
- data/lib/active_record/type/serialized.rb +5 -0
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +22 -49
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +6 -2
- data/lib/arel/table.rb +3 -7
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -0
- data/lib/arel/visitors/to_sql.rb +3 -1
- metadata +11 -15
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -3,6 +3,38 @@
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
module AttributeMethods
|
|
5
5
|
# = Active Record Attribute Methods \Query
|
|
6
|
+
#
|
|
7
|
+
# Adds query methods for attributes that return either +true+ or +false+
|
|
8
|
+
# depending on the attribute type and value.
|
|
9
|
+
#
|
|
10
|
+
# For Boolean attributes this will return +true+ if the value is present
|
|
11
|
+
# and return +false+ otherwise:
|
|
12
|
+
#
|
|
13
|
+
# class Product < ActiveRecord::Base
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# product = Product.new(archived: false)
|
|
17
|
+
# product.archived? # => false
|
|
18
|
+
# product.archived = true
|
|
19
|
+
# product.archived? # => true
|
|
20
|
+
#
|
|
21
|
+
# For Numeric attributes this will return +true+ if the value is a non-zero
|
|
22
|
+
# number and return +false+ otherwise:
|
|
23
|
+
#
|
|
24
|
+
# product.inventory_count = 0
|
|
25
|
+
# product.inventory_count? # => false
|
|
26
|
+
# product.inventory_count = 1
|
|
27
|
+
# product.inventory_count? # => true
|
|
28
|
+
#
|
|
29
|
+
# For other attributes it will return +true+ if the value is present
|
|
30
|
+
# and return +false+ otherwise:
|
|
31
|
+
#
|
|
32
|
+
# product.name = nil
|
|
33
|
+
# product.name? # => false
|
|
34
|
+
# product.name = " "
|
|
35
|
+
# product.name? # => false
|
|
36
|
+
# product.name = "Orange"
|
|
37
|
+
# product.name? # => true
|
|
6
38
|
module Query
|
|
7
39
|
extend ActiveSupport::Concern
|
|
8
40
|
|
|
@@ -10,6 +42,8 @@ module ActiveRecord
|
|
|
10
42
|
attribute_method_suffix "?", parameters: false
|
|
11
43
|
end
|
|
12
44
|
|
|
45
|
+
# Returns +true+ or +false+ for the attribute identified by +attr_name+,
|
|
46
|
+
# depending on the attribute type and value.
|
|
13
47
|
def query_attribute(attr_name)
|
|
14
48
|
value = self.public_send(attr_name)
|
|
15
49
|
|
|
@@ -28,7 +28,7 @@ module ActiveRecord
|
|
|
28
28
|
elsif value.respond_to?(:infinite?) && value.infinite?
|
|
29
29
|
value
|
|
30
30
|
else
|
|
31
|
-
|
|
31
|
+
map(super) { |v| cast(v) }
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
|
|
@@ -45,23 +45,13 @@ module ActiveRecord
|
|
|
45
45
|
elsif value.respond_to?(:infinite?) && value.infinite?
|
|
46
46
|
value
|
|
47
47
|
else
|
|
48
|
-
|
|
48
|
+
map(value) { |v| convert_time_to_time_zone(v) }
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def set_time_zone_without_conversion(value)
|
|
53
53
|
::Time.zone.local_to_utc(value).try(:in_time_zone) if value
|
|
54
54
|
end
|
|
55
|
-
|
|
56
|
-
def map_avoiding_infinite_recursion(value)
|
|
57
|
-
map(value) do |v|
|
|
58
|
-
if value.equal?(v)
|
|
59
|
-
nil
|
|
60
|
-
else
|
|
61
|
-
yield(v)
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
55
|
end
|
|
66
56
|
|
|
67
57
|
extend ActiveSupport::Concern
|
|
@@ -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) # :nodoc:
|
|
88
88
|
old_name = old_name.to_s
|
|
89
89
|
|
|
90
90
|
if !abstract_class? && !has_attribute?(old_name)
|
|
@@ -113,13 +113,14 @@ 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") && !_has_attribute?("id_value")
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
-
@attribute_methods_generated = true
|
|
120
|
-
|
|
121
119
|
generate_alias_attributes
|
|
120
|
+
|
|
121
|
+
@attribute_methods_generated = true
|
|
122
122
|
end
|
|
123
|
+
|
|
123
124
|
true
|
|
124
125
|
end
|
|
125
126
|
|
|
@@ -472,23 +473,27 @@ module ActiveRecord
|
|
|
472
473
|
end
|
|
473
474
|
|
|
474
475
|
def method_missing(name, ...)
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
# Some attribute methods weren't generated yet, we retry the call
|
|
487
|
-
return public_send(name, ...)
|
|
488
|
-
end
|
|
476
|
+
# We can't know whether some method was defined or not because
|
|
477
|
+
# multiple thread might be concurrently be in this code path.
|
|
478
|
+
# So the first one would define the methods and the others would
|
|
479
|
+
# appear to already have them.
|
|
480
|
+
self.class.define_attribute_methods
|
|
481
|
+
|
|
482
|
+
# So in all cases we must behave as if the method was just defined.
|
|
483
|
+
method = begin
|
|
484
|
+
self.class.public_instance_method(name)
|
|
485
|
+
rescue NameError
|
|
486
|
+
nil
|
|
489
487
|
end
|
|
490
488
|
|
|
491
|
-
|
|
489
|
+
# The method might be explicitly defined in the model, but call a generated
|
|
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
|
|
492
497
|
end
|
|
493
498
|
|
|
494
499
|
def attribute_method?(attr_name)
|
|
@@ -21,28 +21,34 @@ 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
|
-
#
|
|
25
|
-
# column which this will persist to.
|
|
24
|
+
# ==== Parameters
|
|
26
25
|
#
|
|
27
|
-
# +
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
# Otherwise, the type will be ActiveModel::Type::Value.
|
|
31
|
-
# See the examples below for more information about providing custom type objects.
|
|
26
|
+
# [+name+]
|
|
27
|
+
# The name of the methods to define attribute methods for, and the
|
|
28
|
+
# column which this will persist to.
|
|
32
29
|
#
|
|
33
|
-
#
|
|
30
|
+
# [+cast_type+]
|
|
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.
|
|
34
36
|
#
|
|
35
|
-
#
|
|
37
|
+
# ==== Options
|
|
36
38
|
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
39
|
+
# [+:default+]
|
|
40
|
+
# The default value to use when no value is provided. If this option is
|
|
41
|
+
# not passed, the previously defined default value (if any) on the
|
|
42
|
+
# superclass or in the schema will be used. Otherwise, the default will
|
|
43
|
+
# be +nil+.
|
|
40
44
|
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
45
|
+
# [+:array+]
|
|
46
|
+
# (PostgreSQL only) Specifies that the type should be an array. See the
|
|
47
|
+
# examples below.
|
|
43
48
|
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
49
|
+
# [+:range+]
|
|
50
|
+
# (PostgreSQL only) Specifies that the type should be a range. See the
|
|
51
|
+
# examples below.
|
|
46
52
|
#
|
|
47
53
|
# When using a symbol for +cast_type+, extra options are forwarded to the
|
|
48
54
|
# constructor of the type object.
|
|
@@ -178,8 +184,8 @@ module ActiveRecord
|
|
|
178
184
|
# @currency_converter = currency_converter
|
|
179
185
|
# end
|
|
180
186
|
#
|
|
181
|
-
# # value will be the result of
|
|
182
|
-
# #
|
|
187
|
+
# # value will be the result of #deserialize or
|
|
188
|
+
# # #cast. Assumed to be an instance of Money in
|
|
183
189
|
# # this case.
|
|
184
190
|
# def serialize(value)
|
|
185
191
|
# value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
|
|
@@ -217,17 +223,22 @@ module ActiveRecord
|
|
|
217
223
|
# is provided so it can be used by plugin authors, application code
|
|
218
224
|
# should probably use ClassMethods#attribute.
|
|
219
225
|
#
|
|
220
|
-
#
|
|
226
|
+
# ==== Parameters
|
|
227
|
+
#
|
|
228
|
+
# [+name+]
|
|
229
|
+
# The name of the attribute being defined. Expected to be a +String+.
|
|
221
230
|
#
|
|
222
|
-
# +cast_type+
|
|
231
|
+
# [+cast_type+]
|
|
232
|
+
# The type object to use for this attribute.
|
|
223
233
|
#
|
|
224
|
-
# +default+
|
|
225
|
-
#
|
|
226
|
-
#
|
|
227
|
-
# will be
|
|
234
|
+
# [+default+]
|
|
235
|
+
# The default value to use when no value is provided. If this option
|
|
236
|
+
# is not passed, the previous default value (if any) will be used.
|
|
237
|
+
# Otherwise, the default will be +nil+. A proc can also be passed, and
|
|
238
|
+
# will be called once each time a new value is needed.
|
|
228
239
|
#
|
|
229
|
-
# +user_provided_default+
|
|
230
|
-
# +cast+ or +deserialize+.
|
|
240
|
+
# [+user_provided_default+]
|
|
241
|
+
# Whether the default value should be cast using +cast+ or +deserialize+.
|
|
231
242
|
def define_attribute(
|
|
232
243
|
name,
|
|
233
244
|
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,19 +372,29 @@ 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?
|
|
375
|
+
return true if record.valid?(context)
|
|
341
376
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
else
|
|
350
|
-
errors.add(association.reflection.name)
|
|
351
|
-
end
|
|
377
|
+
if context || record.changed_for_autosave?
|
|
378
|
+
associated_errors = record.errors.objects
|
|
379
|
+
else
|
|
380
|
+
# If there are existing invalid records in the DB, we should still be able to reference them.
|
|
381
|
+
# Unless a record (no matter where in the association chain) is invalid and is being changed.
|
|
382
|
+
associated_errors = record.errors.objects.select { |error| error.is_a?(Associations::NestedError) }
|
|
352
383
|
end
|
|
353
|
-
|
|
384
|
+
|
|
385
|
+
if association.options[:autosave]
|
|
386
|
+
return if equal?(record)
|
|
387
|
+
|
|
388
|
+
associated_errors.each { |error|
|
|
389
|
+
errors.objects.append(
|
|
390
|
+
Associations::NestedError.new(association, error)
|
|
391
|
+
)
|
|
392
|
+
}
|
|
393
|
+
elsif associated_errors.any?
|
|
394
|
+
errors.add(association.reflection.name)
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
errors.any?
|
|
354
398
|
end
|
|
355
399
|
|
|
356
400
|
# Is used as an around_save callback to check while saving a collection
|
|
@@ -431,33 +475,34 @@ module ActiveRecord
|
|
|
431
475
|
return unless association && association.loaded?
|
|
432
476
|
|
|
433
477
|
record = association.load_target
|
|
478
|
+
return unless record && !record.destroyed?
|
|
434
479
|
|
|
435
|
-
|
|
436
|
-
autosave = reflection.options[:autosave]
|
|
437
|
-
|
|
438
|
-
if autosave && record.marked_for_destruction?
|
|
439
|
-
record.destroy
|
|
440
|
-
elsif autosave != false
|
|
441
|
-
primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
|
|
442
|
-
primary_key_value = primary_key.map { |key| _read_attribute(key) }
|
|
480
|
+
autosave = reflection.options[:autosave]
|
|
443
481
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
482
|
+
if autosave && record.marked_for_destruction?
|
|
483
|
+
record.destroy
|
|
484
|
+
elsif autosave != false
|
|
485
|
+
primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
|
|
486
|
+
primary_key_value = primary_key.map { |key| _read_attribute(key) }
|
|
487
|
+
return unless (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
|
|
448
488
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
end
|
|
453
|
-
association.set_inverse_instance(record)
|
|
454
|
-
end
|
|
489
|
+
unless reflection.through_reflection
|
|
490
|
+
foreign_key = Array(reflection.foreign_key)
|
|
491
|
+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
|
455
492
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
493
|
+
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
|
|
494
|
+
association_id = _read_attribute(primary_key)
|
|
495
|
+
record[foreign_key] = association_id unless record[foreign_key] == association_id
|
|
459
496
|
end
|
|
497
|
+
association.set_inverse_instance(record)
|
|
460
498
|
end
|
|
499
|
+
|
|
500
|
+
inverse_association = reflection.inverse_of && record.association(reflection.inverse_of.name)
|
|
501
|
+
return if inverse_association && record.autosaving_belongs_to_for?(inverse_association)
|
|
502
|
+
|
|
503
|
+
saved = record.save(validate: !autosave)
|
|
504
|
+
raise ActiveRecord::Rollback if !saved && autosave
|
|
505
|
+
saved
|
|
461
506
|
end
|
|
462
507
|
end
|
|
463
508
|
|
|
@@ -482,8 +527,7 @@ module ActiveRecord
|
|
|
482
527
|
return false unless reflection.inverse_of&.polymorphic?
|
|
483
528
|
|
|
484
529
|
class_name = record._read_attribute(reflection.inverse_of.foreign_type)
|
|
485
|
-
|
|
486
|
-
reflection.active_record != record.class.polymorphic_class_for(class_name)
|
|
530
|
+
reflection.active_record.polymorphic_name != class_name
|
|
487
531
|
end
|
|
488
532
|
|
|
489
533
|
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
|
|
@@ -502,7 +546,15 @@ module ActiveRecord
|
|
|
502
546
|
foreign_key.each { |key| self[key] = nil }
|
|
503
547
|
record.destroy
|
|
504
548
|
elsif autosave != false
|
|
505
|
-
saved =
|
|
549
|
+
saved = if record.new_record? || (autosave && record.changed_for_autosave?)
|
|
550
|
+
begin
|
|
551
|
+
@autosaving_belongs_to_for ||= {}
|
|
552
|
+
@autosaving_belongs_to_for[association] = true
|
|
553
|
+
record.save(validate: !autosave)
|
|
554
|
+
ensure
|
|
555
|
+
@autosaving_belongs_to_for[association] = false
|
|
556
|
+
end
|
|
557
|
+
end
|
|
506
558
|
|
|
507
559
|
if association.updated?
|
|
508
560
|
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:AttributeAssignment#attributes=] method.
|
|
259
|
+
# {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::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:AttributeAssignment#attributes=] method.
|
|
265
|
+
# {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::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.
|
|
@@ -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
|
|
@@ -55,19 +54,22 @@ module ActiveRecord
|
|
|
55
54
|
# about the model. The model needs to pass a connection specification name to the handler,
|
|
56
55
|
# in order to look up the correct connection pool.
|
|
57
56
|
class ConnectionHandler
|
|
58
|
-
class
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def initialize(name)
|
|
57
|
+
class ConnectionDescriptor # :nodoc:
|
|
58
|
+
def initialize(name, primary = false)
|
|
62
59
|
@name = name
|
|
60
|
+
@primary = primary
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def name
|
|
64
|
+
primary_class? ? "ActiveRecord::Base" : @name
|
|
63
65
|
end
|
|
64
66
|
|
|
65
67
|
def primary_class?
|
|
66
|
-
|
|
68
|
+
@primary
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
def current_preventing_writes
|
|
70
|
-
|
|
72
|
+
ActiveRecord::Base.preventing_writes?(@name)
|
|
71
73
|
end
|
|
72
74
|
end
|
|
73
75
|
|
|
@@ -116,7 +118,7 @@ module ActiveRecord
|
|
|
116
118
|
pool_config = resolve_pool_config(config, owner_name, role, shard)
|
|
117
119
|
db_config = pool_config.db_config
|
|
118
120
|
|
|
119
|
-
pool_manager = set_pool_manager(pool_config.
|
|
121
|
+
pool_manager = set_pool_manager(pool_config.connection_descriptor)
|
|
120
122
|
|
|
121
123
|
# If there is an existing pool with the same values as the pool_config
|
|
122
124
|
# don't remove the connection. Connections should only be removed if we are
|
|
@@ -128,8 +130,8 @@ module ActiveRecord
|
|
|
128
130
|
# Update the pool_config's connection class if it differs. This is used
|
|
129
131
|
# for ensuring that ActiveRecord::Base and the primary_abstract_class use
|
|
130
132
|
# the same pool. Without this granular swapping will not work correctly.
|
|
131
|
-
if owner_name.primary_class? && (existing_pool_config.
|
|
132
|
-
existing_pool_config.
|
|
133
|
+
if owner_name.primary_class? && (existing_pool_config.connection_descriptor != owner_name)
|
|
134
|
+
existing_pool_config.connection_descriptor = owner_name
|
|
133
135
|
end
|
|
134
136
|
|
|
135
137
|
existing_pool_config.pool
|
|
@@ -138,7 +140,7 @@ module ActiveRecord
|
|
|
138
140
|
pool_manager.set_pool_config(role, shard, pool_config)
|
|
139
141
|
|
|
140
142
|
payload = {
|
|
141
|
-
connection_name: pool_config.
|
|
143
|
+
connection_name: pool_config.connection_descriptor.name,
|
|
142
144
|
role: role,
|
|
143
145
|
shard: shard,
|
|
144
146
|
config: db_config.configuration_hash
|
|
@@ -166,7 +168,7 @@ module ActiveRecord
|
|
|
166
168
|
end
|
|
167
169
|
end
|
|
168
170
|
|
|
169
|
-
# Clears
|
|
171
|
+
# Clears reloadable connection caches in all connection pools.
|
|
170
172
|
#
|
|
171
173
|
# See ConnectionPool#clear_reloadable_connections! for details.
|
|
172
174
|
def clear_reloadable_connections!(role = nil)
|
|
@@ -210,18 +212,25 @@ module ActiveRecord
|
|
|
210
212
|
# This makes retrieving the connection pool O(1) once the process is warm.
|
|
211
213
|
# When a connection is established or removed, we invalidate the cache.
|
|
212
214
|
def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false)
|
|
213
|
-
|
|
215
|
+
pool_manager = get_pool_manager(connection_name)
|
|
216
|
+
pool = pool_manager&.get_pool_config(role, shard)&.pool
|
|
214
217
|
|
|
215
218
|
if strict && !pool
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
selector = [
|
|
220
|
+
("'#{shard}' shard" unless shard == ActiveRecord::Base.default_shard),
|
|
221
|
+
("'#{role}' role" unless role == ActiveRecord::Base.default_role),
|
|
222
|
+
].compact.join(" and ")
|
|
223
|
+
|
|
224
|
+
selector = [
|
|
225
|
+
(connection_name unless connection_name == "ActiveRecord::Base"),
|
|
226
|
+
selector.presence,
|
|
227
|
+
].compact.join(" with ")
|
|
228
|
+
|
|
229
|
+
selector = " for #{selector}" if selector.present?
|
|
230
|
+
|
|
231
|
+
message = "No database connection defined#{selector}."
|
|
223
232
|
|
|
224
|
-
raise
|
|
233
|
+
raise ConnectionNotDefined.new(message, connection_name: connection_name, shard: shard, role: role)
|
|
225
234
|
end
|
|
226
235
|
|
|
227
236
|
pool
|
|
@@ -236,8 +245,8 @@ module ActiveRecord
|
|
|
236
245
|
end
|
|
237
246
|
|
|
238
247
|
# Get the existing pool manager or initialize and assign a new one.
|
|
239
|
-
def set_pool_manager(
|
|
240
|
-
connection_name_to_pool_manager[
|
|
248
|
+
def set_pool_manager(connection_descriptor)
|
|
249
|
+
connection_name_to_pool_manager[connection_descriptor.name] ||= PoolManager.new
|
|
241
250
|
end
|
|
242
251
|
|
|
243
252
|
def pool_managers
|
|
@@ -272,9 +281,9 @@ module ActiveRecord
|
|
|
272
281
|
|
|
273
282
|
def determine_owner_name(owner_name, config)
|
|
274
283
|
if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
|
|
275
|
-
|
|
284
|
+
ConnectionDescriptor.new(owner_name.to_s)
|
|
276
285
|
elsif config.is_a?(Symbol)
|
|
277
|
-
|
|
286
|
+
ConnectionDescriptor.new(config.to_s)
|
|
278
287
|
else
|
|
279
288
|
owner_name
|
|
280
289
|
end
|