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
@@ -8,6 +8,8 @@ module Familia
|
|
8
8
|
dbclient.zcard 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?
|
@@ -24,14 +26,13 @@ module Familia
|
|
24
26
|
# @return [Integer] Returns 1 if the element is new and added, 0 if the
|
25
27
|
# element already existed and the score was updated.
|
26
28
|
#
|
27
|
-
# @example
|
28
|
-
# sorted_set << "new_element"
|
29
|
+
# @example sorted_set << "new_element"
|
29
30
|
#
|
30
31
|
# @note This is a non-standard operation for sorted sets as it doesn't allow
|
31
32
|
# specifying a custom score. Use `add` or `[]=` for more control.
|
32
33
|
#
|
33
34
|
def <<(val)
|
34
|
-
add(
|
35
|
+
add(val)
|
35
36
|
end
|
36
37
|
|
37
38
|
# NOTE: The argument order is the reverse of #add. We do this to
|
@@ -42,14 +43,88 @@ module Familia
|
|
42
43
|
# obj.metrics[VALUE] # => SCORE
|
43
44
|
#
|
44
45
|
def []=(val, score)
|
45
|
-
add
|
46
|
+
add val, score
|
46
47
|
end
|
47
48
|
|
48
|
-
|
49
|
-
|
49
|
+
# Adds an element to the sorted set with an optional score and ZADD options.
|
50
|
+
#
|
51
|
+
# This method supports Redis ZADD options for conditional adds and updates:
|
52
|
+
# - **NX**: Only add new elements (don't update existing)
|
53
|
+
# - **XX**: Only update existing elements (don't add new)
|
54
|
+
# - **GT**: Only update if new score > current score
|
55
|
+
# - **LT**: Only update if new score < current score
|
56
|
+
# - **CH**: Return changed count (new + updated) instead of just new count
|
57
|
+
#
|
58
|
+
# @param val [Object] The value to add to the sorted set
|
59
|
+
# @param score [Numeric, nil] The score for ranking (defaults to current timestamp)
|
60
|
+
# @param nx [Boolean] Only add new elements, don't update existing (default: false)
|
61
|
+
# @param xx [Boolean] Only update existing elements, don't add new (default: false)
|
62
|
+
# @param gt [Boolean] Only update if new score > current score (default: false)
|
63
|
+
# @param lt [Boolean] Only update if new score < current score (default: false)
|
64
|
+
# @param ch [Boolean] Return changed count instead of added count (default: false)
|
65
|
+
#
|
66
|
+
# @return [Boolean] Returns the return value from the redis gem's ZADD
|
67
|
+
# command. Returns true if element was added or changed (with CH option),
|
68
|
+
# false if element score was updated without change tracking or no
|
69
|
+
# operation occurred due to option constraints (NX, XX, GT, LT).
|
70
|
+
#
|
71
|
+
# @raise [ArgumentError] If mutually exclusive options are specified together
|
72
|
+
# (NX+XX, GT+LT, NX+GT, NX+LT)
|
73
|
+
#
|
74
|
+
# @example Add new element with timestamp
|
75
|
+
# metrics.add('pageview', Time.now.to_f) #=> true
|
76
|
+
#
|
77
|
+
# @example Preserve original timestamp on subsequent saves
|
78
|
+
# index.add(email, Time.now.to_f, nx: true) #=> true
|
79
|
+
# index.add(email, Time.now.to_f, nx: true) #=> false (unchanged)
|
80
|
+
#
|
81
|
+
# @example Update timestamp only for existing entries
|
82
|
+
# index.add(email, Time.now.to_f, xx: true) #=> false (if doesn't exist)
|
83
|
+
#
|
84
|
+
# @example Only update if new score is higher (leaderboard)
|
85
|
+
# scores.add(player, 1000, gt: true) #=> true (new entry)
|
86
|
+
# scores.add(player, 1500, gt: true) #=> false (updated)
|
87
|
+
# scores.add(player, 1200, gt: true) #=> false (not updated, score lower)
|
88
|
+
#
|
89
|
+
# @example Track total changes for analytics
|
90
|
+
# changed = metrics.add(user, score, ch: true) #=> true (new or updated)
|
91
|
+
#
|
92
|
+
# @example Combined options: only update existing, only if score increases
|
93
|
+
# index.add(key, new_score, xx: true, gt: true)
|
94
|
+
#
|
95
|
+
# @note GT and LT options do NOT prevent adding new elements, they only
|
96
|
+
# affect update behavior for existing elements.
|
97
|
+
#
|
98
|
+
# @note Default behavior (no options) adds new elements and updates existing
|
99
|
+
# ones unconditionally, matching standard Redis ZADD semantics.
|
100
|
+
#
|
101
|
+
# @note INCR option is not supported. Use the increment method for ZINCRBY operations.
|
102
|
+
#
|
103
|
+
def add(val, score = nil, nx: false, xx: false, gt: false, lt: false, ch: false)
|
104
|
+
score ||= Familia.now
|
105
|
+
|
106
|
+
# Validate mutual exclusivity
|
107
|
+
validate_zadd_options!(nx: nx, xx: xx, gt: gt, lt: lt)
|
108
|
+
|
109
|
+
# Build options hash for redis gem
|
110
|
+
opts = {}
|
111
|
+
opts[:nx] = true if nx
|
112
|
+
opts[:xx] = true if xx
|
113
|
+
opts[:gt] = true if gt
|
114
|
+
opts[:lt] = true if lt
|
115
|
+
opts[:ch] = true if ch
|
116
|
+
|
117
|
+
# Pass options to ZADD
|
118
|
+
ret = if opts.empty?
|
119
|
+
dbclient.zadd(dbkey, score, serialize_value(val))
|
120
|
+
else
|
121
|
+
dbclient.zadd(dbkey, score, serialize_value(val), **opts)
|
122
|
+
end
|
123
|
+
|
50
124
|
update_expiration
|
51
125
|
ret
|
52
126
|
end
|
127
|
+
alias add_element add
|
53
128
|
|
54
129
|
def score(val)
|
55
130
|
ret = dbclient.zscore dbkey, serialize_value(val, strict_values: false)
|
@@ -58,7 +133,7 @@ module Familia
|
|
58
133
|
alias [] score
|
59
134
|
|
60
135
|
def member?(val)
|
61
|
-
Familia.trace :MEMBER,
|
136
|
+
Familia.trace :MEMBER, nil, "#{val}<#{val.class}>" if Familia.debug?
|
62
137
|
!rank(val).nil?
|
63
138
|
end
|
64
139
|
alias include? member?
|
@@ -132,7 +207,7 @@ module Familia
|
|
132
207
|
end
|
133
208
|
|
134
209
|
def range(sidx, eidx, opts = {})
|
135
|
-
echo :range,
|
210
|
+
echo :range, Familia.pretty_stack(limit: 1) if Familia.debug
|
136
211
|
elements = rangeraw(sidx, eidx, opts)
|
137
212
|
deserialize_values(*elements)
|
138
213
|
end
|
@@ -149,7 +224,7 @@ module Familia
|
|
149
224
|
end
|
150
225
|
|
151
226
|
def revrange(sidx, eidx, opts = {})
|
152
|
-
echo :revrange,
|
227
|
+
echo :revrange, Familia.pretty_stack(limit: 1) if Familia.debug
|
153
228
|
elements = revrangeraw(sidx, eidx, opts)
|
154
229
|
deserialize_values(*elements)
|
155
230
|
end
|
@@ -160,25 +235,25 @@ module Familia
|
|
160
235
|
|
161
236
|
# e.g. obj.metrics.rangebyscore (now-12.hours), now, :limit => [0, 10]
|
162
237
|
def rangebyscore(sscore, escore, opts = {})
|
163
|
-
echo :rangebyscore,
|
238
|
+
echo :rangebyscore, Familia.pretty_stack(limit: 1) if Familia.debug
|
164
239
|
elements = rangebyscoreraw(sscore, escore, opts)
|
165
240
|
deserialize_values(*elements)
|
166
241
|
end
|
167
242
|
|
168
243
|
def rangebyscoreraw(sscore, escore, opts = {})
|
169
|
-
echo :rangebyscoreraw,
|
244
|
+
echo :rangebyscoreraw, Familia.pretty_stack(limit: 1) if Familia.debug
|
170
245
|
dbclient.zrangebyscore(dbkey, sscore, escore, **opts)
|
171
246
|
end
|
172
247
|
|
173
248
|
# e.g. obj.metrics.revrangebyscore (now-12.hours), now, :limit => [0, 10]
|
174
249
|
def revrangebyscore(sscore, escore, opts = {})
|
175
|
-
echo :revrangebyscore,
|
250
|
+
echo :revrangebyscore, Familia.pretty_stack(limit: 1) if Familia.debug
|
176
251
|
elements = revrangebyscoreraw(sscore, escore, opts)
|
177
252
|
deserialize_values(*elements)
|
178
253
|
end
|
179
254
|
|
180
255
|
def revrangebyscoreraw(sscore, escore, opts = {})
|
181
|
-
echo :revrangebyscoreraw,
|
256
|
+
echo :revrangebyscoreraw, Familia.pretty_stack(limit: 1) if Familia.debug
|
182
257
|
opts[:with_scores] = true if opts[:withscores]
|
183
258
|
dbclient.zrevrangebyscore(dbkey, sscore, escore, opts)
|
184
259
|
end
|
@@ -207,7 +282,7 @@ module Familia
|
|
207
282
|
# @param value The value to remove from the sorted set
|
208
283
|
# @return [Integer] The number of members that were removed (0 or 1)
|
209
284
|
def remove_element(value)
|
210
|
-
Familia.trace :REMOVE_ELEMENT,
|
285
|
+
Familia.trace :REMOVE_ELEMENT, nil, "#{value}<#{value.class}>" if Familia.debug?
|
211
286
|
# We use `strict_values: false` here to allow for the deletion of values
|
212
287
|
# that are in the sorted set. If it's a horreum object, the value is
|
213
288
|
# the identifier and not a serialized version of the object. So either
|
@@ -231,6 +306,45 @@ module Familia
|
|
231
306
|
at(-1)
|
232
307
|
end
|
233
308
|
|
309
|
+
|
310
|
+
private
|
311
|
+
|
312
|
+
# Validates that mutually exclusive ZADD options are not specified together.
|
313
|
+
#
|
314
|
+
# @param nx [Boolean] NX option flag
|
315
|
+
# @param xx [Boolean] XX option flag
|
316
|
+
# @param gt [Boolean] GT option flag
|
317
|
+
# @param lt [Boolean] LT option flag
|
318
|
+
#
|
319
|
+
# @raise [ArgumentError] If mutually exclusive options are specified
|
320
|
+
#
|
321
|
+
# @note Valid combinations: XX+GT, XX+LT
|
322
|
+
# @note Invalid combinations: NX+XX, GT+LT, NX+GT, NX+LT
|
323
|
+
#
|
324
|
+
def validate_zadd_options!(nx:, xx:, gt:, lt:)
|
325
|
+
# NX and XX are mutually exclusive
|
326
|
+
if nx && xx
|
327
|
+
raise ArgumentError, "ZADD options NX and XX are mutually exclusive"
|
328
|
+
end
|
329
|
+
|
330
|
+
# GT and LT are mutually exclusive
|
331
|
+
if gt && lt
|
332
|
+
raise ArgumentError, "ZADD options GT and LT are mutually exclusive"
|
333
|
+
end
|
334
|
+
|
335
|
+
# NX is mutually exclusive with GT
|
336
|
+
if nx && gt
|
337
|
+
raise ArgumentError, "ZADD options NX and GT are mutually exclusive"
|
338
|
+
end
|
339
|
+
|
340
|
+
# NX is mutually exclusive with LT
|
341
|
+
if nx && lt
|
342
|
+
raise ArgumentError, "ZADD options NX and LT are mutually exclusive"
|
343
|
+
end
|
344
|
+
|
345
|
+
# Note: XX + GT and XX + LT are valid combinations
|
346
|
+
end
|
347
|
+
|
234
348
|
Familia::DataType.register self, :sorted_set
|
235
349
|
Familia::DataType.register self, :zset
|
236
350
|
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
# lib/familia/data_type/types/
|
1
|
+
# lib/familia/data_type/types/stringkey.rb
|
2
2
|
|
3
3
|
module Familia
|
4
|
-
class
|
4
|
+
class StringKey < DataType
|
5
5
|
def init; end
|
6
6
|
|
7
7
|
# Returns the number of elements in the list
|
@@ -10,13 +10,14 @@ module Familia
|
|
10
10
|
to_s.size
|
11
11
|
end
|
12
12
|
alias size char_count
|
13
|
+
alias length char_count
|
13
14
|
|
14
15
|
def empty?
|
15
16
|
char_count.zero?
|
16
17
|
end
|
17
18
|
|
18
19
|
def value
|
19
|
-
echo :value,
|
20
|
+
echo :value, Familia.pretty_stack(limit: 1) if Familia.debug
|
20
21
|
dbclient.setnx dbkey, @opts[:default] if @opts[:default]
|
21
22
|
deserialize_value dbclient.get(dbkey)
|
22
23
|
end
|
@@ -30,7 +31,7 @@ module Familia
|
|
30
31
|
end
|
31
32
|
|
32
33
|
def to_i
|
33
|
-
value
|
34
|
+
value.to_i
|
34
35
|
end
|
35
36
|
|
36
37
|
def value=(val)
|
@@ -113,14 +114,11 @@ module Familia
|
|
113
114
|
ret.positive?
|
114
115
|
end
|
115
116
|
|
116
|
-
def nil?
|
117
|
-
value.nil?
|
118
|
-
end
|
119
|
-
|
120
117
|
Familia::DataType.register self, :string
|
118
|
+
Familia::DataType.register self, :stringkey
|
121
119
|
end
|
122
120
|
end
|
123
121
|
|
124
|
-
# Both subclass
|
122
|
+
# Both subclass StringKey
|
125
123
|
require_relative 'lock'
|
126
124
|
require_relative 'counter'
|
@@ -1,13 +1,17 @@
|
|
1
1
|
# lib/familia/data_type/types/unsorted_set.rb
|
2
2
|
|
3
3
|
module Familia
|
4
|
-
|
4
|
+
# Familia::UnsortedSet
|
5
|
+
#
|
6
|
+
class UnsortedSet < DataType
|
5
7
|
# Returns the number of elements in the unsorted set
|
6
8
|
# @return [Integer] number of elements
|
7
9
|
def element_count
|
8
10
|
dbclient.scard dbkey
|
9
11
|
end
|
10
12
|
alias size element_count
|
13
|
+
alias length element_count
|
14
|
+
alias count element_count
|
11
15
|
|
12
16
|
def empty?
|
13
17
|
element_count.zero?
|
@@ -18,13 +22,14 @@ module Familia
|
|
18
22
|
update_expiration
|
19
23
|
self
|
20
24
|
end
|
25
|
+
alias add_element add
|
21
26
|
|
22
27
|
def <<(v)
|
23
28
|
add v
|
24
29
|
end
|
25
30
|
|
26
31
|
def members
|
27
|
-
echo :members,
|
32
|
+
echo :members, Familia.pretty_stack(limit: 1) if Familia.debug
|
28
33
|
elements = membersraw
|
29
34
|
deserialize_values(*elements)
|
30
35
|
end
|
@@ -92,35 +97,23 @@ module Familia
|
|
92
97
|
dbclient.smove dbkey, dstkey, val
|
93
98
|
end
|
94
99
|
|
95
|
-
|
96
|
-
|
100
|
+
# Get one or more random members from the set
|
101
|
+
# @param count [Integer] Number of random members to return (default: 1)
|
102
|
+
# @return [Array] Array of deserialized random members
|
103
|
+
def sample(count = 1)
|
104
|
+
deserialize_values(*sampleraw(count))
|
97
105
|
end
|
106
|
+
alias random sample
|
98
107
|
|
99
|
-
|
100
|
-
|
108
|
+
# Get one or more random members from the set without deserialization
|
109
|
+
# @param count [Integer] Number of random members to return (default: 1)
|
110
|
+
# @return [Array] Array of raw random members
|
111
|
+
def sampleraw(count = 1)
|
112
|
+
dbclient.srandmember(dbkey, count) || []
|
101
113
|
end
|
102
|
-
|
103
|
-
## Make the value stored at KEY identical to the given list
|
104
|
-
# define_method :"#{name}_sync" do |*latest|
|
105
|
-
# latest = latest.flatten.compact
|
106
|
-
# # Do nothing if we're given an empty Array.
|
107
|
-
# # Otherwise this would clear all current values
|
108
|
-
# if latest.empty?
|
109
|
-
# false
|
110
|
-
# else
|
111
|
-
# # Convert to a list of index values if we got the actual objects
|
112
|
-
# latest = latest.collect { |obj| obj.index } if klass === latest.first
|
113
|
-
# current = send("#{name_plural}raw")
|
114
|
-
# added = latest-current
|
115
|
-
# removed = current-latest
|
116
|
-
# #Familia.info "#{self.index}: adding: #{added}"
|
117
|
-
# added.each { |v| self.send("add_#{name_singular}", v) }
|
118
|
-
# #Familia.info "#{self.index}: removing: #{removed}"
|
119
|
-
# removed.each { |v| self.send("remove_#{name_singular}", v) }
|
120
|
-
# true
|
121
|
-
# end
|
122
|
-
# end
|
114
|
+
alias random sampleraw
|
123
115
|
|
124
116
|
Familia::DataType.register self, :set
|
117
|
+
Familia::DataType.register self, :unsorted_set
|
125
118
|
end
|
126
119
|
end
|
data/lib/familia/data_type.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# lib/familia/data_type.rb
|
2
2
|
|
3
|
+
require_relative 'data_type/class_methods'
|
4
|
+
require_relative 'data_type/settings'
|
5
|
+
require_relative 'data_type/connection'
|
3
6
|
require_relative 'data_type/commands'
|
4
7
|
require_relative 'data_type/serialization'
|
5
8
|
|
@@ -9,72 +12,27 @@ module Familia
|
|
9
12
|
# DataType - Base class for Database data type wrappers
|
10
13
|
#
|
11
14
|
# This class provides common functionality for various Database data types
|
12
|
-
# such as String, List,
|
15
|
+
# such as String, List, UnsortedSet, SortedSet, and HashKey.
|
13
16
|
#
|
14
17
|
# @abstract Subclass and implement Database data type specific methods
|
15
18
|
class DataType
|
16
19
|
include Familia::Base
|
20
|
+
extend ClassMethods
|
17
21
|
extend Familia::Features
|
18
22
|
|
19
23
|
using Familia::Refinements::TimeLiterals
|
20
24
|
|
21
25
|
@registered_types = {}
|
22
|
-
@valid_options = %i[class parent default_expiration default logical_database dbkey dbclient suffix prefix]
|
26
|
+
@valid_options = %i[class parent default_expiration default logical_database dbkey dbclient suffix prefix].freeze
|
23
27
|
@logical_database = nil
|
24
28
|
|
25
29
|
feature :expiration
|
26
30
|
feature :quantization
|
27
31
|
|
28
32
|
class << self
|
29
|
-
attr_reader :registered_types, :valid_options, :
|
30
|
-
attr_accessor :parent
|
31
|
-
attr_writer :logical_database, :uri
|
33
|
+
attr_reader :registered_types, :valid_options, :has_related_fields
|
32
34
|
end
|
33
35
|
|
34
|
-
# DataType::ClassMethods
|
35
|
-
#
|
36
|
-
module ClassMethods
|
37
|
-
# To be called inside every class that inherits DataType
|
38
|
-
# +methname+ is the term used for the class and instance methods
|
39
|
-
# that are created for the given +klass+ (e.g. set, list, etc)
|
40
|
-
def register(klass, methname)
|
41
|
-
Familia.trace :REGISTER, nil, "[#{self}] Registering #{klass} as #{methname.inspect}", caller(1..1) if Familia.debug?
|
42
|
-
|
43
|
-
@registered_types[methname] = klass
|
44
|
-
end
|
45
|
-
|
46
|
-
def logical_database(val = nil)
|
47
|
-
@logical_database = val unless val.nil?
|
48
|
-
@logical_database || parent&.logical_database
|
49
|
-
end
|
50
|
-
|
51
|
-
def uri(val = nil)
|
52
|
-
@uri = val unless val.nil?
|
53
|
-
@uri || (parent ? parent.uri : Familia.uri)
|
54
|
-
end
|
55
|
-
|
56
|
-
def inherited(obj)
|
57
|
-
Familia.trace :DATATYPE, nil, "#{obj} is my kinda type", caller(1..1) if Familia.debug?
|
58
|
-
obj.logical_database = logical_database
|
59
|
-
obj.default_expiration = default_expiration # method added via Features::Expiration
|
60
|
-
obj.uri = uri
|
61
|
-
obj.parent = self
|
62
|
-
super
|
63
|
-
end
|
64
|
-
|
65
|
-
def valid_keys_only(opts)
|
66
|
-
opts.slice(*DataType.valid_options)
|
67
|
-
end
|
68
|
-
|
69
|
-
def relations?
|
70
|
-
@has_relations ||= false # rubocop:disable ThreadSafety/ClassInstanceVariable
|
71
|
-
end
|
72
|
-
end
|
73
|
-
extend ClassMethods
|
74
|
-
|
75
|
-
attr_reader :keystring, :opts
|
76
|
-
attr_writer :dump_method, :load_method
|
77
|
-
|
78
36
|
# +keystring+: If parent is set, this will be used as the suffix
|
79
37
|
# for dbkey. Otherwise this becomes the value of the key.
|
80
38
|
# If this is an Array, the elements will be joined.
|
@@ -94,10 +52,6 @@ module Familia
|
|
94
52
|
#
|
95
53
|
# :default => the default value (String-only)
|
96
54
|
#
|
97
|
-
# :logical_database => the logical database index to use (ignored if :dbclient is used).
|
98
|
-
#
|
99
|
-
# :dbclient => an instance of database client.
|
100
|
-
#
|
101
55
|
# :dbkey => a hardcoded key to use instead of the deriving the from
|
102
56
|
# the name and parent (e.g. a derived key: customer:custid:secret_counter).
|
103
57
|
#
|
@@ -111,138 +65,25 @@ module Familia
|
|
111
65
|
@keystring = @keystring.join(Familia.delim) if @keystring.is_a?(Array)
|
112
66
|
|
113
67
|
# Remove all keys from the opts that are not in the allowed list
|
114
|
-
@opts = opts || {}
|
115
|
-
@opts = DataType.valid_keys_only(@opts)
|
68
|
+
@opts = DataType.valid_keys_only(opts || {})
|
116
69
|
|
117
70
|
# Apply the options to instance method setters of the same name
|
118
71
|
@opts.each do |k, v|
|
119
|
-
# Bewarde logging :parent instance here implicitly calls #to_s which for
|
120
|
-
# some classes could include the identifier which could still be nil at
|
121
|
-
# this point. This would result in a Familia::Problem being raised. So
|
122
|
-
# to be on the safe-side here until we have a better understanding of
|
123
|
-
# the issue, we'll just log the class name for each key-value pair.
|
124
|
-
Familia.trace :SETTING, nil, " [setting] #{k} #{v.class}", caller(1..1) if Familia.debug?
|
125
72
|
send(:"#{k}=", v) if respond_to? :"#{k}="
|
126
73
|
end
|
127
74
|
|
128
75
|
init if respond_to? :init
|
129
76
|
end
|
130
77
|
|
131
|
-
|
132
|
-
|
133
|
-
return @dbclient if @dbclient
|
134
|
-
|
135
|
-
parent? ? parent.dbclient : Familia.dbclient(opts[:logical_database])
|
136
|
-
end
|
137
|
-
|
138
|
-
# Produces the full dbkey for this object.
|
139
|
-
#
|
140
|
-
# @return [String] The full dbkey.
|
141
|
-
#
|
142
|
-
# This method determines the appropriate dbkey based on the context of the DataType object:
|
143
|
-
#
|
144
|
-
# 1. If a hardcoded key is set in the options, it returns that key.
|
145
|
-
# 2. For instance-level DataType objects, it uses the parent instance's dbkey method.
|
146
|
-
# 3. For class-level DataType objects, it uses the parent class's dbkey method.
|
147
|
-
# 4. For standalone DataType objects, it uses the keystring as the full dbkey.
|
148
|
-
#
|
149
|
-
# For class-level DataType objects (parent_class? == true):
|
150
|
-
# - The suffix is optional and used to differentiate between different types of objects.
|
151
|
-
# - If no suffix is provided, the class's default suffix is used (via the self.suffix method).
|
152
|
-
# - If a nil suffix is explicitly passed, it won't appear in the resulting dbkey.
|
153
|
-
# - Passing nil as the suffix is how class-level DataType objects are created without
|
154
|
-
# the global default 'object' suffix.
|
155
|
-
#
|
156
|
-
# @example Instance-level DataType
|
157
|
-
# user_instance.some_datatype.dbkey # => "user:123:some_datatype"
|
158
|
-
#
|
159
|
-
# @example Class-level DataType
|
160
|
-
# User.some_datatype.dbkey # => "user:some_datatype"
|
161
|
-
#
|
162
|
-
# @example Standalone DataType
|
163
|
-
# DataType.new("mykey").dbkey # => "mykey"
|
164
|
-
#
|
165
|
-
# @example Class-level DataType with explicit nil suffix
|
166
|
-
# User.dbkey("123", nil) # => "user:123"
|
167
|
-
#
|
168
|
-
def dbkey
|
169
|
-
# Return the hardcoded key if it's set. This is useful for
|
170
|
-
# support legacy keys that aren't derived in the same way.
|
171
|
-
return opts[:dbkey] if opts[:dbkey]
|
172
|
-
|
173
|
-
if parent_instance?
|
174
|
-
# This is an instance-level datatype object so the parent instance's
|
175
|
-
# dbkey method is defined in Familia::Horreum::InstanceMethods.
|
176
|
-
parent.dbkey(keystring)
|
177
|
-
elsif parent_class?
|
178
|
-
# This is a class-level datatype object so the parent class' dbkey
|
179
|
-
# method is defined in Familia::Horreum::DefinitionMethods.
|
180
|
-
parent.dbkey(keystring, nil)
|
181
|
-
else
|
182
|
-
# This is a standalone DataType object where it's keystring
|
183
|
-
# is the full database key (dbkey).
|
184
|
-
keystring
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
def class?
|
189
|
-
!@opts[:class].to_s.empty? && @opts[:class].is_a?(Familia)
|
190
|
-
end
|
191
|
-
|
192
|
-
def parent_instance?
|
193
|
-
parent.is_a?(Familia::Horreum)
|
194
|
-
end
|
195
|
-
|
196
|
-
def parent_class?
|
197
|
-
parent.is_a?(Class) && parent <= Familia::Horreum
|
198
|
-
end
|
199
|
-
|
200
|
-
def parent?
|
201
|
-
parent_class? || parent_instance?
|
202
|
-
end
|
203
|
-
|
204
|
-
def parent
|
205
|
-
@opts[:parent]
|
206
|
-
end
|
207
|
-
|
208
|
-
|
209
|
-
def logical_database
|
210
|
-
@opts[:logical_database] || self.class.logical_database
|
211
|
-
end
|
212
|
-
|
213
|
-
def uri
|
214
|
-
# If a specific URI is set in opts, use it
|
215
|
-
return @opts[:uri] if @opts[:uri]
|
216
|
-
|
217
|
-
# If parent has a DB set, create a URI with that DB
|
218
|
-
if parent? && parent.respond_to?(:logical_database) && parent.logical_database
|
219
|
-
base_uri = self.class.uri || Familia.uri
|
220
|
-
if base_uri
|
221
|
-
uri_with_db = base_uri.dup
|
222
|
-
uri_with_db.db = parent.logical_database
|
223
|
-
return uri_with_db
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
# Otherwise fall back to class URI
|
228
|
-
self.class.uri
|
229
|
-
end
|
230
|
-
|
231
|
-
def dump_method
|
232
|
-
@dump_method || self.class.dump_method
|
233
|
-
end
|
234
|
-
|
235
|
-
def load_method
|
236
|
-
@load_method || self.class.load_method
|
237
|
-
end
|
238
|
-
|
78
|
+
include Settings
|
79
|
+
include Connection
|
239
80
|
include Commands
|
240
81
|
include Serialization
|
241
82
|
end
|
242
83
|
|
243
|
-
require_relative 'data_type/types/
|
84
|
+
require_relative 'data_type/types/listkey'
|
244
85
|
require_relative 'data_type/types/unsorted_set'
|
245
86
|
require_relative 'data_type/types/sorted_set'
|
246
87
|
require_relative 'data_type/types/hashkey'
|
247
|
-
require_relative 'data_type/types/
|
88
|
+
require_relative 'data_type/types/stringkey'
|
248
89
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# lib/familia/distinguisher.rb
|
2
|
+
|
3
|
+
module Familia
|
4
|
+
module Distinguisher
|
5
|
+
# This method determines the appropriate transformation to apply based on
|
6
|
+
# the class of the input argument.
|
7
|
+
#
|
8
|
+
# @param [Object] value_to_distinguish The value to be processed. Keep in
|
9
|
+
# mind that all data is stored as a string so whatever the type
|
10
|
+
# of the value, it will be converted to a string.
|
11
|
+
# @param [Boolean] strict_values Whether to enforce strict value handling.
|
12
|
+
# Defaults to true.
|
13
|
+
# @return [String, nil] The processed value as a string or nil for unsupported
|
14
|
+
# classes.
|
15
|
+
#
|
16
|
+
# The method uses a case statement to handle different classes:
|
17
|
+
# - For `Symbol`, `String`, `Integer`, and `Float` classes, it traces the
|
18
|
+
# operation and converts the value to a string.
|
19
|
+
# - For `Familia::Horreum` class, it traces the operation and returns the
|
20
|
+
# identifier of the value.
|
21
|
+
# - For `TrueClass`, `FalseClass`, and `NilClass`, it traces the operation and
|
22
|
+
# converts the value to a string ("true", "false", or "").
|
23
|
+
# - For any other class, it traces the operation and returns nil.
|
24
|
+
#
|
25
|
+
# Alternative names for `value_to_distinguish` could be `input_value`, `value`,
|
26
|
+
# or `object`.
|
27
|
+
#
|
28
|
+
def distinguisher(value_to_distinguish, strict_values: true)
|
29
|
+
case value_to_distinguish
|
30
|
+
when ::Symbol, ::String, ::Integer, ::Float
|
31
|
+
Familia.trace :TOREDIS_DISTINGUISHER, nil, 'string' if Familia.debug?
|
32
|
+
|
33
|
+
# Symbols and numerics are naturally serializable to strings
|
34
|
+
# so it's a relatively low risk operation.
|
35
|
+
value_to_distinguish.to_s
|
36
|
+
|
37
|
+
when ::TrueClass, ::FalseClass, ::NilClass
|
38
|
+
Familia.trace :TOREDIS_DISTINGUISHER, nil, 'true/false/nil' if Familia.debug?
|
39
|
+
|
40
|
+
# TrueClass, FalseClass, and NilClass are considered high risk because their
|
41
|
+
# original types cannot be reliably determined from their serialized string
|
42
|
+
# representations. This can lead to unexpected behavior during deserialization.
|
43
|
+
# For instance, a TrueClass value serialized as "true" might be deserialized as
|
44
|
+
# a String, causing application errors. Even more problematic, a NilClass value
|
45
|
+
# serialized as an empty string makes it impossible to distinguish between a
|
46
|
+
# nil value and an empty string upon deserialization. Such scenarios can result
|
47
|
+
# in subtle, hard-to-diagnose bugs. To mitigate these risks, we raise an
|
48
|
+
# exception when encountering these types unless the strict_values option is
|
49
|
+
# explicitly set to false.
|
50
|
+
#
|
51
|
+
raise Familia::NotDistinguishableError, value_to_distinguish if strict_values
|
52
|
+
|
53
|
+
value_to_distinguish.to_s #=> "true", "false", ""
|
54
|
+
|
55
|
+
when Familia::Base, Class
|
56
|
+
Familia.trace :TOREDIS_DISTINGUISHER, nil, 'base' if Familia.debug?
|
57
|
+
|
58
|
+
# When called with a class we simply transform it to its name. For
|
59
|
+
# instances of Familia class, we store the identifier.
|
60
|
+
if value_to_distinguish.is_a?(Class)
|
61
|
+
value_to_distinguish.name
|
62
|
+
else
|
63
|
+
value_to_distinguish.identifier
|
64
|
+
end
|
65
|
+
|
66
|
+
else
|
67
|
+
Familia.trace :TOREDIS_DISTINGUISHER, nil, "else1 #{strict_values}" if Familia.debug?
|
68
|
+
|
69
|
+
if value_to_distinguish.class.ancestors.member?(Familia::Base)
|
70
|
+
Familia.trace :TOREDIS_DISTINGUISHER, nil, 'isabase' if Familia.debug?
|
71
|
+
|
72
|
+
value_to_distinguish.identifier
|
73
|
+
|
74
|
+
else
|
75
|
+
Familia.trace :TOREDIS_DISTINGUISHER, nil, "else2 #{strict_values}" if Familia.debug?
|
76
|
+
raise Familia::NotDistinguishableError, value_to_distinguish if strict_values
|
77
|
+
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
extend Distinguisher
|
85
|
+
end
|