familia 2.0.0.pre18 → 2.0.0.pre21
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/claude-code-review.yml +4 -9
- data/.github/workflows/code-smells.yml +64 -3
- data/.pre-commit-config.yaml +8 -6
- data/.reek.yml +10 -9
- data/.rubocop.yml +4 -0
- data/CHANGELOG.rst +205 -88
- data/CLAUDE.md +62 -10
- data/Gemfile +3 -3
- data/Gemfile.lock +27 -62
- data/README.md +39 -0
- data/bin/try +16 -0
- data/bin/tryouts +16 -0
- data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
- data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
- data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
- data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
- data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
- data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -0
- data/docs/1106-participates_in-bidirectional-solution.md +129 -0
- data/docs/guides/encryption.md +486 -0
- data/docs/guides/feature-encrypted-fields.md +123 -7
- data/docs/guides/feature-expiration.md +177 -133
- data/docs/guides/feature-external-identifiers.md +415 -443
- data/docs/guides/feature-object-identifiers.md +400 -269
- data/docs/guides/feature-quantization.md +120 -6
- data/docs/guides/feature-relationships-indexing.md +318 -0
- data/docs/guides/feature-relationships-methods.md +146 -604
- data/docs/guides/feature-relationships-participation.md +263 -0
- data/docs/guides/feature-relationships.md +118 -136
- data/docs/guides/feature-system-devs.md +176 -693
- data/docs/guides/feature-system.md +119 -6
- data/docs/guides/feature-transient-fields.md +81 -0
- data/docs/guides/field-system.md +778 -0
- data/docs/guides/index.md +32 -15
- data/docs/guides/logging.md +187 -0
- data/docs/guides/optimized-loading.md +674 -0
- data/docs/guides/thread-safety-monitoring.md +61 -0
- data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
- data/docs/migrating/v2.0.0-pre19.md +197 -0
- data/docs/migrating/v2.0.0-pre22.md +241 -0
- data/docs/overview.md +7 -9
- data/docs/reference/api-technical.md +267 -320
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
- data/examples/autoloader/mega_customer.rb +2 -0
- data/examples/datatype_standalone.rb +282 -0
- data/examples/encrypted_fields.rb +2 -1
- data/examples/json_usage_patterns.rb +2 -0
- data/examples/relationships.rb +3 -0
- data/examples/safe_dump.rb +2 -1
- data/examples/sampling_demo.rb +53 -0
- data/examples/single_connection_transaction_confusions.rb +2 -1
- data/familia.gemspec +2 -1
- data/lib/familia/base.rb +2 -0
- data/lib/familia/connection/behavior.rb +254 -0
- data/lib/familia/connection/handlers.rb +97 -0
- data/lib/familia/connection/individual_command_proxy.rb +2 -0
- data/lib/familia/connection/middleware.rb +34 -24
- data/lib/familia/connection/operation_core.rb +3 -1
- data/lib/familia/connection/operations.rb +2 -0
- data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +4 -2
- data/lib/familia/connection/transaction_core.rb +75 -9
- data/lib/familia/connection.rb +21 -5
- data/lib/familia/data_type/class_methods.rb +3 -1
- data/lib/familia/data_type/connection.rb +153 -7
- data/lib/familia/data_type/database_commands.rb +9 -4
- data/lib/familia/data_type/serialization.rb +10 -4
- data/lib/familia/data_type/settings.rb +2 -0
- data/lib/familia/data_type/types/counter.rb +2 -0
- data/lib/familia/data_type/types/hashkey.rb +8 -6
- data/lib/familia/data_type/types/listkey.rb +2 -0
- data/lib/familia/data_type/types/lock.rb +2 -0
- data/lib/familia/data_type/types/sorted_set.rb +2 -0
- data/lib/familia/data_type/types/stringkey.rb +2 -0
- data/lib/familia/data_type/types/unsorted_set.rb +2 -0
- data/lib/familia/data_type.rb +2 -0
- data/lib/familia/encryption/encrypted_data.rb +4 -2
- data/lib/familia/encryption/manager.rb +2 -0
- data/lib/familia/encryption/provider.rb +2 -0
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
- data/lib/familia/encryption/registry.rb +2 -0
- data/lib/familia/encryption/request_cache.rb +2 -0
- data/lib/familia/encryption.rb +9 -2
- data/lib/familia/errors.rb +53 -14
- data/lib/familia/features/autoloader.rb +2 -0
- data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
- data/lib/familia/features/encrypted_fields.rb +2 -2
- data/lib/familia/features/expiration/extensions.rb +11 -11
- data/lib/familia/features/expiration.rb +29 -21
- data/lib/familia/features/external_identifier.rb +33 -7
- data/lib/familia/features/object_identifier.rb +2 -0
- data/lib/familia/features/quantization.rb +3 -1
- data/lib/familia/features/relationships/README.md +3 -1
- data/lib/familia/features/relationships/collection_operations.rb +2 -0
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +177 -47
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +203 -63
- data/lib/familia/features/relationships/indexing.rb +40 -42
- data/lib/familia/features/relationships/indexing_relationship.rb +17 -5
- data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
- data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
- data/lib/familia/features/relationships/participation.rb +155 -69
- data/lib/familia/features/relationships/participation_membership.rb +69 -0
- data/lib/familia/features/relationships/participation_relationship.rb +34 -6
- data/lib/familia/features/relationships/score_encoding.rb +2 -0
- data/lib/familia/features/relationships.rb +5 -3
- data/lib/familia/features/safe_dump.rb +2 -0
- data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
- data/lib/familia/features/transient_fields.rb +2 -0
- data/lib/familia/features.rb +2 -0
- data/lib/familia/field_type.rb +5 -2
- data/lib/familia/horreum/connection.rb +28 -36
- data/lib/familia/horreum/database_commands.rb +131 -10
- data/lib/familia/horreum/definition.rb +18 -7
- data/lib/familia/horreum/management.rb +233 -57
- data/lib/familia/horreum/persistence.rb +314 -122
- data/lib/familia/horreum/related_fields.rb +2 -0
- data/lib/familia/horreum/serialization.rb +26 -4
- data/lib/familia/horreum/settings.rb +2 -0
- data/lib/familia/horreum/utils.rb +2 -8
- data/lib/familia/horreum.rb +46 -13
- data/lib/familia/identifier_extractor.rb +2 -0
- data/lib/familia/instrumentation.rb +156 -0
- data/lib/familia/json_serializer.rb +2 -0
- data/lib/familia/logging.rb +94 -37
- data/lib/familia/refinements/dear_json.rb +2 -0
- data/lib/familia/refinements/stylize_words.rb +2 -14
- data/lib/familia/refinements/time_literals.rb +2 -0
- data/lib/familia/refinements.rb +2 -0
- data/lib/familia/secure_identifier.rb +10 -2
- data/lib/familia/settings.rb +9 -7
- data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
- data/lib/familia/thread_safety/monitor.rb +328 -0
- data/lib/familia/utils.rb +13 -0
- data/lib/familia/verifiable_identifier.rb +3 -1
- data/lib/familia/version.rb +3 -1
- data/lib/familia.rb +31 -4
- data/lib/middleware/database_command_counter.rb +152 -0
- data/lib/middleware/database_logger.rb +325 -129
- data/lib/multi_result.rb +2 -0
- data/try/edge_cases/empty_identifiers_try.rb +2 -0
- data/try/edge_cases/hash_symbolization_try.rb +2 -0
- data/try/edge_cases/json_serialization_try.rb +2 -0
- data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
- data/try/edge_cases/race_conditions_try.rb +4 -0
- data/try/edge_cases/reserved_keywords_try.rb +4 -0
- data/try/edge_cases/string_coercion_try.rb +6 -4
- data/try/edge_cases/ttl_side_effects_try.rb +4 -0
- data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
- data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
- data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
- data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
- data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
- data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
- data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
- data/try/features/encrypted_fields/memory_security_try.rb +4 -0
- data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
- data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
- data/try/features/encryption/config_persistence_try.rb +4 -0
- data/try/features/encryption/core_try.rb +4 -0
- data/try/features/encryption/instance_variable_scope_try.rb +4 -0
- data/try/features/encryption/module_loading_try.rb +4 -0
- data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
- data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
- data/try/features/encryption/roundtrip_validation_try.rb +4 -0
- data/try/features/encryption/secure_memory_handling_try.rb +4 -0
- data/try/features/expiration/expiration_try.rb +5 -1
- data/try/features/external_identifier/external_identifier_try.rb +171 -8
- data/try/features/feature_dependencies_try.rb +2 -0
- data/try/features/feature_improvements_try.rb +2 -0
- data/try/features/field_groups_try.rb +2 -0
- data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
- data/try/features/object_identifier/object_identifier_try.rb +2 -0
- data/try/features/quantization/quantization_try.rb +4 -0
- data/try/features/real_feature_integration_try.rb +2 -0
- data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
- data/try/features/relationships/indexing_rebuild_try.rb +600 -0
- data/try/features/relationships/indexing_try.rb +30 -4
- data/try/features/relationships/participation_bidirectional_try.rb +242 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
- data/try/features/relationships/participation_commands_verification_try.rb +2 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
- data/try/features/relationships/participation_reverse_index_try.rb +15 -13
- data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
- data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
- data/try/features/relationships/relationships_api_changes_try.rb +6 -4
- data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
- data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
- data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
- data/try/features/relationships/relationships_performance_try.rb +4 -0
- data/try/features/relationships/relationships_performance_working_try.rb +4 -0
- data/try/features/relationships/relationships_try.rb +6 -4
- data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
- data/try/features/safe_dump/safe_dump_try.rb +4 -0
- data/try/features/transient_fields/redacted_string_try.rb +2 -0
- data/try/features/transient_fields/refresh_reset_try.rb +3 -0
- data/try/features/transient_fields/simple_refresh_test.rb +3 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
- data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
- data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
- data/try/integration/connection/fiber_context_preservation_try.rb +7 -3
- data/try/integration/connection/handler_constraints_try.rb +4 -0
- data/try/integration/connection/isolated_dbclient_try.rb +4 -0
- data/try/integration/connection/middleware_reconnect_try.rb +2 -0
- data/try/integration/connection/operation_mode_guards_try.rb +5 -1
- data/try/integration/connection/pipeline_fallback_integration_try.rb +15 -12
- data/try/integration/connection/pools_try.rb +4 -0
- data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
- data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
- data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
- data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
- data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
- data/try/integration/connection/transaction_modes_try.rb +4 -0
- data/try/integration/conventional_inheritance_try.rb +4 -0
- data/try/integration/create_method_try.rb +26 -22
- data/try/integration/cross_component_try.rb +4 -0
- data/try/integration/data_types/datatype_pipelines_try.rb +108 -0
- data/try/integration/data_types/datatype_transactions_try.rb +251 -0
- data/try/integration/database_consistency_try.rb +4 -0
- data/try/integration/familia_extended_try.rb +4 -0
- data/try/integration/familia_members_methods_try.rb +4 -0
- data/try/integration/models/customer_safe_dump_try.rb +9 -1
- data/try/integration/models/customer_try.rb +4 -0
- data/try/integration/models/datatype_base_try.rb +4 -0
- data/try/integration/models/familia_object_try.rb +5 -1
- data/try/integration/persistence_operations_try.rb +166 -10
- data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
- data/try/integration/save_methods_consistency_try.rb +241 -0
- data/try/integration/scenarios_try.rb +4 -0
- data/try/integration/secure_identifier_try.rb +4 -0
- data/try/integration/transaction_safety_core_try.rb +176 -0
- data/try/integration/transaction_safety_workflow_try.rb +291 -0
- data/try/integration/verifiable_identifier_try.rb +4 -0
- data/try/investigation/pipeline_routing/README.md +228 -0
- data/try/performance/benchmarks_try.rb +4 -0
- data/try/performance/transaction_safety_benchmark_try.rb +238 -0
- data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
- data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
- data/try/support/debugging/cache_behavior_tracer.rb +4 -0
- data/try/support/debugging/debug_aad_process.rb +3 -0
- data/try/support/debugging/debug_concealed_internal.rb +3 -0
- data/try/support/debugging/debug_concealed_reveal.rb +3 -0
- data/try/support/debugging/debug_context_aad.rb +3 -0
- data/try/support/debugging/debug_context_simple.rb +3 -0
- data/try/support/debugging/debug_cross_context.rb +3 -0
- data/try/support/debugging/debug_database_load.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
- data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
- data/try/support/debugging/debug_field_decrypt.rb +3 -0
- data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
- data/try/support/debugging/debug_load_path.rb +3 -0
- data/try/support/debugging/debug_method_definition.rb +3 -0
- data/try/support/debugging/debug_method_resolution.rb +3 -0
- data/try/support/debugging/debug_minimal.rb +3 -0
- data/try/support/debugging/debug_provider.rb +3 -0
- data/try/support/debugging/debug_secure_behavior.rb +3 -0
- data/try/support/debugging/debug_string_class.rb +3 -0
- data/try/support/debugging/debug_test.rb +3 -0
- data/try/support/debugging/debug_test_design.rb +3 -0
- data/try/support/debugging/encryption_method_tracer.rb +4 -0
- data/try/support/debugging/provider_diagnostics.rb +4 -0
- data/try/support/helpers/test_cleanup.rb +4 -0
- data/try/support/helpers/test_helpers.rb +5 -0
- data/try/support/memory/memory_basic_test.rb +4 -0
- data/try/support/memory/memory_detailed_test.rb +4 -0
- data/try/support/memory/memory_search_for_string.rb +4 -0
- data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
- data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
- data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
- data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
- data/try/support/prototypes/atomic_saves_v4.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
- data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
- data/try/support/prototypes/pooling/pool_siege.rb +4 -2
- data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
- data/try/thread_safety/README.md +496 -0
- data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
- data/try/thread_safety/connection_chain_race_try.rb +148 -0
- data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
- data/try/thread_safety/feature_registry_race_try.rb +226 -0
- data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
- data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
- data/try/thread_safety/field_registration_race_try.rb +222 -0
- data/try/thread_safety/logger_initialization_race_try.rb +170 -0
- data/try/thread_safety/middleware_registration_race_try.rb +154 -0
- data/try/thread_safety/module_config_race_try.rb +175 -0
- data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
- data/try/unit/core/autoloader_try.rb +4 -0
- data/try/unit/core/base_enhancements_try.rb +4 -0
- data/try/unit/core/connection_try.rb +4 -0
- data/try/unit/core/errors_try.rb +4 -0
- data/try/unit/core/extensions_try.rb +4 -0
- data/try/unit/core/familia_logger_try.rb +2 -0
- data/try/unit/core/familia_try.rb +4 -0
- data/try/unit/core/middleware_sampling_try.rb +335 -0
- data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
- data/try/unit/core/middleware_thread_safety_try.rb +245 -0
- data/try/unit/core/middleware_try.rb +4 -0
- data/try/unit/core/settings_try.rb +4 -0
- data/try/unit/core/time_utils_try.rb +4 -0
- data/try/unit/core/tools_try.rb +4 -0
- data/try/unit/core/utils_try.rb +37 -0
- data/try/unit/data_types/boolean_try.rb +5 -1
- data/try/unit/data_types/counter_try.rb +4 -0
- data/try/unit/data_types/datatype_base_try.rb +4 -0
- data/try/unit/data_types/hash_try.rb +4 -0
- data/try/unit/data_types/list_try.rb +4 -0
- data/try/unit/data_types/lock_try.rb +4 -0
- data/try/unit/data_types/sorted_set_try.rb +4 -0
- data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
- data/try/unit/data_types/string_try.rb +5 -1
- data/try/unit/data_types/unsortedset_try.rb +4 -0
- data/try/unit/familia_resolve_class_try.rb +116 -0
- data/try/unit/horreum/auto_indexing_on_save_try.rb +36 -16
- data/try/unit/horreum/automatic_index_validation_try.rb +255 -0
- data/try/unit/horreum/base_try.rb +5 -1
- data/try/unit/horreum/class_methods_try.rb +6 -2
- data/try/unit/horreum/commands_try.rb +4 -0
- data/try/unit/horreum/defensive_initialization_try.rb +4 -0
- data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +4 -0
- data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
- data/try/unit/horreum/field_categories_try.rb +4 -0
- data/try/unit/horreum/field_definition_try.rb +4 -0
- data/try/unit/horreum/initialization_try.rb +5 -1
- data/try/unit/horreum/json_type_preservation_try.rb +2 -0
- data/try/unit/horreum/optimized_loading_try.rb +156 -0
- data/try/unit/horreum/relations_try.rb +8 -4
- data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
- data/try/unit/horreum/serialization_try.rb +6 -2
- data/try/unit/horreum/settings_try.rb +4 -0
- data/try/unit/horreum/unique_index_edge_cases_try.rb +380 -0
- data/try/unit/horreum/unique_index_guard_validation_try.rb +283 -0
- data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
- data/try/unit/middleware/database_logger_methods_try.rb +251 -0
- data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
- data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
- data/try/unit/thread_safety_monitor_try.rb +149 -0
- metadata +81 -14
- data/.github/workflows/code-quality.yml +0 -138
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
- data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
- data/docs/archive/FAMILIA_UPDATE.md +0 -226
- data/docs/archive/README.md +0 -64
- data/docs/archive/api-reference.md +0 -333
- data/docs/guides/core-field-system.md +0 -806
- data/docs/guides/implementation.md +0 -276
- data/docs/guides/security-model.md +0 -183
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# lib/familia/features/relationships/participation_membership.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
module Familia
|
|
6
|
+
module Features
|
|
7
|
+
module Relationships
|
|
8
|
+
#
|
|
9
|
+
# ParticipationMembership
|
|
10
|
+
#
|
|
11
|
+
# Represents runtime snapshot of a participant's membership in a target collection.
|
|
12
|
+
# Returned by current_participations to provide a type-safe, structured view
|
|
13
|
+
# of actual participation state.
|
|
14
|
+
#
|
|
15
|
+
# @note This represents what currently exists in Redis, not just configuration.
|
|
16
|
+
# See ParticipationRelationship for static configuration metadata.
|
|
17
|
+
#
|
|
18
|
+
# @example
|
|
19
|
+
# membership = user.current_participations.first
|
|
20
|
+
# membership.target_class # => "Team"
|
|
21
|
+
# membership.target_id # => "team123"
|
|
22
|
+
# membership.collection_name # => :members
|
|
23
|
+
# membership.type # => :sorted_set
|
|
24
|
+
# membership.score # => 1762554020.05
|
|
25
|
+
#
|
|
26
|
+
ParticipationMembership = Data.define(
|
|
27
|
+
:target_class, # String - class name (e.g., "Customer")
|
|
28
|
+
:target_id, # String - target instance identifier
|
|
29
|
+
:collection_name, # Symbol - collection name (e.g., :domains)
|
|
30
|
+
:type, # Symbol - collection type (:sorted_set, :set, :list)
|
|
31
|
+
:score, # Float - optional, for sorted_set only
|
|
32
|
+
:decoded_score, # Hash - optional, decoded score data
|
|
33
|
+
:position # Integer - optional, for list only
|
|
34
|
+
) do
|
|
35
|
+
# Check if this membership is a sorted set
|
|
36
|
+
# @return [Boolean]
|
|
37
|
+
def sorted_set?
|
|
38
|
+
type == :sorted_set
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Check if this membership is a set
|
|
42
|
+
# @return [Boolean]
|
|
43
|
+
def set?
|
|
44
|
+
type == :set
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Check if this membership is a list
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
def list?
|
|
50
|
+
type == :list
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Get the target instance (requires loading from database)
|
|
54
|
+
# @return [Familia::Horreum, nil] the loaded target instance
|
|
55
|
+
def target_instance
|
|
56
|
+
return nil unless target_class
|
|
57
|
+
|
|
58
|
+
# Resolve class from string name
|
|
59
|
+
# Only rescue NameError (class doesn't exist), not all exceptions
|
|
60
|
+
klass = Object.const_get(target_class)
|
|
61
|
+
klass.find_by_id(target_id)
|
|
62
|
+
rescue NameError
|
|
63
|
+
# Target class doesn't exist or isn't loaded
|
|
64
|
+
nil
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/features/relationships/participation_relationship.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
module Features
|
|
@@ -10,20 +12,46 @@ module Familia
|
|
|
10
12
|
# Used to configure code generation and runtime behavior for participates_in
|
|
11
13
|
# and class_participates_in declarations.
|
|
12
14
|
#
|
|
15
|
+
# @note target_class is resolved once at definition time for performance.
|
|
16
|
+
# Use _original_target for debugging/introspection to see what was passed.
|
|
17
|
+
#
|
|
13
18
|
ParticipationRelationship = Data.define(
|
|
14
|
-
:
|
|
19
|
+
:_original_target, # Original Symbol/String/Class as passed to participates_in
|
|
20
|
+
:target_class, # Resolved Class object (e.g., User class, not :User symbol)
|
|
15
21
|
:collection_name, # Symbol name of the collection (e.g., :members, :domains)
|
|
16
22
|
:score, # Proc/Symbol/nil - score calculator for sorted sets
|
|
17
23
|
:type, # Symbol - collection type (:sorted_set, :set, :list)
|
|
18
|
-
:bidirectional,
|
|
24
|
+
:bidirectional, # Boolean/Symbol - whether to generate reverse methods
|
|
19
25
|
) do
|
|
26
|
+
# Get a unique key for this participation relationship
|
|
27
|
+
# Useful for comparisons and hash keys
|
|
20
28
|
#
|
|
21
|
-
#
|
|
29
|
+
# @return [String] unique identifier in format "TargetClass:collection_name"
|
|
30
|
+
def unique_key
|
|
31
|
+
Familia.join(target_class_base, collection_name)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Get the base class name without namespace
|
|
35
|
+
# Handles anonymous class wrappers like "#<Class:0x123>::SymbolResolutionCustomer"
|
|
22
36
|
#
|
|
23
|
-
# @return [String]
|
|
37
|
+
# @return [String] base class name (e.g., "Customer")
|
|
38
|
+
def target_class_base
|
|
39
|
+
target_class.name.split('::').last
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Check if this relationship matches the given target and collection
|
|
43
|
+
# Handles namespace-agnostic class comparison
|
|
24
44
|
#
|
|
25
|
-
|
|
26
|
-
|
|
45
|
+
# @param comparison_target [Class, String, Symbol] target to compare against
|
|
46
|
+
# @param comparison_collection [Symbol, String] collection name to compare
|
|
47
|
+
# @return [Boolean] true if both target and collection match
|
|
48
|
+
def matches?(comparison_target, comparison_collection)
|
|
49
|
+
# Normalize comparison target to base class name
|
|
50
|
+
comparison_target = comparison_target.name if comparison_target.is_a?(Class)
|
|
51
|
+
comparison_target_base = comparison_target.to_s.split('::').last
|
|
52
|
+
|
|
53
|
+
target_class_base == comparison_target_base &&
|
|
54
|
+
collection_name == comparison_collection.to_sym
|
|
27
55
|
end
|
|
28
56
|
end
|
|
29
57
|
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/features/relationships.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require 'securerandom'
|
|
4
6
|
require_relative 'relationships/score_encoding'
|
|
@@ -84,7 +86,7 @@ module Familia
|
|
|
84
86
|
|
|
85
87
|
# Feature initialization
|
|
86
88
|
def self.included(base)
|
|
87
|
-
Familia.
|
|
89
|
+
Familia.debug "[#{base}] Relationships included"
|
|
88
90
|
base.extend ModelClassMethods
|
|
89
91
|
base.include ModelInstanceMethods
|
|
90
92
|
|
|
@@ -157,7 +159,7 @@ module Familia
|
|
|
157
159
|
def create_temp_key(base_name, ttl = 300)
|
|
158
160
|
timestamp = Familia.now.to_i
|
|
159
161
|
random_suffix = SecureRandom.hex(3)
|
|
160
|
-
temp_key =
|
|
162
|
+
temp_key = Familia.join('temp', base_name, timestamp, random_suffix)
|
|
161
163
|
|
|
162
164
|
# UnsortedSet immediate expiry to ensure cleanup even if operation fails
|
|
163
165
|
if respond_to?(:dbclient)
|
|
@@ -256,7 +258,7 @@ module Familia
|
|
|
256
258
|
def create_temp_key(base_name, ttl = 300)
|
|
257
259
|
timestamp = Familia.now.to_i
|
|
258
260
|
random_suffix = SecureRandom.hex(3)
|
|
259
|
-
temp_key =
|
|
261
|
+
temp_key = Familia.join('temp', base_name, timestamp, random_suffix)
|
|
260
262
|
|
|
261
263
|
# UnsortedSet immediate expiry to ensure cleanup even if operation fails
|
|
262
264
|
dbclient.expire(temp_key, ttl)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/features/transient_fields/transient_field_type.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require 'familia/field_type'
|
|
4
6
|
|
|
@@ -80,7 +82,7 @@ module Familia
|
|
|
80
82
|
#
|
|
81
83
|
def define_fast_writer(_klass)
|
|
82
84
|
# No fast writer for transient fields since they're not persisted
|
|
83
|
-
Familia.
|
|
85
|
+
Familia.debug "[TransientFieldType] Skipping fast writer for transient field: #{@name}"
|
|
84
86
|
nil
|
|
85
87
|
end
|
|
86
88
|
|
|
@@ -117,7 +119,7 @@ module Familia
|
|
|
117
119
|
#
|
|
118
120
|
def serialize(_value, _record = nil)
|
|
119
121
|
# Transient fields should never be serialized
|
|
120
|
-
Familia.
|
|
122
|
+
Familia.debug "[TransientFieldType] WARNING: serialize called on transient field #{@name}"
|
|
121
123
|
nil
|
|
122
124
|
end
|
|
123
125
|
|
|
@@ -132,7 +134,7 @@ module Familia
|
|
|
132
134
|
#
|
|
133
135
|
def deserialize(_value, _record = nil)
|
|
134
136
|
# Transient fields should never be deserialized
|
|
135
|
-
Familia.
|
|
137
|
+
Familia.debug "[TransientFieldType] WARNING: deserialize called on transient field #{@name}"
|
|
136
138
|
nil
|
|
137
139
|
end
|
|
138
140
|
end
|
data/lib/familia/features.rb
CHANGED
data/lib/familia/field_type.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/field_type.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
# Base class for all field types in Familia
|
|
@@ -143,7 +145,8 @@ module Familia
|
|
|
143
145
|
val = args.first
|
|
144
146
|
|
|
145
147
|
# If no value provided, return current stored value
|
|
146
|
-
|
|
148
|
+
# Handle Redis::Future objects during transactions
|
|
149
|
+
return hget(field_name) if val.nil? || val.is_a?(Redis::Future)
|
|
147
150
|
|
|
148
151
|
begin
|
|
149
152
|
# Trace the operation if debugging is enabled
|
|
@@ -151,7 +154,7 @@ module Familia
|
|
|
151
154
|
|
|
152
155
|
# Convert value for database storage
|
|
153
156
|
prepared = serialize_value(val)
|
|
154
|
-
Familia.
|
|
157
|
+
Familia.debug "[FieldType#define_fast_writer] #{fast_method_name} val: #{val.class} prepared: #{prepared.class}"
|
|
155
158
|
|
|
156
159
|
# Use the setter method to update instance variable
|
|
157
160
|
send(:"#{method_name}=", val) if method_name
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/horreum/connection.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
class Horreum
|
|
@@ -6,40 +8,16 @@ module Familia
|
|
|
6
8
|
# Provides connection handling, transactions, and URI normalization for both
|
|
7
9
|
# class-level operations (e.g., Customer.dbclient) and instance-level operations
|
|
8
10
|
# (e.g., customer.dbclient)
|
|
11
|
+
#
|
|
12
|
+
# Includes shared connection behavior from Familia::Connection::Behavior, providing:
|
|
13
|
+
# - URI normalization (normalize_uri)
|
|
14
|
+
# - Connection creation (create_dbclient)
|
|
15
|
+
# - Transaction method signatures
|
|
16
|
+
# - Pipeline method signatures
|
|
9
17
|
module Connection
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# Normalizes various URI formats to a consistent URI object
|
|
13
|
-
# Considers the class/instance logical_database when uri is nil or Integer
|
|
14
|
-
def normalize_uri(uri)
|
|
15
|
-
case uri
|
|
16
|
-
when Integer
|
|
17
|
-
new_uri = Familia.uri.dup
|
|
18
|
-
new_uri.db = uri
|
|
19
|
-
new_uri
|
|
20
|
-
when ->(obj) { obj.is_a?(String) || obj.instance_of?(::String) }
|
|
21
|
-
URI.parse(uri)
|
|
22
|
-
when URI
|
|
23
|
-
uri
|
|
24
|
-
when nil
|
|
25
|
-
# Use logical_database if available, otherwise fall back to Familia.uri
|
|
26
|
-
if respond_to?(:logical_database) && logical_database
|
|
27
|
-
new_uri = Familia.uri.dup
|
|
28
|
-
new_uri.db = logical_database
|
|
29
|
-
new_uri
|
|
30
|
-
else
|
|
31
|
-
Familia.uri
|
|
32
|
-
end
|
|
33
|
-
else
|
|
34
|
-
raise ArgumentError, "Invalid URI type: #{uri.class.name}"
|
|
35
|
-
end
|
|
36
|
-
end
|
|
18
|
+
include Familia::Connection::Behavior
|
|
37
19
|
|
|
38
|
-
|
|
39
|
-
def create_dbclient(uri = nil)
|
|
40
|
-
parsed_uri = normalize_uri(uri)
|
|
41
|
-
Familia.create_dbclient(parsed_uri)
|
|
42
|
-
end
|
|
20
|
+
attr_reader :uri
|
|
43
21
|
|
|
44
22
|
# Returns the Database connection for the class using Chain of Responsibility pattern.
|
|
45
23
|
#
|
|
@@ -48,10 +26,19 @@ module Familia
|
|
|
48
26
|
# 2. DefaultConnectionHandler - Horreum model class-level @dbclient
|
|
49
27
|
# 3. GlobalFallbackHandler - Familia.dbclient(uri || logical_database) (global fallback)
|
|
50
28
|
#
|
|
29
|
+
# Thread-safe lazy initialization using double-checked locking to ensure
|
|
30
|
+
# only a single connection chain is built even under high concurrent load.
|
|
31
|
+
#
|
|
51
32
|
# @return [Redis] the Database connection instance.
|
|
52
33
|
#
|
|
53
34
|
def dbclient(uri = nil)
|
|
54
|
-
|
|
35
|
+
# Fast path: return existing chain if already initialized
|
|
36
|
+
return @class_connection_chain.handle(uri) if @class_connection_chain
|
|
37
|
+
|
|
38
|
+
# Slow path: thread-safe initialization
|
|
39
|
+
@class_connection_chain_mutex.synchronize do
|
|
40
|
+
@class_connection_chain ||= build_connection_chain
|
|
41
|
+
end
|
|
55
42
|
@class_connection_chain.handle(uri)
|
|
56
43
|
end
|
|
57
44
|
|
|
@@ -277,9 +264,9 @@ module Familia
|
|
|
277
264
|
@provider_connection_handler ||= Familia::Connection::ProviderConnectionHandler.new
|
|
278
265
|
|
|
279
266
|
# Determine the appropriate class context
|
|
280
|
-
# When called from instance: self is instance,
|
|
281
|
-
# When called from class:
|
|
282
|
-
klass =
|
|
267
|
+
# When called from instance: self is instance, use the model class connection
|
|
268
|
+
# When called from class: we'll use our own connection
|
|
269
|
+
klass = is_a?(Class) ? self : self.class
|
|
283
270
|
|
|
284
271
|
# Always check class first for @dbclient since instance-level connections were removed
|
|
285
272
|
@cached_connection_handler ||= Familia::Connection::CachedConnectionHandler.new(klass)
|
|
@@ -292,6 +279,11 @@ module Familia
|
|
|
292
279
|
.add_handler(@cached_connection_handler)
|
|
293
280
|
.add_handler(@create_connection_handler)
|
|
294
281
|
end
|
|
282
|
+
|
|
283
|
+
# Thread-safe mutex initialization when module is extended
|
|
284
|
+
def self.extended(base)
|
|
285
|
+
base.instance_variable_set(:@class_connection_chain_mutex, Mutex.new)
|
|
286
|
+
end
|
|
295
287
|
end
|
|
296
288
|
end
|
|
297
289
|
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/horreum/database_commands.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
# Familia::Horreum
|
|
@@ -16,6 +18,10 @@ module Familia
|
|
|
16
18
|
# just load the object again.
|
|
17
19
|
#
|
|
18
20
|
module DatabaseCommands
|
|
21
|
+
# Moves the object's key to a different logical database.
|
|
22
|
+
#
|
|
23
|
+
# @param logical_database [Integer] The target database number
|
|
24
|
+
# @return [Boolean] true if the key was moved successfully
|
|
19
25
|
def move(logical_database)
|
|
20
26
|
dbclient.move dbkey, logical_database
|
|
21
27
|
end
|
|
@@ -40,7 +46,18 @@ module Familia
|
|
|
40
46
|
key_exists = self.class.exists?(identifier)
|
|
41
47
|
return key_exists unless check_size
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
# Handle Redis::Future in transactions - skip size check
|
|
50
|
+
if key_exists.is_a?(Redis::Future)
|
|
51
|
+
return key_exists
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
current_size = size
|
|
55
|
+
# Handle Redis::Future from size call too
|
|
56
|
+
if current_size.is_a?(Redis::Future)
|
|
57
|
+
return current_size
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
key_exists && !current_size.zero?
|
|
44
61
|
end
|
|
45
62
|
|
|
46
63
|
# Returns the number of fields in the main object hash
|
|
@@ -55,6 +72,8 @@ module Familia
|
|
|
55
72
|
# automatically be deleted. Returns 1 if the timeout was set, 0 if key
|
|
56
73
|
# does not exist or the timeout could not be set.
|
|
57
74
|
#
|
|
75
|
+
# @param default_expiration [Integer] TTL in seconds (uses class default if nil)
|
|
76
|
+
# @return [Integer] 1 if timeout was set, 0 otherwise
|
|
58
77
|
def expire(default_expiration = nil)
|
|
59
78
|
default_expiration ||= self.class.default_expiration
|
|
60
79
|
Familia.trace :EXPIRE, nil, default_expiration if Familia.debug?
|
|
@@ -69,7 +88,7 @@ module Familia
|
|
|
69
88
|
# @return [Integer] The TTL of the key in seconds. Returns -1 if the key does not exist
|
|
70
89
|
# or has no associated expire time.
|
|
71
90
|
def current_expiration
|
|
72
|
-
Familia.trace :CURRENT_EXPIRATION, nil, uri if Familia.debug?
|
|
91
|
+
Familia.trace :CURRENT_EXPIRATION, nil, self.class.uri if Familia.debug?
|
|
73
92
|
dbclient.ttl dbkey
|
|
74
93
|
end
|
|
75
94
|
|
|
@@ -83,24 +102,38 @@ module Familia
|
|
|
83
102
|
end
|
|
84
103
|
alias remove remove_field # deprecated
|
|
85
104
|
|
|
105
|
+
# Returns the Redis data type of the key.
|
|
106
|
+
#
|
|
107
|
+
# @return [String] The data type (e.g., 'hash', 'string', 'list')
|
|
86
108
|
def data_type
|
|
87
|
-
Familia.trace :DATATYPE, nil, uri if Familia.debug?
|
|
109
|
+
Familia.trace :DATATYPE, nil, self.class.uri if Familia.debug?
|
|
88
110
|
dbclient.type dbkey(suffix)
|
|
89
111
|
end
|
|
90
112
|
|
|
91
|
-
#
|
|
113
|
+
# Returns all fields and values in the hash.
|
|
114
|
+
#
|
|
115
|
+
# @return [Hash] All field-value pairs in the hash
|
|
116
|
+
# @note For parity with DataType#hgetall
|
|
92
117
|
def hgetall
|
|
93
|
-
Familia.trace :HGETALL, nil, uri if Familia.debug?
|
|
118
|
+
Familia.trace :HGETALL, nil, self.class.uri if Familia.debug?
|
|
94
119
|
dbclient.hgetall dbkey(suffix)
|
|
95
120
|
end
|
|
96
121
|
alias all hgetall
|
|
97
122
|
|
|
123
|
+
# Gets the value of a hash field.
|
|
124
|
+
#
|
|
125
|
+
# @param field [String] The field name
|
|
126
|
+
# @return [String, nil] The value of the field, or nil if field doesn't exist
|
|
98
127
|
def hget(field)
|
|
99
128
|
Familia.trace :HGET, nil, field if Familia.debug?
|
|
100
129
|
dbclient.hget dbkey(suffix), field
|
|
101
130
|
end
|
|
102
131
|
|
|
103
|
-
#
|
|
132
|
+
# Sets the value of a hash field.
|
|
133
|
+
#
|
|
134
|
+
# @param field [String] The field name
|
|
135
|
+
# @param value [String] The value to set
|
|
136
|
+
# @return [Integer] The number of fields that were added to the hash. If the
|
|
104
137
|
# field already exists, this will return 0.
|
|
105
138
|
def hset(field, value)
|
|
106
139
|
Familia.trace :HSET, nil, field if Familia.debug?
|
|
@@ -120,51 +153,92 @@ module Familia
|
|
|
120
153
|
dbclient.hsetnx dbkey, field, value
|
|
121
154
|
end
|
|
122
155
|
|
|
156
|
+
# Sets multiple hash fields to multiple values.
|
|
157
|
+
#
|
|
158
|
+
# @param hsh [Hash] Hash of field-value pairs to set
|
|
159
|
+
# @return [String] 'OK' on success
|
|
123
160
|
def hmset(hsh = {})
|
|
124
161
|
hsh ||= to_h_for_storage
|
|
125
162
|
Familia.trace :HMSET, nil, hsh if Familia.debug?
|
|
126
163
|
dbclient.hmset dbkey(suffix), hsh
|
|
127
164
|
end
|
|
128
165
|
|
|
166
|
+
# Returns all field names in the hash.
|
|
167
|
+
#
|
|
168
|
+
# @return [Array<String>] Array of field names
|
|
129
169
|
def hkeys
|
|
130
|
-
Familia.trace :HKEYS, nil,
|
|
170
|
+
Familia.trace :HKEYS, nil, self.class.uri if Familia.debug?
|
|
131
171
|
dbclient.hkeys dbkey(suffix)
|
|
132
172
|
end
|
|
133
173
|
|
|
174
|
+
# Returns all values in the hash.
|
|
175
|
+
#
|
|
176
|
+
# @return [Array<String>] Array of values
|
|
134
177
|
def hvals
|
|
135
178
|
dbclient.hvals dbkey(suffix)
|
|
136
179
|
end
|
|
137
180
|
|
|
181
|
+
# Increments the integer value of a hash field by 1.
|
|
182
|
+
#
|
|
183
|
+
# @param field [String] The field name
|
|
184
|
+
# @return [Integer] The value after incrementing
|
|
138
185
|
def incr(field)
|
|
139
186
|
dbclient.hincrby dbkey(suffix), field, 1
|
|
140
187
|
end
|
|
141
188
|
alias increment incr
|
|
142
189
|
|
|
190
|
+
# Increments the integer value of a hash field by the given amount.
|
|
191
|
+
#
|
|
192
|
+
# @param field [String] The field name
|
|
193
|
+
# @param increment [Integer] The increment value
|
|
194
|
+
# @return [Integer] The value after incrementing
|
|
143
195
|
def incrby(field, increment)
|
|
144
196
|
dbclient.hincrby dbkey(suffix), field, increment
|
|
145
197
|
end
|
|
146
198
|
alias incrementby incrby
|
|
147
199
|
|
|
200
|
+
# Increments the float value of a hash field by the given amount.
|
|
201
|
+
#
|
|
202
|
+
# @param field [String] The field name
|
|
203
|
+
# @param increment [Float] The increment value
|
|
204
|
+
# @return [Float] The value after incrementing
|
|
148
205
|
def incrbyfloat(field, increment)
|
|
149
206
|
dbclient.hincrbyfloat dbkey(suffix), field, increment
|
|
150
207
|
end
|
|
151
208
|
alias incrementbyfloat incrbyfloat
|
|
152
209
|
|
|
210
|
+
# Decrements the integer value of a hash field by the given amount.
|
|
211
|
+
#
|
|
212
|
+
# @param field [String] The field name
|
|
213
|
+
# @param decrement [Integer] The decrement value
|
|
214
|
+
# @return [Integer] The value after decrementing
|
|
153
215
|
def decrby(field, decrement)
|
|
154
216
|
dbclient.decrby dbkey(suffix), field, decrement
|
|
155
217
|
end
|
|
156
218
|
alias decrementby decrby
|
|
157
219
|
|
|
220
|
+
# Decrements the integer value of a hash field by 1.
|
|
221
|
+
#
|
|
222
|
+
# @param field [String] The field name
|
|
223
|
+
# @return [Integer] The value after decrementing
|
|
158
224
|
def decr(field)
|
|
159
225
|
dbclient.hdecr field
|
|
160
226
|
end
|
|
161
227
|
alias decrement decr
|
|
162
228
|
|
|
229
|
+
# Returns the string length of the value associated with field in the hash.
|
|
230
|
+
#
|
|
231
|
+
# @param field [String] The field name
|
|
232
|
+
# @return [Integer] The string length of the field value, or 0 if field doesn't exist
|
|
163
233
|
def hstrlen(field)
|
|
164
234
|
dbclient.hstrlen dbkey(suffix), field
|
|
165
235
|
end
|
|
166
236
|
alias hstrlength hstrlen
|
|
167
237
|
|
|
238
|
+
# Determines if a hash field exists.
|
|
239
|
+
#
|
|
240
|
+
# @param field [String] The field name
|
|
241
|
+
# @return [Boolean] true if the field exists, false otherwise
|
|
168
242
|
def key?(field)
|
|
169
243
|
dbclient.hexists dbkey(suffix), field
|
|
170
244
|
end
|
|
@@ -176,14 +250,61 @@ module Familia
|
|
|
176
250
|
#
|
|
177
251
|
# @return [Boolean] true if the key was deleted, false otherwise
|
|
178
252
|
def delete!
|
|
179
|
-
Familia.trace :DELETE!, nil, uri if Familia.debug?
|
|
253
|
+
Familia.trace :DELETE!, nil, self.class.uri if Familia.debug?
|
|
180
254
|
|
|
181
255
|
# Delete the main object key
|
|
182
|
-
|
|
183
|
-
ret.positive?
|
|
256
|
+
dbclient.del dbkey
|
|
184
257
|
end
|
|
185
258
|
alias clear delete!
|
|
186
259
|
|
|
260
|
+
# Watches the key for changes during a MULTI/EXEC transaction.
|
|
261
|
+
#
|
|
262
|
+
# Decision Matrix:
|
|
263
|
+
#
|
|
264
|
+
# | Scenario | Use | Why |
|
|
265
|
+
# |----------|-----|-----|
|
|
266
|
+
# | Check if exists, then create | WATCH | Must prevent duplicate creation |
|
|
267
|
+
# | Read value, update conditionally | WATCH | Decision depends on current state |
|
|
268
|
+
# | Compare-and-swap operations | WATCH | Need optimistic locking |
|
|
269
|
+
# | Version-based updates | WATCH | Must detect concurrent changes |
|
|
270
|
+
# | Batch field updates | MULTI only | No conditional logic |
|
|
271
|
+
# | Increment + timestamp together | MULTI only | Concurrent increments OK |
|
|
272
|
+
# | Save object atomically | MULTI only | Just need atomicity |
|
|
273
|
+
# | Update indexes with save | MULTI only | No state checking needed |
|
|
274
|
+
#
|
|
275
|
+
# @param suffix_override [String, nil] Optional suffix override
|
|
276
|
+
# @return [String] 'OK' on success
|
|
277
|
+
def watch(...)
|
|
278
|
+
raise ArgumentError, 'Block required' unless block_given?
|
|
279
|
+
|
|
280
|
+
# Forward all arguments including the block to the watch command
|
|
281
|
+
dbclient.watch(dbkey, ...)
|
|
282
|
+
|
|
283
|
+
rescue Redis::BaseError => e
|
|
284
|
+
raise OptimisticLockError, "Redis error: #{e.message}"
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Flushes all the previously watched keys for a transaction.
|
|
288
|
+
#
|
|
289
|
+
# If a transaction completes successfully or discard is called, there's
|
|
290
|
+
# no need to manually call unwatch.
|
|
291
|
+
#
|
|
292
|
+
# NOTE: This command operates on the connection itself; not a specific key
|
|
293
|
+
#
|
|
294
|
+
# @return [String] 'OK' always, regardless of whether the key was watched or not
|
|
295
|
+
def unwatch(...) = dbclient.unwatch(...)
|
|
296
|
+
|
|
297
|
+
# Flushes all previously queued commands in a transaction and all watched keys
|
|
298
|
+
#
|
|
299
|
+
# NOTE: This command operates on the connection itself; not a specific key
|
|
300
|
+
#
|
|
301
|
+
# @return [String] 'OK' always
|
|
302
|
+
def discard(...) = dbclient.discard(...)
|
|
303
|
+
|
|
304
|
+
# Echoes a message through the Redis connection.
|
|
305
|
+
#
|
|
306
|
+
# @param args [Array] Arguments to join and echo
|
|
307
|
+
# @return [String] The echoed message
|
|
187
308
|
def echo(*args)
|
|
188
309
|
dbclient.echo "[#{self.class}] #{args.join(' ')}"
|
|
189
310
|
end
|