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,245 @@
|
|
|
1
|
+
# try/unit/core/middleware_thread_safety_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Thread Safety Tests for DatabaseLogger Middleware
|
|
6
|
+
#
|
|
7
|
+
# These tests specifically target potential race conditions and concurrency issues
|
|
8
|
+
# in the DatabaseLogger middleware that could lead to nil entries in the @commands array.
|
|
9
|
+
#
|
|
10
|
+
# Covers:
|
|
11
|
+
# - Concurrent append_command operations
|
|
12
|
+
# - Thread-safe pipeline command logging
|
|
13
|
+
# - Mixed operation types under contention
|
|
14
|
+
# - Sampling counter atomicity
|
|
15
|
+
# - Rapid sequential calls within threads
|
|
16
|
+
# - clear_commands during active logging
|
|
17
|
+
#
|
|
18
|
+
# Background:
|
|
19
|
+
# An intermittent NoMethodError was observed where .command was called on nil
|
|
20
|
+
# during teardown of middleware_sampling_try.rb. The error suggests potential
|
|
21
|
+
# corruption of the @commands Concurrent::Array, possibly due to race conditions
|
|
22
|
+
# between append_command, clear_commands, or middleware state management.
|
|
23
|
+
|
|
24
|
+
require_relative '../../support/helpers/test_helpers'
|
|
25
|
+
require 'concurrent'
|
|
26
|
+
|
|
27
|
+
# Setup: Reset DatabaseLogger and ensure middleware is registered
|
|
28
|
+
Familia.connection_provider = nil
|
|
29
|
+
Familia.enable_database_logging = true
|
|
30
|
+
Familia.reconnect!
|
|
31
|
+
DatabaseLogger.clear_commands
|
|
32
|
+
DatabaseLogger.sample_rate = nil
|
|
33
|
+
DatabaseLogger.structured_logging = false
|
|
34
|
+
|
|
35
|
+
## DatabaseLogger.append_command is thread-safe under concurrent access from 50 threads
|
|
36
|
+
# First verify middleware is working in main thread
|
|
37
|
+
DatabaseLogger.clear_commands
|
|
38
|
+
test_client = Familia.dbclient
|
|
39
|
+
test_client.set("setup_test", "value")
|
|
40
|
+
setup_commands = DatabaseLogger.commands.size
|
|
41
|
+
|
|
42
|
+
# Now run the actual test
|
|
43
|
+
DatabaseLogger.clear_commands
|
|
44
|
+
barrier = Concurrent::CyclicBarrier.new(50)
|
|
45
|
+
threads = []
|
|
46
|
+
dbclient = Familia.dbclient # Get connection in main thread (has middleware)
|
|
47
|
+
|
|
48
|
+
50.times do |i|
|
|
49
|
+
threads << Thread.new do
|
|
50
|
+
barrier.wait # Synchronize start to maximize contention
|
|
51
|
+
dbclient.set("thread_key_#{i}", "value_#{i}")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
threads.each(&:join)
|
|
56
|
+
commands = DatabaseLogger.commands
|
|
57
|
+
|
|
58
|
+
# All commands should be captured (no lost writes)
|
|
59
|
+
# No nil entries should exist in the commands array
|
|
60
|
+
# Note: setup_commands verifies middleware is working (should be 1)
|
|
61
|
+
[setup_commands, commands.size, commands.any?(nil), commands.all? { |cmd| cmd.respond_to?(:command) }]
|
|
62
|
+
#=> [1, 50, false, true]
|
|
63
|
+
|
|
64
|
+
## Pipelined commands are thread-safe under concurrent access from 25 threads
|
|
65
|
+
DatabaseLogger.clear_commands
|
|
66
|
+
barrier = Concurrent::CyclicBarrier.new(25)
|
|
67
|
+
threads = []
|
|
68
|
+
dbclient = Familia.dbclient # Get connection in main thread (has middleware)
|
|
69
|
+
|
|
70
|
+
25.times do |i|
|
|
71
|
+
threads << Thread.new do
|
|
72
|
+
barrier.wait
|
|
73
|
+
dbclient.pipelined do |pipeline|
|
|
74
|
+
pipeline.set("pipeline_thread_#{i}_key1", "val1")
|
|
75
|
+
pipeline.set("pipeline_thread_#{i}_key2", "val2")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
threads.each(&:join)
|
|
81
|
+
commands = DatabaseLogger.commands
|
|
82
|
+
|
|
83
|
+
# Each thread creates one pipeline command (25 total)
|
|
84
|
+
# Verify no nil entries and all are proper CommandMessage objects
|
|
85
|
+
[commands.size, commands.any?(nil), commands.all? { |cmd| cmd.respond_to?(:command) }]
|
|
86
|
+
#=> [25, false, true]
|
|
87
|
+
|
|
88
|
+
## Mixed middleware operations (call, call_pipelined, call_once) are thread-safe
|
|
89
|
+
DatabaseLogger.clear_commands
|
|
90
|
+
barrier = Concurrent::CyclicBarrier.new(60)
|
|
91
|
+
threads = []
|
|
92
|
+
dbclient = Familia.dbclient # Get connection in main thread (has middleware)
|
|
93
|
+
|
|
94
|
+
60.times do |i|
|
|
95
|
+
threads << Thread.new do
|
|
96
|
+
barrier.wait
|
|
97
|
+
|
|
98
|
+
case i % 3
|
|
99
|
+
when 0 # Regular call
|
|
100
|
+
dbclient.set("mixed_#{i}", "val")
|
|
101
|
+
when 1 # Pipelined
|
|
102
|
+
dbclient.pipelined { |p| p.get("mixed_#{i}") }
|
|
103
|
+
when 2 # Multiple rapid calls
|
|
104
|
+
3.times { dbclient.get("mixed_#{i}") }
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
threads.each(&:join)
|
|
110
|
+
commands = DatabaseLogger.commands
|
|
111
|
+
|
|
112
|
+
# Verify no nil entries regardless of operation type
|
|
113
|
+
# All entries should be valid CommandMessage instances
|
|
114
|
+
[commands.any?(nil), commands.all? { |cmd| cmd.is_a?(DatabaseLogger::CommandMessage) }]
|
|
115
|
+
#=> [false, true]
|
|
116
|
+
|
|
117
|
+
## sample_rate counter is thread-safe with 100 concurrent operations
|
|
118
|
+
# REMOVED: This test had incorrect expectations. The sample_rate controls LOGGING
|
|
119
|
+
# output, not COMMAND CAPTURE. Per database_logger.rb:153-154:
|
|
120
|
+
# "Command capture is unaffected - only logger output is sampled."
|
|
121
|
+
# The commands array always contains all commands regardless of sample_rate.
|
|
122
|
+
# Thread safety of the AtomicFixnum counter is verified by other tests.
|
|
123
|
+
|
|
124
|
+
## Rapid sequential calls within threads don't corrupt shared state
|
|
125
|
+
DatabaseLogger.clear_commands
|
|
126
|
+
DatabaseLogger.sample_rate = nil # Log everything
|
|
127
|
+
latch = Concurrent::CountDownLatch.new(20)
|
|
128
|
+
threads = []
|
|
129
|
+
dbclient = Familia.dbclient # Get connection in main thread (has middleware)
|
|
130
|
+
|
|
131
|
+
20.times do |i|
|
|
132
|
+
threads << Thread.new do
|
|
133
|
+
# Rapid-fire 10 operations per thread to test state isolation
|
|
134
|
+
10.times do |j|
|
|
135
|
+
dbclient.set("rapid_#{i}_#{j}", "val")
|
|
136
|
+
end
|
|
137
|
+
latch.count_down
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
latch.wait(5) # 5 second timeout
|
|
142
|
+
commands = DatabaseLogger.commands
|
|
143
|
+
|
|
144
|
+
# Should have 200 commands (20 threads × 10 calls)
|
|
145
|
+
# Most importantly: NO nil entries from rapid sequential access
|
|
146
|
+
[commands.size, commands.any?(nil)]
|
|
147
|
+
#=> [200, false]
|
|
148
|
+
|
|
149
|
+
## clear_commands doesn't cause nil entries during concurrent logging
|
|
150
|
+
DatabaseLogger.clear_commands
|
|
151
|
+
DatabaseLogger.sample_rate = nil
|
|
152
|
+
barrier = Concurrent::CyclicBarrier.new(51) # 50 loggers + 1 clearer
|
|
153
|
+
threads = []
|
|
154
|
+
dbclient = Familia.dbclient # Get connection in main thread (has middleware)
|
|
155
|
+
|
|
156
|
+
# 50 threads logging continuously
|
|
157
|
+
50.times do |i|
|
|
158
|
+
threads << Thread.new do
|
|
159
|
+
barrier.wait
|
|
160
|
+
10.times do |j|
|
|
161
|
+
dbclient.set("clear_test_#{i}_#{j}", "val")
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# 1 thread clearing repeatedly to create contention
|
|
167
|
+
clearer = Thread.new do
|
|
168
|
+
barrier.wait
|
|
169
|
+
5.times do
|
|
170
|
+
sleep 0.001 # Small delay between clears
|
|
171
|
+
DatabaseLogger.clear_commands
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
threads.each(&:join)
|
|
176
|
+
clearer.join
|
|
177
|
+
|
|
178
|
+
# After clearing, commands should be empty or valid (never contain nil)
|
|
179
|
+
# This tests whether clear_commands can corrupt the array during active logging
|
|
180
|
+
commands = DatabaseLogger.commands
|
|
181
|
+
[commands.any?(nil)]
|
|
182
|
+
#=> [false]
|
|
183
|
+
|
|
184
|
+
## CommandMessage structure is preserved under concurrent access
|
|
185
|
+
DatabaseLogger.clear_commands
|
|
186
|
+
barrier = Concurrent::CyclicBarrier.new(30)
|
|
187
|
+
threads = []
|
|
188
|
+
dbclient = Familia.dbclient # Get connection in main thread (has middleware)
|
|
189
|
+
|
|
190
|
+
30.times do |i|
|
|
191
|
+
threads << Thread.new do
|
|
192
|
+
barrier.wait
|
|
193
|
+
dbclient.set("structure_test_#{i}", "value_#{i}")
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
threads.each(&:join)
|
|
198
|
+
commands = DatabaseLogger.commands
|
|
199
|
+
|
|
200
|
+
# Verify all CommandMessage fields are properly initialized
|
|
201
|
+
# This ensures the Data.define structure isn't corrupted by concurrency
|
|
202
|
+
all_valid = commands.all? do |cmd|
|
|
203
|
+
cmd.command.is_a?(String) &&
|
|
204
|
+
cmd.μs.is_a?(Integer) &&
|
|
205
|
+
cmd.timeline.is_a?(Float)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
[commands.size, commands.any?(nil), all_valid]
|
|
209
|
+
#=> [30, false, true]
|
|
210
|
+
|
|
211
|
+
## Concurrent pipelined operations with varying sizes don't cause corruption
|
|
212
|
+
# REMOVED: This test was checking exact pipeline command count which can vary
|
|
213
|
+
# due to intentionally non-atomic append_command trimming logic.
|
|
214
|
+
# See database_logger.rb:214-217 - we don't care about exact count when trimming.
|
|
215
|
+
# The important invariants (no nil entries, successful operations) are tested elsewhere.
|
|
216
|
+
|
|
217
|
+
## AtomicFixnum counter increments correctly under high contention
|
|
218
|
+
DatabaseLogger.clear_commands
|
|
219
|
+
DatabaseLogger.sample_rate = 1.0 # Track every increment but log everything
|
|
220
|
+
counter_start = DatabaseLogger.instance_variable_get(:@sample_counter).value
|
|
221
|
+
|
|
222
|
+
barrier = Concurrent::CyclicBarrier.new(100)
|
|
223
|
+
threads = []
|
|
224
|
+
dbclient = Familia.dbclient # Get connection in main thread (has middleware)
|
|
225
|
+
|
|
226
|
+
100.times do |i|
|
|
227
|
+
threads << Thread.new do
|
|
228
|
+
barrier.wait
|
|
229
|
+
dbclient.set("counter_test_#{i}", "val")
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
threads.each(&:join)
|
|
234
|
+
counter_end = DatabaseLogger.instance_variable_get(:@sample_counter).value
|
|
235
|
+
commands = DatabaseLogger.commands
|
|
236
|
+
|
|
237
|
+
# Counter should have incremented by exactly 100
|
|
238
|
+
# Commands should have all 100 entries with no nil values
|
|
239
|
+
[counter_end - counter_start, commands.size, commands.any?(nil)]
|
|
240
|
+
#=> [100, 100, false]
|
|
241
|
+
|
|
242
|
+
# Teardown: Reset to defaults
|
|
243
|
+
DatabaseLogger.sample_rate = nil
|
|
244
|
+
DatabaseLogger.structured_logging = false
|
|
245
|
+
DatabaseLogger.clear_commands
|
data/try/unit/core/tools_try.rb
CHANGED
data/try/unit/core/utils_try.rb
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# try/unit/core/utils_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
1
5
|
# try/core/utils_try.rb
|
|
2
6
|
|
|
3
7
|
require_relative '../../support/helpers/test_helpers'
|
|
@@ -85,6 +89,39 @@ rescue Familia::NotDistinguishableError => e
|
|
|
85
89
|
end
|
|
86
90
|
#=> Familia::NotDistinguishableError
|
|
87
91
|
|
|
92
|
+
##
|
|
93
|
+
## Time utilities
|
|
94
|
+
##
|
|
95
|
+
|
|
96
|
+
## now returns Float timestamp in seconds
|
|
97
|
+
time_result = Familia.now
|
|
98
|
+
[time_result.is_a?(Float), time_result > 0]
|
|
99
|
+
#=> [true, true]
|
|
100
|
+
|
|
101
|
+
## now accepts Time argument
|
|
102
|
+
test_time = Time.utc(2023, 6, 15, 14, 30, 0)
|
|
103
|
+
result_time = Familia.now(test_time)
|
|
104
|
+
[result_time.is_a?(Float), result_time == test_time.utc.to_f]
|
|
105
|
+
#=> [true, true]
|
|
106
|
+
|
|
107
|
+
## now_in_μs returns Integer microseconds
|
|
108
|
+
μs_result = Familia.now_in_μs
|
|
109
|
+
[μs_result.is_a?(Integer), μs_result > 0]
|
|
110
|
+
#=> [true, true]
|
|
111
|
+
|
|
112
|
+
## now_in_microseconds is an alias for now_in_μs
|
|
113
|
+
alias_result = Familia.now_in_microseconds
|
|
114
|
+
[alias_result.is_a?(Integer), alias_result > 0]
|
|
115
|
+
#=> [true, true]
|
|
116
|
+
|
|
117
|
+
## now_in_μs returns monotonic time suitable for duration measurement
|
|
118
|
+
start = Familia.now_in_μs
|
|
119
|
+
sleep 0.001 # Sleep for at least 1ms
|
|
120
|
+
finish = Familia.now_in_μs
|
|
121
|
+
duration = finish - start
|
|
122
|
+
[duration.is_a?(Integer), duration >= 1000] # At least 1000 microseconds (1ms)
|
|
123
|
+
#=> [true, true]
|
|
124
|
+
|
|
88
125
|
# Cleanup - restore defaults, leave nothing but footprints
|
|
89
126
|
Familia.delim(':')
|
|
90
127
|
Familia.suffix(:object)
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# try/unit/data_types/boolean_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
1
5
|
# try/data_types/boolean_try.rb
|
|
2
6
|
|
|
3
7
|
require_relative '../../support/helpers/test_helpers'
|
|
@@ -40,4 +44,4 @@ end
|
|
|
40
44
|
|
|
41
45
|
## Clear the hash key
|
|
42
46
|
@hashkey.delete!
|
|
43
|
-
#=>
|
|
47
|
+
#=> 1
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# try/unit/data_types/string_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
1
5
|
# try/data_types/string_try.rb
|
|
2
6
|
|
|
3
7
|
require_relative '../../support/helpers/test_helpers'
|
|
@@ -22,7 +26,7 @@ require_relative '../../support/helpers/test_helpers'
|
|
|
22
26
|
|
|
23
27
|
## Familia::StringKey#destroy!
|
|
24
28
|
@a.value.delete!
|
|
25
|
-
#=>
|
|
29
|
+
#=> 1
|
|
26
30
|
|
|
27
31
|
## Familia::StringKey.new
|
|
28
32
|
@ret = Familia::StringKey.new 'arbitrary:key'
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# try/unit/familia_resolve_class_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Unit tests for Familia.resolve_class method
|
|
6
|
+
#
|
|
7
|
+
# This test ensures the public API for resolving class references works
|
|
8
|
+
# correctly across different input types (Class, Symbol, String).
|
|
9
|
+
#
|
|
10
|
+
# Related to the fix for NoMethodError in participates_in where the private
|
|
11
|
+
# method member_by_config_name was being called directly instead of using
|
|
12
|
+
# the public resolve_class API.
|
|
13
|
+
|
|
14
|
+
require_relative '../support/helpers/test_helpers'
|
|
15
|
+
|
|
16
|
+
# Test class for resolution
|
|
17
|
+
class ResolveTestClass < Familia::Horreum
|
|
18
|
+
identifier_field :id
|
|
19
|
+
field :id
|
|
20
|
+
field :name
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Test class in module for modularized class resolution
|
|
24
|
+
module ResolveTestModule
|
|
25
|
+
class ModularClass < Familia::Horreum
|
|
26
|
+
identifier_field :id
|
|
27
|
+
field :id
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
## Test resolve_class with Class object returns the class unchanged
|
|
32
|
+
@resolved_class = Familia.resolve_class(ResolveTestClass)
|
|
33
|
+
@resolved_class == ResolveTestClass
|
|
34
|
+
#=> true
|
|
35
|
+
|
|
36
|
+
## Test resolve_class with Symbol returns the correct class
|
|
37
|
+
@resolved_symbol = Familia.resolve_class(:ResolveTestClass)
|
|
38
|
+
@resolved_symbol == ResolveTestClass
|
|
39
|
+
#=> true
|
|
40
|
+
|
|
41
|
+
## Test resolve_class with String returns the correct class
|
|
42
|
+
@resolved_string = Familia.resolve_class('ResolveTestClass')
|
|
43
|
+
@resolved_string == ResolveTestClass
|
|
44
|
+
#=> true
|
|
45
|
+
|
|
46
|
+
## Test resolve_class with PascalCase symbol works
|
|
47
|
+
@resolved_pascal_sym = Familia.resolve_class(:ResolveTestClass)
|
|
48
|
+
@resolved_pascal_sym == ResolveTestClass
|
|
49
|
+
#=> true
|
|
50
|
+
|
|
51
|
+
## Test resolve_class with PascalCase string works
|
|
52
|
+
@resolved_pascal_str = Familia.resolve_class('ResolveTestClass')
|
|
53
|
+
@resolved_pascal_str == ResolveTestClass
|
|
54
|
+
#=> true
|
|
55
|
+
|
|
56
|
+
## Test resolve_class with snake_case symbol works
|
|
57
|
+
@resolved_snake = Familia.resolve_class(:resolve_test_class)
|
|
58
|
+
@resolved_snake == ResolveTestClass
|
|
59
|
+
#=> true
|
|
60
|
+
|
|
61
|
+
## Test resolve_class with snake_case string works
|
|
62
|
+
@resolved_snake_str = Familia.resolve_class('resolve_test_class')
|
|
63
|
+
@resolved_snake_str == ResolveTestClass
|
|
64
|
+
#=> true
|
|
65
|
+
|
|
66
|
+
## Test resolve_class raises ArgumentError for invalid types
|
|
67
|
+
begin
|
|
68
|
+
Familia.resolve_class(123)
|
|
69
|
+
@raised = false
|
|
70
|
+
rescue ArgumentError => e
|
|
71
|
+
@raised = true
|
|
72
|
+
@error_message = e.message
|
|
73
|
+
end
|
|
74
|
+
@raised
|
|
75
|
+
#=> true
|
|
76
|
+
|
|
77
|
+
## Test error message is descriptive
|
|
78
|
+
@error_message.include?('Expected Class, String, or Symbol')
|
|
79
|
+
#=> true
|
|
80
|
+
|
|
81
|
+
## Test resolve_class returns nil for non-existent class
|
|
82
|
+
@nonexistent = Familia.resolve_class(:NonExistentClass)
|
|
83
|
+
@nonexistent.nil?
|
|
84
|
+
#=> true
|
|
85
|
+
|
|
86
|
+
## Test resolve_class with variations of proper naming
|
|
87
|
+
# Note: All-caps or all-lowercase don't work because snake_case needs case boundaries
|
|
88
|
+
# Realistic usage: PascalCase, snake_case, or mixed-case with boundaries
|
|
89
|
+
@case_variant_snake = Familia.resolve_class('resolve_test_class')
|
|
90
|
+
@case_variant_snake == ResolveTestClass
|
|
91
|
+
#=> true
|
|
92
|
+
|
|
93
|
+
## Test resolve_class handles already snake_cased symbols
|
|
94
|
+
@already_snake = Familia.resolve_class(:resolve_test_class)
|
|
95
|
+
@already_snake == ResolveTestClass
|
|
96
|
+
#=> true
|
|
97
|
+
|
|
98
|
+
## Test resolve_class with modularized Symbol (without module prefix)
|
|
99
|
+
@modular_resolved = Familia.resolve_class(:ModularClass)
|
|
100
|
+
@modular_resolved == ResolveTestModule::ModularClass
|
|
101
|
+
#=> true
|
|
102
|
+
|
|
103
|
+
## Test resolve_class with modularized String (without module prefix)
|
|
104
|
+
@modular_resolved_str = Familia.resolve_class('ModularClass')
|
|
105
|
+
@modular_resolved_str == ResolveTestModule::ModularClass
|
|
106
|
+
#=> true
|
|
107
|
+
|
|
108
|
+
## Test resolve_class with snake_case modularized Symbol
|
|
109
|
+
@modular_snake = Familia.resolve_class(:modular_class)
|
|
110
|
+
@modular_snake == ResolveTestModule::ModularClass
|
|
111
|
+
#=> true
|
|
112
|
+
|
|
113
|
+
## Test resolve_class with Class object for modularized class
|
|
114
|
+
@modular_class_obj = Familia.resolve_class(ResolveTestModule::ModularClass)
|
|
115
|
+
@modular_class_obj == ResolveTestModule::ModularClass
|
|
116
|
+
#=> true
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# try/unit/horreum/auto_indexing_on_save_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
1
5
|
# try/horreum/auto_indexing_on_save_try.rb
|
|
2
6
|
|
|
3
7
|
#
|
|
@@ -43,6 +47,19 @@ class ::AutoIndexEmployee < Familia::Horreum
|
|
|
43
47
|
multi_index :department, :dept_index, within: AutoIndexCompany
|
|
44
48
|
end
|
|
45
49
|
|
|
50
|
+
class ::AutoIndexWithTransient < Familia::Horreum
|
|
51
|
+
feature :transient_fields
|
|
52
|
+
feature :relationships
|
|
53
|
+
|
|
54
|
+
identifier_field :id
|
|
55
|
+
field :id
|
|
56
|
+
field :email
|
|
57
|
+
transient_field :temp_value
|
|
58
|
+
|
|
59
|
+
unique_index :email, :email_index
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
|
|
46
63
|
# Setup
|
|
47
64
|
@user_id = "user_#{rand(1000000)}"
|
|
48
65
|
@user = AutoIndexUser.new(user_id: @user_id, email: 'test@example.com', username: 'testuser', department: 'engineering')
|
|
@@ -125,9 +142,9 @@ AutoIndexUser.email_index.has_key?('')
|
|
|
125
142
|
AutoIndexUser.email_index.has_key?('')
|
|
126
143
|
#=> true
|
|
127
144
|
|
|
128
|
-
## Auto-indexing works with create method
|
|
145
|
+
## Auto-indexing works with create! method
|
|
129
146
|
@user2_id = "user_#{rand(1000000)}"
|
|
130
|
-
@user2 = AutoIndexUser.create(user_id: @user2_id, email: 'create@example.com', username: 'createuser', department: 'marketing')
|
|
147
|
+
@user2 = AutoIndexUser.create!(user_id: @user2_id, email: 'create@example.com', username: 'createuser', department: 'marketing')
|
|
131
148
|
AutoIndexUser.find_by_email('create@example.com')&.user_id
|
|
132
149
|
#=> @user2_id
|
|
133
150
|
|
|
@@ -152,22 +169,11 @@ old_email = @user2.email
|
|
|
152
169
|
# =============================================
|
|
153
170
|
|
|
154
171
|
## Auto-indexing works with transient fields
|
|
155
|
-
class ::AutoIndexWithTransient < Familia::Horreum
|
|
156
|
-
feature :transient_fields
|
|
157
|
-
feature :relationships
|
|
158
|
-
|
|
159
|
-
identifier_field :id
|
|
160
|
-
field :id
|
|
161
|
-
field :email
|
|
162
|
-
transient_field :temp_value
|
|
163
|
-
|
|
164
|
-
unique_index :email, :email_index
|
|
165
|
-
end
|
|
166
|
-
|
|
167
172
|
@transient_id = "trans_#{rand(1000000)}"
|
|
168
|
-
@transient_obj = AutoIndexWithTransient.new(id: @transient_id, email:
|
|
173
|
+
@transient_obj = AutoIndexWithTransient.new(id: @transient_id, email: "transient_#{rand(1000000)}@example.com", temp_value: 'ignored')
|
|
169
174
|
@transient_obj.save
|
|
170
|
-
|
|
175
|
+
@transient_email = @transient_obj.email
|
|
176
|
+
AutoIndexWithTransient.find_by_email(@transient_email)&.id
|
|
171
177
|
#=> @transient_id
|
|
172
178
|
|
|
173
179
|
## Auto-indexing works regardless of other features
|
|
@@ -175,6 +181,20 @@ AutoIndexWithTransient.find_by_email('transient@example.com')&.id
|
|
|
175
181
|
@transient_obj.class.respond_to?(:indexing_relationships)
|
|
176
182
|
#=> true
|
|
177
183
|
|
|
184
|
+
## Guard validation prevents duplicate transient field email
|
|
185
|
+
@transient_dup = AutoIndexWithTransient.new(id: "trans_dup_#{rand(1000000)}", email: @transient_email, temp_value: 'duplicate')
|
|
186
|
+
begin
|
|
187
|
+
@transient_dup.save
|
|
188
|
+
rescue Familia::RecordExistsError => ex
|
|
189
|
+
Familia.debug ex.backtrace.join("\n")
|
|
190
|
+
ex.message
|
|
191
|
+
end
|
|
192
|
+
#=:> String
|
|
193
|
+
#=~> /Key already exists/
|
|
194
|
+
#=~> /AutoIndexWithTransient exists /
|
|
195
|
+
#=~> /email=#{@transient_email}/
|
|
196
|
+
#=/=> @transient_email.nil?
|
|
197
|
+
|
|
178
198
|
# =============================================
|
|
179
199
|
# 5. Performance and Behavior Verification
|
|
180
200
|
# =============================================
|