familia 2.0.0.pre19 → 2.0.0.pre22
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/.talismanrc +5 -1
- data/CHANGELOG.rst +220 -112
- data/CLAUDE.md +28 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +20 -17
- data/bin/try +16 -0
- data/bin/tryouts +16 -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 +161 -117
- 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-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 +4 -3
- 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 +2 -0
- data/lib/familia/connection/handlers.rb +2 -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 -2
- data/lib/familia/connection/operations.rb +2 -0
- data/lib/familia/connection/pipelined_core.rb +3 -3
- data/lib/familia/connection/transaction_core.rb +69 -2
- data/lib/familia/connection.rb +18 -3
- data/lib/familia/data_type/class_methods.rb +3 -1
- data/lib/familia/data_type/connection.rb +2 -0
- data/lib/familia/data_type/database_commands.rb +2 -0
- data/lib/familia/data_type/serialization.rb +79 -52
- 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 +7 -5
- 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 +7 -10
- data/lib/familia/data_type/types/stringkey.rb +24 -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 +2 -0
- 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 +3 -1
- data/lib/familia/features/expiration.rb +12 -4
- data/lib/familia/features/external_identifier.rb +62 -7
- data/lib/familia/features/object_identifier.rb +49 -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 +138 -9
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +97 -21
- data/lib/familia/features/relationships/indexing.rb +3 -0
- data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
- 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 +3 -1
- data/lib/familia/horreum/connection.rb +17 -1
- data/lib/familia/horreum/database_commands.rb +8 -1
- data/lib/familia/horreum/definition.rb +16 -6
- data/lib/familia/horreum/management.rb +353 -52
- data/lib/familia/horreum/persistence.rb +179 -108
- data/lib/familia/horreum/related_fields.rb +2 -0
- data/lib/familia/horreum/serialization.rb +23 -4
- data/lib/familia/horreum/settings.rb +2 -0
- data/lib/familia/horreum/utils.rb +2 -0
- data/lib/familia/horreum.rb +15 -1
- data/lib/familia/identifier_extractor.rb +3 -1
- data/lib/familia/instrumentation.rb +156 -0
- data/lib/familia/json_serializer.rb +2 -0
- data/lib/familia/logging.rb +92 -32
- 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 +2 -0
- 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 +295 -170
- data/lib/multi_result.rb +61 -31
- 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 +2 -0
- data/try/edge_cases/ttl_side_effects_try.rb +4 -0
- data/try/features/count_any_edge_cases_try.rb +486 -0
- data/try/features/count_any_methods_try.rb +197 -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 +4 -0
- data/try/features/external_identifier/external_identifier_try.rb +305 -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 +140 -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 +606 -0
- data/try/features/relationships/indexing_try.rb +2 -0
- 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 +2 -0
- 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 +4 -0
- 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 +4 -0
- data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
- 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 +4 -0
- data/try/integration/cross_component_try.rb +4 -0
- data/try/integration/data_types/datatype_pipelines_try.rb +9 -3
- data/try/integration/data_types/datatype_transactions_try.rb +17 -7
- 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 +4 -0
- data/try/integration/models/customer_try.rb +7 -3
- data/try/integration/models/datatype_base_try.rb +4 -0
- data/try/integration/models/familia_object_try.rb +4 -0
- data/try/integration/persistence_operations_try.rb +4 -0
- 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 +39 -22
- 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 +6 -2
- 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/serialization_try.rb +386 -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 +4 -0
- 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 +5 -1
- data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
- data/try/unit/horreum/base_try.rb +4 -0
- data/try/unit/horreum/class_methods_try.rb +4 -0
- 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 +6 -1
- 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 +4 -0
- 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 +4 -0
- data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
- data/try/unit/horreum/serialization_try.rb +4 -0
- data/try/unit/horreum/settings_try.rb +4 -0
- data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
- data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -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 +69 -17
- data/.github/workflows/code-quality.yml +0 -138
- data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
- data/changelog.d/20251011_203905_delano_next.rst +0 -30
- data/changelog.d/20251011_212633_delano_next.rst +0 -13
- data/changelog.d/20251011_221253_delano_next.rst +0 -26
- 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
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/horreum/management.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
class Horreum
|
|
@@ -104,56 +106,71 @@ module Familia
|
|
|
104
106
|
# key.
|
|
105
107
|
#
|
|
106
108
|
# @param objkey [String] The full dbkey for the object.
|
|
109
|
+
# @param check_exists [Boolean] Whether to check key existence before HGETALL
|
|
110
|
+
# (default: true). When false, skips EXISTS check for better performance
|
|
111
|
+
# but still returns nil for non-existent keys (detected via empty hash).
|
|
107
112
|
# @return [Object, nil] An instance of the class if the key exists, nil
|
|
108
113
|
# otherwise.
|
|
109
114
|
# @raise [ArgumentError] If the provided key is empty.
|
|
110
115
|
#
|
|
111
|
-
# This method
|
|
112
|
-
# instantiate objects:
|
|
116
|
+
# This method can operate in two modes:
|
|
113
117
|
#
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
118
|
+
# **Safe mode (check_exists: true, default):**
|
|
119
|
+
# 1. First checks if the key exists with EXISTS command
|
|
120
|
+
# 2. Returns nil immediately if key doesn't exist
|
|
121
|
+
# 3. If exists, retrieves data with HGETALL and instantiates object
|
|
122
|
+
# - Best for: Single object lookups, defensive code
|
|
123
|
+
# - Commands: 2 per object (EXISTS + HGETALL)
|
|
119
124
|
#
|
|
120
|
-
#
|
|
121
|
-
#
|
|
125
|
+
# **Optimized mode (check_exists: false):**
|
|
126
|
+
# 1. Directly calls HGETALL without EXISTS check
|
|
127
|
+
# 2. Returns nil if HGETALL returns empty hash (key doesn't exist)
|
|
128
|
+
# 3. Otherwise instantiates object with returned data
|
|
129
|
+
# - Best for: Bulk operations, performance-critical paths, when keys likely exist
|
|
130
|
+
# - Commands: 1 per object (HGETALL only)
|
|
131
|
+
# - Reduction: 50% fewer Redis commands
|
|
122
132
|
#
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
# debugging.
|
|
133
|
+
# @example Safe mode (default)
|
|
134
|
+
# User.find_by_key("user:123") # 2 commands: EXISTS + HGETALL
|
|
126
135
|
#
|
|
127
|
-
# @example
|
|
128
|
-
# User.find_by_key("user:123") #
|
|
129
|
-
#
|
|
136
|
+
# @example Optimized mode (skip existence check)
|
|
137
|
+
# User.find_by_key("user:123", check_exists: false) # 1 command: HGETALL
|
|
138
|
+
#
|
|
139
|
+
# @note When check_exists: false, HGETALL on non-existent keys returns {}
|
|
140
|
+
# which we detect and return nil (not an empty object instance).
|
|
130
141
|
#
|
|
131
|
-
def find_by_dbkey(objkey)
|
|
142
|
+
def find_by_dbkey(objkey, check_exists: true)
|
|
132
143
|
raise ArgumentError, 'Empty key' if objkey.to_s.empty?
|
|
133
144
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
if check_exists
|
|
146
|
+
# Safe mode: Check existence first (original behavior)
|
|
147
|
+
# We use a lower-level method here b/c we're working with the
|
|
148
|
+
# full key and not just the identifier.
|
|
149
|
+
does_exist = dbclient.exists(objkey).positive?
|
|
150
|
+
|
|
151
|
+
Familia.debug "[find_by_key] #{self} from key #{objkey} (exists: #{does_exist})"
|
|
152
|
+
Familia.trace :FIND_BY_DBKEY_KEY, nil, objkey
|
|
153
|
+
|
|
154
|
+
# This is the reason for calling exists first. We want to definitively
|
|
155
|
+
# and without any ambiguity know if the object exists in the database. If it
|
|
156
|
+
# doesn't, we return nil. If it does, we proceed to load the object.
|
|
157
|
+
# Otherwise, hgetall will return an empty hash, which will be passed to
|
|
158
|
+
# the constructor, which will then be annoying to debug.
|
|
159
|
+
return unless does_exist
|
|
160
|
+
else
|
|
161
|
+
# Optimized mode: Skip existence check
|
|
162
|
+
Familia.debug "[find_by_key] #{self} from key #{objkey} (check_exists: false)"
|
|
163
|
+
Familia.trace :FIND_BY_DBKEY_KEY, nil, objkey
|
|
164
|
+
end
|
|
147
165
|
|
|
148
166
|
obj = dbclient.hgetall(objkey) # horreum objects are persisted as database hashes
|
|
149
167
|
Familia.trace :FIND_BY_DBKEY_INSPECT, nil, "#{objkey}: #{obj.inspect}"
|
|
150
168
|
|
|
151
|
-
#
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
instance
|
|
155
|
-
|
|
156
|
-
instance
|
|
169
|
+
# If we skipped existence check and got empty hash, key doesn't exist
|
|
170
|
+
return nil if !check_exists && obj.empty?
|
|
171
|
+
|
|
172
|
+
# Create instance and deserialize fields using shared helper method
|
|
173
|
+
instantiate_from_hash(obj)
|
|
157
174
|
end
|
|
158
175
|
alias find_by_key find_by_dbkey
|
|
159
176
|
|
|
@@ -161,8 +178,10 @@ module Familia
|
|
|
161
178
|
#
|
|
162
179
|
# @param identifier [String, Integer] The unique identifier for the
|
|
163
180
|
# object.
|
|
164
|
-
# @param suffix [Symbol] The suffix to use in the dbkey (default:
|
|
165
|
-
#
|
|
181
|
+
# @param suffix [Symbol, nil] The suffix to use in the dbkey (default:
|
|
182
|
+
# class suffix). Keyword parameter for consistency with check_exists.
|
|
183
|
+
# @param check_exists [Boolean] Whether to check key existence before HGETALL
|
|
184
|
+
# (default: true). See find_by_dbkey for details.
|
|
166
185
|
# @return [Object, nil] An instance of the class if found, nil otherwise.
|
|
167
186
|
#
|
|
168
187
|
# This method constructs the full dbkey using the provided identifier
|
|
@@ -173,23 +192,153 @@ module Familia
|
|
|
173
192
|
# making it easier to retrieve objects when you only have their
|
|
174
193
|
# identifier.
|
|
175
194
|
#
|
|
176
|
-
# @example
|
|
177
|
-
# User.find_by_id(123) #
|
|
195
|
+
# @example Safe mode (default)
|
|
196
|
+
# User.find_by_id(123) # 2 commands: EXISTS + HGETALL
|
|
197
|
+
#
|
|
198
|
+
# @example Optimized mode
|
|
199
|
+
# User.find_by_id(123, check_exists: false) # 1 command: HGETALL
|
|
178
200
|
#
|
|
179
|
-
|
|
201
|
+
# @example Custom suffix
|
|
202
|
+
# Session.find_by_id('abc', suffix: :session)
|
|
203
|
+
#
|
|
204
|
+
def find_by_identifier(identifier, suffix: nil, check_exists: true)
|
|
180
205
|
suffix ||= self.suffix
|
|
181
206
|
return nil if identifier.to_s.empty?
|
|
182
207
|
|
|
183
208
|
objkey = dbkey(identifier, suffix)
|
|
184
209
|
|
|
185
|
-
Familia.
|
|
210
|
+
Familia.debug "[find_by_id] #{self} from key #{objkey})"
|
|
186
211
|
Familia.trace :FIND_BY_ID, nil, objkey if Familia.debug?
|
|
187
|
-
find_by_dbkey objkey
|
|
212
|
+
find_by_dbkey objkey, check_exists: check_exists
|
|
188
213
|
end
|
|
189
214
|
alias find_by_id find_by_identifier
|
|
190
215
|
alias find find_by_id
|
|
191
216
|
alias load find_by_id
|
|
192
217
|
|
|
218
|
+
# Loads multiple objects by their identifiers using pipelined HGETALL commands.
|
|
219
|
+
#
|
|
220
|
+
# This method provides significant performance improvements for bulk loading by:
|
|
221
|
+
# 1. Batching all HGETALL commands into a single Redis pipeline
|
|
222
|
+
# 2. Eliminating network round-trip overhead
|
|
223
|
+
# 3. Skipping individual EXISTS checks (like check_exists: false)
|
|
224
|
+
#
|
|
225
|
+
# @param identifiers [Array<String, Integer>] Array of identifiers to load
|
|
226
|
+
# @param suffix [Symbol, nil] The suffix to use in dbkeys (default: class suffix)
|
|
227
|
+
# @return [Array<Object>] Array of instantiated objects (nils for non-existent)
|
|
228
|
+
#
|
|
229
|
+
# Performance characteristics:
|
|
230
|
+
# - Standard approach: N objects × 2 commands (EXISTS + HGETALL) = 2N round trips
|
|
231
|
+
# - check_exists: false: N objects × 1 command (HGETALL) = N round trips
|
|
232
|
+
# - load_multi: 1 pipeline with N commands = 1 round trip
|
|
233
|
+
# - Improvement: Up to 2N× faster for bulk operations
|
|
234
|
+
#
|
|
235
|
+
# @example Load multiple users efficiently
|
|
236
|
+
# users = User.load_multi([123, 456, 789])
|
|
237
|
+
# # 1 pipeline with 3 HGETALL commands instead of 6 individual commands
|
|
238
|
+
#
|
|
239
|
+
# @example Filter out nils
|
|
240
|
+
# existing_users = User.load_multi(ids).compact
|
|
241
|
+
#
|
|
242
|
+
# @note Returns nil for non-existent keys (maintains same contract as find_by_id)
|
|
243
|
+
# @note Objects are returned in the same order as input identifiers
|
|
244
|
+
# @note Empty/nil identifiers are skipped and return nil in result array
|
|
245
|
+
#
|
|
246
|
+
def load_multi(identifiers, suffix = nil)
|
|
247
|
+
suffix ||= self.suffix
|
|
248
|
+
return [] if identifiers.empty?
|
|
249
|
+
|
|
250
|
+
# Build list of valid keys and track their original positions
|
|
251
|
+
valid_keys = []
|
|
252
|
+
valid_positions = []
|
|
253
|
+
|
|
254
|
+
identifiers.each_with_index do |identifier, idx|
|
|
255
|
+
next if identifier.to_s.empty?
|
|
256
|
+
|
|
257
|
+
valid_keys << dbkey(identifier, suffix)
|
|
258
|
+
valid_positions << idx
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
Familia.trace :LOAD_MULTI, nil, "Loading #{identifiers.size} objects" if Familia.debug?
|
|
262
|
+
|
|
263
|
+
# Pipeline all HGETALL commands
|
|
264
|
+
multi_result = pipelined do |pipeline|
|
|
265
|
+
valid_keys.each do |objkey|
|
|
266
|
+
pipeline.hgetall(objkey)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Extract results array from MultiResult
|
|
271
|
+
results = multi_result.results
|
|
272
|
+
|
|
273
|
+
# Map results back to original positions
|
|
274
|
+
objects = Array.new(identifiers.size)
|
|
275
|
+
valid_positions.each_with_index do |pos, result_idx|
|
|
276
|
+
obj_hash = results[result_idx]
|
|
277
|
+
|
|
278
|
+
# Skip empty hashes (non-existent keys)
|
|
279
|
+
next if obj_hash.nil? || obj_hash.empty?
|
|
280
|
+
|
|
281
|
+
# Instantiate object using shared helper method
|
|
282
|
+
objects[pos] = instantiate_from_hash(obj_hash)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
objects
|
|
286
|
+
end
|
|
287
|
+
alias load_batch load_multi
|
|
288
|
+
|
|
289
|
+
# Loads multiple objects by their full dbkeys using pipelined HGETALL commands.
|
|
290
|
+
#
|
|
291
|
+
# This is a lower-level variant of load_multi that works directly with dbkeys
|
|
292
|
+
# instead of identifiers. Useful when you already have the full keys.
|
|
293
|
+
#
|
|
294
|
+
# @param objkeys [Array<String>] Array of full dbkeys to load
|
|
295
|
+
# @return [Array<Object>] Array of instantiated objects (nils for non-existent)
|
|
296
|
+
#
|
|
297
|
+
# @example Load objects by full keys
|
|
298
|
+
# keys = ["user:123:object", "user:456:object"]
|
|
299
|
+
# users = User.load_multi_by_keys(keys)
|
|
300
|
+
#
|
|
301
|
+
# @note Returns nil for empty/nil keys, maintaining position alignment with input array
|
|
302
|
+
#
|
|
303
|
+
# @see load_multi For loading by identifiers
|
|
304
|
+
#
|
|
305
|
+
def load_multi_by_keys(objkeys)
|
|
306
|
+
return [] if objkeys.empty?
|
|
307
|
+
|
|
308
|
+
Familia.trace :LOAD_MULTI_BY_KEYS, nil, "Loading #{objkeys.size} objects" if Familia.debug?
|
|
309
|
+
|
|
310
|
+
# Track which positions have valid keys to maintain result array alignment
|
|
311
|
+
valid_positions = []
|
|
312
|
+
objkeys.each_with_index do |objkey, idx|
|
|
313
|
+
valid_positions << idx unless objkey.to_s.empty?
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Pipeline all HGETALL commands for valid keys
|
|
317
|
+
multi_result = pipelined do |pipeline|
|
|
318
|
+
objkeys.each do |objkey|
|
|
319
|
+
next if objkey.to_s.empty?
|
|
320
|
+
pipeline.hgetall(objkey)
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Extract results array from MultiResult
|
|
325
|
+
results = multi_result.results
|
|
326
|
+
|
|
327
|
+
# Map results back to original positions
|
|
328
|
+
objects = Array.new(objkeys.size)
|
|
329
|
+
valid_positions.each_with_index do |pos, result_idx|
|
|
330
|
+
obj_hash = results[result_idx]
|
|
331
|
+
|
|
332
|
+
# Skip empty hashes (non-existent keys)
|
|
333
|
+
next if obj_hash.nil? || obj_hash.empty?
|
|
334
|
+
|
|
335
|
+
# Instantiate object using shared helper method
|
|
336
|
+
objects[pos] = instantiate_from_hash(obj_hash)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
objects
|
|
340
|
+
end
|
|
341
|
+
|
|
193
342
|
# Checks if an object with the given identifier exists in the database.
|
|
194
343
|
#
|
|
195
344
|
# @param identifier [String, Integer] The unique identifier for the object.
|
|
@@ -234,7 +383,7 @@ module Familia
|
|
|
234
383
|
#
|
|
235
384
|
def destroy!(identifier, suffix = nil)
|
|
236
385
|
suffix ||= self.suffix
|
|
237
|
-
|
|
386
|
+
raise Familia::NoIdentifier, "#{self} requires non-empty identifier" if identifier.to_s.empty?
|
|
238
387
|
|
|
239
388
|
objkey = dbkey identifier, suffix
|
|
240
389
|
|
|
@@ -301,22 +450,174 @@ module Familia
|
|
|
301
450
|
def all(suffix = nil)
|
|
302
451
|
suffix ||= self.suffix
|
|
303
452
|
# objects that could not be parsed will be nil
|
|
304
|
-
|
|
453
|
+
find_keys(suffix).filter_map { |k| find_by_key(k) }
|
|
305
454
|
end
|
|
306
455
|
|
|
307
|
-
|
|
308
|
-
|
|
456
|
+
# Returns the number of tracked instances (fast, from instances sorted set).
|
|
457
|
+
#
|
|
458
|
+
# This method provides O(1) performance by querying the `instances` sorted set,
|
|
459
|
+
# which is automatically maintained when objects are created/destroyed through
|
|
460
|
+
# Familia. However, objects deleted outside Familia (e.g., direct Redis commands)
|
|
461
|
+
# may leave stale entries.
|
|
462
|
+
#
|
|
463
|
+
# @return [Integer] Number of instances in the instances sorted set
|
|
464
|
+
#
|
|
465
|
+
# @example
|
|
466
|
+
# User.create(email: 'test@example.com')
|
|
467
|
+
# User.count #=> 1
|
|
468
|
+
#
|
|
469
|
+
# @note For authoritative count, use {#scan_count} (production-safe) or {#keys_count} (blocking)
|
|
470
|
+
# @see #scan_count Production-safe authoritative count via SCAN
|
|
471
|
+
# @see #keys_count Blocking authoritative count via KEYS
|
|
472
|
+
# @see #instances The underlying sorted set
|
|
473
|
+
#
|
|
474
|
+
def count
|
|
475
|
+
instances.count
|
|
309
476
|
end
|
|
477
|
+
alias size count
|
|
478
|
+
alias length count
|
|
310
479
|
|
|
311
|
-
# Returns
|
|
312
|
-
#
|
|
313
|
-
#
|
|
480
|
+
# Returns authoritative count using blocking KEYS command (production-dangerous).
|
|
481
|
+
#
|
|
482
|
+
# ⚠️ WARNING: This method uses the KEYS command which blocks Redis during execution.
|
|
483
|
+
# It scans ALL keys in the database and should NEVER be used in production.
|
|
314
484
|
#
|
|
315
|
-
|
|
485
|
+
# @param filter [String] Key pattern to match (default: '*')
|
|
486
|
+
# @return [Integer] Number of matching keys in Redis
|
|
487
|
+
#
|
|
488
|
+
# @example
|
|
489
|
+
# User.keys_count #=> 1 (all User objects)
|
|
490
|
+
# User.keys_count('a*') #=> 1 (Users with IDs starting with 'a')
|
|
491
|
+
#
|
|
492
|
+
# @note For production-safe authoritative count, use {#scan_count}
|
|
493
|
+
# @see #scan_count Production-safe alternative using SCAN
|
|
494
|
+
# @see #count Fast count from instances sorted set
|
|
495
|
+
#
|
|
496
|
+
def keys_count(filter = '*')
|
|
316
497
|
dbclient.keys(dbkey(filter)).compact.size
|
|
317
498
|
end
|
|
318
|
-
|
|
319
|
-
|
|
499
|
+
|
|
500
|
+
# Returns authoritative count using non-blocking SCAN command (production-safe).
|
|
501
|
+
#
|
|
502
|
+
# This method uses cursor-based SCAN iteration to count matching keys without
|
|
503
|
+
# blocking Redis. Safe for production use as it processes keys in chunks.
|
|
504
|
+
#
|
|
505
|
+
# @param filter [String] Key pattern to match (default: '*')
|
|
506
|
+
# @return [Integer] Number of matching keys in Redis
|
|
507
|
+
#
|
|
508
|
+
# @example
|
|
509
|
+
# User.scan_count #=> 1 (all User objects)
|
|
510
|
+
# User.scan_count('a*') #=> 1 (Users with IDs starting with 'a')
|
|
511
|
+
#
|
|
512
|
+
# @note For fast count (potentially stale), use {#count}
|
|
513
|
+
# @see #count Fast count from instances sorted set
|
|
514
|
+
# @see #keys_count Blocking alternative (production-dangerous)
|
|
515
|
+
#
|
|
516
|
+
def scan_count(filter = '*')
|
|
517
|
+
pattern = dbkey(filter)
|
|
518
|
+
count = 0
|
|
519
|
+
cursor = "0"
|
|
520
|
+
|
|
521
|
+
loop do
|
|
522
|
+
cursor, keys = dbclient.scan(cursor, match: pattern, count: 1000)
|
|
523
|
+
count += keys.size
|
|
524
|
+
break if cursor == "0"
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
count
|
|
528
|
+
end
|
|
529
|
+
alias count! scan_count
|
|
530
|
+
|
|
531
|
+
# Checks if any tracked instances exist (fast, from instances sorted set).
|
|
532
|
+
#
|
|
533
|
+
# This method provides O(1) performance by querying the `instances` sorted set.
|
|
534
|
+
# However, objects deleted outside Familia may leave stale entries.
|
|
535
|
+
#
|
|
536
|
+
# @return [Boolean] true if instances sorted set is non-empty
|
|
537
|
+
#
|
|
538
|
+
# @example
|
|
539
|
+
# User.create(email: 'test@example.com')
|
|
540
|
+
# User.any? #=> true
|
|
541
|
+
#
|
|
542
|
+
# @note For authoritative check, use {#scan_any?} (production-safe) or {#keys_any?} (blocking)
|
|
543
|
+
# @see #scan_any? Production-safe authoritative check via SCAN
|
|
544
|
+
# @see #keys_any? Blocking authoritative check via KEYS
|
|
545
|
+
# @see #count Fast count of instances
|
|
546
|
+
#
|
|
547
|
+
def any?
|
|
548
|
+
count.positive?
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
# Checks if any objects exist using blocking KEYS command (production-dangerous).
|
|
552
|
+
#
|
|
553
|
+
# ⚠️ WARNING: This method uses the KEYS command which blocks Redis during execution.
|
|
554
|
+
# It scans ALL keys in the database and should NEVER be used in production.
|
|
555
|
+
#
|
|
556
|
+
# @param filter [String] Key pattern to match (default: '*')
|
|
557
|
+
# @return [Boolean] true if any matching keys exist in Redis
|
|
558
|
+
#
|
|
559
|
+
# @example
|
|
560
|
+
# User.keys_any? #=> true (any User objects)
|
|
561
|
+
# User.keys_any?('a*') #=> true (Users with IDs starting with 'a')
|
|
562
|
+
#
|
|
563
|
+
# @note For production-safe authoritative check, use {#scan_any?}
|
|
564
|
+
# @see #scan_any? Production-safe alternative using SCAN
|
|
565
|
+
# @see #any? Fast existence check from instances sorted set
|
|
566
|
+
#
|
|
567
|
+
def keys_any?(filter = '*')
|
|
568
|
+
keys_count(filter).positive?
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
# Checks if any objects exist using non-blocking SCAN command (production-safe).
|
|
572
|
+
#
|
|
573
|
+
# This method uses cursor-based SCAN iteration to check for matching keys without
|
|
574
|
+
# blocking Redis. Safe for production use and returns early on first match.
|
|
575
|
+
#
|
|
576
|
+
# @param filter [String] Key pattern to match (default: '*')
|
|
577
|
+
# @return [Boolean] true if any matching keys exist in Redis
|
|
578
|
+
#
|
|
579
|
+
# @example
|
|
580
|
+
# User.scan_any? #=> true (any User objects)
|
|
581
|
+
# User.scan_any?('a*') #=> true (Users with IDs starting with 'a')
|
|
582
|
+
#
|
|
583
|
+
# @note For fast check (potentially stale), use {#any?}
|
|
584
|
+
# @see #any? Fast existence check from instances sorted set
|
|
585
|
+
# @see #keys_any? Blocking alternative (production-dangerous)
|
|
586
|
+
#
|
|
587
|
+
def scan_any?(filter = '*')
|
|
588
|
+
pattern = dbkey(filter)
|
|
589
|
+
cursor = "0"
|
|
590
|
+
|
|
591
|
+
loop do
|
|
592
|
+
cursor, keys = dbclient.scan(cursor, match: pattern, count: 100)
|
|
593
|
+
return true unless keys.empty?
|
|
594
|
+
break if cursor == "0"
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
false
|
|
598
|
+
end
|
|
599
|
+
alias any! scan_any?
|
|
600
|
+
|
|
601
|
+
# Instantiates an object from a hash of field values.
|
|
602
|
+
#
|
|
603
|
+
# This is an internal helper method used by find_by_dbkey, load_multi, and
|
|
604
|
+
# load_multi_by_keys to eliminate code duplication. Not intended for direct use.
|
|
605
|
+
#
|
|
606
|
+
# @param obj_hash [Hash] Hash of field names to serialized values from Redis
|
|
607
|
+
# @return [Object] Instantiated object with deserialized fields
|
|
608
|
+
#
|
|
609
|
+
# @note This method:
|
|
610
|
+
# 1. Allocates a new instance without calling initialize
|
|
611
|
+
# 2. Initializes related DataType fields
|
|
612
|
+
# 3. Deserializes and assigns field values from the hash
|
|
613
|
+
#
|
|
614
|
+
# @api private
|
|
615
|
+
def instantiate_from_hash(obj_hash)
|
|
616
|
+
instance = allocate
|
|
617
|
+
instance.send(:initialize_relatives)
|
|
618
|
+
instance.send(:initialize_with_keyword_args_deserialize_value, **obj_hash)
|
|
619
|
+
instance
|
|
620
|
+
end
|
|
320
621
|
end
|
|
321
622
|
end
|
|
322
623
|
end
|