activerecord 7.2.4 → 8.1.4
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 +1013 -878
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/association.rb +69 -27
- data/lib/active_record/associations/association_scope.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +2 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +36 -8
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +7 -3
- data/lib/active_record/associations/collection_proxy.rb +26 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/foreign_association.rb +3 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/has_one_association.rb +1 -0
- data/lib/active_record/associations/join_dependency.rb +3 -2
- data/lib/active_record/associations/preloader/association.rb +3 -3
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +192 -24
- 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 +18 -5
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/base.rb +1 -2
- data/lib/active_record/callbacks.rb +1 -2
- data/lib/active_record/coders/column_serializer.rb +1 -1
- data/lib/active_record/coders/json.rb +16 -6
- data/lib/active_record/coders/yaml_column.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +429 -93
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +148 -74
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +124 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +129 -63
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +132 -65
- data/lib/active_record/connection_adapters/column.rb +14 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -66
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +79 -61
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +156 -68
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
- 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 +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- 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 +15 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +104 -37
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +41 -66
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +15 -18
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +25 -2
- data/lib/active_record/core.rb +33 -17
- data/lib/active_record/counter_cache.rb +35 -8
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +74 -9
- data/lib/active_record/database_configurations/url_config.rb +15 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/disable_joins_association_relation.rb +5 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +8 -8
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
- data/lib/active_record/encryption/encryptor.rb +28 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +33 -30
- data/lib/active_record/errors.rb +33 -9
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +76 -0
- data/lib/active_record/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +9 -4
- data/lib/active_record/insert_all.rb +14 -9
- data/lib/active_record/locking/optimistic.rb +8 -1
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +31 -29
- data/lib/active_record/message_pack.rb +13 -0
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +56 -13
- data/lib/active_record/migration/compatibility.rb +37 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +48 -42
- data/lib/active_record/model_schema.rb +40 -15
- data/lib/active_record/nested_attributes.rb +14 -12
- data/lib/active_record/persistence.rb +163 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +100 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +35 -30
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -38
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +54 -22
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +147 -73
- data/lib/active_record/relation/calculations.rb +60 -43
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +73 -43
- data/lib/active_record/relation/merger.rb +12 -10
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +23 -8
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +200 -96
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +2 -9
- data/lib/active_record/relation.rb +127 -87
- data/lib/active_record/result.rb +109 -24
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +9 -6
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +17 -10
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +44 -16
- data/lib/active_record/statement_cache.rb +24 -20
- data/lib/active_record/store.rb +53 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +6 -23
- data/lib/active_record/tasks/abstract_tasks.rb +83 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +4 -58
- data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +18 -4
- data/lib/active_record/test_fixtures.rb +52 -6
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +56 -32
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +13 -2
- data/lib/active_record/type/serialized.rb +16 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +85 -50
- data/lib/arel/alias_predication.rb +2 -0
- 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 +6 -11
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/table.rb +3 -7
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +4 -22
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +15 -12
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
- 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
|
|
|
@@ -51,6 +51,16 @@ module ActiveRecord
|
|
|
51
51
|
# ActiveRecord::SerializationTypeMismatch error.
|
|
52
52
|
# * If the column is +NULL+ or starting from a new record, the default value
|
|
53
53
|
# will set to +type.new+
|
|
54
|
+
# * +comparable+ - Specify whether the deserialized object is safely comparable
|
|
55
|
+
# for the purpose of detecting changes. Defaults to +false+
|
|
56
|
+
# When set to +false+ the old and new values will be compared by their serialized
|
|
57
|
+
# representation (e.g. JSON or YAML), which can sometimes cause two objects that are
|
|
58
|
+
# semantically equal to be considered different.
|
|
59
|
+
# For instance two hashes with the same keys and values but a different order have a
|
|
60
|
+
# different serialized representation, but are semantically equal once deserialized.
|
|
61
|
+
# If set to +true+ the comparison will be done on the deserialized object.
|
|
62
|
+
# This options should only be enabled if the +type+ is known to have
|
|
63
|
+
# a proper <tt>==</tt> method that deeply compare the objects.
|
|
54
64
|
# * +yaml+ - Optional. Yaml specific options. The allowed config is:
|
|
55
65
|
# * +:permitted_classes+ - +Array+ with the permitted classes.
|
|
56
66
|
# * +:unsafe_load+ - Unsafely load YAML blobs, allow YAML to load any class.
|
|
@@ -180,7 +190,7 @@ module ActiveRecord
|
|
|
180
190
|
# serialize :preferences, coder: Rot13JSON
|
|
181
191
|
# end
|
|
182
192
|
#
|
|
183
|
-
def serialize(attr_name, coder: nil, type: Object, yaml: {}, **options)
|
|
193
|
+
def serialize(attr_name, coder: nil, type: Object, comparable: false, yaml: {}, **options)
|
|
184
194
|
coder ||= default_column_serializer
|
|
185
195
|
unless coder
|
|
186
196
|
raise ArgumentError, <<~MSG.squish
|
|
@@ -195,12 +205,12 @@ module ActiveRecord
|
|
|
195
205
|
attribute(attr_name, **options)
|
|
196
206
|
|
|
197
207
|
decorate_attributes([attr_name]) do |attr_name, cast_type|
|
|
198
|
-
if type_incompatible_with_serialize?(cast_type,
|
|
208
|
+
if type_incompatible_with_serialize?(cast_type, column_serializer, type)
|
|
199
209
|
raise ColumnNotSerializableError.new(attr_name, cast_type)
|
|
200
210
|
end
|
|
201
211
|
|
|
202
212
|
cast_type = cast_type.subtype if Type::Serialized === cast_type
|
|
203
|
-
Type::Serialized.new(cast_type, column_serializer)
|
|
213
|
+
Type::Serialized.new(cast_type, column_serializer, comparable: comparable)
|
|
204
214
|
end
|
|
205
215
|
end
|
|
206
216
|
|
|
@@ -209,7 +219,10 @@ module ActiveRecord
|
|
|
209
219
|
# When ::JSON is used, force it to go through the Active Support JSON encoder
|
|
210
220
|
# to ensure special objects (e.g. Active Record models) are dumped correctly
|
|
211
221
|
# using the #as_json hook.
|
|
212
|
-
|
|
222
|
+
|
|
223
|
+
if coder == ::JSON || coder == Coders::JSON
|
|
224
|
+
coder = Coders::JSON.new
|
|
225
|
+
end
|
|
213
226
|
|
|
214
227
|
if coder == ::YAML || coder == Coders::YAMLColumn
|
|
215
228
|
Coders::YAMLColumn.new(attr_name, type, **(yaml || {}))
|
|
@@ -223,7 +236,7 @@ module ActiveRecord
|
|
|
223
236
|
end
|
|
224
237
|
|
|
225
238
|
def type_incompatible_with_serialize?(cast_type, coder, type)
|
|
226
|
-
cast_type.is_a?(ActiveRecord::Type::Json) &&
|
|
239
|
+
cast_type.is_a?(ActiveRecord::Type::Json) && Coders::JSON === coder ||
|
|
227
240
|
cast_type.respond_to?(:type_cast_array, true) && type == ::Array
|
|
228
241
|
end
|
|
229
242
|
end
|
|
@@ -21,14 +21,18 @@ module ActiveRecord
|
|
|
21
21
|
set_time_zone_without_conversion(super)
|
|
22
22
|
elsif value.respond_to?(:in_time_zone)
|
|
23
23
|
begin
|
|
24
|
-
super(user_input_in_time_zone(value)) || super
|
|
24
|
+
result = super(user_input_in_time_zone(value)) || super
|
|
25
|
+
if result && type == :time
|
|
26
|
+
result = result.change(year: 2000, month: 1, day: 1)
|
|
27
|
+
end
|
|
28
|
+
result
|
|
25
29
|
rescue ArgumentError
|
|
26
30
|
nil
|
|
27
31
|
end
|
|
28
32
|
elsif value.respond_to?(:infinite?) && value.infinite?
|
|
29
33
|
value
|
|
30
34
|
else
|
|
31
|
-
|
|
35
|
+
map(super) { |v| cast(v) }
|
|
32
36
|
end
|
|
33
37
|
end
|
|
34
38
|
|
|
@@ -41,27 +45,21 @@ module ActiveRecord
|
|
|
41
45
|
return if value.nil?
|
|
42
46
|
|
|
43
47
|
if value.acts_like?(:time)
|
|
44
|
-
value.in_time_zone
|
|
48
|
+
converted = value.in_time_zone
|
|
49
|
+
if type == :time && converted
|
|
50
|
+
converted = converted.change(year: 2000, month: 1, day: 1)
|
|
51
|
+
end
|
|
52
|
+
converted
|
|
45
53
|
elsif value.respond_to?(:infinite?) && value.infinite?
|
|
46
54
|
value
|
|
47
55
|
else
|
|
48
|
-
|
|
56
|
+
map(value) { |v| convert_time_to_time_zone(v) }
|
|
49
57
|
end
|
|
50
58
|
end
|
|
51
59
|
|
|
52
60
|
def set_time_zone_without_conversion(value)
|
|
53
61
|
::Time.zone.local_to_utc(value).try(:in_time_zone) if value
|
|
54
62
|
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
63
|
end
|
|
66
64
|
|
|
67
65
|
extend ActiveSupport::Concern
|
|
@@ -7,6 +7,7 @@ module ActiveRecord
|
|
|
7
7
|
module Attributes
|
|
8
8
|
extend ActiveSupport::Concern
|
|
9
9
|
include ActiveModel::AttributeRegistration
|
|
10
|
+
include ActiveModel::Attributes::Normalization
|
|
10
11
|
|
|
11
12
|
# = Active Record \Attributes
|
|
12
13
|
module ClassMethods
|
|
@@ -251,6 +252,7 @@ module ActiveRecord
|
|
|
251
252
|
|
|
252
253
|
def _default_attributes # :nodoc:
|
|
253
254
|
@default_attributes ||= begin
|
|
255
|
+
# TODO: Remove the need for a connection after we release 8.1.
|
|
254
256
|
attributes_hash = with_connection do |connection|
|
|
255
257
|
columns_hash.transform_values do |column|
|
|
256
258
|
ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(connection, column))
|
|
@@ -310,6 +312,7 @@ module ActiveRecord
|
|
|
310
312
|
end
|
|
311
313
|
|
|
312
314
|
def type_for_column(connection, column)
|
|
315
|
+
# TODO: Remove the need for a connection after we release 8.1.
|
|
313
316
|
hook_attribute_type(column.name, super)
|
|
314
317
|
end
|
|
315
318
|
end
|
|
@@ -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
|
|
@@ -441,33 +475,34 @@ module ActiveRecord
|
|
|
441
475
|
return unless association && association.loaded?
|
|
442
476
|
|
|
443
477
|
record = association.load_target
|
|
478
|
+
return unless record && !record.destroyed?
|
|
444
479
|
|
|
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) }
|
|
480
|
+
autosave = reflection.options[:autosave]
|
|
453
481
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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)
|
|
458
488
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
end
|
|
463
|
-
association.set_inverse_instance(record)
|
|
464
|
-
end
|
|
489
|
+
unless reflection.through_reflection
|
|
490
|
+
foreign_key = Array(reflection.foreign_key)
|
|
491
|
+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
|
465
492
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
|
469
496
|
end
|
|
497
|
+
association.set_inverse_instance(record)
|
|
470
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
|
|
471
506
|
end
|
|
472
507
|
end
|
|
473
508
|
|
|
@@ -492,7 +527,6 @@ module ActiveRecord
|
|
|
492
527
|
return false unless reflection.inverse_of&.polymorphic?
|
|
493
528
|
|
|
494
529
|
class_name = record._read_attribute(reflection.inverse_of.foreign_type)
|
|
495
|
-
|
|
496
530
|
reflection.active_record.polymorphic_name != class_name
|
|
497
531
|
end
|
|
498
532
|
|
|
@@ -512,7 +546,15 @@ module ActiveRecord
|
|
|
512
546
|
foreign_key.each { |key| self[key] = nil }
|
|
513
547
|
record.destroy
|
|
514
548
|
elsif autosave != false
|
|
515
|
-
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
|
|
516
558
|
|
|
517
559
|
if association.updated?
|
|
518
560
|
primary_key = Array(compute_primary_key(reflection, record)).map(&:to_s)
|
data/lib/active_record/base.rb
CHANGED
|
@@ -6,7 +6,7 @@ require "active_support/descendants_tracker"
|
|
|
6
6
|
require "active_support/time"
|
|
7
7
|
require "active_support/core_ext/class/subclasses"
|
|
8
8
|
require "active_record/log_subscriber"
|
|
9
|
-
require "active_record/
|
|
9
|
+
require "active_record/structured_event_subscriber"
|
|
10
10
|
require "active_record/relation/delegation"
|
|
11
11
|
require "active_record/attributes"
|
|
12
12
|
require "active_record/type_caster"
|
|
@@ -328,7 +328,6 @@ module ActiveRecord # :nodoc:
|
|
|
328
328
|
include TokenFor
|
|
329
329
|
include SignedId
|
|
330
330
|
include Suppressor
|
|
331
|
-
include Normalization
|
|
332
331
|
include Marshalling::Methods
|
|
333
332
|
|
|
334
333
|
self.param_delimiter = "_"
|
|
@@ -173,8 +173,7 @@ module ActiveRecord
|
|
|
173
173
|
#
|
|
174
174
|
# If a <tt>before_*</tt> callback throws +:abort+, all the later callbacks and
|
|
175
175
|
# the associated action are cancelled.
|
|
176
|
-
# \Callbacks are
|
|
177
|
-
# methods on the model, which are called last.
|
|
176
|
+
# \Callbacks are run in the order they are defined.
|
|
178
177
|
#
|
|
179
178
|
# == Ordering callbacks
|
|
180
179
|
#
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_support/json"
|
|
4
|
+
|
|
3
5
|
module ActiveRecord
|
|
4
|
-
module Coders
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
module Coders
|
|
7
|
+
class JSON # :nodoc:
|
|
8
|
+
DEFAULT_ENCODE_OPTIONS = { escape: false }.freeze
|
|
9
|
+
|
|
10
|
+
def initialize(encode_options: nil, decode_options: nil)
|
|
11
|
+
encode_options = encode_options ? DEFAULT_ENCODE_OPTIONS.merge(encode_options) : DEFAULT_ENCODE_OPTIONS
|
|
12
|
+
@decode_options = decode_options
|
|
13
|
+
@encoder = ActiveSupport::JSON::Encoding.json_encoder.new(encode_options)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def dump(obj)
|
|
17
|
+
@encoder.encode(obj)
|
|
8
18
|
end
|
|
9
19
|
|
|
10
|
-
def
|
|
11
|
-
ActiveSupport::JSON.decode(json) unless json.blank?
|
|
20
|
+
def load(json)
|
|
21
|
+
ActiveSupport::JSON.decode(json, @decode_options) unless json.blank?
|
|
12
22
|
end
|
|
13
23
|
end
|
|
14
24
|
end
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
require "yaml"
|
|
4
4
|
|
|
5
5
|
module ActiveRecord
|
|
6
|
-
module Coders
|
|
6
|
+
module Coders
|
|
7
7
|
class YAMLColumn < ColumnSerializer # :nodoc:
|
|
8
|
-
class SafeCoder
|
|
8
|
+
class SafeCoder # :nodoc:
|
|
9
9
|
def initialize(permitted_classes: [], unsafe_load: nil)
|
|
10
10
|
@permitted_classes = permitted_classes
|
|
11
11
|
@unsafe_load = unsafe_load
|
|
@@ -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
|
|
@@ -156,9 +158,7 @@ module ActiveRecord
|
|
|
156
158
|
each_connection_pool(role).any?(&:active_connection?)
|
|
157
159
|
end
|
|
158
160
|
|
|
159
|
-
# Returns any connections in use by the current thread back to the pool
|
|
160
|
-
# and also returns connections to the pool cached by threads that are no
|
|
161
|
-
# longer alive.
|
|
161
|
+
# Returns any connections in use by the current thread back to the pool.
|
|
162
162
|
def clear_active_connections!(role = nil)
|
|
163
163
|
each_connection_pool(role).each do |pool|
|
|
164
164
|
pool.release_connection
|
|
@@ -166,7 +166,7 @@ module ActiveRecord
|
|
|
166
166
|
end
|
|
167
167
|
end
|
|
168
168
|
|
|
169
|
-
# Clears
|
|
169
|
+
# Clears reloadable connection caches in all connection pools.
|
|
170
170
|
#
|
|
171
171
|
# See ConnectionPool#clear_reloadable_connections! for details.
|
|
172
172
|
def clear_reloadable_connections!(role = nil)
|
|
@@ -210,18 +210,25 @@ module ActiveRecord
|
|
|
210
210
|
# This makes retrieving the connection pool O(1) once the process is warm.
|
|
211
211
|
# When a connection is established or removed, we invalidate the cache.
|
|
212
212
|
def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false)
|
|
213
|
-
|
|
213
|
+
pool_manager = get_pool_manager(connection_name)
|
|
214
|
+
pool = pool_manager&.get_pool_config(role, shard)&.pool
|
|
214
215
|
|
|
215
216
|
if strict && !pool
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
217
|
+
selector = [
|
|
218
|
+
("'#{shard}' shard" unless shard == ActiveRecord::Base.default_shard),
|
|
219
|
+
("'#{role}' role" unless role == ActiveRecord::Base.default_role),
|
|
220
|
+
].compact.join(" and ")
|
|
221
|
+
|
|
222
|
+
selector = [
|
|
223
|
+
(connection_name unless connection_name == "ActiveRecord::Base"),
|
|
224
|
+
selector.presence,
|
|
225
|
+
].compact.join(" with ")
|
|
226
|
+
|
|
227
|
+
selector = " for #{selector}" if selector.present?
|
|
228
|
+
|
|
229
|
+
message = "No database connection defined#{selector}."
|
|
223
230
|
|
|
224
|
-
raise
|
|
231
|
+
raise ConnectionNotDefined.new(message, connection_name: connection_name, shard: shard, role: role)
|
|
225
232
|
end
|
|
226
233
|
|
|
227
234
|
pool
|
|
@@ -236,8 +243,8 @@ module ActiveRecord
|
|
|
236
243
|
end
|
|
237
244
|
|
|
238
245
|
# Get the existing pool manager or initialize and assign a new one.
|
|
239
|
-
def set_pool_manager(
|
|
240
|
-
connection_name_to_pool_manager[
|
|
246
|
+
def set_pool_manager(connection_descriptor)
|
|
247
|
+
connection_name_to_pool_manager[connection_descriptor.name] ||= PoolManager.new
|
|
241
248
|
end
|
|
242
249
|
|
|
243
250
|
def pool_managers
|
|
@@ -272,9 +279,9 @@ module ActiveRecord
|
|
|
272
279
|
|
|
273
280
|
def determine_owner_name(owner_name, config)
|
|
274
281
|
if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
|
|
275
|
-
|
|
282
|
+
ConnectionDescriptor.new(owner_name.to_s)
|
|
276
283
|
elsif config.is_a?(Symbol)
|
|
277
|
-
|
|
284
|
+
ConnectionDescriptor.new(config.to_s)
|
|
278
285
|
else
|
|
279
286
|
owner_name
|
|
280
287
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "thread"
|
|
4
3
|
require "monitor"
|
|
5
4
|
|
|
6
5
|
module ActiveRecord
|
|
@@ -41,6 +40,14 @@ module ActiveRecord
|
|
|
41
40
|
end
|
|
42
41
|
end
|
|
43
42
|
|
|
43
|
+
# Add +element+ to the back of the queue. Never blocks.
|
|
44
|
+
def add_back(element)
|
|
45
|
+
synchronize do
|
|
46
|
+
@queue.unshift element
|
|
47
|
+
@cond.signal
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
44
51
|
# If +element+ is in the queue, remove and return it, or +nil+.
|
|
45
52
|
def delete(element)
|
|
46
53
|
synchronize do
|
|
@@ -55,6 +62,13 @@ module ActiveRecord
|
|
|
55
62
|
end
|
|
56
63
|
end
|
|
57
64
|
|
|
65
|
+
# Number of elements in the queue.
|
|
66
|
+
def size
|
|
67
|
+
synchronize do
|
|
68
|
+
@queue.size
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
58
72
|
# Remove the head of the queue.
|
|
59
73
|
#
|
|
60
74
|
# If +timeout+ is not given, remove and return the head of the
|
|
@@ -115,9 +129,7 @@ module ActiveRecord
|
|
|
115
129
|
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
116
130
|
elapsed = 0
|
|
117
131
|
loop do
|
|
118
|
-
|
|
119
|
-
@cond.wait(timeout - elapsed)
|
|
120
|
-
end
|
|
132
|
+
@cond.wait(timeout - elapsed)
|
|
121
133
|
|
|
122
134
|
return remove if any?
|
|
123
135
|
|