activerecord 7.2.2.1 → 8.1.2
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 +564 -753
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +35 -11
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- 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 +10 -8
- data/lib/active_record/associations/collection_proxy.rb +22 -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/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 +4 -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/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 +17 -4
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attribute_methods.rb +24 -19
- data/lib/active_record/attributes.rb +40 -26
- data/lib/active_record/autosave_association.rb +91 -39
- data/lib/active_record/base.rb +3 -4
- data/lib/active_record/coders/json.rb +14 -5
- 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 +51 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -117
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
- 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 +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
- 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 +73 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -11
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
- 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/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 +28 -45
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -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 +13 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -19
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +37 -10
- data/lib/active_record/core.rb +61 -25
- data/lib/active_record/counter_cache.rb +34 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +67 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +19 -19
- 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 +9 -9
- data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
- data/lib/active_record/encryption/encryptor.rb +49 -28
- 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 +46 -42
- data/lib/active_record/errors.rb +36 -12
- 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 +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +13 -9
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +12 -7
- 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 +3 -13
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +44 -11
- 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 +50 -43
- data/lib/active_record/model_schema.rb +38 -13
- data/lib/active_record/nested_attributes.rb +6 -6
- data/lib/active_record/persistence.rb +162 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +104 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +12 -12
- data/lib/active_record/railtie.rb +37 -32
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -37
- 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 +53 -21
- 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 +80 -63
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +54 -37
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -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 +22 -7
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +156 -95
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +10 -11
- data/lib/active_record/relation.rb +122 -80
- 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_dumper.rb +47 -22
- 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/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +47 -18
- data/lib/active_record/statement_cache.rb +24 -20
- data/lib/active_record/store.rb +51 -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 +76 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +39 -2
- 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 +39 -16
- 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 +15 -2
- data/lib/active_record/type/serialized.rb +11 -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 +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -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/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +13 -4
- data/lib/arel/table.rb +3 -7
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +6 -22
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +17 -17
- 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.
|
|
@@ -130,7 +140,7 @@ module ActiveRecord
|
|
|
130
140
|
# silently cast unsupported types to +String+:
|
|
131
141
|
#
|
|
132
142
|
# >> JSON.parse(JSON.dump(Struct.new(:foo)))
|
|
133
|
-
# => "#<Class:0x000000013090b4c0>"
|
|
143
|
+
# # => "#<Class:0x000000013090b4c0>"
|
|
134
144
|
#
|
|
135
145
|
# ==== Examples
|
|
136
146
|
#
|
|
@@ -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
|
|
@@ -200,7 +210,7 @@ module ActiveRecord
|
|
|
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 || {}))
|
|
@@ -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
|
|
@@ -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)
|
|
@@ -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
|
|
@@ -21,28 +22,34 @@ module ActiveRecord
|
|
|
21
22
|
# your domain objects across much of Active Record, without having to
|
|
22
23
|
# rely on implementation details or monkey patching.
|
|
23
24
|
#
|
|
24
|
-
#
|
|
25
|
-
# column which this will persist to.
|
|
25
|
+
# ==== Parameters
|
|
26
26
|
#
|
|
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.
|
|
27
|
+
# [+name+]
|
|
28
|
+
# The name of the methods to define attribute methods for, and the
|
|
29
|
+
# column which this will persist to.
|
|
32
30
|
#
|
|
33
|
-
#
|
|
31
|
+
# [+cast_type+]
|
|
32
|
+
# A symbol such as +:string+ or +:integer+, or a type object to be used
|
|
33
|
+
# for this attribute. If this parameter is not passed, the previously
|
|
34
|
+
# defined type (if any) will be used. Otherwise, the type will be
|
|
35
|
+
# ActiveModel::Type::Value. See the examples below for more information
|
|
36
|
+
# about providing custom type objects.
|
|
34
37
|
#
|
|
35
|
-
#
|
|
38
|
+
# ==== Options
|
|
36
39
|
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
+
# [+:default+]
|
|
41
|
+
# The default value to use when no value is provided. If this option is
|
|
42
|
+
# not passed, the previously defined default value (if any) on the
|
|
43
|
+
# superclass or in the schema will be used. Otherwise, the default will
|
|
44
|
+
# be +nil+.
|
|
40
45
|
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
46
|
+
# [+:array+]
|
|
47
|
+
# (PostgreSQL only) Specifies that the type should be an array. See the
|
|
48
|
+
# examples below.
|
|
43
49
|
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
50
|
+
# [+:range+]
|
|
51
|
+
# (PostgreSQL only) Specifies that the type should be a range. See the
|
|
52
|
+
# examples below.
|
|
46
53
|
#
|
|
47
54
|
# When using a symbol for +cast_type+, extra options are forwarded to the
|
|
48
55
|
# constructor of the type object.
|
|
@@ -178,8 +185,8 @@ module ActiveRecord
|
|
|
178
185
|
# @currency_converter = currency_converter
|
|
179
186
|
# end
|
|
180
187
|
#
|
|
181
|
-
# # value will be the result of
|
|
182
|
-
# #
|
|
188
|
+
# # value will be the result of #deserialize or
|
|
189
|
+
# # #cast. Assumed to be an instance of Money in
|
|
183
190
|
# # this case.
|
|
184
191
|
# def serialize(value)
|
|
185
192
|
# value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
|
|
@@ -217,17 +224,22 @@ module ActiveRecord
|
|
|
217
224
|
# is provided so it can be used by plugin authors, application code
|
|
218
225
|
# should probably use ClassMethods#attribute.
|
|
219
226
|
#
|
|
220
|
-
#
|
|
227
|
+
# ==== Parameters
|
|
228
|
+
#
|
|
229
|
+
# [+name+]
|
|
230
|
+
# The name of the attribute being defined. Expected to be a +String+.
|
|
221
231
|
#
|
|
222
|
-
# +cast_type+
|
|
232
|
+
# [+cast_type+]
|
|
233
|
+
# The type object to use for this attribute.
|
|
223
234
|
#
|
|
224
|
-
# +default+
|
|
225
|
-
#
|
|
226
|
-
#
|
|
227
|
-
# will be
|
|
235
|
+
# [+default+]
|
|
236
|
+
# The default value to use when no value is provided. If this option
|
|
237
|
+
# is not passed, the previous default value (if any) will be used.
|
|
238
|
+
# Otherwise, the default will be +nil+. A proc can also be passed, and
|
|
239
|
+
# will be called once each time a new value is needed.
|
|
228
240
|
#
|
|
229
|
-
# +user_provided_default+
|
|
230
|
-
# +cast+ or +deserialize+.
|
|
241
|
+
# [+user_provided_default+]
|
|
242
|
+
# Whether the default value should be cast using +cast+ or +deserialize+.
|
|
231
243
|
def define_attribute(
|
|
232
244
|
name,
|
|
233
245
|
cast_type,
|
|
@@ -240,6 +252,7 @@ module ActiveRecord
|
|
|
240
252
|
|
|
241
253
|
def _default_attributes # :nodoc:
|
|
242
254
|
@default_attributes ||= begin
|
|
255
|
+
# TODO: Remove the need for a connection after we release 8.1.
|
|
243
256
|
attributes_hash = with_connection do |connection|
|
|
244
257
|
columns_hash.transform_values do |column|
|
|
245
258
|
ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(connection, column))
|
|
@@ -299,6 +312,7 @@ module ActiveRecord
|
|
|
299
312
|
end
|
|
300
313
|
|
|
301
314
|
def type_for_column(connection, column)
|
|
315
|
+
# TODO: Remove the need for a connection after we release 8.1.
|
|
302
316
|
hook_attribute_type(column.name, super)
|
|
303
317
|
end
|
|
304
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
|
|
@@ -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
|
@@ -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"
|
|
@@ -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.
|
|
@@ -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 = "_"
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_support/json"
|
|
4
|
+
|
|
3
5
|
module ActiveRecord
|
|
4
6
|
module Coders # :nodoc:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
class JSON # :nodoc:
|
|
8
|
+
DEFAULT_OPTIONS = { escape: false }.freeze
|
|
9
|
+
|
|
10
|
+
def initialize(options = nil)
|
|
11
|
+
@options = options ? DEFAULT_OPTIONS.merge(options) : DEFAULT_OPTIONS
|
|
12
|
+
@encoder = ActiveSupport::JSON::Encoding.json_encoder.new(options)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def dump(obj)
|
|
16
|
+
@encoder.encode(obj)
|
|
8
17
|
end
|
|
9
18
|
|
|
10
|
-
def
|
|
11
|
-
ActiveSupport::JSON.decode(json) unless json.blank?
|
|
19
|
+
def load(json)
|
|
20
|
+
ActiveSupport::JSON.decode(json, @options) unless json.blank?
|
|
12
21
|
end
|
|
13
22
|
end
|
|
14
23
|
end
|