activerecord 7.1.5.2 → 8.1.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 +567 -2443
- data/README.rdoc +16 -16
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +3 -2
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +31 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +18 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- 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 +33 -5
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +36 -5
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +268 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +4 -3
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +8 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +250 -315
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +25 -61
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +20 -27
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +96 -68
- data/lib/active_record/autosave_association.rb +93 -58
- data/lib/active_record/base.rb +5 -7
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
- data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
- data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +66 -0
- data/lib/active_record/connection_handling.rb +88 -46
- data/lib/active_record/core.rb +142 -58
- data/lib/active_record/counter_cache.rb +52 -18
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +28 -5
- data/lib/active_record/database_configurations/hash_config.rb +103 -41
- data/lib/active_record/database_configurations/url_config.rb +30 -1
- data/lib/active_record/database_configurations.rb +8 -4
- data/lib/active_record/delegated_type.rb +43 -19
- data/lib/active_record/dynamic_matchers.rb +56 -71
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +12 -12
- data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
- data/lib/active_record/encryption/encryptor.rb +58 -22
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- 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 +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +57 -36
- data/lib/active_record/errors.rb +76 -26
- data/lib/active_record/explain.rb +14 -25
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +39 -35
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +5 -3
- data/lib/active_record/insert_all.rb +28 -20
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +15 -7
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -34
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +46 -14
- data/lib/active_record/migration/compatibility.rb +40 -25
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +119 -103
- data/lib/active_record/model_schema.rb +66 -78
- data/lib/active_record/nested_attributes.rb +17 -9
- data/lib/active_record/persistence.rb +161 -454
- data/lib/active_record/query_cache.rb +33 -15
- data/lib/active_record/query_logs.rb +106 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +28 -16
- data/lib/active_record/railtie.rb +61 -74
- data/lib/active_record/railties/controller_runtime.rb +19 -5
- data/lib/active_record/railties/databases.rake +64 -78
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +147 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +153 -73
- data/lib/active_record/relation/calculations.rb +146 -96
- data/lib/active_record/relation/delegation.rb +30 -23
- data/lib/active_record/relation/finder_methods.rb +64 -34
- data/lib/active_record/relation/merger.rb +12 -14
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +25 -10
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +351 -124
- data/lib/active_record/relation/spawn_methods.rb +9 -25
- data/lib/active_record/relation/where_clause.rb +17 -30
- data/lib/active_record/relation.rb +590 -124
- data/lib/active_record/result.rb +131 -59
- data/lib/active_record/runtime_registry.rb +44 -21
- data/lib/active_record/sanitization.rb +33 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +66 -31
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +68 -20
- data/lib/active_record/statement_cache.rb +29 -25
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +7 -33
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +118 -90
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +123 -89
- data/lib/active_record/testing/query_assertions.rb +127 -0
- data/lib/active_record/timestamp.rb +6 -4
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +129 -0
- data/lib/active_record/transactions.rb +97 -18
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +12 -7
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +5 -4
- data/lib/active_record/validations/associated.rb +10 -4
- data/lib/active_record/validations/uniqueness.rb +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +203 -59
- data/lib/arel/alias_predication.rb +3 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +6 -5
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +2 -4
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +14 -5
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -3
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +56 -12
- data/lib/arel/visitors/sqlite.rb +80 -8
- data/lib/arel/visitors/to_sql.rb +34 -37
- data/lib/arel.rb +10 -4
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +23 -14
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -167
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
|
@@ -16,7 +16,7 @@ module ActiveRecord
|
|
|
16
16
|
class_methods do
|
|
17
17
|
# Encrypts the +name+ attribute.
|
|
18
18
|
#
|
|
19
|
-
#
|
|
19
|
+
# ==== Options
|
|
20
20
|
#
|
|
21
21
|
# * <tt>:key_provider</tt> - A key provider to provide encryption and decryption keys. Defaults to
|
|
22
22
|
# +ActiveRecord::Encryption.key_provider+.
|
|
@@ -30,10 +30,10 @@ module ActiveRecord
|
|
|
30
30
|
# will use the oldest encryption scheme to encrypt new data by default. You can change this by setting
|
|
31
31
|
# <tt>deterministic: { fixed: false }</tt>. That will make it use the newest encryption scheme for encrypting new
|
|
32
32
|
# data.
|
|
33
|
-
# * <tt>:support_unencrypted_data</tt> -
|
|
34
|
-
#
|
|
35
|
-
# scenarios where you encrypt one column, and want to disable support for unencrypted data
|
|
36
|
-
# the global setting.
|
|
33
|
+
# * <tt>:support_unencrypted_data</tt> - When true, unencrypted data can be read normally. When false, it will raise errors.
|
|
34
|
+
# Falls back to +config.active_record.encryption.support_unencrypted_data+ if no value is provided.
|
|
35
|
+
# This is useful for scenarios where you encrypt one column, and want to disable support for unencrypted data
|
|
36
|
+
# without having to tweak the global setting.
|
|
37
37
|
# * <tt>:downcase</tt> - When true, it converts the encrypted content to downcase automatically. This allows to
|
|
38
38
|
# effectively ignore case when querying data. Notice that the case is lost. Use +:ignore_case+ if you are interested
|
|
39
39
|
# in preserving it.
|
|
@@ -46,11 +46,11 @@ module ActiveRecord
|
|
|
46
46
|
# * <tt>:previous</tt> - List of previous encryption schemes. When provided, they will be used in order when trying to read
|
|
47
47
|
# the attribute. Each entry of the list can contain the properties supported by #encrypts. Also, when deterministic
|
|
48
48
|
# encryption is used, they will be used to generate additional ciphertexts to check in the queries.
|
|
49
|
-
def encrypts(*names, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
|
|
49
|
+
def encrypts(*names, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], compress: true, compressor: nil, **context_properties)
|
|
50
50
|
self.encrypted_attributes ||= Set.new # not using :default because the instance would be shared across classes
|
|
51
51
|
|
|
52
52
|
names.each do |name|
|
|
53
|
-
encrypt_attribute name, key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, downcase: downcase, ignore_case: ignore_case, previous: previous, **context_properties
|
|
53
|
+
encrypt_attribute name, key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, downcase: downcase, ignore_case: ignore_case, previous: previous, compress: compress, compressor: compressor, **context_properties
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
@@ -81,12 +81,12 @@ module ActiveRecord
|
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
-
def encrypt_attribute(name, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
|
|
84
|
+
def encrypt_attribute(name, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], compress: true, compressor: nil, **context_properties)
|
|
85
85
|
encrypted_attributes << name.to_sym
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
decorate_attributes([name]) do |name, cast_type|
|
|
88
88
|
scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, \
|
|
89
|
-
downcase: downcase, ignore_case: ignore_case, previous: previous, **context_properties
|
|
89
|
+
downcase: downcase, ignore_case: ignore_case, previous: previous, compress: compress, compressor: compressor, **context_properties
|
|
90
90
|
|
|
91
91
|
ActiveRecord::Encryption::EncryptedAttributeType.new(scheme: scheme, cast_type: cast_type, default: columns_hash[name.to_s]&.default)
|
|
92
92
|
end
|
|
@@ -123,7 +123,7 @@ module ActiveRecord
|
|
|
123
123
|
end)
|
|
124
124
|
end
|
|
125
125
|
|
|
126
|
-
def load_schema!
|
|
126
|
+
def load_schema! # :nodoc:
|
|
127
127
|
super
|
|
128
128
|
|
|
129
129
|
add_length_validation_for_encrypted_columns if ActiveRecord::Encryption.config.validate_column_size
|
|
@@ -221,7 +221,7 @@ module ActiveRecord
|
|
|
221
221
|
end
|
|
222
222
|
|
|
223
223
|
def cant_modify_encrypted_attributes_when_frozen
|
|
224
|
-
self.class
|
|
224
|
+
self.class.encrypted_attributes.each do |attribute|
|
|
225
225
|
errors.add(attribute.to_sym, "can't be modified because it is encrypted") if changed_attributes.include?(attribute)
|
|
226
226
|
end
|
|
227
227
|
end
|
|
@@ -7,15 +7,15 @@ module ActiveRecord
|
|
|
7
7
|
# This is the central piece that connects the encryption system with +encrypts+ declarations in the
|
|
8
8
|
# model classes. Whenever you declare an attribute as encrypted, it configures an +EncryptedAttributeType+
|
|
9
9
|
# for that attribute.
|
|
10
|
-
class EncryptedAttributeType < ::
|
|
10
|
+
class EncryptedAttributeType < ::ActiveModel::Type::Value
|
|
11
11
|
include ActiveModel::Type::Helpers::Mutable
|
|
12
12
|
|
|
13
13
|
attr_reader :scheme, :cast_type
|
|
14
14
|
|
|
15
15
|
delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme
|
|
16
|
-
delegate :accessor, to: :cast_type
|
|
16
|
+
delegate :accessor, :type, to: :cast_type
|
|
17
17
|
|
|
18
|
-
#
|
|
18
|
+
# ==== Options
|
|
19
19
|
#
|
|
20
20
|
# * <tt>:scheme</tt> - A +Scheme+ with the encryption properties for this attribute.
|
|
21
21
|
# * <tt>:cast_type</tt> - A type that will be used to serialize (before encrypting) and deserialize
|
|
@@ -59,7 +59,7 @@ module ActiveRecord
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def support_unencrypted_data?
|
|
62
|
-
|
|
62
|
+
scheme.support_unencrypted_data? && !previous_type?
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
private
|
|
@@ -81,7 +81,7 @@ module ActiveRecord
|
|
|
81
81
|
@previous_type
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
-
def
|
|
84
|
+
def decrypt_as_text(value)
|
|
85
85
|
with_context do
|
|
86
86
|
unless value.nil?
|
|
87
87
|
if @default && @default == value
|
|
@@ -99,6 +99,10 @@ module ActiveRecord
|
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
+
def decrypt(value)
|
|
103
|
+
text_to_database_type decrypt_as_text(database_type_to_text(value))
|
|
104
|
+
end
|
|
105
|
+
|
|
102
106
|
def try_to_deserialize_with_previous_encrypted_types(value)
|
|
103
107
|
previous_types.each.with_index do |type, index|
|
|
104
108
|
break type.deserialize(value)
|
|
@@ -129,12 +133,20 @@ module ActiveRecord
|
|
|
129
133
|
encrypt(casted_value.to_s) unless casted_value.nil?
|
|
130
134
|
end
|
|
131
135
|
|
|
132
|
-
def
|
|
136
|
+
def encrypt_as_text(value)
|
|
133
137
|
with_context do
|
|
138
|
+
if encryptor.binary? && !cast_type.binary?
|
|
139
|
+
raise Errors::Encoding, "Binary encoded data can only be stored in binary columns"
|
|
140
|
+
end
|
|
141
|
+
|
|
134
142
|
encryptor.encrypt(value, **encryption_options)
|
|
135
143
|
end
|
|
136
144
|
end
|
|
137
145
|
|
|
146
|
+
def encrypt(value)
|
|
147
|
+
text_to_database_type encrypt_as_text(value)
|
|
148
|
+
end
|
|
149
|
+
|
|
138
150
|
def encryptor
|
|
139
151
|
ActiveRecord::Encryption.encryptor
|
|
140
152
|
end
|
|
@@ -150,6 +162,23 @@ module ActiveRecord
|
|
|
150
162
|
def clean_text_scheme
|
|
151
163
|
@clean_text_scheme ||= ActiveRecord::Encryption::Scheme.new(downcase: downcase?, encryptor: ActiveRecord::Encryption::NullEncryptor.new)
|
|
152
164
|
end
|
|
165
|
+
|
|
166
|
+
def text_to_database_type(value)
|
|
167
|
+
if value && cast_type.binary?
|
|
168
|
+
ActiveModel::Type::Binary::Data.new(value)
|
|
169
|
+
else
|
|
170
|
+
value
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def database_type_to_text(value)
|
|
175
|
+
if value && cast_type.binary?
|
|
176
|
+
binary_cast_type = cast_type.serialized? ? cast_type.subtype : cast_type
|
|
177
|
+
binary_cast_type.deserialize(value)
|
|
178
|
+
else
|
|
179
|
+
value
|
|
180
|
+
end
|
|
181
|
+
end
|
|
153
182
|
end
|
|
154
183
|
end
|
|
155
184
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "openssl"
|
|
4
|
-
require "zlib"
|
|
5
4
|
require "active_support/core_ext/numeric"
|
|
6
5
|
|
|
7
6
|
module ActiveRecord
|
|
@@ -12,25 +11,43 @@ module ActiveRecord
|
|
|
12
11
|
# It interacts with a KeyProvider for getting the keys, and delegate to
|
|
13
12
|
# ActiveRecord::Encryption::Cipher the actual encryption algorithm.
|
|
14
13
|
class Encryptor
|
|
15
|
-
#
|
|
14
|
+
# The compressor to use for compressing the payload.
|
|
15
|
+
attr_reader :compressor
|
|
16
|
+
|
|
17
|
+
# ==== Options
|
|
18
|
+
#
|
|
19
|
+
# [+:compress+]
|
|
20
|
+
# Boolean indicating whether records should be compressed before
|
|
21
|
+
# encryption. Defaults to +true+.
|
|
22
|
+
#
|
|
23
|
+
# [+:compressor+]
|
|
24
|
+
# The compressor to use. It must respond to +deflate+ and +inflate+.
|
|
25
|
+
# If not provided, will default to +ActiveRecord::Encryption.config.compressor+,
|
|
26
|
+
# which itself defaults to +Zlib+.
|
|
27
|
+
def initialize(compress: true, compressor: nil)
|
|
28
|
+
@compress = compress
|
|
29
|
+
@compressor = compressor || ActiveRecord::Encryption.config.compressor
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Encrypts +clean_text+ and returns the encrypted result.
|
|
16
33
|
#
|
|
17
34
|
# Internally, it will:
|
|
18
35
|
#
|
|
19
|
-
# 1. Create a new ActiveRecord::Encryption::Message
|
|
20
|
-
# 2. Compress and encrypt +clean_text+ as the message payload
|
|
21
|
-
# 3. Serialize it with +ActiveRecord::Encryption.message_serializer+
|
|
22
|
-
# by default)
|
|
23
|
-
# 4. Encode the result with
|
|
36
|
+
# 1. Create a new ActiveRecord::Encryption::Message.
|
|
37
|
+
# 2. Compress and encrypt +clean_text+ as the message payload.
|
|
38
|
+
# 3. Serialize it with +ActiveRecord::Encryption.message_serializer+
|
|
39
|
+
# (+ActiveRecord::Encryption::SafeMarshal+ by default).
|
|
40
|
+
# 4. Encode the result with Base64.
|
|
24
41
|
#
|
|
25
|
-
#
|
|
42
|
+
# ==== Options
|
|
26
43
|
#
|
|
27
|
-
# [
|
|
44
|
+
# [+:key_provider+]
|
|
28
45
|
# Key provider to use for the encryption operation. It will default to
|
|
29
46
|
# +ActiveRecord::Encryption.key_provider+ when not provided.
|
|
30
47
|
#
|
|
31
|
-
# [
|
|
48
|
+
# [+:cipher_options+]
|
|
32
49
|
# Cipher-specific options that will be passed to the Cipher configured in
|
|
33
|
-
# +ActiveRecord::Encryption.cipher
|
|
50
|
+
# +ActiveRecord::Encryption.cipher+.
|
|
34
51
|
def encrypt(clear_text, key_provider: default_key_provider, cipher_options: {})
|
|
35
52
|
clear_text = force_encoding_if_needed(clear_text) if cipher_options[:deterministic]
|
|
36
53
|
|
|
@@ -38,17 +55,17 @@ module ActiveRecord
|
|
|
38
55
|
serialize_message build_encrypted_message(clear_text, key_provider: key_provider, cipher_options: cipher_options)
|
|
39
56
|
end
|
|
40
57
|
|
|
41
|
-
# Decrypts
|
|
58
|
+
# Decrypts an +encrypted_text+ and returns the result as clean text.
|
|
42
59
|
#
|
|
43
|
-
#
|
|
60
|
+
# ==== Options
|
|
44
61
|
#
|
|
45
|
-
# [
|
|
62
|
+
# [+:key_provider+]
|
|
46
63
|
# Key provider to use for the encryption operation. It will default to
|
|
47
|
-
# +ActiveRecord::Encryption.key_provider+ when not provided
|
|
64
|
+
# +ActiveRecord::Encryption.key_provider+ when not provided.
|
|
48
65
|
#
|
|
49
|
-
# [
|
|
66
|
+
# [+:cipher_options+]
|
|
50
67
|
# Cipher-specific options that will be passed to the Cipher configured in
|
|
51
|
-
# +ActiveRecord::Encryption.cipher
|
|
68
|
+
# +ActiveRecord::Encryption.cipher+.
|
|
52
69
|
def decrypt(encrypted_text, key_provider: default_key_provider, cipher_options: {})
|
|
53
70
|
message = deserialize_message(encrypted_text)
|
|
54
71
|
keys = key_provider.decryption_keys(message)
|
|
@@ -58,7 +75,7 @@ module ActiveRecord
|
|
|
58
75
|
raise Errors::Decryption
|
|
59
76
|
end
|
|
60
77
|
|
|
61
|
-
# Returns whether the text is encrypted or not
|
|
78
|
+
# Returns whether the text is encrypted or not.
|
|
62
79
|
def encrypted?(text)
|
|
63
80
|
deserialize_message(text)
|
|
64
81
|
true
|
|
@@ -66,9 +83,29 @@ module ActiveRecord
|
|
|
66
83
|
false
|
|
67
84
|
end
|
|
68
85
|
|
|
86
|
+
def binary?
|
|
87
|
+
serializer.binary?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def compress? # :nodoc:
|
|
91
|
+
@compress
|
|
92
|
+
end
|
|
93
|
+
|
|
69
94
|
private
|
|
70
95
|
DECRYPT_ERRORS = [OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption]
|
|
71
96
|
ENCODING_ERRORS = [EncodingError, Errors::Encoding]
|
|
97
|
+
|
|
98
|
+
# This threshold cannot be changed.
|
|
99
|
+
#
|
|
100
|
+
# Users can search for attributes encrypted with `deterministic: true`.
|
|
101
|
+
# That is possible because we are able to generate the message for the
|
|
102
|
+
# given clear text deterministically, and with that perform a regular
|
|
103
|
+
# string lookup in SQL.
|
|
104
|
+
#
|
|
105
|
+
# Problem is, messages may have a "c" header that is present or not
|
|
106
|
+
# depending on whether compression was applied on encryption. If this
|
|
107
|
+
# threshold was modified, the message generated for lookup could vary
|
|
108
|
+
# for the same clear text, and searches on exisiting data could fail.
|
|
72
109
|
THRESHOLD_TO_JUSTIFY_COMPRESSION = 140.bytes
|
|
73
110
|
|
|
74
111
|
def default_key_provider
|
|
@@ -100,7 +137,6 @@ module ActiveRecord
|
|
|
100
137
|
end
|
|
101
138
|
|
|
102
139
|
def deserialize_message(message)
|
|
103
|
-
raise Errors::Encoding unless message.is_a?(String)
|
|
104
140
|
serializer.load message
|
|
105
141
|
rescue ArgumentError, TypeError, Errors::ForbiddenClass
|
|
106
142
|
raise Errors::Encoding
|
|
@@ -112,7 +148,7 @@ module ActiveRecord
|
|
|
112
148
|
|
|
113
149
|
# Under certain threshold, ZIP compression is actually worse that not compressing
|
|
114
150
|
def compress_if_worth_it(string)
|
|
115
|
-
if string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
|
|
151
|
+
if compress? && string.bytesize > THRESHOLD_TO_JUSTIFY_COMPRESSION
|
|
116
152
|
[compress(string), true]
|
|
117
153
|
else
|
|
118
154
|
[string, false]
|
|
@@ -120,7 +156,7 @@ module ActiveRecord
|
|
|
120
156
|
end
|
|
121
157
|
|
|
122
158
|
def compress(data)
|
|
123
|
-
|
|
159
|
+
@compressor.deflate(data).tap do |compressed_data|
|
|
124
160
|
compressed_data.force_encoding(data.encoding)
|
|
125
161
|
end
|
|
126
162
|
end
|
|
@@ -134,7 +170,7 @@ module ActiveRecord
|
|
|
134
170
|
end
|
|
135
171
|
|
|
136
172
|
def uncompress(data)
|
|
137
|
-
|
|
173
|
+
@compressor.inflate(data).tap do |uncompressed_data|
|
|
138
174
|
uncompressed_data.force_encoding(data.encoding)
|
|
139
175
|
end
|
|
140
176
|
end
|
|
@@ -41,6 +41,8 @@ module ActiveRecord
|
|
|
41
41
|
module EncryptedQuery # :nodoc:
|
|
42
42
|
class << self
|
|
43
43
|
def process_arguments(owner, args, check_for_additional_values)
|
|
44
|
+
owner = owner.model if owner.is_a?(Relation)
|
|
45
|
+
|
|
44
46
|
return args if owner.deterministic_encrypted_attributes&.empty?
|
|
45
47
|
|
|
46
48
|
if args.is_a?(Array) && (options = args.first).is_a?(Hash)
|
|
@@ -102,12 +104,12 @@ module ActiveRecord
|
|
|
102
104
|
end
|
|
103
105
|
|
|
104
106
|
def scope_for_create
|
|
105
|
-
return super unless
|
|
107
|
+
return super unless model.deterministic_encrypted_attributes&.any?
|
|
106
108
|
|
|
107
109
|
scope_attributes = super
|
|
108
110
|
wheres = where_values_hash
|
|
109
111
|
|
|
110
|
-
|
|
112
|
+
model.deterministic_encrypted_attributes.each do |attribute_name|
|
|
111
113
|
attribute_name = attribute_name.to_s
|
|
112
114
|
values = wheres[attribute_name]
|
|
113
115
|
if values.is_a?(Array) && values[1..].all?(AdditionalValue)
|
|
@@ -12,7 +12,7 @@ module ActiveRecord
|
|
|
12
12
|
@keys = Array(keys)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
# Returns the
|
|
15
|
+
# Returns the last key in the list as the active key to perform encryptions
|
|
16
16
|
#
|
|
17
17
|
# When +ActiveRecord::Encryption.config.store_key_references+ is true, the key will include
|
|
18
18
|
# a public tag referencing the key itself. That key will be stored in the public
|
|
@@ -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
|
|
@@ -11,7 +11,7 @@ module ActiveRecord
|
|
|
11
11
|
attr_accessor :previous_schemes
|
|
12
12
|
|
|
13
13
|
def initialize(key_provider: nil, key: nil, deterministic: nil, support_unencrypted_data: nil, downcase: nil, ignore_case: nil,
|
|
14
|
-
previous_schemes: nil, **context_properties)
|
|
14
|
+
previous_schemes: nil, compress: true, compressor: 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+
|
|
17
17
|
|
|
@@ -24,8 +24,13 @@ module ActiveRecord
|
|
|
24
24
|
@previous_schemes_param = previous_schemes
|
|
25
25
|
@previous_schemes = Array.wrap(previous_schemes)
|
|
26
26
|
@context_properties = context_properties
|
|
27
|
+
@compress = compress
|
|
28
|
+
@compressor = compressor
|
|
27
29
|
|
|
28
30
|
validate_config!
|
|
31
|
+
|
|
32
|
+
@context_properties[:encryptor] = Encryptor.new(compress: @compress) unless @compress
|
|
33
|
+
@context_properties[:encryptor] = Encryptor.new(compressor: compressor) if compressor
|
|
29
34
|
end
|
|
30
35
|
|
|
31
36
|
def ignore_case?
|
|
@@ -54,7 +59,7 @@ module ActiveRecord
|
|
|
54
59
|
end
|
|
55
60
|
|
|
56
61
|
def merge(other_scheme)
|
|
57
|
-
self.class.new(**to_h
|
|
62
|
+
self.class.new(**to_h, **other_scheme.to_h)
|
|
58
63
|
end
|
|
59
64
|
|
|
60
65
|
def to_h
|
|
@@ -78,6 +83,8 @@ module ActiveRecord
|
|
|
78
83
|
def validate_config!
|
|
79
84
|
raise Errors::Configuration, "ignore_case: can only be used with deterministic encryption" if @ignore_case && !@deterministic
|
|
80
85
|
raise Errors::Configuration, "key_provider: and key: can't be used simultaneously" if @key_provider_param && @key
|
|
86
|
+
raise Errors::Configuration, "compressor: can't be used with compress: false" if !@compress && @compressor
|
|
87
|
+
raise Errors::Configuration, "compressor: can't be used with encryptor" if @compressor && @context_properties[:encryptor]
|
|
81
88
|
end
|
|
82
89
|
|
|
83
90
|
def key_provider_from_key
|
data/lib/active_record/enum.rb
CHANGED
|
@@ -119,7 +119,18 @@ module ActiveRecord
|
|
|
119
119
|
# enum :status, [ :active, :archived ], instance_methods: false
|
|
120
120
|
# end
|
|
121
121
|
#
|
|
122
|
-
#
|
|
122
|
+
# By default, an +ArgumentError+ will be raised when assigning an invalid value:
|
|
123
|
+
#
|
|
124
|
+
# class Conversation < ActiveRecord::Base
|
|
125
|
+
# enum :status, [ :active, :archived ]
|
|
126
|
+
# end
|
|
127
|
+
#
|
|
128
|
+
# conversation = Conversation.new
|
|
129
|
+
#
|
|
130
|
+
# conversation.status = :unknown # 'unknown' is not a valid status (ArgumentError)
|
|
131
|
+
#
|
|
132
|
+
# If, instead, you want the enum value to be validated before saving, use the
|
|
133
|
+
# +:validate+ option:
|
|
123
134
|
#
|
|
124
135
|
# class Conversation < ActiveRecord::Base
|
|
125
136
|
# enum :status, [ :active, :archived ], validate: true
|
|
@@ -136,7 +147,7 @@ module ActiveRecord
|
|
|
136
147
|
# conversation.status = :active
|
|
137
148
|
# conversation.valid? # => true
|
|
138
149
|
#
|
|
139
|
-
#
|
|
150
|
+
# You may also pass additional validation options:
|
|
140
151
|
#
|
|
141
152
|
# class Conversation < ActiveRecord::Base
|
|
142
153
|
# enum :status, [ :active, :archived ], validate: { allow_nil: true }
|
|
@@ -152,16 +163,6 @@ module ActiveRecord
|
|
|
152
163
|
#
|
|
153
164
|
# conversation.status = :active
|
|
154
165
|
# 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)
|
|
165
166
|
module Enum
|
|
166
167
|
def self.extended(base) # :nodoc:
|
|
167
168
|
base.class_attribute(:defined_enums, instance_writer: false, default: {})
|
|
@@ -213,26 +214,16 @@ module ActiveRecord
|
|
|
213
214
|
attr_reader :name, :mapping
|
|
214
215
|
end
|
|
215
216
|
|
|
216
|
-
def enum(name
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return _enum(name, values, **options)
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default, :_instance_methods)
|
|
223
|
-
options.transform_keys! { |key| :"#{key[1..-1]}" }
|
|
224
|
-
|
|
225
|
-
definitions.each { |name, values| _enum(name, values, **options) }
|
|
217
|
+
def enum(name, values = nil, **options)
|
|
218
|
+
values, options = options, {} unless values
|
|
219
|
+
_enum(name, values, **options)
|
|
226
220
|
end
|
|
227
221
|
|
|
228
222
|
private
|
|
229
|
-
def inherited(base)
|
|
230
|
-
base.defined_enums = defined_enums.deep_dup
|
|
231
|
-
super
|
|
232
|
-
end
|
|
233
|
-
|
|
234
223
|
def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options)
|
|
235
|
-
assert_valid_enum_definition_values(values)
|
|
224
|
+
values = assert_valid_enum_definition_values(values)
|
|
225
|
+
assert_valid_enum_options(options)
|
|
226
|
+
|
|
236
227
|
# statuses = { }
|
|
237
228
|
enum_values = ActiveSupport::HashWithIndifferentAccess.new
|
|
238
229
|
name = name.to_s
|
|
@@ -245,7 +236,9 @@ module ActiveRecord
|
|
|
245
236
|
detect_enum_conflict!(name, name)
|
|
246
237
|
detect_enum_conflict!(name, "#{name}=")
|
|
247
238
|
|
|
248
|
-
attribute(name, **options)
|
|
239
|
+
attribute(name, **options)
|
|
240
|
+
|
|
241
|
+
decorate_attributes([name]) do |_name, subtype|
|
|
249
242
|
if subtype == ActiveModel::Type.default_value
|
|
250
243
|
raise "Undeclared attribute type for enum '#{name}' in #{self.name}. Enums must be" \
|
|
251
244
|
" backed by a database column or declared with an explicit type" \
|
|
@@ -294,6 +287,11 @@ module ActiveRecord
|
|
|
294
287
|
enum_values.freeze
|
|
295
288
|
end
|
|
296
289
|
|
|
290
|
+
def inherited(base)
|
|
291
|
+
base.defined_enums = defined_enums.deep_dup
|
|
292
|
+
super
|
|
293
|
+
end
|
|
294
|
+
|
|
297
295
|
class EnumMethods < Module # :nodoc:
|
|
298
296
|
def initialize(klass)
|
|
299
297
|
@klass = klass
|
|
@@ -344,6 +342,20 @@ module ActiveRecord
|
|
|
344
342
|
if values.keys.any?(&:blank?)
|
|
345
343
|
raise ArgumentError, "Enum values #{values} must not contain a blank name."
|
|
346
344
|
end
|
|
345
|
+
|
|
346
|
+
values = values.transform_values do |value|
|
|
347
|
+
value.is_a?(Symbol) ? value.name : value
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
values.each_value do |value|
|
|
351
|
+
case value
|
|
352
|
+
when String, Integer, Float, true, false, nil
|
|
353
|
+
# noop
|
|
354
|
+
else
|
|
355
|
+
raise ArgumentError, "Enum values #{values} must be only booleans, integers, floats, symbols or strings, got: #{value.class}"
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
347
359
|
when Array
|
|
348
360
|
if values.empty?
|
|
349
361
|
raise ArgumentError, "Enum values #{values} must not be empty."
|
|
@@ -359,29 +371,38 @@ module ActiveRecord
|
|
|
359
371
|
else
|
|
360
372
|
raise ArgumentError, "Enum values #{values} must be either a non-empty hash or an array."
|
|
361
373
|
end
|
|
374
|
+
|
|
375
|
+
values
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def assert_valid_enum_options(options)
|
|
379
|
+
invalid_keys = options.keys & %i[_prefix _suffix _scopes _default _instance_methods]
|
|
380
|
+
unless invalid_keys.empty?
|
|
381
|
+
raise ArgumentError, "invalid option(s): #{invalid_keys.map(&:inspect).join(", ")}. Valid options are: :prefix, :suffix, :scopes, :default, :instance_methods, and :validate."
|
|
382
|
+
end
|
|
362
383
|
end
|
|
363
384
|
|
|
364
385
|
ENUM_CONFLICT_MESSAGE = \
|
|
365
386
|
"You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
|
|
366
|
-
"this will generate
|
|
387
|
+
"this will generate %{type} method \"%{method}\", which is already defined " \
|
|
367
388
|
"by %{source}."
|
|
368
389
|
private_constant :ENUM_CONFLICT_MESSAGE
|
|
369
390
|
|
|
370
391
|
def detect_enum_conflict!(enum_name, method_name, klass_method = false)
|
|
371
392
|
if klass_method && dangerous_class_method?(method_name)
|
|
372
|
-
raise_conflict_error(enum_name, method_name,
|
|
393
|
+
raise_conflict_error(enum_name, method_name, "a class")
|
|
373
394
|
elsif klass_method && method_defined_within?(method_name, Relation)
|
|
374
|
-
raise_conflict_error(enum_name, method_name,
|
|
395
|
+
raise_conflict_error(enum_name, method_name, "a class", source: Relation.name)
|
|
375
396
|
elsif klass_method && method_name.to_sym == :id
|
|
376
|
-
raise_conflict_error(enum_name, method_name)
|
|
397
|
+
raise_conflict_error(enum_name, method_name, "an instance")
|
|
377
398
|
elsif !klass_method && dangerous_attribute_method?(method_name)
|
|
378
|
-
raise_conflict_error(enum_name, method_name)
|
|
399
|
+
raise_conflict_error(enum_name, method_name, "an instance")
|
|
379
400
|
elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
|
|
380
|
-
raise_conflict_error(enum_name, method_name, source: "another enum")
|
|
401
|
+
raise_conflict_error(enum_name, method_name, "an instance", source: "another enum")
|
|
381
402
|
end
|
|
382
403
|
end
|
|
383
404
|
|
|
384
|
-
def raise_conflict_error(enum_name, method_name, type
|
|
405
|
+
def raise_conflict_error(enum_name, method_name, type, source: "Active Record")
|
|
385
406
|
raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
|
|
386
407
|
enum: enum_name,
|
|
387
408
|
klass: name,
|