activerecord 7.0.0 → 7.2.1
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 +515 -1268
- data/MIT-LICENSE +1 -1
- data/README.rdoc +31 -31
- 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 +25 -19
- 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 +23 -8
- 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 +28 -17
- data/lib/active_record/associations/collection_proxy.rb +36 -13
- 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 +28 -18
- 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/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +18 -14
- 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 +2 -4
- 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 +378 -491
- data/lib/active_record/attribute_assignment.rb +1 -13
- 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 +153 -70
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +153 -40
- data/lib/active_record/attributes.rb +63 -48
- data/lib/active_record/autosave_association.rb +70 -38
- data/lib/active_record/base.rb +12 -8
- 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 +124 -132
- 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 +297 -88
- 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 +215 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
- 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 +319 -135
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
- 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 +27 -140
- data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
- 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 +45 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -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 +16 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- 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 +4 -2
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
- 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 +151 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -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 +61 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
- 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 +98 -106
- data/lib/active_record/core.rb +220 -177
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
- 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 +88 -35
- data/lib/active_record/delegated_type.rb +40 -11
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/disable_joins_association_relation.rb +1 -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 +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 +47 -25
- data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
- data/lib/active_record/encryption/encryptor.rb +25 -10
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
- 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_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 +4 -4
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +23 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +131 -27
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- 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 +169 -99
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +13 -10
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +39 -24
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +28 -27
- 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 +110 -13
- data/lib/active_record/migration/compatibility.rb +174 -64
- 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 +292 -125
- data/lib/active_record/model_schema.rb +113 -112
- data/lib/active_record/nested_attributes.rb +35 -9
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +177 -345
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +19 -25
- data/lib/active_record/query_logs.rb +102 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +34 -9
- data/lib/active_record/railtie.rb +153 -100
- data/lib/active_record/railties/controller_runtime.rb +24 -10
- data/lib/active_record/railties/databases.rake +148 -152
- 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 +278 -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 +293 -108
- data/lib/active_record/relation/delegation.rb +31 -20
- data/lib/active_record/relation/finder_methods.rb +93 -18
- 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 +38 -4
- 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 +25 -1
- data/lib/active_record/relation/query_methods.rb +625 -107
- 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 +7 -19
- data/lib/active_record/relation.rb +602 -96
- data/lib/active_record/result.rb +55 -52
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +76 -30
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +82 -30
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +20 -12
- 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/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +29 -8
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +191 -121
- 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 +16 -7
- data/lib/active_record/test_fixtures.rb +174 -152
- 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 +109 -27
- data/lib/active_record/translation.rb +1 -3
- 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 +9 -7
- 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 +12 -6
- 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 +63 -14
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +266 -30
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- 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/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +1 -1
- 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/filter.rb +1 -1
- 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} +9 -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.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +17 -5
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/to_sql.rb +112 -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 +59 -17
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
|
@@ -6,6 +6,12 @@ module ActiveRecord
|
|
|
6
6
|
module Encryption
|
|
7
7
|
# Utility for generating and deriving random keys.
|
|
8
8
|
class KeyGenerator
|
|
9
|
+
attr_reader :hash_digest_class
|
|
10
|
+
|
|
11
|
+
def initialize(hash_digest_class: ActiveRecord::Encryption.config.hash_digest_class)
|
|
12
|
+
@hash_digest_class = hash_digest_class
|
|
13
|
+
end
|
|
14
|
+
|
|
9
15
|
# Returns a random key. The key will have a size in bytes of +:length+ (configured +Cipher+'s length by default)
|
|
10
16
|
def generate_random_key(length: key_length)
|
|
11
17
|
SecureRandom.random_bytes(length)
|
|
@@ -30,10 +36,15 @@ module ActiveRecord
|
|
|
30
36
|
#
|
|
31
37
|
# The generated key will be salted with the value of +ActiveRecord::Encryption.key_derivation_salt+
|
|
32
38
|
def derive_key_from(password, length: key_length)
|
|
33
|
-
ActiveSupport::KeyGenerator.new(password
|
|
39
|
+
ActiveSupport::KeyGenerator.new(password, hash_digest_class: hash_digest_class)
|
|
40
|
+
.generate_key(key_derivation_salt, length)
|
|
34
41
|
end
|
|
35
42
|
|
|
36
43
|
private
|
|
44
|
+
def key_derivation_salt
|
|
45
|
+
@key_derivation_salt ||= ActiveRecord::Encryption.config.key_derivation_salt
|
|
46
|
+
end
|
|
47
|
+
|
|
37
48
|
def key_length
|
|
38
49
|
@key_length ||= ActiveRecord::Encryption.cipher.key_length
|
|
39
50
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/message_pack"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
module Encryption
|
|
7
|
+
# A message serializer that serializes +Messages+ with MessagePack.
|
|
8
|
+
#
|
|
9
|
+
# The message is converted to a hash with this structure:
|
|
10
|
+
#
|
|
11
|
+
# {
|
|
12
|
+
# p: <payload>,
|
|
13
|
+
# h: {
|
|
14
|
+
# header1: value1,
|
|
15
|
+
# header2: value2,
|
|
16
|
+
# ...
|
|
17
|
+
# }
|
|
18
|
+
# }
|
|
19
|
+
#
|
|
20
|
+
# Then it is converted to the MessagePack format.
|
|
21
|
+
class MessagePackMessageSerializer
|
|
22
|
+
def dump(message)
|
|
23
|
+
raise Errors::ForbiddenClass unless message.is_a?(Message)
|
|
24
|
+
ActiveSupport::MessagePack.dump(message_to_hash(message))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def load(serialized_content)
|
|
28
|
+
data = ActiveSupport::MessagePack.load(serialized_content)
|
|
29
|
+
hash_to_message(data, 1)
|
|
30
|
+
rescue RuntimeError
|
|
31
|
+
raise Errors::Decryption
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def binary?
|
|
35
|
+
true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
def message_to_hash(message)
|
|
40
|
+
{
|
|
41
|
+
"p" => message.payload,
|
|
42
|
+
"h" => headers_to_hash(message.headers)
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def headers_to_hash(headers)
|
|
47
|
+
headers.transform_values do |value|
|
|
48
|
+
value.is_a?(Message) ? message_to_hash(value) : value
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def hash_to_message(data, level)
|
|
53
|
+
validate_message_data_format(data, level)
|
|
54
|
+
Message.new(payload: data["p"], headers: parse_properties(data["h"], level))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def validate_message_data_format(data, level)
|
|
58
|
+
if level > 2
|
|
59
|
+
raise Errors::Decryption, "More than one level of hash nesting in headers is not supported"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
unless data.is_a?(Hash) && data.has_key?("p")
|
|
63
|
+
raise Errors::Decryption, "Invalid data format: hash without payload"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def parse_properties(headers, level)
|
|
68
|
+
Properties.new.tap do |properties|
|
|
69
|
+
headers&.each do |key, value|
|
|
70
|
+
properties[key] = value.is_a?(Hash) ? hash_to_message(value, level + 1) : value
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "base64"
|
|
4
|
+
|
|
3
5
|
module ActiveRecord
|
|
4
6
|
module Encryption
|
|
5
7
|
# A message serializer that serializes +Messages+ with JSON.
|
|
@@ -31,6 +33,10 @@ module ActiveRecord
|
|
|
31
33
|
JSON.dump message_to_json(message)
|
|
32
34
|
end
|
|
33
35
|
|
|
36
|
+
def binary?
|
|
37
|
+
false
|
|
38
|
+
end
|
|
39
|
+
|
|
34
40
|
private
|
|
35
41
|
def parse_message(data, level)
|
|
36
42
|
validate_message_data_format(data, level)
|
|
@@ -12,12 +12,12 @@ module ActiveRecord
|
|
|
12
12
|
#
|
|
13
13
|
# message.headers.encrypted_data_key # instead of message.headers[:k]
|
|
14
14
|
#
|
|
15
|
-
# See +Properties
|
|
15
|
+
# See +Properties::DEFAULT_PROPERTIES+, Key, Message
|
|
16
16
|
class Properties
|
|
17
|
-
ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, TrueClass, FalseClass, Symbol, NilClass]
|
|
17
|
+
ALLOWED_VALUE_CLASSES = [String, ActiveRecord::Encryption::Message, Numeric, Integer, Float, BigDecimal, TrueClass, FalseClass, Symbol, NilClass]
|
|
18
18
|
|
|
19
19
|
delegate_missing_to :data
|
|
20
|
-
delegate :==, to: :data
|
|
20
|
+
delegate :==, :[], :each, :key?, to: :data
|
|
21
21
|
|
|
22
22
|
# For each entry it generates an accessor exposing the full name
|
|
23
23
|
DEFAULT_PROPERTIES = {
|
|
@@ -54,7 +54,7 @@ module ActiveRecord
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def validate_value_type(value)
|
|
57
|
-
unless ALLOWED_VALUE_CLASSES.
|
|
57
|
+
unless ALLOWED_VALUE_CLASSES.include?(value.class) || ALLOWED_VALUE_CLASSES.any? { |klass| value.is_a?(klass) }
|
|
58
58
|
raise ActiveRecord::Encryption::Errors::ForbiddenClass, "Can't store a #{value.class}, only properties of type #{ALLOWED_VALUE_CLASSES.inspect} are allowed"
|
|
59
59
|
end
|
|
60
60
|
end
|
|
@@ -6,11 +6,11 @@ module ActiveRecord
|
|
|
6
6
|
#
|
|
7
7
|
# It validates and serves attribute encryption options.
|
|
8
8
|
#
|
|
9
|
-
# See
|
|
9
|
+
# See EncryptedAttributeType, Context
|
|
10
10
|
class Scheme
|
|
11
11
|
attr_accessor :previous_schemes
|
|
12
12
|
|
|
13
|
-
def initialize(key_provider: nil, key: nil, deterministic: nil, downcase: nil, ignore_case: nil,
|
|
13
|
+
def initialize(key_provider: nil, key: nil, deterministic: nil, support_unencrypted_data: nil, downcase: nil, ignore_case: nil,
|
|
14
14
|
previous_schemes: nil, **context_properties)
|
|
15
15
|
# Initializing all attributes to +nil+ as we want to allow a "not set" semantics so that we
|
|
16
16
|
# can merge schemes without overriding values with defaults. See +#merge+
|
|
@@ -18,6 +18,7 @@ module ActiveRecord
|
|
|
18
18
|
@key_provider_param = key_provider
|
|
19
19
|
@key = key
|
|
20
20
|
@deterministic = deterministic
|
|
21
|
+
@support_unencrypted_data = support_unencrypted_data
|
|
21
22
|
@downcase = downcase || ignore_case
|
|
22
23
|
@ignore_case = ignore_case
|
|
23
24
|
@previous_schemes_param = previous_schemes
|
|
@@ -36,7 +37,11 @@ module ActiveRecord
|
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
def deterministic?
|
|
39
|
-
|
|
40
|
+
!!@deterministic
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def support_unencrypted_data?
|
|
44
|
+
@support_unencrypted_data.nil? ? ActiveRecord::Encryption.config.support_unencrypted_data : @support_unencrypted_data
|
|
40
45
|
end
|
|
41
46
|
|
|
42
47
|
def fixed?
|
|
@@ -45,10 +50,7 @@ module ActiveRecord
|
|
|
45
50
|
end
|
|
46
51
|
|
|
47
52
|
def key_provider
|
|
48
|
-
@
|
|
49
|
-
validate_keys!
|
|
50
|
-
@key_provider_param || build_key_provider
|
|
51
|
-
end
|
|
53
|
+
@key_provider_param || key_provider_from_key || deterministic_key_provider || default_key_provider
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
def merge(other_scheme)
|
|
@@ -56,7 +58,7 @@ module ActiveRecord
|
|
|
56
58
|
end
|
|
57
59
|
|
|
58
60
|
def to_h
|
|
59
|
-
{ key_provider: @key_provider_param,
|
|
61
|
+
{ key_provider: @key_provider_param, deterministic: @deterministic, downcase: @downcase, ignore_case: @ignore_case,
|
|
60
62
|
previous_schemes: @previous_schemes_param, **@context_properties }.compact
|
|
61
63
|
end
|
|
62
64
|
|
|
@@ -68,31 +70,30 @@ module ActiveRecord
|
|
|
68
70
|
end
|
|
69
71
|
end
|
|
70
72
|
|
|
73
|
+
def compatible_with?(other_scheme)
|
|
74
|
+
deterministic? == other_scheme.deterministic?
|
|
75
|
+
end
|
|
76
|
+
|
|
71
77
|
private
|
|
72
78
|
def validate_config!
|
|
73
79
|
raise Errors::Configuration, "ignore_case: can only be used with deterministic encryption" if @ignore_case && !@deterministic
|
|
74
80
|
raise Errors::Configuration, "key_provider: and key: can't be used simultaneously" if @key_provider_param && @key
|
|
75
81
|
end
|
|
76
82
|
|
|
77
|
-
def
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
def key_provider_from_key
|
|
84
|
+
@key_provider_from_key ||= if @key.present?
|
|
85
|
+
DerivedSecretKeyProvider.new(@key)
|
|
86
|
+
end
|
|
81
87
|
end
|
|
82
88
|
|
|
83
|
-
def
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"active_record_encryption.#{key} or by setting config.active_record.encryption.#{key}"
|
|
89
|
+
def deterministic_key_provider
|
|
90
|
+
@deterministic_key_provider ||= if @deterministic
|
|
91
|
+
DeterministicKeyProvider.new(ActiveRecord::Encryption.config.deterministic_key)
|
|
87
92
|
end
|
|
88
93
|
end
|
|
89
94
|
|
|
90
|
-
def
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if @deterministic && (deterministic_key = ActiveRecord::Encryption.config.deterministic_key)
|
|
94
|
-
DeterministicKeyProvider.new(deterministic_key)
|
|
95
|
-
end
|
|
95
|
+
def default_key_provider
|
|
96
|
+
ActiveRecord::Encryption.key_provider
|
|
96
97
|
end
|
|
97
98
|
end
|
|
98
99
|
end
|
data/lib/active_record/enum.rb
CHANGED
|
@@ -83,7 +83,7 @@ module ActiveRecord
|
|
|
83
83
|
#
|
|
84
84
|
# In rare circumstances you might need to access the mapping directly.
|
|
85
85
|
# The mappings are exposed through a class method with the pluralized attribute
|
|
86
|
-
# name, which return the mapping in a
|
|
86
|
+
# name, which return the mapping in a ActiveSupport::HashWithIndifferentAccess :
|
|
87
87
|
#
|
|
88
88
|
# Conversation.statuses[:active] # => 0
|
|
89
89
|
# Conversation.statuses["archived"] # => 1
|
|
@@ -111,23 +111,79 @@ module ActiveRecord
|
|
|
111
111
|
#
|
|
112
112
|
# conversation.comments_inactive!
|
|
113
113
|
# conversation.comments_active? # => false
|
|
114
|
+
#
|
|
115
|
+
# If you want to disable the auto-generated methods on the model, you can do
|
|
116
|
+
# so by setting the +:instance_methods+ option to false:
|
|
117
|
+
#
|
|
118
|
+
# class Conversation < ActiveRecord::Base
|
|
119
|
+
# enum :status, [ :active, :archived ], instance_methods: false
|
|
120
|
+
# end
|
|
121
|
+
#
|
|
122
|
+
# If you want the enum value to be validated before saving, use the option +:validate+:
|
|
123
|
+
#
|
|
124
|
+
# class Conversation < ActiveRecord::Base
|
|
125
|
+
# enum :status, [ :active, :archived ], validate: true
|
|
126
|
+
# end
|
|
127
|
+
#
|
|
128
|
+
# conversation = Conversation.new
|
|
129
|
+
#
|
|
130
|
+
# conversation.status = :unknown
|
|
131
|
+
# conversation.valid? # => false
|
|
132
|
+
#
|
|
133
|
+
# conversation.status = nil
|
|
134
|
+
# conversation.valid? # => false
|
|
135
|
+
#
|
|
136
|
+
# conversation.status = :active
|
|
137
|
+
# conversation.valid? # => true
|
|
138
|
+
#
|
|
139
|
+
# It is also possible to pass additional validation options:
|
|
140
|
+
#
|
|
141
|
+
# class Conversation < ActiveRecord::Base
|
|
142
|
+
# enum :status, [ :active, :archived ], validate: { allow_nil: true }
|
|
143
|
+
# end
|
|
144
|
+
#
|
|
145
|
+
# conversation = Conversation.new
|
|
146
|
+
#
|
|
147
|
+
# conversation.status = :unknown
|
|
148
|
+
# conversation.valid? # => false
|
|
149
|
+
#
|
|
150
|
+
# conversation.status = nil
|
|
151
|
+
# conversation.valid? # => true
|
|
152
|
+
#
|
|
153
|
+
# conversation.status = :active
|
|
154
|
+
# conversation.valid? # => true
|
|
155
|
+
#
|
|
156
|
+
# Otherwise +ArgumentError+ will raise:
|
|
157
|
+
#
|
|
158
|
+
# class Conversation < ActiveRecord::Base
|
|
159
|
+
# enum :status, [ :active, :archived ]
|
|
160
|
+
# end
|
|
161
|
+
#
|
|
162
|
+
# conversation = Conversation.new
|
|
163
|
+
#
|
|
164
|
+
# conversation.status = :unknown # 'unknown' is not a valid status (ArgumentError)
|
|
114
165
|
module Enum
|
|
115
166
|
def self.extended(base) # :nodoc:
|
|
116
167
|
base.class_attribute(:defined_enums, instance_writer: false, default: {})
|
|
117
168
|
end
|
|
118
169
|
|
|
119
|
-
def
|
|
120
|
-
|
|
121
|
-
|
|
170
|
+
def load_schema! # :nodoc:
|
|
171
|
+
defined_enums.each_key do |name|
|
|
172
|
+
unless columns_hash.key?(resolve_attribute_name(name))
|
|
173
|
+
raise "Unknown enum attribute '#{name}' for #{self.name}. Enums must be" \
|
|
174
|
+
" backed by a database column."
|
|
175
|
+
end
|
|
176
|
+
end
|
|
122
177
|
end
|
|
123
178
|
|
|
124
179
|
class EnumType < Type::Value # :nodoc:
|
|
125
180
|
delegate :type, to: :subtype
|
|
126
181
|
|
|
127
|
-
def initialize(name, mapping, subtype)
|
|
182
|
+
def initialize(name, mapping, subtype, raise_on_invalid_values: true)
|
|
128
183
|
@name = name
|
|
129
184
|
@mapping = mapping
|
|
130
185
|
@subtype = subtype
|
|
186
|
+
@_raise_on_invalid_values = raise_on_invalid_values
|
|
131
187
|
end
|
|
132
188
|
|
|
133
189
|
def cast(value)
|
|
@@ -153,6 +209,8 @@ module ActiveRecord
|
|
|
153
209
|
end
|
|
154
210
|
|
|
155
211
|
def assert_valid_value(value)
|
|
212
|
+
return unless @_raise_on_invalid_values
|
|
213
|
+
|
|
156
214
|
unless value.blank? || mapping.has_key?(value) || mapping.has_value?(value)
|
|
157
215
|
raise ArgumentError, "'#{value}' is not a valid #{name}"
|
|
158
216
|
end
|
|
@@ -170,15 +228,28 @@ module ActiveRecord
|
|
|
170
228
|
return _enum(name, values, **options)
|
|
171
229
|
end
|
|
172
230
|
|
|
173
|
-
definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default)
|
|
231
|
+
definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default, :_instance_methods)
|
|
174
232
|
options.transform_keys! { |key| :"#{key[1..-1]}" }
|
|
175
233
|
|
|
176
234
|
definitions.each { |name, values| _enum(name, values, **options) }
|
|
235
|
+
|
|
236
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
|
237
|
+
Defining enums with keyword arguments is deprecated and will be removed
|
|
238
|
+
in Rails 8.0. Positional arguments should be used instead:
|
|
239
|
+
|
|
240
|
+
#{definitions.map { |name, values| "enum :#{name}, #{values}" }.join("\n")}
|
|
241
|
+
MSG
|
|
177
242
|
end
|
|
178
243
|
|
|
179
244
|
private
|
|
180
|
-
def
|
|
245
|
+
def inherited(base)
|
|
246
|
+
base.defined_enums = defined_enums.deep_dup
|
|
247
|
+
super
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options)
|
|
181
251
|
assert_valid_enum_definition_values(values)
|
|
252
|
+
assert_valid_enum_options(options)
|
|
182
253
|
# statuses = { }
|
|
183
254
|
enum_values = ActiveSupport::HashWithIndifferentAccess.new
|
|
184
255
|
name = name.to_s
|
|
@@ -191,9 +262,11 @@ module ActiveRecord
|
|
|
191
262
|
detect_enum_conflict!(name, name)
|
|
192
263
|
detect_enum_conflict!(name, "#{name}=")
|
|
193
264
|
|
|
194
|
-
attribute(name, **options)
|
|
265
|
+
attribute(name, **options)
|
|
266
|
+
|
|
267
|
+
decorate_attributes([name]) do |name, subtype|
|
|
195
268
|
subtype = subtype.subtype if EnumType === subtype
|
|
196
|
-
EnumType.new(name, enum_values, subtype)
|
|
269
|
+
EnumType.new(name, enum_values, subtype, raise_on_invalid_values: !validate)
|
|
197
270
|
end
|
|
198
271
|
|
|
199
272
|
value_method_names = []
|
|
@@ -213,18 +286,24 @@ module ActiveRecord
|
|
|
213
286
|
|
|
214
287
|
value_method_name = "#{prefix}#{label}#{suffix}"
|
|
215
288
|
value_method_names << value_method_name
|
|
216
|
-
define_enum_methods(name, value_method_name, value, scopes)
|
|
289
|
+
define_enum_methods(name, value_method_name, value, scopes, instance_methods)
|
|
217
290
|
|
|
218
291
|
method_friendly_label = label.gsub(/[\W&&[:ascii:]]+/, "_")
|
|
219
292
|
value_method_alias = "#{prefix}#{method_friendly_label}#{suffix}"
|
|
220
293
|
|
|
221
294
|
if value_method_alias != value_method_name && !value_method_names.include?(value_method_alias)
|
|
222
295
|
value_method_names << value_method_alias
|
|
223
|
-
define_enum_methods(name, value_method_alias, value, scopes)
|
|
296
|
+
define_enum_methods(name, value_method_alias, value, scopes, instance_methods)
|
|
224
297
|
end
|
|
225
298
|
end
|
|
226
299
|
end
|
|
227
300
|
detect_negative_enum_conditions!(value_method_names) if scopes
|
|
301
|
+
|
|
302
|
+
if validate
|
|
303
|
+
validate = {} unless Hash === validate
|
|
304
|
+
validates_inclusion_of name, in: enum_values.keys, **validate
|
|
305
|
+
end
|
|
306
|
+
|
|
228
307
|
enum_values.freeze
|
|
229
308
|
end
|
|
230
309
|
|
|
@@ -236,21 +315,23 @@ module ActiveRecord
|
|
|
236
315
|
private
|
|
237
316
|
attr_reader :klass
|
|
238
317
|
|
|
239
|
-
def define_enum_methods(name, value_method_name, value, scopes)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
318
|
+
def define_enum_methods(name, value_method_name, value, scopes, instance_methods)
|
|
319
|
+
if instance_methods
|
|
320
|
+
# def active?() status_for_database == 0 end
|
|
321
|
+
klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
|
|
322
|
+
define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
|
|
243
323
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
324
|
+
# def active!() update!(status: 0) end
|
|
325
|
+
klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
|
|
326
|
+
define_method("#{value_method_name}!") { update!(name => value) }
|
|
327
|
+
end
|
|
247
328
|
|
|
248
|
-
# scope :active, -> { where(status: 0) }
|
|
249
|
-
# scope :not_active, -> { where.not(status: 0) }
|
|
250
329
|
if scopes
|
|
330
|
+
# scope :active, -> { where(status: 0) }
|
|
251
331
|
klass.send(:detect_enum_conflict!, name, value_method_name, true)
|
|
252
332
|
klass.scope value_method_name, -> { where(name => value) }
|
|
253
333
|
|
|
334
|
+
# scope :not_active, -> { where.not(status: 0) }
|
|
254
335
|
klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
|
|
255
336
|
klass.scope "not_#{value_method_name}", -> { where.not(name => value) }
|
|
256
337
|
end
|
|
@@ -267,15 +348,36 @@ module ActiveRecord
|
|
|
267
348
|
end
|
|
268
349
|
|
|
269
350
|
def assert_valid_enum_definition_values(values)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
351
|
+
case values
|
|
352
|
+
when Hash
|
|
353
|
+
if values.empty?
|
|
354
|
+
raise ArgumentError, "Enum values #{values} must not be empty."
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
if values.keys.any?(&:blank?)
|
|
358
|
+
raise ArgumentError, "Enum values #{values} must not contain a blank name."
|
|
359
|
+
end
|
|
360
|
+
when Array
|
|
361
|
+
if values.empty?
|
|
362
|
+
raise ArgumentError, "Enum values #{values} must not be empty."
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
unless values.all?(Symbol) || values.all?(String)
|
|
366
|
+
raise ArgumentError, "Enum values #{values} must only contain symbols or strings."
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
if values.any?(&:blank?)
|
|
370
|
+
raise ArgumentError, "Enum values #{values} must not contain a blank name."
|
|
371
|
+
end
|
|
372
|
+
else
|
|
373
|
+
raise ArgumentError, "Enum values #{values} must be either a non-empty hash or an array."
|
|
275
374
|
end
|
|
375
|
+
end
|
|
276
376
|
|
|
277
|
-
|
|
278
|
-
|
|
377
|
+
def assert_valid_enum_options(options)
|
|
378
|
+
invalid_keys = options.keys & %i[_prefix _suffix _scopes _default _instance_methods]
|
|
379
|
+
unless invalid_keys.empty?
|
|
380
|
+
raise ArgumentError, "invalid option(s): #{invalid_keys.map(&:inspect).join(", ")}. Valid options are: :prefix, :suffix, :scopes, :default, :instance_methods, and :validate."
|
|
279
381
|
end
|
|
280
382
|
end
|
|
281
383
|
|
|
@@ -290,6 +392,8 @@ module ActiveRecord
|
|
|
290
392
|
raise_conflict_error(enum_name, method_name, type: "class")
|
|
291
393
|
elsif klass_method && method_defined_within?(method_name, Relation)
|
|
292
394
|
raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name)
|
|
395
|
+
elsif klass_method && method_name.to_sym == :id
|
|
396
|
+
raise_conflict_error(enum_name, method_name)
|
|
293
397
|
elsif !klass_method && dangerous_attribute_method?(method_name)
|
|
294
398
|
raise_conflict_error(enum_name, method_name)
|
|
295
399
|
elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
|