activerecord 7.0.8.7 → 7.2.3
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 +781 -1777
- data/MIT-LICENSE +1 -1
- data/README.rdoc +30 -30
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +35 -12
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +40 -9
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +22 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +35 -21
- data/lib/active_record/associations/collection_proxy.rb +29 -11
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +21 -14
- data/lib/active_record/associations/has_many_through_association.rb +17 -7
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +4 -3
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +33 -8
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +354 -485
- data/lib/active_record/attribute_assignment.rb +0 -4
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +45 -25
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +8 -7
- data/lib/active_record/attribute_methods/serialization.rb +131 -32
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +153 -33
- data/lib/active_record/attributes.rb +96 -71
- data/lib/active_record/autosave_association.rb +81 -39
- data/lib/active_record/base.rb +11 -7
- data/lib/active_record/callbacks.rb +11 -25
- 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 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +343 -91
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +229 -64
- data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
- 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 +142 -12
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +539 -111
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +289 -128
- 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 +26 -139
- data/lib/active_record/connection_adapters/mysql/quoting.rb +60 -55
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +108 -68
- data/lib/active_record/connection_adapters/pool_config.rb +20 -10
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -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 +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +371 -64
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +374 -203
- data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -45
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +51 -8
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +298 -113
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
- data/lib/active_record/connection_adapters.rb +124 -1
- data/lib/active_record/connection_handling.rb +101 -105
- data/lib/active_record/core.rb +273 -178
- data/lib/active_record/counter_cache.rb +69 -35
- data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -3
- data/lib/active_record/database_configurations/database_config.rb +26 -5
- data/lib/active_record/database_configurations/hash_config.rb +52 -34
- data/lib/active_record/database_configurations/url_config.rb +37 -12
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +56 -27
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- 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 +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +46 -22
- data/lib/active_record/encryption/encrypted_attribute_type.rb +48 -13
- data/lib/active_record/encryption/encryptor.rb +35 -19
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- 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/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +6 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +130 -28
- data/lib/active_record/errors.rb +154 -34
- data/lib/active_record/explain.rb +21 -12
- 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 +48 -10
- data/lib/active_record/fixtures.rb +167 -97
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +11 -8
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +18 -22
- data/lib/active_record/marshalling.rb +59 -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 +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +106 -8
- data/lib/active_record/migration/compatibility.rb +147 -5
- data/lib/active_record/migration/default_strategy.rb +22 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +236 -118
- data/lib/active_record/model_schema.rb +90 -102
- data/lib/active_record/nested_attributes.rb +48 -11
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +168 -339
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -25
- data/lib/active_record/query_logs.rb +96 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +35 -10
- data/lib/active_record/railtie.rb +131 -87
- data/lib/active_record/railties/controller_runtime.rb +22 -7
- data/lib/active_record/railties/databases.rake +147 -155
- 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 +267 -69
- data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +270 -108
- data/lib/active_record/relation/delegation.rb +30 -19
- data/lib/active_record/relation/finder_methods.rb +97 -21
- data/lib/active_record/relation/merger.rb +6 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +20 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +28 -16
- data/lib/active_record/relation/query_attribute.rb +3 -2
- data/lib/active_record/relation/query_methods.rb +585 -109
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +5 -4
- data/lib/active_record/relation/where_clause.rb +15 -21
- data/lib/active_record/relation.rb +592 -92
- data/lib/active_record/result.rb +49 -48
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +70 -25
- data/lib/active_record/schema.rb +8 -7
- data/lib/active_record/schema_dumper.rb +90 -23
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +3 -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/signed_id.rb +33 -11
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +190 -118
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +23 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_fixtures.rb +170 -155
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +31 -17
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +108 -24
- data/lib/active_record/translation.rb +0 -2
- 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 +1 -3
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +9 -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 +61 -11
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +247 -33
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +3 -1
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/crud.rb +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +7 -3
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -0
- data/lib/arel/visitors/mysql.rb +17 -5
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +114 -34
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +21 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- 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 +56 -17
- 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,
|
|
@@ -36,21 +41,19 @@ module ActiveRecord
|
|
|
36
41
|
# ==== Parameters
|
|
37
42
|
#
|
|
38
43
|
# * +attr_name+ - The name of the attribute to serialize.
|
|
39
|
-
# * +
|
|
40
|
-
# *
|
|
41
|
-
# The attribute value must respond to +to_yaml+.
|
|
42
|
-
# * +Array+ - The attribute value will be serialized as YAML, but an
|
|
43
|
-
# empty +Array+ will be serialized as +NULL+. The attribute value
|
|
44
|
-
# must be an +Array+.
|
|
45
|
-
# * +Hash+ - The attribute value will be serialized as YAML, but an
|
|
46
|
-
# empty +Hash+ will be serialized as +NULL+. The attribute value
|
|
47
|
-
# must be a +Hash+.
|
|
48
|
-
# * +JSON+ - The attribute value will be serialized as JSON. The
|
|
49
|
-
# attribute value must respond to +to_json+.
|
|
50
|
-
# * <em>custom coder</em> - The attribute value will be serialized
|
|
44
|
+
# * +coder+ The serializer implementation to use, e.g. +JSON+.
|
|
45
|
+
# * The attribute value will be serialized
|
|
51
46
|
# using the coder's <tt>dump(value)</tt> method, and will be
|
|
52
47
|
# deserialized using the coder's <tt>load(string)</tt> method. The
|
|
53
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.
|
|
54
57
|
#
|
|
55
58
|
# ==== Options
|
|
56
59
|
#
|
|
@@ -58,24 +61,101 @@ module ActiveRecord
|
|
|
58
61
|
# this option is not passed, the previous default value (if any) will
|
|
59
62
|
# be used. Otherwise, the default will be +nil+.
|
|
60
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:
|
|
94
|
+
#
|
|
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
|
|
115
|
+
#
|
|
116
|
+
# class User < ActiveRecord::Base
|
|
117
|
+
# serialize :address, coder: Address
|
|
118
|
+
# end
|
|
119
|
+
#
|
|
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
|
+
#
|
|
61
135
|
# ==== Examples
|
|
62
136
|
#
|
|
63
137
|
# ===== Serialize the +preferences+ attribute using YAML
|
|
64
138
|
#
|
|
65
139
|
# class User < ActiveRecord::Base
|
|
66
|
-
# serialize :preferences
|
|
140
|
+
# serialize :preferences, coder: YAML
|
|
67
141
|
# end
|
|
68
142
|
#
|
|
69
143
|
# ===== Serialize the +preferences+ attribute using JSON
|
|
70
144
|
#
|
|
71
145
|
# class User < ActiveRecord::Base
|
|
72
|
-
# serialize :preferences, JSON
|
|
146
|
+
# serialize :preferences, coder: JSON
|
|
73
147
|
# end
|
|
74
148
|
#
|
|
75
149
|
# ===== Serialize the +preferences+ +Hash+ using YAML
|
|
76
150
|
#
|
|
77
151
|
# class User < ActiveRecord::Base
|
|
78
|
-
# serialize :preferences, Hash
|
|
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] }
|
|
79
159
|
# end
|
|
80
160
|
#
|
|
81
161
|
# ===== Serialize the +preferences+ attribute using a custom coder
|
|
@@ -97,35 +177,54 @@ module ActiveRecord
|
|
|
97
177
|
# end
|
|
98
178
|
#
|
|
99
179
|
# class User < ActiveRecord::Base
|
|
100
|
-
# serialize :preferences, Rot13JSON
|
|
180
|
+
# serialize :preferences, coder: Rot13JSON
|
|
101
181
|
# end
|
|
102
182
|
#
|
|
103
|
-
def serialize(attr_name,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
else
|
|
112
|
-
Coders::YAMLColumn.new(attr_name, class_name_or_coder)
|
|
183
|
+
def serialize(attr_name, coder: nil, type: Object, yaml: {}, **options)
|
|
184
|
+
coder ||= default_column_serializer
|
|
185
|
+
unless coder
|
|
186
|
+
raise ArgumentError, <<~MSG.squish
|
|
187
|
+
missing keyword: :coder
|
|
188
|
+
|
|
189
|
+
If no default coder is configured, a coder must be provided to `serialize`.
|
|
190
|
+
MSG
|
|
113
191
|
end
|
|
114
192
|
|
|
115
|
-
|
|
116
|
-
|
|
193
|
+
column_serializer = build_column_serializer(attr_name, coder, type, yaml)
|
|
194
|
+
|
|
195
|
+
attribute(attr_name, **options)
|
|
196
|
+
|
|
197
|
+
decorate_attributes([attr_name]) do |attr_name, cast_type|
|
|
198
|
+
if type_incompatible_with_serialize?(cast_type, coder, type)
|
|
117
199
|
raise ColumnNotSerializableError.new(attr_name, cast_type)
|
|
118
200
|
end
|
|
119
201
|
|
|
120
202
|
cast_type = cast_type.subtype if Type::Serialized === cast_type
|
|
121
|
-
Type::Serialized.new(cast_type,
|
|
203
|
+
Type::Serialized.new(cast_type, column_serializer)
|
|
122
204
|
end
|
|
123
205
|
end
|
|
124
206
|
|
|
125
207
|
private
|
|
126
|
-
def
|
|
127
|
-
|
|
128
|
-
|
|
208
|
+
def build_column_serializer(attr_name, coder, type, yaml = nil)
|
|
209
|
+
# When ::JSON is used, force it to go through the Active Support JSON encoder
|
|
210
|
+
# to ensure special objects (e.g. Active Record models) are dumped correctly
|
|
211
|
+
# using the #as_json hook.
|
|
212
|
+
coder = Coders::JSON if coder == ::JSON
|
|
213
|
+
|
|
214
|
+
if coder == ::YAML || coder == Coders::YAMLColumn
|
|
215
|
+
Coders::YAMLColumn.new(attr_name, type, **(yaml || {}))
|
|
216
|
+
elsif coder.respond_to?(:new) && !coder.respond_to?(:load)
|
|
217
|
+
coder.new(attr_name, type)
|
|
218
|
+
elsif type && type != Object
|
|
219
|
+
Coders::ColumnSerializer.new(attr_name, coder, type)
|
|
220
|
+
else
|
|
221
|
+
coder
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def type_incompatible_with_serialize?(cast_type, coder, type)
|
|
226
|
+
cast_type.is_a?(ActiveRecord::Type::Json) && coder == ::JSON ||
|
|
227
|
+
cast_type.respond_to?(:type_cast_array, true) && type == ::Array
|
|
129
228
|
end
|
|
130
229
|
end
|
|
131
230
|
end
|
|
@@ -32,6 +32,10 @@ module ActiveRecord
|
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
def ==(other)
|
|
36
|
+
other.is_a?(self.class) && __getobj__ == other.__getobj__
|
|
37
|
+
end
|
|
38
|
+
|
|
35
39
|
private
|
|
36
40
|
def convert_time_to_time_zone(value)
|
|
37
41
|
return if value.nil?
|
|
@@ -69,14 +73,15 @@ module ActiveRecord
|
|
|
69
73
|
end
|
|
70
74
|
|
|
71
75
|
module ClassMethods # :nodoc:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
private
|
|
77
|
+
def hook_attribute_type(name, cast_type)
|
|
78
|
+
if create_time_zone_conversion_attribute?(name, cast_type)
|
|
79
|
+
cast_type = TimeZoneConverter.new(cast_type)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
super
|
|
75
83
|
end
|
|
76
|
-
super
|
|
77
|
-
end
|
|
78
84
|
|
|
79
|
-
private
|
|
80
85
|
def create_time_zone_conversion_attribute?(name, cast_type)
|
|
81
86
|
enabled_for_column = time_zone_aware_attributes &&
|
|
82
87
|
!skip_time_zone_conversion_for_attributes.include?(name.to_sym)
|
|
@@ -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
|
|
|
@@ -11,11 +12,11 @@ module ActiveRecord
|
|
|
11
12
|
|
|
12
13
|
module ClassMethods # :nodoc:
|
|
13
14
|
private
|
|
14
|
-
def define_method_attribute=(
|
|
15
|
+
def define_method_attribute=(canonical_name, owner:, as: canonical_name)
|
|
15
16
|
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
|
16
|
-
owner,
|
|
17
|
+
owner, canonical_name, writer: true,
|
|
17
18
|
) do |temp_method_name, attr_name_expr|
|
|
18
|
-
owner.define_cached_method("#{
|
|
19
|
+
owner.define_cached_method(temp_method_name, as: "#{as}=", namespace: :active_record) do |batch|
|
|
19
20
|
batch <<
|
|
20
21
|
"def #{temp_method_name}(value)" <<
|
|
21
22
|
" _write_attribute(#{attr_name_expr}, value)" <<
|
|
@@ -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
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "mutex_m"
|
|
4
3
|
require "active_support/core_ext/enumerable"
|
|
5
4
|
|
|
6
5
|
module ActiveRecord
|
|
@@ -21,10 +20,10 @@ module ActiveRecord
|
|
|
21
20
|
include Serialization
|
|
22
21
|
end
|
|
23
22
|
|
|
24
|
-
RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name
|
|
23
|
+
RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name superclass)
|
|
25
24
|
|
|
26
25
|
class GeneratedAttributeMethods < Module # :nodoc:
|
|
27
|
-
|
|
26
|
+
LOCK = Monitor.new
|
|
28
27
|
end
|
|
29
28
|
|
|
30
29
|
class << self
|
|
@@ -33,44 +32,119 @@ module ActiveRecord
|
|
|
33
32
|
Base.instance_methods +
|
|
34
33
|
Base.private_instance_methods -
|
|
35
34
|
Base.superclass.instance_methods -
|
|
36
|
-
Base.superclass.private_instance_methods
|
|
35
|
+
Base.superclass.private_instance_methods +
|
|
36
|
+
%i[__id__ dup freeze frozen? hash class clone]
|
|
37
37
|
).map { |m| -m.to_s }.to_set.freeze
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
module ClassMethods
|
|
42
|
-
def inherited(child_class) # :nodoc:
|
|
43
|
-
child_class.initialize_generated_modules
|
|
44
|
-
super
|
|
45
|
-
end
|
|
46
|
-
|
|
47
42
|
def initialize_generated_modules # :nodoc:
|
|
48
43
|
@generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethods.new)
|
|
49
44
|
private_constant :GeneratedAttributeMethods
|
|
50
45
|
@attribute_methods_generated = false
|
|
46
|
+
@alias_attributes_mass_generated = false
|
|
51
47
|
include @generated_attribute_methods
|
|
52
48
|
|
|
53
49
|
super
|
|
54
50
|
end
|
|
55
51
|
|
|
52
|
+
# Allows you to make aliases for attributes.
|
|
53
|
+
#
|
|
54
|
+
# class Person < ActiveRecord::Base
|
|
55
|
+
# alias_attribute :nickname, :name
|
|
56
|
+
# end
|
|
57
|
+
#
|
|
58
|
+
# person = Person.create(name: 'Bob')
|
|
59
|
+
# person.name # => "Bob"
|
|
60
|
+
# person.nickname # => "Bob"
|
|
61
|
+
#
|
|
62
|
+
# The alias can also be used for querying:
|
|
63
|
+
#
|
|
64
|
+
# Person.where(nickname: "Bob")
|
|
65
|
+
# # SELECT "people".* FROM "people" WHERE "people"."name" = "Bob"
|
|
66
|
+
def alias_attribute(new_name, old_name)
|
|
67
|
+
super
|
|
68
|
+
|
|
69
|
+
if @alias_attributes_mass_generated
|
|
70
|
+
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
|
|
71
|
+
generate_alias_attribute_methods(code_generator, new_name, old_name)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def eagerly_generate_alias_attribute_methods(_new_name, _old_name) # :nodoc:
|
|
77
|
+
# alias attributes in Active Record are lazily generated
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def generate_alias_attribute_methods(code_generator, new_name, old_name) # :nodoc:
|
|
81
|
+
attribute_method_patterns.each do |pattern|
|
|
82
|
+
alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
|
|
83
|
+
end
|
|
84
|
+
attribute_method_patterns_cache.clear
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) # :nodoc:
|
|
88
|
+
old_name = old_name.to_s
|
|
89
|
+
|
|
90
|
+
if !abstract_class? && !has_attribute?(old_name)
|
|
91
|
+
raise ArgumentError, "#{self.name} model aliases `#{old_name}`, but `#{old_name}` is not an attribute. " \
|
|
92
|
+
"Use `alias_method :#{new_name}, :#{old_name}` or define the method manually."
|
|
93
|
+
else
|
|
94
|
+
define_attribute_method_pattern(pattern, old_name, owner: code_generator, as: new_name, override: true)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def attribute_methods_generated? # :nodoc:
|
|
99
|
+
@attribute_methods_generated
|
|
100
|
+
end
|
|
101
|
+
|
|
56
102
|
# Generates all the attribute related methods for columns in the database
|
|
57
103
|
# accessors, mutators and query methods.
|
|
58
104
|
def define_attribute_methods # :nodoc:
|
|
59
105
|
return false if @attribute_methods_generated
|
|
60
106
|
# Use a mutex; we don't want two threads simultaneously trying to define
|
|
61
107
|
# attribute methods.
|
|
62
|
-
|
|
108
|
+
GeneratedAttributeMethods::LOCK.synchronize do
|
|
63
109
|
return false if @attribute_methods_generated
|
|
110
|
+
|
|
64
111
|
superclass.define_attribute_methods unless base_class?
|
|
65
|
-
|
|
112
|
+
|
|
113
|
+
unless abstract_class?
|
|
114
|
+
load_schema
|
|
115
|
+
super(attribute_names)
|
|
116
|
+
alias_attribute :id_value, :id if _has_attribute?("id") && !_has_attribute?("id_value")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
generate_alias_attributes
|
|
120
|
+
|
|
66
121
|
@attribute_methods_generated = true
|
|
67
122
|
end
|
|
123
|
+
|
|
124
|
+
true
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def generate_alias_attributes # :nodoc:
|
|
128
|
+
superclass.generate_alias_attributes unless superclass == Base
|
|
129
|
+
|
|
130
|
+
return if @alias_attributes_mass_generated
|
|
131
|
+
|
|
132
|
+
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
|
|
133
|
+
aliases_by_attribute_name.each do |old_name, new_names|
|
|
134
|
+
new_names.each do |new_name|
|
|
135
|
+
generate_alias_attribute_methods(code_generator, new_name, old_name)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
@alias_attributes_mass_generated = true
|
|
68
141
|
end
|
|
69
142
|
|
|
70
143
|
def undefine_attribute_methods # :nodoc:
|
|
71
|
-
|
|
72
|
-
super if
|
|
144
|
+
GeneratedAttributeMethods::LOCK.synchronize do
|
|
145
|
+
super if @attribute_methods_generated
|
|
73
146
|
@attribute_methods_generated = false
|
|
147
|
+
@alias_attributes_mass_generated = false
|
|
74
148
|
end
|
|
75
149
|
end
|
|
76
150
|
|
|
@@ -186,6 +260,16 @@ module ActiveRecord
|
|
|
186
260
|
def _has_attribute?(attr_name) # :nodoc:
|
|
187
261
|
attribute_types.key?(attr_name)
|
|
188
262
|
end
|
|
263
|
+
|
|
264
|
+
private
|
|
265
|
+
def inherited(child_class)
|
|
266
|
+
super
|
|
267
|
+
child_class.initialize_generated_modules
|
|
268
|
+
child_class.class_eval do
|
|
269
|
+
@alias_attributes_mass_generated = false
|
|
270
|
+
@attribute_names = nil
|
|
271
|
+
end
|
|
272
|
+
end
|
|
189
273
|
end
|
|
190
274
|
|
|
191
275
|
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
|
|
@@ -209,9 +293,7 @@ module ActiveRecord
|
|
|
209
293
|
|
|
210
294
|
# If the result is true then check for the select case.
|
|
211
295
|
# For queries selecting a subset of columns, return false for unselected columns.
|
|
212
|
-
|
|
213
|
-
# have been allocated but not yet initialized.
|
|
214
|
-
if defined?(@attributes)
|
|
296
|
+
if @attributes
|
|
215
297
|
if name = self.class.symbol_column_to_string(name.to_sym)
|
|
216
298
|
return _has_attribute?(name)
|
|
217
299
|
end
|
|
@@ -309,36 +391,40 @@ module ActiveRecord
|
|
|
309
391
|
!value.nil? && !(value.respond_to?(:empty?) && value.empty?)
|
|
310
392
|
end
|
|
311
393
|
|
|
312
|
-
# Returns the value of the attribute identified by
|
|
313
|
-
#
|
|
314
|
-
#
|
|
315
|
-
#
|
|
316
|
-
# Note: +:id+ is always present.
|
|
394
|
+
# Returns the value of the attribute identified by +attr_name+ after it has
|
|
395
|
+
# been type cast. (For information about specific type casting behavior, see
|
|
396
|
+
# the types under ActiveModel::Type.)
|
|
317
397
|
#
|
|
318
398
|
# class Person < ActiveRecord::Base
|
|
319
399
|
# belongs_to :organization
|
|
320
400
|
# end
|
|
321
401
|
#
|
|
322
|
-
# person = Person.new(name:
|
|
323
|
-
# person[:name]
|
|
324
|
-
# person[:
|
|
402
|
+
# person = Person.new(name: "Francesco", date_of_birth: "2004-12-12")
|
|
403
|
+
# person[:name] # => "Francesco"
|
|
404
|
+
# person[:date_of_birth] # => Date.new(2004, 12, 12)
|
|
405
|
+
# person[:organization_id] # => nil
|
|
406
|
+
#
|
|
407
|
+
# Raises ActiveModel::MissingAttributeError if the attribute is missing.
|
|
408
|
+
# Note, however, that the +id+ attribute will never be considered missing.
|
|
325
409
|
#
|
|
326
|
-
# person = Person.select(
|
|
327
|
-
# person[:name] # =>
|
|
328
|
-
# person[:
|
|
410
|
+
# person = Person.select(:name).first
|
|
411
|
+
# person[:name] # => "Francesco"
|
|
412
|
+
# person[:date_of_birth] # => ActiveModel::MissingAttributeError: missing attribute 'date_of_birth' for Person
|
|
413
|
+
# person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute 'organization_id' for Person
|
|
414
|
+
# person[:id] # => nil
|
|
329
415
|
def [](attr_name)
|
|
330
416
|
read_attribute(attr_name) { |n| missing_attribute(n, caller) }
|
|
331
417
|
end
|
|
332
418
|
|
|
333
|
-
# Updates the attribute identified by
|
|
419
|
+
# Updates the attribute identified by +attr_name+ using the specified
|
|
420
|
+
# +value+. The attribute value will be type cast upon being read.
|
|
334
421
|
#
|
|
335
422
|
# class Person < ActiveRecord::Base
|
|
336
423
|
# end
|
|
337
424
|
#
|
|
338
425
|
# person = Person.new
|
|
339
|
-
# person[:
|
|
340
|
-
# person[:
|
|
341
|
-
# person[:age].class # => Integer
|
|
426
|
+
# person[:date_of_birth] = "2004-12-12"
|
|
427
|
+
# person[:date_of_birth] # => Date.new(2004, 12, 12)
|
|
342
428
|
def []=(attr_name, value)
|
|
343
429
|
write_attribute(attr_name, value)
|
|
344
430
|
end
|
|
@@ -376,9 +462,42 @@ module ActiveRecord
|
|
|
376
462
|
end
|
|
377
463
|
|
|
378
464
|
private
|
|
465
|
+
def respond_to_missing?(name, include_private = false)
|
|
466
|
+
if self.class.define_attribute_methods
|
|
467
|
+
# Some methods weren't defined yet.
|
|
468
|
+
return true if self.class.method_defined?(name)
|
|
469
|
+
return true if include_private && self.class.private_method_defined?(name)
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
super
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def method_missing(name, ...)
|
|
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
|
|
487
|
+
end
|
|
488
|
+
|
|
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
|
|
497
|
+
end
|
|
498
|
+
|
|
379
499
|
def attribute_method?(attr_name)
|
|
380
|
-
|
|
381
|
-
defined?(@attributes) && @attributes.key?(attr_name)
|
|
500
|
+
@attributes&.key?(attr_name)
|
|
382
501
|
end
|
|
383
502
|
|
|
384
503
|
def attributes_with_values(attribute_names)
|
|
@@ -390,6 +509,7 @@ module ActiveRecord
|
|
|
390
509
|
attribute_names &= self.class.column_names
|
|
391
510
|
attribute_names.delete_if do |name|
|
|
392
511
|
self.class.readonly_attribute?(name) ||
|
|
512
|
+
self.class.counter_cache_column?(name) ||
|
|
393
513
|
column_for_attribute(name).virtual?
|
|
394
514
|
end
|
|
395
515
|
end
|