familia 2.0.0.pre15 → 2.0.0.pre17
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/.github/workflows/ci.yml +2 -2
- data/.github/workflows/code-quality.yml +138 -0
- data/.github/workflows/code-smells.yml +85 -0
- data/.github/workflows/docs.yml +31 -8
- data/.gitignore +3 -1
- data/.pre-commit-config.yaml +7 -1
- data/.reek.yml +98 -0
- data/.rubocop.yml +54 -10
- data/.talismanrc +9 -0
- data/.yardopts +18 -13
- data/CHANGELOG.rst +86 -4
- data/CLAUDE.md +39 -1
- data/Gemfile +6 -5
- data/Gemfile.lock +99 -23
- data/LICENSE.txt +1 -1
- data/README.md +285 -85
- data/changelog.d/README.md +2 -2
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
- data/docs/archive/FAMILIA_TECHNICAL.md +42 -42
- data/docs/archive/FAMILIA_UPDATE.md +3 -3
- data/docs/archive/README.md +3 -2
- data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
- data/docs/conf.py +29 -0
- data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
- data/docs/guides/feature-encrypted-fields.md +785 -0
- data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
- data/docs/guides/feature-external-identifiers.md +637 -0
- data/docs/guides/feature-object-identifiers.md +435 -0
- data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
- data/docs/guides/feature-relationships-methods.md +684 -0
- data/docs/guides/feature-relationships.md +200 -0
- data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
- data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
- data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
- data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
- data/docs/guides/index.md +176 -0
- data/docs/guides/{Security-Model.md → security-model.md} +1 -1
- data/docs/migrating/v2.0.0-pre.md +1 -1
- data/docs/migrating/v2.0.0-pre11.md +2 -2
- data/docs/migrating/v2.0.0-pre12.md +2 -2
- data/docs/migrating/v2.0.0-pre5.md +33 -12
- data/docs/migrating/v2.0.0-pre6.md +2 -2
- data/docs/migrating/v2.0.0-pre7.md +8 -8
- data/docs/overview.md +624 -20
- data/docs/reference/api-technical.md +1365 -0
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
- data/examples/autoloader/mega_customer.rb +3 -1
- data/examples/encrypted_fields.rb +378 -0
- data/examples/json_usage_patterns.rb +144 -0
- data/examples/relationships.rb +13 -13
- data/examples/safe_dump.rb +7 -7
- data/examples/single_connection_transaction_confusions.rb +379 -0
- data/lib/familia/base.rb +51 -10
- data/lib/familia/connection/handlers.rb +223 -0
- data/lib/familia/connection/individual_command_proxy.rb +64 -0
- data/lib/familia/connection/middleware.rb +75 -0
- data/lib/familia/connection/operation_core.rb +93 -0
- data/lib/familia/connection/operations.rb +277 -0
- data/lib/familia/connection/pipeline_core.rb +87 -0
- data/lib/familia/connection/transaction_core.rb +100 -0
- data/lib/familia/connection.rb +60 -186
- data/lib/familia/data_type/class_methods.rb +63 -0
- data/lib/familia/data_type/commands.rb +53 -51
- data/lib/familia/data_type/connection.rb +83 -0
- data/lib/familia/data_type/serialization.rb +108 -107
- data/lib/familia/data_type/settings.rb +96 -0
- data/lib/familia/data_type/types/counter.rb +1 -1
- data/lib/familia/data_type/types/hashkey.rb +15 -11
- data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
- data/lib/familia/data_type/types/lock.rb +3 -2
- data/lib/familia/data_type/types/sorted_set.rb +128 -14
- data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -9
- data/lib/familia/data_type/types/unsorted_set.rb +20 -27
- data/lib/familia/data_type.rb +12 -171
- data/lib/familia/distinguisher.rb +85 -0
- data/lib/familia/encryption/encrypted_data.rb +15 -24
- data/lib/familia/encryption/manager.rb +6 -4
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
- data/lib/familia/encryption/request_cache.rb +7 -7
- data/lib/familia/encryption.rb +2 -3
- data/lib/familia/errors.rb +9 -3
- data/lib/familia/features/autoloader.rb +30 -12
- data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
- data/lib/familia/features/encrypted_fields.rb +71 -66
- data/lib/familia/features/expiration/extensions.rb +1 -1
- data/lib/familia/features/expiration.rb +31 -26
- data/lib/familia/features/external_identifier.rb +57 -19
- data/lib/familia/features/object_identifier.rb +134 -25
- data/lib/familia/features/quantization.rb +16 -21
- data/lib/familia/features/relationships/README.md +97 -0
- data/lib/familia/features/relationships/collection_operations.rb +104 -0
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +306 -0
- data/lib/familia/features/relationships/indexing.rb +182 -256
- data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
- data/lib/familia/features/relationships/participation/participant_methods.rb +164 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
- data/lib/familia/features/relationships/participation.rb +656 -0
- data/lib/familia/features/relationships/participation_relationship.rb +31 -0
- data/lib/familia/features/relationships/score_encoding.rb +20 -20
- data/lib/familia/features/relationships.rb +65 -266
- data/lib/familia/features/safe_dump.rb +127 -130
- data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
- data/lib/familia/features/transient_fields.rb +10 -7
- data/lib/familia/features.rb +10 -14
- data/lib/familia/field_type.rb +6 -4
- data/lib/familia/horreum/connection.rb +297 -0
- data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +27 -17
- data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +139 -74
- data/lib/familia/horreum/{subclass/management.rb → management.rb} +73 -27
- data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +108 -185
- data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +104 -23
- data/lib/familia/horreum/serialization.rb +172 -0
- data/lib/familia/horreum/{shared/settings.rb → settings.rb} +2 -1
- data/lib/familia/horreum/{core/utils.rb → utils.rb} +2 -1
- data/lib/familia/horreum.rb +222 -119
- data/lib/familia/json_serializer.rb +0 -1
- data/lib/familia/logging.rb +11 -114
- data/lib/familia/refinements/dear_json.rb +122 -0
- data/lib/familia/refinements/logger_trace.rb +20 -17
- data/lib/familia/refinements/stylize_words.rb +65 -0
- data/lib/familia/refinements/time_literals.rb +60 -52
- data/lib/familia/refinements.rb +2 -1
- data/lib/familia/secure_identifier.rb +60 -28
- data/lib/familia/settings.rb +83 -7
- data/lib/familia/utils.rb +5 -87
- data/lib/familia/verifiable_identifier.rb +4 -4
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +72 -14
- data/lib/middleware/database_middleware.rb +56 -14
- data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
- data/try/configuration/scenarios_try.rb +2 -2
- data/try/connection/fiber_context_preservation_try.rb +250 -0
- data/try/connection/handler_constraints_try.rb +59 -0
- data/try/connection/operation_mode_guards_try.rb +208 -0
- data/try/connection/pipeline_fallback_integration_try.rb +128 -0
- data/try/connection/responsibility_chain_tracking_try.rb +72 -0
- data/try/connection/transaction_fallback_integration_try.rb +288 -0
- data/try/connection/transaction_mode_permissive_try.rb +153 -0
- data/try/connection/transaction_mode_strict_try.rb +98 -0
- data/try/connection/transaction_mode_warn_try.rb +131 -0
- data/try/connection/transaction_modes_try.rb +249 -0
- data/try/core/autoloader_try.rb +120 -2
- data/try/core/connection_try.rb +10 -10
- data/try/core/conventional_inheritance_try.rb +130 -0
- data/try/core/create_method_try.rb +15 -23
- data/try/core/database_consistency_try.rb +11 -10
- data/try/core/errors_try.rb +11 -14
- data/try/core/familia_extended_try.rb +2 -2
- data/try/core/familia_members_methods_try.rb +76 -0
- data/try/core/familia_try.rb +1 -1
- data/try/core/isolated_dbclient_try.rb +165 -0
- data/try/core/middleware_try.rb +16 -16
- data/try/core/persistence_operations_try.rb +4 -4
- data/try/core/pools_try.rb +42 -26
- data/try/core/secure_identifier_try.rb +28 -24
- data/try/core/time_utils_try.rb +10 -10
- data/try/core/tools_try.rb +3 -3
- data/try/core/utils_try.rb +2 -2
- data/try/data_types/boolean_try.rb +4 -4
- data/try/data_types/datatype_base_try.rb +0 -2
- data/try/data_types/list_try.rb +10 -10
- data/try/data_types/sorted_set_try.rb +5 -5
- data/try/data_types/sorted_set_zadd_options_try.rb +625 -0
- data/try/data_types/string_try.rb +12 -12
- data/try/data_types/unsortedset_try.rb +33 -0
- data/try/debugging/cache_behavior_tracer.rb +7 -7
- data/try/debugging/debug_aad_process.rb +1 -1
- data/try/debugging/debug_concealed_internal.rb +1 -1
- data/try/debugging/debug_cross_context.rb +1 -1
- data/try/debugging/debug_fresh_cross_context.rb +1 -1
- data/try/debugging/encryption_method_tracer.rb +10 -10
- data/try/edge_cases/hash_symbolization_try.rb +1 -1
- data/try/edge_cases/ttl_side_effects_try.rb +1 -1
- data/try/encryption/config_persistence_try.rb +2 -2
- data/try/encryption/encryption_core_try.rb +19 -19
- data/try/encryption/instance_variable_scope_try.rb +1 -1
- data/try/encryption/module_loading_try.rb +2 -2
- data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
- data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
- data/try/encryption/secure_memory_handling_try.rb +1 -1
- data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
- data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
- data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
- data/try/features/external_identifier/external_identifier_try.rb +1 -1
- data/try/features/feature_dependencies_try.rb +3 -3
- data/try/features/field_groups_try.rb +244 -0
- data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
- data/try/features/object_identifier/object_identifier_try.rb +10 -0
- data/try/features/quantization/quantization_try.rb +1 -1
- data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
- data/try/features/relationships/indexing_try.rb +443 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
- data/try/features/relationships/participation_commands_verification_try.rb +105 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
- data/try/features/relationships/participation_reverse_index_try.rb +196 -0
- data/try/features/relationships/relationships_api_changes_try.rb +72 -71
- data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
- data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
- data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
- data/try/features/relationships/relationships_performance_try.rb +20 -20
- data/try/features/relationships/relationships_try.rb +27 -38
- data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
- data/try/features/transient_fields/refresh_reset_try.rb +3 -1
- data/try/features/transient_fields/simple_refresh_test.rb +1 -1
- data/try/helpers/test_cleanup.rb +86 -0
- data/try/helpers/test_helpers.rb +6 -7
- data/try/horreum/auto_indexing_on_save_try.rb +212 -0
- data/try/horreum/base_try.rb +3 -2
- data/try/horreum/commands_try.rb +3 -1
- data/try/horreum/defensive_initialization_try.rb +86 -0
- data/try/horreum/destroy_related_fields_cleanup_try.rb +332 -0
- data/try/horreum/initialization_try.rb +11 -7
- data/try/horreum/relations_try.rb +21 -13
- data/try/horreum/serialization_try.rb +12 -11
- data/try/horreum/settings_try.rb +2 -0
- data/try/integration/cross_component_try.rb +3 -3
- data/try/memory/memory_basic_test.rb +1 -1
- data/try/memory/memory_docker_ruby_dump.sh +2 -2
- data/try/models/customer_safe_dump_try.rb +1 -1
- data/try/models/customer_try.rb +13 -15
- data/try/models/datatype_base_try.rb +3 -3
- data/try/models/familia_object_try.rb +9 -8
- data/try/performance/benchmarks_try.rb +2 -2
- data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
- data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
- data/try/prototypes/atomic_saves_v4.rb +1 -1
- data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
- data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
- data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
- data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
- data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
- data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
- data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
- data/try/prototypes/pooling/pool_siege.rb +11 -11
- data/try/prototypes/pooling/run_stress_tests.rb +7 -7
- data/try/refinements/dear_json_array_methods_try.rb +53 -0
- data/try/refinements/dear_json_hash_methods_try.rb +54 -0
- data/try/refinements/logger_trace_methods_try.rb +44 -0
- data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
- data/try/refinements/time_literals_string_methods_try.rb +80 -0
- data/try/valkey.conf +26 -0
- metadata +92 -52
- data/.rubocop_todo.yml +0 -208
- data/docs/connection_pooling.md +0 -192
- data/docs/guides/Connection-Pooling-Guide.md +0 -437
- data/docs/guides/Encrypted-Fields-Overview.md +0 -101
- data/docs/guides/Feature-System-Autoloading.md +0 -198
- data/docs/guides/Home.md +0 -116
- data/docs/guides/Relationships-Guide.md +0 -737
- data/docs/guides/relationships-methods.md +0 -266
- data/docs/reference/auditing_database_commands.rb +0 -228
- data/examples/permissions.rb +0 -240
- data/lib/familia/features/relationships/cascading.rb +0 -437
- data/lib/familia/features/relationships/membership.rb +0 -497
- data/lib/familia/features/relationships/permission_management.rb +0 -264
- data/lib/familia/features/relationships/querying.rb +0 -615
- data/lib/familia/features/relationships/redis_operations.rb +0 -274
- data/lib/familia/features/relationships/tracking.rb +0 -418
- data/lib/familia/horreum/core/connection.rb +0 -73
- data/lib/familia/horreum/core.rb +0 -21
- data/lib/familia/refinements/snake_case.rb +0 -40
- data/lib/familia/validation/command_recorder.rb +0 -336
- data/lib/familia/validation/expectations.rb +0 -519
- data/lib/familia/validation/validation_helpers.rb +0 -443
- data/lib/familia/validation/validator.rb +0 -412
- data/lib/familia/validation.rb +0 -140
- data/try/data_types/set_try.rb +0 -33
- data/try/features/relationships/categorical_permissions_try.rb +0 -515
- data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
- data/try/validation/atomic_operations_try.rb.disabled +0 -320
- data/try/validation/command_validation_try.rb.disabled +0 -207
- data/try/validation/performance_validation_try.rb.disabled +0 -324
- data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -1,127 +1,128 @@
|
|
1
1
|
# lib/familia/data_type/serialization.rb
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
3
|
+
module Familia
|
4
|
+
class DataType
|
5
|
+
module Serialization
|
6
|
+
# Serializes a value for storage in the database.
|
7
|
+
#
|
8
|
+
# @param val [Object] The value to be serialized.
|
9
|
+
# @param strict_values [Boolean] Whether to enforce strict value
|
10
|
+
# serialization (default: true).
|
11
|
+
# @return [String, nil] The serialized representation of the value, or nil
|
12
|
+
# if serialization fails.
|
13
|
+
#
|
14
|
+
# @note When a class option is specified, it uses that class's
|
15
|
+
# serialization method. Otherwise, it relies on Familia.distinguisher for
|
16
|
+
# serialization.
|
17
|
+
#
|
18
|
+
# @example With a class option
|
19
|
+
# serialize_value(User.new(name: "Cloe"), strict_values: false) #=> '{"name":"Cloe"}'
|
20
|
+
#
|
21
|
+
# @example Without a class option
|
22
|
+
# serialize_value(123) #=> "123"
|
23
|
+
# serialize_value("hello") #=> "hello"
|
24
|
+
#
|
25
|
+
# @raise [Familia::NotDistinguishableError] If serialization fails under strict
|
26
|
+
# mode.
|
27
|
+
#
|
28
|
+
def serialize_value(val, strict_values: true)
|
29
|
+
prepared = nil
|
29
30
|
|
30
|
-
|
31
|
+
Familia.trace :TOREDIS, nil, "#{val}<#{val.class}|#{opts[:class]}>" if Familia.debug?
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
if opts[:class]
|
34
|
+
prepared = Familia.distinguisher(opts[:class], strict_values: strict_values)
|
35
|
+
Familia.ld " from opts[class] <#{opts[:class]}>: #{prepared || '<nil>'}"
|
36
|
+
end
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
if prepared.nil?
|
39
|
+
# Enforce strict values when no class option is specified
|
40
|
+
prepared = Familia.distinguisher(val, strict_values: true)
|
41
|
+
Familia.ld " from <#{val.class}> => <#{prepared.class}>"
|
42
|
+
end
|
43
|
+
|
44
|
+
if Familia.debug?
|
45
|
+
Familia.trace :TOREDIS, nil, "#{val}<#{val.class}|#{opts[:class]}> => #{prepared}<#{prepared.class}>"
|
46
|
+
end
|
42
47
|
|
43
|
-
|
44
|
-
|
45
|
-
caller(1..1)
|
48
|
+
Familia.warn "[#{self.class}#serialize_value] nil returned for #{opts[:class]}##{name}" if prepared.nil?
|
49
|
+
prepared
|
46
50
|
end
|
47
51
|
|
48
|
-
|
49
|
-
|
50
|
-
|
52
|
+
# Deserializes multiple values from Valkey/Redis, removing nil values.
|
53
|
+
#
|
54
|
+
# @param values [Array<String>] The values to deserialize.
|
55
|
+
# @return [Array<Object>] Deserialized objects, with nil values removed.
|
56
|
+
#
|
57
|
+
# @see #deserialize_values_with_nil
|
58
|
+
#
|
59
|
+
def deserialize_values(*values)
|
60
|
+
# Avoid using compact! here. Using compact! as the last expression in the
|
61
|
+
# method can unintentionally return nil if no changes are made, which is
|
62
|
+
# not desirable. Instead, use compact to ensure the method returns the
|
63
|
+
# expected value.
|
64
|
+
deserialize_values_with_nil(*values).compact
|
65
|
+
end
|
51
66
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
#
|
61
|
-
# method
|
62
|
-
#
|
63
|
-
#
|
64
|
-
deserialize_values_with_nil(*values)
|
65
|
-
|
67
|
+
# Deserializes multiple values from Valkey/Redis, preserving nil values.
|
68
|
+
#
|
69
|
+
# @param values [Array<String>] The values to deserialize.
|
70
|
+
# @return [Array<Object, nil>] Deserialized objects, including nil values.
|
71
|
+
#
|
72
|
+
# @raise [Familia::Problem] If the specified class doesn't respond to the
|
73
|
+
# load method.
|
74
|
+
#
|
75
|
+
# @note This method attempts to deserialize each value using the specified
|
76
|
+
# class's load method. If deserialization fails for a value, it's
|
77
|
+
# replaced with nil.
|
78
|
+
#
|
79
|
+
def deserialize_values_with_nil(*values)
|
80
|
+
Familia.ld "deserialize_values: (#{@opts}) #{values}"
|
81
|
+
return [] if values.empty?
|
82
|
+
return values.flatten unless @opts[:class]
|
66
83
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
# @return [Array<Object, nil>] Deserialized objects, including nil values.
|
71
|
-
#
|
72
|
-
# @raise [Familia::Problem] If the specified class doesn't respond to the
|
73
|
-
# load method.
|
74
|
-
#
|
75
|
-
# @note This method attempts to deserialize each value using the specified
|
76
|
-
# class's load method. If deserialization fails for a value, it's
|
77
|
-
# replaced with nil.
|
78
|
-
#
|
79
|
-
def deserialize_values_with_nil(*values)
|
80
|
-
Familia.ld "deserialize_values: (#{@opts}) #{values}"
|
81
|
-
return [] if values.empty?
|
82
|
-
return values.flatten unless @opts[:class]
|
84
|
+
unless @opts[:class].respond_to?(load_method)
|
85
|
+
raise Familia::Problem, "No such method: #{@opts[:class]}##{load_method}"
|
86
|
+
end
|
83
87
|
|
84
|
-
|
85
|
-
|
86
|
-
end
|
88
|
+
values.collect! do |obj|
|
89
|
+
next if obj.nil?
|
87
90
|
|
88
|
-
|
89
|
-
|
91
|
+
val = @opts[:class].send load_method, obj
|
92
|
+
Familia.ld "[#{self.class}#deserialize_values] nil returned for #{@opts[:class]}##{name}" if val.nil?
|
90
93
|
|
91
|
-
|
92
|
-
|
94
|
+
val
|
95
|
+
rescue StandardError => e
|
96
|
+
Familia.info val
|
97
|
+
Familia.info "Parse error for #{dbkey} (#{load_method}): #{e.message}"
|
98
|
+
Familia.info e.backtrace
|
99
|
+
nil
|
100
|
+
end
|
93
101
|
|
94
|
-
|
95
|
-
rescue StandardError => e
|
96
|
-
Familia.info val
|
97
|
-
Familia.info "Parse error for #{dbkey} (#{load_method}): #{e.message}"
|
98
|
-
Familia.info e.backtrace
|
99
|
-
nil
|
102
|
+
values
|
100
103
|
end
|
101
104
|
|
102
|
-
|
103
|
-
|
105
|
+
# Deserializes a single value from the database.
|
106
|
+
#
|
107
|
+
# @param val [String, nil] The value to deserialize.
|
108
|
+
# @return [Object, nil] The deserialized object, the default value if
|
109
|
+
# val is nil, or nil if deserialization fails.
|
110
|
+
#
|
111
|
+
# @note If no class option is specified, the original value is
|
112
|
+
# returned unchanged.
|
113
|
+
#
|
114
|
+
# NOTE: Currently only the DataType class uses this method. Horreum
|
115
|
+
# fields are a newer addition and don't support the full range of
|
116
|
+
# deserialization options that DataType supports. It uses serialize_value
|
117
|
+
# for serialization since everything becomes a string in Valkey.
|
118
|
+
#
|
119
|
+
def deserialize_value(val)
|
120
|
+
return @opts[:default] if val.nil?
|
121
|
+
return val unless @opts[:class]
|
104
122
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
# @return [Object, nil] The deserialized object, the default value if
|
109
|
-
# val is nil, or nil if deserialization fails.
|
110
|
-
#
|
111
|
-
# @note If no class option is specified, the original value is
|
112
|
-
# returned unchanged.
|
113
|
-
#
|
114
|
-
# NOTE: Currently only the DataType class uses this method. Horreum
|
115
|
-
# fields are a newer addition and don't support the full range of
|
116
|
-
# deserialization options that DataType supports. It uses serialize_value
|
117
|
-
# for serialization since everything becomes a string in Valkey.
|
118
|
-
#
|
119
|
-
def deserialize_value(val)
|
120
|
-
return @opts[:default] if val.nil?
|
121
|
-
return val unless @opts[:class]
|
122
|
-
|
123
|
-
ret = deserialize_values val
|
124
|
-
ret&.first # return the object or nil
|
123
|
+
ret = deserialize_values val
|
124
|
+
ret&.first # return the object or nil
|
125
|
+
end
|
125
126
|
end
|
126
127
|
end
|
127
128
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# lib/familia/data_type/settings.rb
|
2
|
+
|
3
|
+
module Familia
|
4
|
+
class DataType
|
5
|
+
# Settings - Instance-level configuration and introspection methods
|
6
|
+
#
|
7
|
+
# This module provides instance methods for accessing and managing
|
8
|
+
# DataType object configuration, parent relationships, and serialization.
|
9
|
+
#
|
10
|
+
# Key features:
|
11
|
+
# * Parent object relationship management
|
12
|
+
# * URI and database configuration
|
13
|
+
# * Serialization method delegation
|
14
|
+
# * Type introspection
|
15
|
+
#
|
16
|
+
module Settings
|
17
|
+
attr_reader :keystring, :opts, :logical_database
|
18
|
+
attr_reader :uri
|
19
|
+
|
20
|
+
alias url uri
|
21
|
+
|
22
|
+
def class?
|
23
|
+
!@opts[:class].to_s.empty? && @opts[:class].is_a?(Familia)
|
24
|
+
end
|
25
|
+
|
26
|
+
def parent_instance?
|
27
|
+
parent&.is_a?(Horreum::ParentDefinition)
|
28
|
+
end
|
29
|
+
|
30
|
+
def parent_class?
|
31
|
+
parent.is_a?(Class) && parent.ancestors.include?(Familia::Horreum)
|
32
|
+
end
|
33
|
+
|
34
|
+
def parent?
|
35
|
+
parent_class? || parent_instance?
|
36
|
+
end
|
37
|
+
|
38
|
+
def parent
|
39
|
+
# Return cached ParentDefinition if available
|
40
|
+
return @parent if @parent
|
41
|
+
|
42
|
+
# Return class-level parent if no instance parent
|
43
|
+
return self.class.parent unless @parent_ref
|
44
|
+
|
45
|
+
# Create ParentDefinition dynamically from stored reference.
|
46
|
+
# This ensures we get the current identifier value (available after initialization)
|
47
|
+
# rather than a stale nil value from initialization time. Cannot cache due to frozen object.
|
48
|
+
Horreum::ParentDefinition.from_parent(@parent_ref)
|
49
|
+
end
|
50
|
+
|
51
|
+
def parent=(value)
|
52
|
+
case value
|
53
|
+
when Horreum::ParentDefinition
|
54
|
+
@parent = value
|
55
|
+
when nil
|
56
|
+
@parent = nil
|
57
|
+
@parent_ref = nil
|
58
|
+
else
|
59
|
+
# Store parent instance reference for lazy ParentDefinition creation.
|
60
|
+
# During initialization, the parent's identifier may not be available yet,
|
61
|
+
# so we defer ParentDefinition creation until first access for memory efficiency.
|
62
|
+
# Note: @parent_ref is not cleared after use because DataType objects are frozen.
|
63
|
+
@parent_ref = value
|
64
|
+
@parent = nil # Will be created dynamically in parent method
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def uri
|
69
|
+
# Return explicit instance URI if set
|
70
|
+
return @uri if @uri
|
71
|
+
|
72
|
+
# If we have a parent with logical_database, build URI with that database
|
73
|
+
if parent && parent.respond_to?(:logical_database) && parent.logical_database
|
74
|
+
new_uri = (self.class.uri || Familia.uri).dup
|
75
|
+
new_uri.db = parent.logical_database
|
76
|
+
new_uri
|
77
|
+
else
|
78
|
+
# Fall back to class-level URI or global Familia.uri
|
79
|
+
self.class.uri || Familia.uri
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def uri=(value)
|
84
|
+
@uri = value
|
85
|
+
end
|
86
|
+
|
87
|
+
def dump_method
|
88
|
+
self.class.dump_method
|
89
|
+
end
|
90
|
+
|
91
|
+
def load_method
|
92
|
+
self.class.load_method
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -8,6 +8,8 @@ module Familia
|
|
8
8
|
dbclient.hlen dbkey
|
9
9
|
end
|
10
10
|
alias size field_count
|
11
|
+
alias length field_count
|
12
|
+
alias count field_count
|
11
13
|
|
12
14
|
def empty?
|
13
15
|
field_count.zero?
|
@@ -22,13 +24,14 @@ module Familia
|
|
22
24
|
rescue TypeError => e
|
23
25
|
Familia.le "[hset]= #{e.message}"
|
24
26
|
Familia.ld "[hset]= #{dbkey} #{field}=#{val}" if Familia.debug
|
25
|
-
echo :hset,
|
27
|
+
echo :hset, Familia.pretty_stack(limit: 1) if Familia.debug # logs via echo to the db and back
|
26
28
|
klass = val.class
|
27
29
|
msg = "Cannot store #{field} => #{val.inspect} (#{klass}) in #{dbkey}"
|
28
30
|
raise e.class, msg
|
29
31
|
end
|
30
32
|
alias put []=
|
31
33
|
alias store []=
|
34
|
+
alias add []=
|
32
35
|
|
33
36
|
def [](field)
|
34
37
|
deserialize_value dbclient.hget(dbkey, field.to_s)
|
@@ -55,8 +58,8 @@ module Familia
|
|
55
58
|
end
|
56
59
|
|
57
60
|
def hgetall
|
58
|
-
dbclient.hgetall(dbkey).
|
59
|
-
|
61
|
+
dbclient.hgetall(dbkey).transform_values do |v|
|
62
|
+
deserialize_value v
|
60
63
|
end
|
61
64
|
end
|
62
65
|
alias all hgetall
|
@@ -73,7 +76,7 @@ module Familia
|
|
73
76
|
rescue TypeError => e
|
74
77
|
Familia.le "[hsetnx] #{e.message}"
|
75
78
|
Familia.ld "[hsetnx] #{dbkey} #{field}=#{val}" if Familia.debug
|
76
|
-
echo :hsetnx,
|
79
|
+
echo :hsetnx, Familia.pretty_stack(limit: 1) if Familia.debug # logs via echo to the db and back
|
77
80
|
klass = val.class
|
78
81
|
msg = "Cannot store #{field} => #{val.inspect} (#{klass}) in #{dbkey}"
|
79
82
|
raise e.class, msg
|
@@ -92,7 +95,8 @@ module Familia
|
|
92
95
|
def remove_field(field)
|
93
96
|
dbclient.hdel dbkey, field.to_s
|
94
97
|
end
|
95
|
-
alias remove remove_field
|
98
|
+
alias remove remove_field
|
99
|
+
alias remove_element remove_field
|
96
100
|
|
97
101
|
def increment(field, by = 1)
|
98
102
|
dbclient.hincrby(dbkey, field.to_s, by).to_i
|
@@ -125,15 +129,15 @@ module Familia
|
|
125
129
|
|
126
130
|
# The Great Database Refresh-o-matic 3000 for HashKey!
|
127
131
|
#
|
128
|
-
# This method performs a complete refresh of the hash's state from
|
132
|
+
# This method performs a complete refresh of the hash's state from the database.
|
129
133
|
# It's like giving your hash a memory transfusion - out with the old state,
|
130
|
-
# in with the fresh data straight from Redis!
|
134
|
+
# in with the fresh data straight from Valkey/Redis!
|
131
135
|
#
|
132
136
|
# @note This operation is atomic - it either succeeds completely or fails
|
133
137
|
# safely. Any unsaved changes to the hash will be overwritten.
|
134
138
|
#
|
135
139
|
# @return [void] Returns nothing, but your hash will be sparkling clean
|
136
|
-
# with all its fields synchronized with
|
140
|
+
# with all its fields synchronized with the database.
|
137
141
|
#
|
138
142
|
# @raise [Familia::KeyNotFoundError] If the dbkey for this hash no
|
139
143
|
# longer exists. Time travelers beware!
|
@@ -148,7 +152,7 @@ module Familia
|
|
148
152
|
# puts "Oops! Our hash seems to have vanished into the Database void!"
|
149
153
|
# end
|
150
154
|
def refresh!
|
151
|
-
Familia.trace :REFRESH,
|
155
|
+
Familia.trace :REFRESH, nil, uri if Familia.debug?
|
152
156
|
raise Familia::KeyNotFoundError, dbkey unless dbclient.exists(dbkey)
|
153
157
|
|
154
158
|
fields = hgetall
|
@@ -167,7 +171,7 @@ module Familia
|
|
167
171
|
# @return [self] Returns the refreshed hash, ready for more adventures!
|
168
172
|
#
|
169
173
|
# @raise [Familia::KeyNotFoundError] If the dbkey does not exist.
|
170
|
-
# The hash must exist in Redis-land for this to work!
|
174
|
+
# The hash must exist in Valkey/Redis-land for this to work!
|
171
175
|
#
|
172
176
|
# @example Refresh and chain
|
173
177
|
# my_hash.refresh.keys # Refresh and get all keys
|
@@ -179,7 +183,7 @@ module Familia
|
|
179
183
|
self
|
180
184
|
end
|
181
185
|
|
182
|
-
Familia::DataType.register self, :hash
|
186
|
+
Familia::DataType.register self, :hash
|
183
187
|
Familia::DataType.register self, :hashkey
|
184
188
|
end
|
185
189
|
end
|
@@ -1,20 +1,22 @@
|
|
1
|
-
# lib/familia/data_type/types/
|
1
|
+
# lib/familia/data_type/types/listkey.rb
|
2
2
|
|
3
3
|
module Familia
|
4
|
-
class
|
4
|
+
class ListKey < DataType
|
5
5
|
# Returns the number of elements in the list
|
6
6
|
# @return [Integer] number of elements
|
7
7
|
def element_count
|
8
8
|
dbclient.llen dbkey
|
9
9
|
end
|
10
10
|
alias size element_count
|
11
|
+
alias length element_count
|
12
|
+
alias count element_count
|
11
13
|
|
12
14
|
def empty?
|
13
15
|
element_count.zero?
|
14
16
|
end
|
15
17
|
|
16
18
|
def push *values
|
17
|
-
echo :push,
|
19
|
+
echo :push, Familia.pretty_stack(limit: 1) if Familia.debug
|
18
20
|
values.flatten.compact.each { |v| dbclient.rpush dbkey, serialize_value(v) }
|
19
21
|
dbclient.ltrim dbkey, -@opts[:maxlength], -1 if @opts[:maxlength]
|
20
22
|
update_expiration
|
@@ -23,8 +25,9 @@ module Familia
|
|
23
25
|
alias append push
|
24
26
|
|
25
27
|
def <<(val)
|
26
|
-
push
|
28
|
+
push(val)
|
27
29
|
end
|
30
|
+
alias add_element <<
|
28
31
|
alias add <<
|
29
32
|
|
30
33
|
def unshift *values
|
@@ -59,6 +62,10 @@ module Familia
|
|
59
62
|
end
|
60
63
|
alias slice []
|
61
64
|
|
65
|
+
def member?(value)
|
66
|
+
!dbclient.lpos(dbkey, serialize_value(value)).nil?
|
67
|
+
end
|
68
|
+
|
62
69
|
# Removes elements equal to value from the list
|
63
70
|
# @param value The value to remove
|
64
71
|
# @param count [Integer] Number of elements to remove (0 means all)
|
@@ -78,7 +85,7 @@ module Familia
|
|
78
85
|
end
|
79
86
|
|
80
87
|
def members(count = -1)
|
81
|
-
echo :members,
|
88
|
+
echo :members, Familia.pretty_stack(limit: 1) if Familia.debug
|
82
89
|
count -= 1 if count.positive?
|
83
90
|
range 0, count
|
84
91
|
end
|
@@ -157,5 +164,6 @@ module Familia
|
|
157
164
|
# end
|
158
165
|
|
159
166
|
Familia::DataType.register self, :list
|
167
|
+
Familia::DataType.register self, :listkey
|
160
168
|
end
|
161
169
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# lib/familia/data_type/types/lock.rb
|
2
2
|
|
3
3
|
module Familia
|
4
|
-
class Lock <
|
4
|
+
class Lock < StringKey
|
5
5
|
def initialize(*args)
|
6
6
|
super
|
7
7
|
@opts[:default] = nil
|
@@ -14,9 +14,10 @@ module Familia
|
|
14
14
|
def acquire(token = SecureRandom.uuid, ttl: 10)
|
15
15
|
success = setnx(token)
|
16
16
|
# Handle both integer (1/0) and boolean (true/false) return values
|
17
|
-
return false unless
|
17
|
+
return false unless [1, true].include?(success)
|
18
18
|
return del && false if ttl&.<=(0)
|
19
19
|
return del && false if ttl&.positive? && !expire(ttl)
|
20
|
+
|
20
21
|
token
|
21
22
|
end
|
22
23
|
|