activerecord 7.0.0 → 7.1.0
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 +1607 -1040
- data/MIT-LICENSE +1 -1
- data/README.rdoc +17 -18
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +18 -3
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +17 -12
- data/lib/active_record/associations/collection_proxy.rb +22 -12
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +27 -17
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +20 -14
- data/lib/active_record/associations/preloader/association.rb +27 -6
- data/lib/active_record/associations/preloader/through_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +345 -219
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +40 -26
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +172 -69
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +110 -28
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +56 -10
- data/lib/active_record/base.rb +10 -5
- data/lib/active_record/callbacks.rb +16 -32
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -34
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +128 -32
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +504 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
- data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +358 -57
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +343 -181
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +45 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +73 -96
- data/lib/active_record/core.rb +136 -148
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +9 -4
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/disable_joins_association_relation.rb +1 -1
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +13 -14
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +8 -4
- data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +38 -22
- data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
- data/lib/active_record/encryption/encryptor.rb +7 -7
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -71
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message.rb +1 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +4 -4
- data/lib/active_record/encryption/scheme.rb +20 -23
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +114 -27
- data/lib/active_record/errors.rb +108 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +121 -73
- data/lib/active_record/future_result.rb +30 -5
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +55 -8
- data/lib/active_record/integration.rb +10 -10
- data/lib/active_record/internal_metadata.rb +118 -30
- data/lib/active_record/locking/optimistic.rb +32 -18
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +39 -17
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +18 -13
- data/lib/active_record/middleware/shard_selector.rb +7 -5
- data/lib/active_record/migration/command_recorder.rb +104 -9
- data/lib/active_record/migration/compatibility.rb +158 -64
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration.rb +271 -117
- data/lib/active_record/model_schema.rb +82 -50
- data/lib/active_record/nested_attributes.rb +23 -3
- data/lib/active_record/normalization.rb +159 -0
- data/lib/active_record/persistence.rb +200 -47
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +87 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +16 -3
- data/lib/active_record/railtie.rb +127 -61
- data/lib/active_record/railties/controller_runtime.rb +12 -8
- data/lib/active_record/railties/databases.rake +142 -143
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +177 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +200 -83
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +429 -76
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +98 -41
- data/lib/active_record/result.rb +25 -9
- data/lib/active_record/runtime_registry.rb +10 -1
- data/lib/active_record/sanitization.rb +57 -16
- data/lib/active_record/schema.rb +36 -22
- data/lib/active_record/schema_dumper.rb +65 -23
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +20 -12
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +9 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +138 -107
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +123 -99
- data/lib/active_record/timestamp.rb +26 -14
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +39 -13
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +8 -4
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +3 -3
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +50 -5
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +143 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +1 -1
- data/lib/arel/nodes/and.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/filter.rb +1 -1
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +0 -8
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +50 -15
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module AttributeMethods
|
5
|
+
# = Active Record Attribute Methods \Serialization
|
5
6
|
module Serialization
|
6
7
|
extend ActiveSupport::Concern
|
7
8
|
|
@@ -15,6 +16,10 @@ module ActiveRecord
|
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
19
|
+
included do
|
20
|
+
class_attribute :default_column_serializer, instance_accessor: false, default: Coders::YAMLColumn
|
21
|
+
end
|
22
|
+
|
18
23
|
module ClassMethods
|
19
24
|
# If you have an attribute that needs to be saved to the database as a
|
20
25
|
# serialized object, and retrieved by deserializing into the same object,
|
@@ -24,38 +29,6 @@ module ActiveRecord
|
|
24
29
|
# The serialization format may be YAML, JSON, or any custom format using a
|
25
30
|
# custom coder class.
|
26
31
|
#
|
27
|
-
# === Serialization formats
|
28
|
-
#
|
29
|
-
# serialize attr_name [, class_name_or_coder]
|
30
|
-
#
|
31
|
-
# | | database storage |
|
32
|
-
# class_name_or_coder | attribute read/write type | serialized | NULL |
|
33
|
-
# ---------------------+---------------------------+------------+--------+
|
34
|
-
# <not given> | any value that supports | YAML | |
|
35
|
-
# | .to_yaml | | |
|
36
|
-
# | | | |
|
37
|
-
# Array | Array ** | YAML | [] |
|
38
|
-
# | | | |
|
39
|
-
# Hash | Hash ** | YAML | {} |
|
40
|
-
# | | | |
|
41
|
-
# JSON | any value that supports | JSON | |
|
42
|
-
# | .to_json | | |
|
43
|
-
# | | | |
|
44
|
-
# <custom coder class> | any value supported by | custom | custom |
|
45
|
-
# | the custom coder class | | |
|
46
|
-
#
|
47
|
-
# ** If +class_name_or_coder+ is +Array+ or +Hash+, values retrieved will
|
48
|
-
# always be of that type, and any value assigned must be of that type or
|
49
|
-
# +SerializationTypeMismatch+ will be raised.
|
50
|
-
#
|
51
|
-
# ==== Custom coders
|
52
|
-
# A custom coder class or module may be given. This must have +self.load+
|
53
|
-
# and +self.dump+ class/module methods. <tt>self.dump(object)</tt> will be called
|
54
|
-
# to serialize an object and should return the serialized value to be
|
55
|
-
# stored in the database (+nil+ to store as +NULL+). <tt>self.load(string)</tt>
|
56
|
-
# will be called to reverse the process and load (unserialize) from the
|
57
|
-
# database.
|
58
|
-
#
|
59
32
|
# Keep in mind that database adapters handle certain serialization tasks
|
60
33
|
# for you. For instance: +json+ and +jsonb+ types in PostgreSQL will be
|
61
34
|
# converted between JSON object/array syntax and Ruby +Hash+ or +Array+
|
@@ -67,81 +40,211 @@ module ActiveRecord
|
|
67
40
|
#
|
68
41
|
# ==== Parameters
|
69
42
|
#
|
70
|
-
# * +attr_name+ - The
|
71
|
-
# * +
|
72
|
-
#
|
73
|
-
#
|
43
|
+
# * +attr_name+ - The name of the attribute to serialize.
|
44
|
+
# * +coder+ The serializer implementation to use, e.g. +JSON+.
|
45
|
+
# * The attribute value will be serialized
|
46
|
+
# using the coder's <tt>dump(value)</tt> method, and will be
|
47
|
+
# deserialized using the coder's <tt>load(string)</tt> method. The
|
48
|
+
# +dump+ method may return +nil+ to serialize the value as +NULL+.
|
49
|
+
# * +type+ - Optional. What the type of the serialized object should be.
|
50
|
+
# * Attempting to serialize another type will raise an
|
51
|
+
# ActiveRecord::SerializationTypeMismatch error.
|
52
|
+
# * If the column is +NULL+ or starting from a new record, the default value
|
53
|
+
# will set to +type.new+
|
54
|
+
# * +yaml+ - Optional. Yaml specific options. The allowed config is:
|
55
|
+
# * +:permitted_classes+ - +Array+ with the permitted classes.
|
56
|
+
# * +:unsafe_load+ - Unsafely load YAML blobs, allow YAML to load any class.
|
74
57
|
#
|
75
58
|
# ==== Options
|
76
59
|
#
|
77
|
-
#
|
78
|
-
# is not passed, the previous default value (if any) will
|
79
|
-
# Otherwise, the default will be +nil+.
|
60
|
+
# * +:default+ - The default value to use when no value is provided. If
|
61
|
+
# this option is not passed, the previous default value (if any) will
|
62
|
+
# be used. Otherwise, the default will be +nil+.
|
63
|
+
#
|
64
|
+
# ==== Choosing a serializer
|
65
|
+
#
|
66
|
+
# While any serialization format can be used, it is recommended to carefully
|
67
|
+
# evaluate the properties of a serializer before using it, as migrating to
|
68
|
+
# another format later on can be difficult.
|
69
|
+
#
|
70
|
+
# ===== Avoid accepting arbitrary types
|
71
|
+
#
|
72
|
+
# When serializing data in a column, it is heavily recommended to make sure
|
73
|
+
# only expected types will be serialized. For instance some serializer like
|
74
|
+
# +Marshal+ or +YAML+ are capable of serializing almost any Ruby object.
|
75
|
+
#
|
76
|
+
# This can lead to unexpected types being serialized, and it is important
|
77
|
+
# that type serialization remains backward and forward compatible as long
|
78
|
+
# as some database records still contain these serialized types.
|
79
|
+
#
|
80
|
+
# class Address
|
81
|
+
# def initialize(line, city, country)
|
82
|
+
# @line, @city, @country = line, city, country
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# In the above example, if any of the +Address+ attributes is renamed,
|
87
|
+
# instances that were persisted before the change will be loaded with the
|
88
|
+
# old attributes. This problem is even worse when the serialized type comes
|
89
|
+
# from a dependency which doesn't expect to be serialized this way and may
|
90
|
+
# change its internal representation without notice.
|
91
|
+
#
|
92
|
+
# As such, it is heavily recommended to instead convert these objects into
|
93
|
+
# primitives of the serialization format, for example:
|
80
94
|
#
|
81
|
-
#
|
95
|
+
# class Address
|
96
|
+
# attr_reader :line, :city, :country
|
97
|
+
#
|
98
|
+
# def self.load(payload)
|
99
|
+
# data = YAML.safe_load(payload)
|
100
|
+
# new(data["line"], data["city"], data["country"])
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# def self.dump(address)
|
104
|
+
# YAML.safe_dump(
|
105
|
+
# "line" => address.line,
|
106
|
+
# "city" => address.city,
|
107
|
+
# "country" => address.country,
|
108
|
+
# )
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# def initialize(line, city, country)
|
112
|
+
# @line, @city, @country = line, city, country
|
113
|
+
# end
|
114
|
+
# end
|
82
115
|
#
|
83
|
-
# # Serialize a preferences attribute using YAML coder.
|
84
116
|
# class User < ActiveRecord::Base
|
85
|
-
# serialize :
|
117
|
+
# serialize :address, coder: Address
|
86
118
|
# end
|
87
119
|
#
|
88
|
-
#
|
120
|
+
# This pattern allows to be more deliberate about what is serialized, and
|
121
|
+
# to evolve the format in a backward compatible way.
|
122
|
+
#
|
123
|
+
# ===== Ensure serialization stability
|
124
|
+
#
|
125
|
+
# Some serialization methods may accept some types they don't support by
|
126
|
+
# silently casting them to other types. This can cause bugs when the
|
127
|
+
# data is deserialized.
|
128
|
+
#
|
129
|
+
# For instance the +JSON+ serializer provided in the standard library will
|
130
|
+
# silently cast unsupported types to +String+:
|
131
|
+
#
|
132
|
+
# >> JSON.parse(JSON.dump(Struct.new(:foo)))
|
133
|
+
# => "#<Class:0x000000013090b4c0>"
|
134
|
+
#
|
135
|
+
# ==== Examples
|
136
|
+
#
|
137
|
+
# ===== Serialize the +preferences+ attribute using YAML
|
138
|
+
#
|
89
139
|
# class User < ActiveRecord::Base
|
90
|
-
# serialize :preferences,
|
140
|
+
# serialize :preferences, coder: YAML
|
91
141
|
# end
|
92
142
|
#
|
93
|
-
#
|
143
|
+
# ===== Serialize the +preferences+ attribute using JSON
|
144
|
+
#
|
94
145
|
# class User < ActiveRecord::Base
|
95
|
-
# serialize :preferences,
|
146
|
+
# serialize :preferences, coder: JSON
|
96
147
|
# end
|
97
148
|
#
|
98
|
-
#
|
149
|
+
# ===== Serialize the +preferences+ +Hash+ using YAML
|
150
|
+
#
|
151
|
+
# class User < ActiveRecord::Base
|
152
|
+
# serialize :preferences, type: Hash, coder: YAML
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
# ===== Serializes +preferences+ to YAML, permitting select classes
|
156
|
+
#
|
157
|
+
# class User < ActiveRecord::Base
|
158
|
+
# serialize :preferences, coder: YAML, yaml: { permitted_classes: [Symbol, Time] }
|
159
|
+
# end
|
160
|
+
#
|
161
|
+
# ===== Serialize the +preferences+ attribute using a custom coder
|
162
|
+
#
|
99
163
|
# class Rot13JSON
|
100
164
|
# def self.rot13(string)
|
101
165
|
# string.tr("a-zA-Z", "n-za-mN-ZA-M")
|
102
166
|
# end
|
103
167
|
#
|
104
|
-
# #
|
105
|
-
# def self.dump(
|
106
|
-
# ActiveSupport::JSON.
|
168
|
+
# # Serializes an attribute value to a string that will be stored in the database.
|
169
|
+
# def self.dump(value)
|
170
|
+
# rot13(ActiveSupport::JSON.dump(value))
|
107
171
|
# end
|
108
172
|
#
|
109
|
-
# #
|
110
|
-
# # back into its original value
|
173
|
+
# # Deserializes a string from the database to an attribute value.
|
111
174
|
# def self.load(string)
|
112
|
-
# ActiveSupport::JSON.
|
175
|
+
# ActiveSupport::JSON.load(rot13(string))
|
113
176
|
# end
|
114
177
|
# end
|
115
178
|
#
|
116
179
|
# class User < ActiveRecord::Base
|
117
|
-
# serialize :preferences, Rot13JSON
|
180
|
+
# serialize :preferences, coder: Rot13JSON
|
118
181
|
# end
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
182
|
+
#
|
183
|
+
def serialize(attr_name, class_name_or_coder = nil, coder: nil, type: Object, yaml: {}, **options)
|
184
|
+
unless class_name_or_coder.nil?
|
185
|
+
if class_name_or_coder == ::JSON || [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
|
186
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
187
|
+
Passing the coder as positional argument is deprecated and will be removed in Rails 7.2.
|
188
|
+
|
189
|
+
Please pass the coder as a keyword argument:
|
190
|
+
|
191
|
+
serialize #{attr_name.inspect}, coder: #{class_name_or_coder}
|
192
|
+
MSG
|
193
|
+
coder = class_name_or_coder
|
194
|
+
else
|
195
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
196
|
+
Passing the class as positional argument is deprecated and will be removed in Rails 7.2.
|
197
|
+
|
198
|
+
Please pass the class as a keyword argument:
|
199
|
+
|
200
|
+
serialize #{attr_name.inspect}, type: #{class_name_or_coder.name}
|
201
|
+
MSG
|
202
|
+
type = class_name_or_coder
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
coder ||= default_column_serializer
|
207
|
+
unless coder
|
208
|
+
raise ArgumentError, <<~MSG.squish
|
209
|
+
missing keyword: :coder
|
210
|
+
|
211
|
+
If no default coder is configured, a coder must be provided to `serialize`.
|
212
|
+
MSG
|
129
213
|
end
|
130
214
|
|
215
|
+
column_serializer = build_column_serializer(attr_name, coder, type, yaml)
|
216
|
+
|
131
217
|
attribute(attr_name, **options) do |cast_type|
|
132
|
-
if type_incompatible_with_serialize?(cast_type,
|
218
|
+
if type_incompatible_with_serialize?(cast_type, coder, type)
|
133
219
|
raise ColumnNotSerializableError.new(attr_name, cast_type)
|
134
220
|
end
|
135
221
|
|
136
222
|
cast_type = cast_type.subtype if Type::Serialized === cast_type
|
137
|
-
Type::Serialized.new(cast_type,
|
223
|
+
Type::Serialized.new(cast_type, column_serializer)
|
138
224
|
end
|
139
225
|
end
|
140
226
|
|
141
227
|
private
|
142
|
-
def
|
143
|
-
|
144
|
-
|
228
|
+
def build_column_serializer(attr_name, coder, type, yaml = nil)
|
229
|
+
# When ::JSON is used, force it to go through the Active Support JSON encoder
|
230
|
+
# to ensure special objects (e.g. Active Record models) are dumped correctly
|
231
|
+
# using the #as_json hook.
|
232
|
+
coder = Coders::JSON if coder == ::JSON
|
233
|
+
|
234
|
+
if coder == ::YAML || coder == Coders::YAMLColumn
|
235
|
+
Coders::YAMLColumn.new(attr_name, type, **(yaml || {}))
|
236
|
+
elsif coder.respond_to?(:new) && !coder.respond_to?(:load)
|
237
|
+
coder.new(attr_name, type)
|
238
|
+
elsif type && type != Object
|
239
|
+
Coders::ColumnSerializer.new(attr_name, coder, type)
|
240
|
+
else
|
241
|
+
coder
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def type_incompatible_with_serialize?(cast_type, coder, type)
|
246
|
+
cast_type.is_a?(ActiveRecord::Type::Json) && coder == ::JSON ||
|
247
|
+
cast_type.respond_to?(:type_cast_array, true) && type == ::Array
|
145
248
|
end
|
146
249
|
end
|
147
250
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module AttributeMethods
|
5
|
+
# = Active Record Attribute Methods \Write
|
5
6
|
module Write
|
6
7
|
extend ActiveSupport::Concern
|
7
8
|
|
@@ -25,9 +26,8 @@ module ActiveRecord
|
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
28
|
-
# Updates the attribute identified by
|
29
|
-
#
|
30
|
-
# turned into +nil+.
|
29
|
+
# Updates the attribute identified by +attr_name+ using the specified
|
30
|
+
# +value+. The attribute value will be type cast upon being read.
|
31
31
|
def write_attribute(attr_name, value)
|
32
32
|
name = attr_name.to_s
|
33
33
|
name = self.class.attribute_aliases[name] || name
|
@@ -33,26 +33,94 @@ module ActiveRecord
|
|
33
33
|
Base.instance_methods +
|
34
34
|
Base.private_instance_methods -
|
35
35
|
Base.superclass.instance_methods -
|
36
|
-
Base.superclass.private_instance_methods
|
36
|
+
Base.superclass.private_instance_methods +
|
37
|
+
%i[__id__ dup freeze frozen? hash object_id class clone]
|
37
38
|
).map { |m| -m.to_s }.to_set.freeze
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
41
42
|
module ClassMethods
|
42
|
-
def inherited(child_class) # :nodoc:
|
43
|
-
child_class.initialize_generated_modules
|
44
|
-
super
|
45
|
-
end
|
46
|
-
|
47
43
|
def initialize_generated_modules # :nodoc:
|
48
44
|
@generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethods.new)
|
49
45
|
private_constant :GeneratedAttributeMethods
|
50
46
|
@attribute_methods_generated = false
|
47
|
+
@alias_attributes_mass_generated = false
|
51
48
|
include @generated_attribute_methods
|
52
49
|
|
53
50
|
super
|
54
51
|
end
|
55
52
|
|
53
|
+
def alias_attribute(new_name, old_name)
|
54
|
+
super
|
55
|
+
|
56
|
+
if @alias_attributes_mass_generated
|
57
|
+
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
|
58
|
+
generate_alias_attribute_methods(code_generator, new_name, old_name)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def eagerly_generate_alias_attribute_methods(_new_name, _old_name) # :nodoc:
|
64
|
+
# alias attributes in Active Record are lazily generated
|
65
|
+
end
|
66
|
+
|
67
|
+
def generate_alias_attributes # :nodoc:
|
68
|
+
superclass.generate_alias_attributes unless superclass == Base
|
69
|
+
return if @alias_attributes_mass_generated
|
70
|
+
|
71
|
+
generated_attribute_methods.synchronize do
|
72
|
+
return if @alias_attributes_mass_generated
|
73
|
+
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
|
74
|
+
aliases_by_attribute_name.each do |old_name, new_names|
|
75
|
+
new_names.each do |new_name|
|
76
|
+
generate_alias_attribute_methods(code_generator, new_name, old_name)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
@alias_attributes_mass_generated = true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
|
86
|
+
method_name = pattern.method_name(new_name).to_s
|
87
|
+
target_name = pattern.method_name(old_name).to_s
|
88
|
+
parameters = pattern.parameters
|
89
|
+
old_name = old_name.to_s
|
90
|
+
|
91
|
+
method_defined = method_defined?(target_name) || private_method_defined?(target_name)
|
92
|
+
manually_defined = method_defined &&
|
93
|
+
!self.instance_method(target_name).owner.is_a?(GeneratedAttributeMethods)
|
94
|
+
reserved_method_name = ::ActiveRecord::AttributeMethods.dangerous_attribute_methods.include?(target_name)
|
95
|
+
|
96
|
+
if !abstract_class? && !has_attribute?(old_name)
|
97
|
+
# We only need to issue this deprecation warning once, so we issue it when defining the original reader method.
|
98
|
+
should_warn = target_name == old_name
|
99
|
+
if should_warn
|
100
|
+
ActiveRecord.deprecator.warn(
|
101
|
+
"#{self} model aliases `#{old_name}`, but `#{old_name}` is not an attribute. " \
|
102
|
+
"Starting in Rails 7.2, alias_attribute with non-attribute targets will raise. " \
|
103
|
+
"Use `alias_method :#{new_name}, :#{old_name}` or define the method manually."
|
104
|
+
)
|
105
|
+
end
|
106
|
+
super
|
107
|
+
elsif manually_defined && !reserved_method_name
|
108
|
+
aliased_method_redefined_as_well = method_defined_within?(method_name, self)
|
109
|
+
return if aliased_method_redefined_as_well
|
110
|
+
|
111
|
+
ActiveRecord.deprecator.warn(
|
112
|
+
"#{self} model aliases `#{old_name}` and has a method called `#{target_name}` defined. " \
|
113
|
+
"Starting in Rails 7.2 `#{method_name}` will not be calling `#{target_name}` anymore. " \
|
114
|
+
"You may want to additionally define `#{method_name}` to preserve the current behavior."
|
115
|
+
)
|
116
|
+
super
|
117
|
+
else
|
118
|
+
define_proxy_call(code_generator, method_name, pattern.proxy_target, parameters, old_name,
|
119
|
+
namespace: :proxy_alias_attribute
|
120
|
+
)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
56
124
|
# Generates all the attribute related methods for columns in the database
|
57
125
|
# accessors, mutators and query methods.
|
58
126
|
def define_attribute_methods # :nodoc:
|
@@ -71,6 +139,7 @@ module ActiveRecord
|
|
71
139
|
generated_attribute_methods.synchronize do
|
72
140
|
super if defined?(@attribute_methods_generated) && @attribute_methods_generated
|
73
141
|
@attribute_methods_generated = false
|
142
|
+
@alias_attributes_mass_generated = false
|
74
143
|
end
|
75
144
|
end
|
76
145
|
|
@@ -97,7 +166,7 @@ module ActiveRecord
|
|
97
166
|
super
|
98
167
|
else
|
99
168
|
# If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
|
100
|
-
# defines its own attribute method, then we don't want to
|
169
|
+
# defines its own attribute method, then we don't want to override that.
|
101
170
|
defined = method_defined_within?(method_name, superclass, Base) &&
|
102
171
|
! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
|
103
172
|
defined || super
|
@@ -186,6 +255,16 @@ module ActiveRecord
|
|
186
255
|
def _has_attribute?(attr_name) # :nodoc:
|
187
256
|
attribute_types.key?(attr_name)
|
188
257
|
end
|
258
|
+
|
259
|
+
private
|
260
|
+
def inherited(child_class)
|
261
|
+
super
|
262
|
+
child_class.initialize_generated_modules
|
263
|
+
child_class.class_eval do
|
264
|
+
@alias_attributes_mass_generated = false
|
265
|
+
@attribute_names = nil
|
266
|
+
end
|
267
|
+
end
|
189
268
|
end
|
190
269
|
|
191
270
|
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
|
@@ -309,37 +388,40 @@ module ActiveRecord
|
|
309
388
|
!value.nil? && !(value.respond_to?(:empty?) && value.empty?)
|
310
389
|
end
|
311
390
|
|
312
|
-
# Returns the value of the attribute identified by
|
313
|
-
#
|
314
|
-
#
|
315
|
-
#
|
316
|
-
# Note: +:id+ is always present.
|
391
|
+
# Returns the value of the attribute identified by +attr_name+ after it has
|
392
|
+
# been type cast. (For information about specific type casting behavior, see
|
393
|
+
# the types under ActiveModel::Type.)
|
317
394
|
#
|
318
395
|
# class Person < ActiveRecord::Base
|
319
396
|
# belongs_to :organization
|
320
397
|
# end
|
321
398
|
#
|
322
|
-
# person = Person.new(name:
|
323
|
-
# person[:name]
|
324
|
-
# person[:
|
399
|
+
# person = Person.new(name: "Francesco", date_of_birth: "2004-12-12")
|
400
|
+
# person[:name] # => "Francesco"
|
401
|
+
# person[:date_of_birth] # => Date.new(2004, 12, 12)
|
402
|
+
# person[:organization_id] # => nil
|
403
|
+
#
|
404
|
+
# Raises ActiveModel::MissingAttributeError if the attribute is missing.
|
405
|
+
# Note, however, that the +id+ attribute will never be considered missing.
|
325
406
|
#
|
326
|
-
# person = Person.select(
|
327
|
-
# person[:name] # =>
|
328
|
-
# person[:
|
407
|
+
# person = Person.select(:name).first
|
408
|
+
# person[:name] # => "Francesco"
|
409
|
+
# person[:date_of_birth] # => ActiveModel::MissingAttributeError: missing attribute 'date_of_birth' for Person
|
410
|
+
# person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute 'organization_id' for Person
|
411
|
+
# person[:id] # => nil
|
329
412
|
def [](attr_name)
|
330
413
|
read_attribute(attr_name) { |n| missing_attribute(n, caller) }
|
331
414
|
end
|
332
415
|
|
333
|
-
# Updates the attribute identified by
|
334
|
-
#
|
416
|
+
# Updates the attribute identified by +attr_name+ using the specified
|
417
|
+
# +value+. The attribute value will be type cast upon being read.
|
335
418
|
#
|
336
419
|
# class Person < ActiveRecord::Base
|
337
420
|
# end
|
338
421
|
#
|
339
422
|
# person = Person.new
|
340
|
-
# person[:
|
341
|
-
# person[:
|
342
|
-
# person[:age].class # => Integer
|
423
|
+
# person[:date_of_birth] = "2004-12-12"
|
424
|
+
# person[:date_of_birth] # => Date.new(2004, 12, 12)
|
343
425
|
def []=(attr_name, value)
|
344
426
|
write_attribute(attr_name, value)
|
345
427
|
end
|
@@ -360,10 +442,9 @@ module ActiveRecord
|
|
360
442
|
# end
|
361
443
|
#
|
362
444
|
# private
|
363
|
-
#
|
364
|
-
#
|
365
|
-
#
|
366
|
-
# end
|
445
|
+
# def print_accessed_fields
|
446
|
+
# p @posts.first.accessed_fields
|
447
|
+
# end
|
367
448
|
# end
|
368
449
|
#
|
369
450
|
# Which allows you to quickly change your code to:
|
@@ -392,6 +473,7 @@ module ActiveRecord
|
|
392
473
|
attribute_names &= self.class.column_names
|
393
474
|
attribute_names.delete_if do |name|
|
394
475
|
self.class.readonly_attribute?(name) ||
|
476
|
+
self.class.counter_cache_column?(name) ||
|
395
477
|
column_for_attribute(name).virtual?
|
396
478
|
end
|
397
479
|
end
|
@@ -413,7 +495,7 @@ module ActiveRecord
|
|
413
495
|
inspected_value = if value.is_a?(String) && value.length > 50
|
414
496
|
"#{value[0, 50]}...".inspect
|
415
497
|
elsif value.is_a?(Date) || value.is_a?(Time)
|
416
|
-
%("#{value.
|
498
|
+
%("#{value.to_fs(:inspect)}")
|
417
499
|
else
|
418
500
|
value.inspect
|
419
501
|
end
|
@@ -10,7 +10,7 @@ module ActiveRecord
|
|
10
10
|
included do
|
11
11
|
class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
|
12
12
|
end
|
13
|
-
|
13
|
+
# = Active Record \Attributes
|
14
14
|
module ClassMethods
|
15
15
|
# Defines an attribute with a type on this model. It will override the
|
16
16
|
# type of existing attributes if needed. This allows control over how
|
@@ -194,10 +194,10 @@ module ActiveRecord
|
|
194
194
|
# end
|
195
195
|
#
|
196
196
|
# Product.where(price_in_bitcoins: Money.new(5, "USD"))
|
197
|
-
# #
|
197
|
+
# # SELECT * FROM products WHERE price_in_bitcoins = 0.02230
|
198
198
|
#
|
199
199
|
# Product.where(price_in_bitcoins: Money.new(5, "GBP"))
|
200
|
-
# #
|
200
|
+
# # SELECT * FROM products WHERE price_in_bitcoins = 0.03412
|
201
201
|
#
|
202
202
|
# ==== Dirty Tracking
|
203
203
|
#
|