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/persistence.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
# Familia::Horreum
|
|
@@ -35,164 +37,172 @@ module Familia
|
|
|
35
37
|
# Handles conversion between Ruby objects and Valkey hash storage
|
|
36
38
|
#
|
|
37
39
|
module Persistence
|
|
38
|
-
# Persists
|
|
40
|
+
# Persists object state to storage with timestamps, validation, and indexing.
|
|
39
41
|
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
42
|
+
# Performs a complete save operation in an atomic transaction:
|
|
43
|
+
# - Sets created/updated timestamps
|
|
44
|
+
# - Validates unique index constraints
|
|
45
|
+
# - Persists all fields
|
|
46
|
+
# - Updates expiration (optional)
|
|
47
|
+
# - Updates class-level indexes
|
|
48
|
+
# - Adds to instances collection
|
|
44
49
|
#
|
|
45
|
-
#
|
|
46
|
-
# time after saving. Defaults to true.
|
|
50
|
+
# ## Transaction Safety
|
|
47
51
|
#
|
|
48
|
-
#
|
|
52
|
+
# This method CANNOT be called within a transaction context. The save process
|
|
53
|
+
# requires reading current state to validate unique constraints, which would
|
|
54
|
+
# return uninspectable Redis::Future objects inside transactions.
|
|
49
55
|
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
# for any class-level unique_index relationships.
|
|
56
|
+
# ### Correct Pattern:
|
|
57
|
+
# customer = Customer.new(email: 'test@example.com')
|
|
58
|
+
# customer.save # Validates unique constraints here
|
|
54
59
|
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
60
|
+
# customer.transaction do
|
|
61
|
+
# # Perform other atomic operations
|
|
62
|
+
# customer.increment(:login_count)
|
|
63
|
+
# customer.hset(:last_login, Time.now.to_i)
|
|
64
|
+
# end
|
|
59
65
|
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
66
|
+
# ### Incorrect Pattern:
|
|
67
|
+
# Customer.transaction do
|
|
68
|
+
# customer = Customer.new(email: 'test@example.com')
|
|
69
|
+
# customer.save # Raises Familia::OperationModeError
|
|
70
|
+
# end
|
|
63
71
|
#
|
|
64
|
-
# @
|
|
65
|
-
#
|
|
66
|
-
# user2.save
|
|
67
|
-
# # => raises Familia::RecordExistsError
|
|
72
|
+
# @param update_expiration [Boolean] Whether to refresh key expiration (default: true)
|
|
73
|
+
# @return [Boolean] true on success
|
|
68
74
|
#
|
|
69
|
-
# @
|
|
70
|
-
#
|
|
71
|
-
# within transactions.
|
|
75
|
+
# @raise [Familia::OperationModeError] If called within a transaction
|
|
76
|
+
# @raise [Familia::RecordExistsError] If unique index constraint violated
|
|
72
77
|
#
|
|
73
|
-
# @
|
|
74
|
-
#
|
|
78
|
+
# @example Basic usage
|
|
79
|
+
# user = User.new(email: "john@example.com")
|
|
80
|
+
# user.save # => true
|
|
75
81
|
#
|
|
76
|
-
# @see #
|
|
77
|
-
# @see #
|
|
82
|
+
# @see #save_if_not_exists! For conditional saves
|
|
83
|
+
# @see #transaction For atomic operations after save
|
|
78
84
|
#
|
|
79
85
|
def save(update_expiration: true)
|
|
86
|
+
start_time = Familia.now_in_μs if Familia.debug?
|
|
87
|
+
|
|
80
88
|
# Prevent save within transaction - unique index guards require read operations
|
|
81
89
|
# which are not available in Redis MULTI/EXEC blocks
|
|
82
90
|
if Fiber[:familia_transaction]
|
|
83
|
-
raise Familia::OperationModeError,
|
|
84
|
-
|
|
91
|
+
raise Familia::OperationModeError, <<~ERROR_MESSAGE
|
|
92
|
+
Cannot call save within a transaction. Save operations must be called outside transactions to ensure unique constraints can be validated.
|
|
93
|
+
ERROR_MESSAGE
|
|
85
94
|
end
|
|
86
95
|
|
|
87
96
|
Familia.trace :SAVE, nil, self.class.uri if Familia.debug?
|
|
88
97
|
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
self.updated = Familia.now if respond_to?(:updated)
|
|
92
|
-
|
|
93
|
-
# Validate unique indexes BEFORE the transaction
|
|
94
|
-
guard_unique_indexes!
|
|
98
|
+
# Prepare object for persistence (timestamps, validation)
|
|
99
|
+
prepare_for_save
|
|
95
100
|
|
|
96
101
|
# Everything in ONE transaction for complete atomicity
|
|
97
102
|
result = transaction do |_conn|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
hmset_result = hmset(prepared_h)
|
|
101
|
-
|
|
102
|
-
# 2. Set expiration in same transaction
|
|
103
|
-
self.update_expiration if update_expiration
|
|
104
|
-
|
|
105
|
-
# 3. Update class-level indexes
|
|
106
|
-
auto_update_class_indexes
|
|
103
|
+
persist_to_storage(update_expiration)
|
|
104
|
+
end
|
|
107
105
|
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
# Structured lifecycle logging and instrumentation
|
|
107
|
+
if Familia.debug? && start_time
|
|
108
|
+
duration = Familia.now_in_μs - start_time
|
|
109
|
+
|
|
110
|
+
begin
|
|
111
|
+
fields_count = to_h_for_storage.size
|
|
112
|
+
rescue => e
|
|
113
|
+
Familia.error "Failed to serialize fields for logging",
|
|
114
|
+
error: e.message,
|
|
115
|
+
class: self.class.name,
|
|
116
|
+
identifier: (identifier rescue nil)
|
|
117
|
+
fields_count = 0
|
|
118
|
+
end
|
|
110
119
|
|
|
111
|
-
|
|
120
|
+
Familia.debug "Horreum saved",
|
|
121
|
+
class: self.class.name,
|
|
122
|
+
identifier: identifier,
|
|
123
|
+
duration: duration,
|
|
124
|
+
fields_count: fields_count,
|
|
125
|
+
update_expiration: update_expiration
|
|
126
|
+
|
|
127
|
+
Familia::Instrumentation.notify_lifecycle(:save, self,
|
|
128
|
+
duration: duration,
|
|
129
|
+
update_expiration: update_expiration,
|
|
130
|
+
fields_count: fields_count
|
|
131
|
+
)
|
|
112
132
|
end
|
|
113
133
|
|
|
114
|
-
Familia.ld "[save] #{self.class} #{dbkey} #{result} (update_expiration: #{update_expiration})"
|
|
115
|
-
|
|
116
134
|
# Return boolean indicating success
|
|
117
135
|
!result.nil?
|
|
118
136
|
end
|
|
119
137
|
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
# Conditionally persists the object to Valkey storage by first checking if the
|
|
123
|
-
# identifier field already exists. If the object already exists in storage,
|
|
124
|
-
# raises an error. Otherwise, proceeds with a normal save operation including
|
|
125
|
-
# automatic timestamping.
|
|
126
|
-
#
|
|
127
|
-
# This method provides atomic conditional creation to prevent duplicate objects
|
|
128
|
-
# from being saved when uniqueness is required based on the identifier field.
|
|
129
|
-
#
|
|
130
|
-
# @param update_expiration [Boolean] Whether to update the key's expiration
|
|
131
|
-
# time after saving. Defaults to true.
|
|
132
|
-
#
|
|
133
|
-
# @return [Boolean] true if the save operation was successful
|
|
134
|
-
#
|
|
135
|
-
# @raise [Familia::RecordExistsError] If an object with the same identifier
|
|
136
|
-
# already exists in Valkey storage
|
|
137
|
-
#
|
|
138
|
-
# @example Save a new user only if it doesn't exist
|
|
139
|
-
# user = User.new(id: 123, name: "John")
|
|
140
|
-
# user.save_if_not_exists
|
|
141
|
-
# # => true (saved successfully)
|
|
138
|
+
# Conditionally persists object only if it doesn't already exist in storage.
|
|
142
139
|
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
# # => raises Familia::RecordExistsError
|
|
140
|
+
# Uses optimistic locking (WATCH) to atomically check existence and save.
|
|
141
|
+
# If the object doesn't exist, performs identical operations as save.
|
|
142
|
+
# If it exists, raises an error with retry logic for optimistic lock failures.
|
|
147
143
|
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
#
|
|
153
|
-
#
|
|
144
|
+
# `save_if_not_exists` doesn't call save because of the gap between checking
|
|
145
|
+
# existence and persisting the data. We can't check for existence inside the
|
|
146
|
+
# transaction because commands are queued and not executed until EXEC
|
|
147
|
+
# is called (if you try you get a Redis::Future object). So here we use a
|
|
148
|
+
# WATCH + MULTI/EXEC pattern to fail the transaction if the key is created
|
|
149
|
+
# (or modified in any way) to avoid silent data corruption♀︎.
|
|
150
|
+
|
|
151
|
+
# ♀︎ Additional note about WATCH + MULTI/EXEC in Valkey/Redis or any two
|
|
152
|
+
# step existence check in any database: although it is more cautious,
|
|
153
|
+
# it is not atomic. The only way to do that is if the database process
|
|
154
|
+
# can determine itself whether the record already exists or not. For
|
|
155
|
+
# Valkey/Redis, that means writing the lua to do that.
|
|
154
156
|
#
|
|
155
|
-
# @
|
|
157
|
+
# @param update_expiration [Boolean] Whether to refresh key expiration (default: true)
|
|
158
|
+
# @return [Boolean] true on successful save
|
|
156
159
|
#
|
|
157
|
-
#
|
|
160
|
+
# @raise [Familia::RecordExistsError] If object already exists
|
|
161
|
+
# @raise [Familia::OptimisticLockError] If retries exhausted (max 3 attempts)
|
|
162
|
+
# @raise [Familia::OperationModeError] If called within a transaction
|
|
158
163
|
#
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
#
|
|
164
|
+
# @example
|
|
165
|
+
# user = User.new(id: 123)
|
|
166
|
+
# user.save_if_not_exists! # => true or raises
|
|
162
167
|
def save_if_not_exists!(update_expiration: true)
|
|
163
168
|
# Prevent save_if_not_exists! within transaction - needs to read existence state
|
|
164
169
|
if Fiber[:familia_transaction]
|
|
165
|
-
raise Familia::OperationModeError,
|
|
166
|
-
|
|
170
|
+
raise Familia::OperationModeError, <<~ERROR_MESSAGE
|
|
171
|
+
Cannot call save_if_not_exists! within a transaction. This method
|
|
172
|
+
must be called outside transactions to properly check existence.
|
|
173
|
+
ERROR_MESSAGE
|
|
167
174
|
end
|
|
168
175
|
|
|
169
176
|
identifier_field = self.class.identifier_field
|
|
170
177
|
|
|
171
|
-
Familia.
|
|
178
|
+
Familia.debug "[save_if_not_exists]: #{self.class} #{identifier_field}=#{identifier}"
|
|
172
179
|
Familia.trace :SAVE_IF_NOT_EXISTS, nil, self.class.uri if Familia.debug?
|
|
173
180
|
|
|
181
|
+
# Prepare object for persistence (timestamps, validation)
|
|
182
|
+
prepare_for_save
|
|
183
|
+
|
|
174
184
|
attempts = 0
|
|
175
185
|
begin
|
|
176
186
|
attempts += 1
|
|
177
187
|
|
|
178
|
-
watch do
|
|
188
|
+
result = watch do
|
|
179
189
|
raise Familia::RecordExistsError, dbkey if exists?
|
|
180
190
|
|
|
181
191
|
txn_result = transaction do |_multi|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
self.update_expiration if update_expiration
|
|
185
|
-
|
|
186
|
-
# Auto-index for class-level indexes after successful save
|
|
187
|
-
auto_update_class_indexes
|
|
192
|
+
persist_to_storage(update_expiration)
|
|
188
193
|
end
|
|
189
194
|
|
|
190
|
-
Familia.
|
|
195
|
+
Familia.debug "[save_if_not_exists]: txn_result=#{txn_result.inspect}"
|
|
191
196
|
|
|
192
|
-
txn_result
|
|
197
|
+
txn_result
|
|
193
198
|
end
|
|
199
|
+
|
|
200
|
+
Familia.debug "[save_if_not_exists]: result=#{result.inspect}"
|
|
201
|
+
|
|
202
|
+
# Return boolean indicating success (consistent with save method)
|
|
203
|
+
!result.nil?
|
|
194
204
|
rescue OptimisticLockError => e
|
|
195
|
-
Familia.
|
|
205
|
+
Familia.debug "[save_if_not_exists]: OptimisticLockError (#{attempts}): #{e.message}"
|
|
196
206
|
raise if attempts >= 3
|
|
197
207
|
|
|
198
208
|
sleep(0.001 * (2**attempts))
|
|
@@ -200,9 +210,13 @@ module Familia
|
|
|
200
210
|
end
|
|
201
211
|
end
|
|
202
212
|
|
|
213
|
+
# Non-raising variant of save_if_not_exists!
|
|
214
|
+
#
|
|
215
|
+
# @return [Boolean] true on success, false if object exists
|
|
216
|
+
# @raise [Familia::OptimisticLockError] If concurrency conflict persists after retries
|
|
203
217
|
def save_if_not_exists(...)
|
|
204
218
|
save_if_not_exists!(...)
|
|
205
|
-
rescue RecordExistsError
|
|
219
|
+
rescue RecordExistsError
|
|
206
220
|
false
|
|
207
221
|
end
|
|
208
222
|
|
|
@@ -233,7 +247,7 @@ module Familia
|
|
|
233
247
|
#
|
|
234
248
|
def commit_fields(update_expiration: true)
|
|
235
249
|
prepared_value = to_h_for_storage
|
|
236
|
-
Familia.
|
|
250
|
+
Familia.debug "[commit_fields] Begin #{self.class} #{dbkey} #{prepared_value} (exp: #{update_expiration})"
|
|
237
251
|
|
|
238
252
|
transaction do |_conn|
|
|
239
253
|
# Set all fields atomically
|
|
@@ -367,7 +381,7 @@ module Familia
|
|
|
367
381
|
Familia.trace :DESTROY!, dbkey, self.class.uri
|
|
368
382
|
|
|
369
383
|
# Execute all deletion operations within a transaction
|
|
370
|
-
transaction do |_conn|
|
|
384
|
+
result = transaction do |_conn|
|
|
371
385
|
# Delete the main object key
|
|
372
386
|
delete!
|
|
373
387
|
|
|
@@ -382,7 +396,20 @@ module Familia
|
|
|
382
396
|
obj.delete!
|
|
383
397
|
end
|
|
384
398
|
end
|
|
399
|
+
|
|
400
|
+
# Remove from instances collection if available
|
|
401
|
+
self.class.instances.remove(identifier) if self.class.respond_to?(:instances)
|
|
385
402
|
end
|
|
403
|
+
|
|
404
|
+
# Structured lifecycle logging and instrumentation
|
|
405
|
+
Familia.debug "Horreum destroyed",
|
|
406
|
+
class: self.class.name,
|
|
407
|
+
identifier: identifier,
|
|
408
|
+
key: dbkey
|
|
409
|
+
|
|
410
|
+
Familia::Instrumentation.notify_lifecycle(:destroy, self, key: dbkey)
|
|
411
|
+
|
|
412
|
+
result
|
|
386
413
|
end
|
|
387
414
|
|
|
388
415
|
# Clears all fields by setting them to nil.
|
|
@@ -433,7 +460,7 @@ module Familia
|
|
|
433
460
|
raise Familia::KeyNotFoundError, dbkey unless dbclient.exists(dbkey)
|
|
434
461
|
|
|
435
462
|
fields = hgetall
|
|
436
|
-
Familia.
|
|
463
|
+
Familia.debug "[refresh!] #{self.class} #{dbkey} fields:#{fields.keys}"
|
|
437
464
|
|
|
438
465
|
# Reset transient fields to nil for semantic clarity and ORM consistency
|
|
439
466
|
# Transient fields have no authoritative source, so they should return to
|
|
@@ -489,7 +516,7 @@ module Familia
|
|
|
489
516
|
|
|
490
517
|
# UnsortedSet the transient field back to nil
|
|
491
518
|
send("#{field_type.method_name}=", nil)
|
|
492
|
-
Familia.
|
|
519
|
+
Familia.debug "[reset_transient_fields!] Reset #{field_name} to nil"
|
|
493
520
|
end
|
|
494
521
|
end
|
|
495
522
|
|
|
@@ -563,7 +590,7 @@ module Familia
|
|
|
563
590
|
# Instance-scoped indexes must be manually populated because they need
|
|
564
591
|
# the scope instance reference (e.g., employee.add_to_company_badge_index(company))
|
|
565
592
|
if rel.within
|
|
566
|
-
Familia.
|
|
593
|
+
Familia.debug <<~LOG_MESSAGE
|
|
567
594
|
[auto_update_class_indexes] Skipping #{rel.index_name} (requires scope context)
|
|
568
595
|
LOG_MESSAGE
|
|
569
596
|
next
|
|
@@ -574,6 +601,50 @@ module Familia
|
|
|
574
601
|
send(add_method) if respond_to?(add_method)
|
|
575
602
|
end
|
|
576
603
|
end
|
|
604
|
+
|
|
605
|
+
# Prepares the object for persistence by setting timestamps and validating constraints
|
|
606
|
+
#
|
|
607
|
+
# This method is called by both save and save_if_not_exists to ensure consistent
|
|
608
|
+
# preparation logic. It updates created/updated timestamps and validates unique
|
|
609
|
+
# indexes before the transaction begins.
|
|
610
|
+
#
|
|
611
|
+
# @return [void]
|
|
612
|
+
#
|
|
613
|
+
def prepare_for_save
|
|
614
|
+
# Update timestamp fields before saving
|
|
615
|
+
self.created ||= Familia.now if respond_to?(:created)
|
|
616
|
+
self.updated = Familia.now if respond_to?(:updated)
|
|
617
|
+
|
|
618
|
+
# Validate unique indexes BEFORE the transaction
|
|
619
|
+
guard_unique_indexes!
|
|
620
|
+
end
|
|
621
|
+
private :prepare_for_save
|
|
622
|
+
|
|
623
|
+
# Persists the object's data to storage within a transaction
|
|
624
|
+
#
|
|
625
|
+
# This method contains the core persistence logic shared by both save and
|
|
626
|
+
# save_if_not_exists. It must be called within a transaction block.
|
|
627
|
+
#
|
|
628
|
+
# @param update_expiration [Boolean] Whether to update the key's expiration
|
|
629
|
+
# @return [Object] The result of the hmset operation
|
|
630
|
+
#
|
|
631
|
+
def persist_to_storage(update_expiration)
|
|
632
|
+
# 1. Save all fields to hashkey at once
|
|
633
|
+
prepared_h = to_h_for_storage
|
|
634
|
+
hmset_result = hmset(prepared_h)
|
|
635
|
+
|
|
636
|
+
# 2. Set expiration in same transaction
|
|
637
|
+
self.update_expiration if update_expiration
|
|
638
|
+
|
|
639
|
+
# 3. Update class-level indexes
|
|
640
|
+
auto_update_class_indexes
|
|
641
|
+
|
|
642
|
+
# 4. Add to instances collection if available
|
|
643
|
+
self.class.instances.add(identifier, Familia.now) if self.class.respond_to?(:instances)
|
|
644
|
+
|
|
645
|
+
hmset_result
|
|
646
|
+
end
|
|
647
|
+
private :persist_to_storage
|
|
577
648
|
end
|
|
578
649
|
end
|
|
579
650
|
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/horreum/serialization.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
class Horreum
|
|
@@ -30,7 +32,7 @@ module Familia
|
|
|
30
32
|
next unless field_type.loggable
|
|
31
33
|
|
|
32
34
|
val = send(field_type.method_name)
|
|
33
|
-
Familia.
|
|
35
|
+
Familia.debug " [to_h] field: #{field} val: #{val.class}"
|
|
34
36
|
|
|
35
37
|
# Use string key for external API compatibility
|
|
36
38
|
# Return Ruby values, not JSON-encoded strings
|
|
@@ -63,7 +65,7 @@ module Familia
|
|
|
63
65
|
prepared = serialize_value(val)
|
|
64
66
|
|
|
65
67
|
if Familia.debug?
|
|
66
|
-
Familia.
|
|
68
|
+
Familia.debug " [to_h_for_storage] field: #{field} val: #{val.class} prepared: #{prepared&.class || '[nil]'}"
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
# Use string key for database compatibility
|
|
@@ -96,7 +98,7 @@ module Familia
|
|
|
96
98
|
|
|
97
99
|
method_name = field_type.method_name
|
|
98
100
|
val = send(method_name)
|
|
99
|
-
Familia.
|
|
101
|
+
Familia.debug " [to_a] field: #{field} method: #{method_name} val: #{val.class}"
|
|
100
102
|
|
|
101
103
|
# Return actual Ruby values, including nil to maintain array positions
|
|
102
104
|
val
|
|
@@ -182,7 +184,24 @@ module Familia
|
|
|
182
184
|
"Legacy plain string in #{context}: #{val.inspect} (#{dbkey_info})"
|
|
183
185
|
end
|
|
184
186
|
|
|
185
|
-
|
|
187
|
+
# Structured error logging with instrumentation
|
|
188
|
+
error_type = looks_like_json?(val) ? :corrupted_json : :legacy_string
|
|
189
|
+
Familia.error msg,
|
|
190
|
+
error_type: error_type,
|
|
191
|
+
field: field_name,
|
|
192
|
+
value_preview: val.to_s[0...50],
|
|
193
|
+
object_class: self.class.name,
|
|
194
|
+
identifier: (identifier rescue nil),
|
|
195
|
+
key: dbkey_info
|
|
196
|
+
|
|
197
|
+
# Notify instrumentation hooks
|
|
198
|
+
Familia::Instrumentation.notify_error(
|
|
199
|
+
StandardError.new(msg),
|
|
200
|
+
operation: :deserialization,
|
|
201
|
+
error_type: error_type,
|
|
202
|
+
field: field_name,
|
|
203
|
+
object_class: self.class.name
|
|
204
|
+
)
|
|
186
205
|
end
|
|
187
206
|
|
|
188
207
|
def looks_like_json?(val)
|
data/lib/familia/horreum.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/horreum.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require_relative 'horreum/settings'
|
|
4
6
|
require_relative 'horreum/connection'
|
|
@@ -186,6 +188,7 @@ module Familia
|
|
|
186
188
|
# `Session.new({sessid: "abc123", custid: "user456"})` # legacy hash (robust)
|
|
187
189
|
#
|
|
188
190
|
def initialize(*args, **kwargs)
|
|
191
|
+
start_time = Familia.now_in_μs if Familia.debug?
|
|
189
192
|
Familia.trace :INITIALIZE, nil, "Initializing #{self.class}" if Familia.debug?
|
|
190
193
|
initialize_relatives
|
|
191
194
|
|
|
@@ -236,6 +239,17 @@ module Familia
|
|
|
236
239
|
# end
|
|
237
240
|
#
|
|
238
241
|
init
|
|
242
|
+
|
|
243
|
+
# Structured lifecycle logging and instrumentation
|
|
244
|
+
if Familia.debug? && start_time
|
|
245
|
+
duration = Familia.now_in_μs - start_time
|
|
246
|
+
Familia.debug "Horreum initialized",
|
|
247
|
+
class: self.class.name,
|
|
248
|
+
duration: duration,
|
|
249
|
+
identifier: (identifier rescue nil)
|
|
250
|
+
|
|
251
|
+
Familia::Instrumentation.notify_lifecycle(:initialize, self, duration: duration)
|
|
252
|
+
end
|
|
239
253
|
end
|
|
240
254
|
|
|
241
255
|
# Initialization method called at the end of initialize
|
|
@@ -338,7 +352,7 @@ module Familia
|
|
|
338
352
|
# the object with.
|
|
339
353
|
# @return [Array] The list of field names that were updated.
|
|
340
354
|
def naive_refresh(**fields)
|
|
341
|
-
Familia.
|
|
355
|
+
Familia.debug "[naive_refresh] #{self.class} #{dbkey} #{fields.keys}"
|
|
342
356
|
initialize_with_keyword_args_deserialize_value(**fields)
|
|
343
357
|
end
|
|
344
358
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/identifier_extractor.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
# IdentifierExtractor - Extracts identifiers from Familia objects for storage
|
|
@@ -27,7 +29,7 @@ module Familia
|
|
|
27
29
|
# @return [String] The extracted identifier or class name
|
|
28
30
|
# @raise [Familia::NotDistinguishableError] If value is not a Class or Familia::Base
|
|
29
31
|
#
|
|
30
|
-
def identifier_extractor(value
|
|
32
|
+
def identifier_extractor(value)
|
|
31
33
|
case value
|
|
32
34
|
when ::Symbol, ::String, ::Integer, ::Float
|
|
33
35
|
Familia.trace :IDENTIFIER_EXTRACTOR, nil, 'simple_value' if Familia.debug?
|