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,255 @@
|
|
|
1
|
+
# try/unit/horreum/automatic_index_validation_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
# Automatic index validation tests
|
|
7
|
+
# Tests that unique index validation happens automatically when adding to indexes
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
require_relative '../../support/helpers/test_helpers'
|
|
11
|
+
|
|
12
|
+
# Test classes for automatic validation
|
|
13
|
+
class ::AutoValidCompany < Familia::Horreum
|
|
14
|
+
feature :relationships
|
|
15
|
+
|
|
16
|
+
identifier_field :company_id
|
|
17
|
+
field :company_id
|
|
18
|
+
field :name
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class ::AutoValidEmployee < Familia::Horreum
|
|
22
|
+
feature :relationships
|
|
23
|
+
|
|
24
|
+
identifier_field :emp_id
|
|
25
|
+
field :emp_id
|
|
26
|
+
field :badge_number
|
|
27
|
+
field :email
|
|
28
|
+
|
|
29
|
+
# Instance-scoped unique index (should auto-validate in add_to_* methods)
|
|
30
|
+
unique_index :badge_number, :badge_index, within: AutoValidCompany
|
|
31
|
+
|
|
32
|
+
# Class-level unique index (auto-validates in save)
|
|
33
|
+
unique_index :email, :email_index
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class ::AutoValidUser < Familia::Horreum
|
|
37
|
+
feature :relationships
|
|
38
|
+
|
|
39
|
+
identifier_field :user_id
|
|
40
|
+
field :user_id
|
|
41
|
+
field :email
|
|
42
|
+
|
|
43
|
+
unique_index :email, :email_index
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Setup
|
|
47
|
+
@company_id = "comp_#{rand(1000000)}"
|
|
48
|
+
@company = AutoValidCompany.new(company_id: @company_id, name: 'Test Corp')
|
|
49
|
+
@company.save
|
|
50
|
+
|
|
51
|
+
@emp1_id = "emp_#{rand(1000000)}"
|
|
52
|
+
@emp2_id = "emp_#{rand(1000000)}"
|
|
53
|
+
|
|
54
|
+
# =============================================
|
|
55
|
+
# 1. Automatic Validation in add_to_* Methods
|
|
56
|
+
# =============================================
|
|
57
|
+
|
|
58
|
+
## First employee can add badge to company index
|
|
59
|
+
@emp1 = AutoValidEmployee.new(emp_id: @emp1_id, badge_number: 'BADGE123', email: 'emp1@example.com')
|
|
60
|
+
@emp1.save # Save first to establish class-level email index
|
|
61
|
+
@emp1.add_to_auto_valid_company_badge_index(@company)
|
|
62
|
+
@company.badge_index.has_key?('BADGE123')
|
|
63
|
+
#=> true
|
|
64
|
+
|
|
65
|
+
## Duplicate badge is automatically rejected without manual guard call
|
|
66
|
+
@emp2 = AutoValidEmployee.new(emp_id: @emp2_id, badge_number: 'BADGE123', email: 'emp2@example.com')
|
|
67
|
+
begin
|
|
68
|
+
@emp2.add_to_auto_valid_company_badge_index(@company)
|
|
69
|
+
false
|
|
70
|
+
rescue Familia::RecordExistsError => e
|
|
71
|
+
e.message.include?('AutoValidEmployee exists in AutoValidCompany with badge_number=BADGE123')
|
|
72
|
+
end
|
|
73
|
+
#=> true
|
|
74
|
+
|
|
75
|
+
## Badge was not added after validation failure
|
|
76
|
+
@company.badge_index.get('BADGE123')
|
|
77
|
+
#=> @emp1_id
|
|
78
|
+
|
|
79
|
+
## Different badge number works fine
|
|
80
|
+
@emp2.badge_number = 'BADGE456'
|
|
81
|
+
@emp2.add_to_auto_valid_company_badge_index(@company)
|
|
82
|
+
@company.badge_index.has_key?('BADGE456')
|
|
83
|
+
#=> true
|
|
84
|
+
|
|
85
|
+
## Same employee can re-add (idempotent)
|
|
86
|
+
@emp1.add_to_auto_valid_company_badge_index(@company)
|
|
87
|
+
@company.badge_index.get('BADGE123')
|
|
88
|
+
#=> @emp1_id
|
|
89
|
+
|
|
90
|
+
## Different company allows same badge (scoped uniqueness)
|
|
91
|
+
@company2_id = "comp_#{rand(1000000)}"
|
|
92
|
+
@company2 = AutoValidCompany.new(company_id: @company2_id, name: 'Other Corp')
|
|
93
|
+
@company2.save
|
|
94
|
+
@emp3_id = "emp_#{rand(1000000)}"
|
|
95
|
+
@emp3 = AutoValidEmployee.new(emp_id: @emp3_id, badge_number: 'BADGE123', email: 'emp3@example.com')
|
|
96
|
+
@emp3.add_to_auto_valid_company_badge_index(@company2)
|
|
97
|
+
@company2.badge_index.has_key?('BADGE123')
|
|
98
|
+
#=> true
|
|
99
|
+
|
|
100
|
+
## Nil badge_number handled gracefully (no validation or addition)
|
|
101
|
+
@emp_nil = AutoValidEmployee.new(emp_id: "emp_nil_#{rand(1000000)}", badge_number: nil, email: 'empnil@example.com')
|
|
102
|
+
@emp_nil.add_to_auto_valid_company_badge_index(@company)
|
|
103
|
+
#=> nil
|
|
104
|
+
|
|
105
|
+
## Nil parent handled gracefully (no validation or addition)
|
|
106
|
+
@emp4 = AutoValidEmployee.new(emp_id: "emp4_#{rand(1000000)}", badge_number: 'BADGE789', email: 'emp4@example.com')
|
|
107
|
+
@emp4.add_to_auto_valid_company_badge_index(nil)
|
|
108
|
+
#=> nil
|
|
109
|
+
|
|
110
|
+
# =============================================
|
|
111
|
+
# 2. Transaction Detection in save()
|
|
112
|
+
# =============================================
|
|
113
|
+
|
|
114
|
+
## Normal save works outside transaction
|
|
115
|
+
@user1_id = "user_#{rand(1000000)}"
|
|
116
|
+
@user1 = AutoValidUser.new(user_id: @user1_id, email: 'user1@example.com')
|
|
117
|
+
@user1.save
|
|
118
|
+
#=> true
|
|
119
|
+
|
|
120
|
+
## save() raises error when called within transaction
|
|
121
|
+
@user2_id = "user_#{rand(1000000)}"
|
|
122
|
+
begin
|
|
123
|
+
AutoValidUser.transaction do
|
|
124
|
+
@user2 = AutoValidUser.new(user_id: @user2_id, email: 'user2@example.com')
|
|
125
|
+
@user2.save
|
|
126
|
+
end
|
|
127
|
+
false
|
|
128
|
+
rescue Familia::OperationModeError => e
|
|
129
|
+
e.message.include?('Cannot call save within a transaction')
|
|
130
|
+
end
|
|
131
|
+
#=> true
|
|
132
|
+
|
|
133
|
+
## Object was not saved due to transaction error
|
|
134
|
+
AutoValidUser.find_by_email('user2@example.com')
|
|
135
|
+
#=> nil
|
|
136
|
+
|
|
137
|
+
## Transaction with explicit field updates works (bypass save)
|
|
138
|
+
@user3_id = "user_#{rand(1000000)}"
|
|
139
|
+
@user3 = AutoValidUser.new(user_id: @user3_id, email: 'user3@example.com')
|
|
140
|
+
AutoValidUser.transaction do |_tx|
|
|
141
|
+
@user3.hmset(@user3.to_h_for_storage)
|
|
142
|
+
end
|
|
143
|
+
@user3.exists?
|
|
144
|
+
#=> true
|
|
145
|
+
|
|
146
|
+
## save() works after transaction completes
|
|
147
|
+
@user4_id = "user_#{rand(1000000)}"
|
|
148
|
+
AutoValidUser.transaction do
|
|
149
|
+
# Do something else in transaction
|
|
150
|
+
end
|
|
151
|
+
@user4 = AutoValidUser.new(user_id: @user4_id, email: 'user4@example.com')
|
|
152
|
+
@user4.save
|
|
153
|
+
#=> true
|
|
154
|
+
|
|
155
|
+
# =============================================
|
|
156
|
+
# 3. Combined Automatic Validation Scenarios
|
|
157
|
+
# =============================================
|
|
158
|
+
|
|
159
|
+
## Employee with duplicate class-level email caught in save
|
|
160
|
+
@emp5_id = "emp_#{rand(1000000)}"
|
|
161
|
+
@emp5 = AutoValidEmployee.new(emp_id: @emp5_id, badge_number: 'BADGE999', email: 'emp1@example.com')
|
|
162
|
+
begin
|
|
163
|
+
@emp5.save
|
|
164
|
+
false
|
|
165
|
+
rescue Familia::RecordExistsError => e
|
|
166
|
+
e.message.include?('AutoValidEmployee exists email=emp1@example.com')
|
|
167
|
+
end
|
|
168
|
+
#=> true
|
|
169
|
+
|
|
170
|
+
## Employee can save with unique email
|
|
171
|
+
@emp1.save
|
|
172
|
+
AutoValidEmployee.find_by_email('emp1@example.com')&.emp_id
|
|
173
|
+
#=> @emp1_id
|
|
174
|
+
|
|
175
|
+
## After save, duplicate instance-scoped index still caught automatically
|
|
176
|
+
@emp6_id = "emp_#{rand(1000000)}"
|
|
177
|
+
@emp6 = AutoValidEmployee.new(emp_id: @emp6_id, badge_number: 'BADGE123', email: 'emp6@example.com')
|
|
178
|
+
@emp6.save # Class-level index is fine
|
|
179
|
+
begin
|
|
180
|
+
@emp6.add_to_auto_valid_company_badge_index(@company) # Instance-scoped duplicate
|
|
181
|
+
false
|
|
182
|
+
rescue Familia::RecordExistsError => e
|
|
183
|
+
e.message.include?('badge_number=BADGE123')
|
|
184
|
+
end
|
|
185
|
+
#=> true
|
|
186
|
+
|
|
187
|
+
# =============================================
|
|
188
|
+
# 4. Error Message Quality
|
|
189
|
+
# =============================================
|
|
190
|
+
|
|
191
|
+
## Instance-scoped validation error includes both class names
|
|
192
|
+
begin
|
|
193
|
+
@emp2.badge_number = 'BADGE123' # Reset to duplicate
|
|
194
|
+
@emp2.add_to_auto_valid_company_badge_index(@company)
|
|
195
|
+
rescue Familia::RecordExistsError => e
|
|
196
|
+
[e.message.include?('AutoValidEmployee'), e.message.include?('AutoValidCompany')]
|
|
197
|
+
end
|
|
198
|
+
#=> [true, true]
|
|
199
|
+
|
|
200
|
+
## Instance-scoped validation error includes field name and value
|
|
201
|
+
begin
|
|
202
|
+
@emp2.add_to_auto_valid_company_badge_index(@company)
|
|
203
|
+
rescue Familia::RecordExistsError => e
|
|
204
|
+
[e.message.include?('badge_number'), e.message.include?('BADGE123')]
|
|
205
|
+
end
|
|
206
|
+
#=> [true, true]
|
|
207
|
+
|
|
208
|
+
## Error type is RecordExistsError
|
|
209
|
+
begin
|
|
210
|
+
@emp2.add_to_auto_valid_company_badge_index(@company)
|
|
211
|
+
rescue => e
|
|
212
|
+
e.class
|
|
213
|
+
end
|
|
214
|
+
#=> Familia::RecordExistsError
|
|
215
|
+
|
|
216
|
+
# =============================================
|
|
217
|
+
# 5. Performance - No Double Validation
|
|
218
|
+
# =============================================
|
|
219
|
+
|
|
220
|
+
## Manual guard call before add_to_* is redundant but harmless
|
|
221
|
+
@emp7_id = "emp_#{rand(1000000)}"
|
|
222
|
+
@emp7 = AutoValidEmployee.new(emp_id: @emp7_id, badge_number: 'BADGE777', email: 'emp7@example.com')
|
|
223
|
+
@emp7.guard_unique_auto_valid_company_badge_index!(@company)
|
|
224
|
+
@emp7.add_to_auto_valid_company_badge_index(@company)
|
|
225
|
+
@company.badge_index.has_key?('BADGE777')
|
|
226
|
+
#=> true
|
|
227
|
+
|
|
228
|
+
## Manual guard call detects duplicate
|
|
229
|
+
@emp8_id = "emp_#{rand(1000000)}"
|
|
230
|
+
@emp8 = AutoValidEmployee.new(emp_id: @emp8_id, badge_number: 'BADGE777', email: 'emp8@example.com')
|
|
231
|
+
begin
|
|
232
|
+
@emp8.guard_unique_auto_valid_company_badge_index!(@company) # Should fail - duplicate badge
|
|
233
|
+
false
|
|
234
|
+
rescue Familia::RecordExistsError
|
|
235
|
+
true
|
|
236
|
+
end
|
|
237
|
+
#=> true
|
|
238
|
+
|
|
239
|
+
# Teardown - clean up test objects
|
|
240
|
+
[@emp1, @emp2, @emp3, @emp_nil, @emp4, @emp5, @emp6, @emp7, @emp8].compact.each do |obj|
|
|
241
|
+
obj.destroy! if obj.respond_to?(:destroy!) && obj.respond_to?(:exists?) && obj.exists?
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
[@user1, @user3, @user4].compact.each do |obj|
|
|
245
|
+
obj.destroy! if obj.respond_to?(:destroy!) && obj.respond_to?(:exists?) && obj.exists?
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
[@company, @company2].each do |obj|
|
|
249
|
+
obj.destroy! if obj.respond_to?(:destroy!) && obj.respond_to?(:exists?) && obj.exists?
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Clean up class-level indexes
|
|
253
|
+
[AutoValidEmployee.email_index, AutoValidUser.email_index].each do |index|
|
|
254
|
+
index.delete! if index.respond_to?(:delete!) && index.respond_to?(:exists?) && index.exists?
|
|
255
|
+
end
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# try/unit/horreum/base_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
1
5
|
# try/horreum/base_try.rb
|
|
2
6
|
|
|
3
7
|
require_relative '../../support/helpers/test_helpers'
|
|
@@ -41,7 +45,7 @@ Familia.debug = false
|
|
|
41
45
|
|
|
42
46
|
## Remove the key
|
|
43
47
|
@hashkey.delete!
|
|
44
|
-
#=>
|
|
48
|
+
#=> 1
|
|
45
49
|
|
|
46
50
|
## Horreum objects can update and save their fields (1 of 2)
|
|
47
51
|
@customer.name = 'John Doe'
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# try/unit/horreum/class_methods_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
1
5
|
# try/horreum/class_methods_try.rb
|
|
2
6
|
|
|
3
7
|
# Test Horreum class methods
|
|
@@ -16,9 +20,9 @@ module AnotherModuleName
|
|
|
16
20
|
end
|
|
17
21
|
end
|
|
18
22
|
|
|
19
|
-
## create factory method with existence checking
|
|
23
|
+
## create! factory method with existence checking
|
|
20
24
|
TestUser
|
|
21
|
-
#==> _.respond_to?(:create)
|
|
25
|
+
#==> _.respond_to?(:create!)
|
|
22
26
|
#==> _.respond_to?(:exists?)
|
|
23
27
|
|
|
24
28
|
## multiget method is available
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# try/unit/horreum/initialization_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
1
5
|
# try/horreum/initialization_try.rb
|
|
2
6
|
|
|
3
7
|
require_relative '../../support/helpers/test_helpers'
|
|
@@ -101,7 +105,7 @@ Familia.debug = false
|
|
|
101
105
|
|
|
102
106
|
## Clean up saved test objects
|
|
103
107
|
[@customer6, @complex].map(&:delete!)
|
|
104
|
-
#=> [
|
|
108
|
+
#=> [1, 1]
|
|
105
109
|
|
|
106
110
|
## "Cleaning up" test objects that were never saved returns true regardless
|
|
107
111
|
## b/c it takes place in a transaction and it's the transaction's success
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# try/unit/horreum/optimized_loading_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Test optimized loading methods with check_exists parameter and pipelined bulk loading
|
|
6
|
+
|
|
7
|
+
require_relative '../../support/helpers/test_helpers'
|
|
8
|
+
|
|
9
|
+
OptimizedUser = Class.new(Familia::Horreum) do
|
|
10
|
+
identifier_field :user_id
|
|
11
|
+
field :user_id
|
|
12
|
+
field :name
|
|
13
|
+
field :email
|
|
14
|
+
field :age
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Setup: Create test users
|
|
18
|
+
setup_user1 = OptimizedUser.new(user_id: 'opt_user_1', name: 'Alice', email: 'alice@example.com', age: 30)
|
|
19
|
+
setup_user1.save
|
|
20
|
+
|
|
21
|
+
setup_user2 = OptimizedUser.new(user_id: 'opt_user_2', name: 'Bob', email: 'bob@example.com', age: 25)
|
|
22
|
+
setup_user2.save
|
|
23
|
+
|
|
24
|
+
setup_user3 = OptimizedUser.new(user_id: 'opt_user_3', name: 'Charlie', email: 'charlie@example.com', age: 35)
|
|
25
|
+
setup_user3.save
|
|
26
|
+
|
|
27
|
+
## find_by_dbkey with check_exists: true (default) returns object for existing key
|
|
28
|
+
found_user = OptimizedUser.find_by_dbkey(OptimizedUser.dbkey('opt_user_1'))
|
|
29
|
+
found_user.name
|
|
30
|
+
#=> 'Alice'
|
|
31
|
+
|
|
32
|
+
## find_by_dbkey with check_exists: true (default) returns nil for non-existent key
|
|
33
|
+
OptimizedUser.find_by_dbkey(OptimizedUser.dbkey('nonexistent'))
|
|
34
|
+
#=> nil
|
|
35
|
+
|
|
36
|
+
## find_by_dbkey with check_exists: false returns object for existing key
|
|
37
|
+
found_user_fast = OptimizedUser.find_by_dbkey(OptimizedUser.dbkey('opt_user_1'), check_exists: false)
|
|
38
|
+
found_user_fast.name
|
|
39
|
+
#=> 'Alice'
|
|
40
|
+
|
|
41
|
+
## find_by_dbkey with check_exists: false returns nil for non-existent key
|
|
42
|
+
OptimizedUser.find_by_dbkey(OptimizedUser.dbkey('nonexistent'), check_exists: false)
|
|
43
|
+
#=> nil
|
|
44
|
+
|
|
45
|
+
## find_by_dbkey with check_exists: false correctly deserializes all fields
|
|
46
|
+
fast_loaded = OptimizedUser.find_by_dbkey(OptimizedUser.dbkey('opt_user_2'), check_exists: false)
|
|
47
|
+
[fast_loaded.user_id, fast_loaded.name, fast_loaded.email, fast_loaded.age]
|
|
48
|
+
#=> ['opt_user_2', 'Bob', 'bob@example.com', 25]
|
|
49
|
+
|
|
50
|
+
## find_by_identifier with check_exists: true (default) returns object
|
|
51
|
+
OptimizedUser.find_by_identifier('opt_user_1').name
|
|
52
|
+
#=> 'Alice'
|
|
53
|
+
|
|
54
|
+
## find_by_identifier with check_exists: false returns object
|
|
55
|
+
OptimizedUser.find_by_identifier('opt_user_1', check_exists: false).name
|
|
56
|
+
#=> 'Alice'
|
|
57
|
+
|
|
58
|
+
## find_by_identifier with check_exists: false returns nil for non-existent
|
|
59
|
+
OptimizedUser.find_by_identifier('nonexistent', check_exists: false)
|
|
60
|
+
#=> nil
|
|
61
|
+
|
|
62
|
+
## find_by_id alias works with check_exists parameter
|
|
63
|
+
OptimizedUser.find_by_id('opt_user_2', check_exists: false).email
|
|
64
|
+
#=> 'bob@example.com'
|
|
65
|
+
|
|
66
|
+
## find alias works with check_exists parameter
|
|
67
|
+
OptimizedUser.find('opt_user_3', check_exists: false).age
|
|
68
|
+
#=> 35
|
|
69
|
+
|
|
70
|
+
## load alias works with check_exists parameter
|
|
71
|
+
OptimizedUser.load('opt_user_1', check_exists: false).user_id
|
|
72
|
+
#=> 'opt_user_1'
|
|
73
|
+
|
|
74
|
+
## load_multi loads multiple existing objects
|
|
75
|
+
users = OptimizedUser.load_multi(['opt_user_1', 'opt_user_2', 'opt_user_3'])
|
|
76
|
+
users.map(&:name)
|
|
77
|
+
#=> ['Alice', 'Bob', 'Charlie']
|
|
78
|
+
|
|
79
|
+
## load_multi returns nils for non-existent objects in correct positions
|
|
80
|
+
users_mixed = OptimizedUser.load_multi(['opt_user_1', 'nonexistent', 'opt_user_3'])
|
|
81
|
+
[users_mixed[0]&.name, users_mixed[1], users_mixed[2]&.name]
|
|
82
|
+
#=> ['Alice', nil, 'Charlie']
|
|
83
|
+
|
|
84
|
+
## load_multi with compact filters out nils
|
|
85
|
+
users_compact = OptimizedUser.load_multi(['opt_user_1', 'nonexistent', 'opt_user_2']).compact
|
|
86
|
+
users_compact.map(&:name)
|
|
87
|
+
#=> ['Alice', 'Bob']
|
|
88
|
+
|
|
89
|
+
## load_multi preserves order of identifiers
|
|
90
|
+
users_ordered = OptimizedUser.load_multi(['opt_user_3', 'opt_user_1', 'opt_user_2'])
|
|
91
|
+
users_ordered.map(&:user_id)
|
|
92
|
+
#=> ['opt_user_3', 'opt_user_1', 'opt_user_2']
|
|
93
|
+
|
|
94
|
+
## load_multi handles empty array
|
|
95
|
+
OptimizedUser.load_multi([])
|
|
96
|
+
#=> []
|
|
97
|
+
|
|
98
|
+
## load_multi handles all non-existent identifiers
|
|
99
|
+
all_missing = OptimizedUser.load_multi(['missing1', 'missing2'])
|
|
100
|
+
all_missing.compact
|
|
101
|
+
#=> []
|
|
102
|
+
|
|
103
|
+
## load_multi correctly deserializes all field types
|
|
104
|
+
multi_loaded = OptimizedUser.load_multi(['opt_user_2']).first
|
|
105
|
+
[multi_loaded.user_id, multi_loaded.name, multi_loaded.email, multi_loaded.age]
|
|
106
|
+
#=> ['opt_user_2', 'Bob', 'bob@example.com', 25]
|
|
107
|
+
|
|
108
|
+
## load_batch alias works
|
|
109
|
+
batch_users = OptimizedUser.load_batch(['opt_user_1', 'opt_user_2'])
|
|
110
|
+
batch_users.map(&:name)
|
|
111
|
+
#=> ['Alice', 'Bob']
|
|
112
|
+
|
|
113
|
+
## load_multi_by_keys loads by full dbkeys
|
|
114
|
+
keys = [OptimizedUser.dbkey('opt_user_1'), OptimizedUser.dbkey('opt_user_2')]
|
|
115
|
+
keyed_users = OptimizedUser.load_multi_by_keys(keys)
|
|
116
|
+
keyed_users.map(&:name)
|
|
117
|
+
#=> ['Alice', 'Bob']
|
|
118
|
+
|
|
119
|
+
## load_multi_by_keys returns nil for non-existent keys
|
|
120
|
+
keys_mixed = [OptimizedUser.dbkey('opt_user_1'), OptimizedUser.dbkey('nonexistent')]
|
|
121
|
+
keyed_mixed = OptimizedUser.load_multi_by_keys(keys_mixed)
|
|
122
|
+
[keyed_mixed[0]&.name, keyed_mixed[1]]
|
|
123
|
+
#=> ['Alice', nil]
|
|
124
|
+
|
|
125
|
+
## load_multi_by_keys handles empty array
|
|
126
|
+
OptimizedUser.load_multi_by_keys([])
|
|
127
|
+
#=> []
|
|
128
|
+
|
|
129
|
+
## load_multi_by_keys handles empty/nil keys and maintains position alignment
|
|
130
|
+
keys_with_empty = [OptimizedUser.dbkey('opt_user_1'), '', OptimizedUser.dbkey('opt_user_2'), nil]
|
|
131
|
+
mixed_keys = OptimizedUser.load_multi_by_keys(keys_with_empty)
|
|
132
|
+
[mixed_keys[0]&.name, mixed_keys[1], mixed_keys[2]&.name, mixed_keys[3]]
|
|
133
|
+
#=> ['Alice', nil, 'Bob', nil]
|
|
134
|
+
|
|
135
|
+
## load_multi handles nil identifiers gracefully
|
|
136
|
+
users_with_nils = OptimizedUser.load_multi(['opt_user_1', nil, 'opt_user_2'])
|
|
137
|
+
[users_with_nils[0]&.name, users_with_nils[1], users_with_nils[2]&.name]
|
|
138
|
+
#=> ['Alice', nil, 'Bob']
|
|
139
|
+
|
|
140
|
+
## load_multi handles empty string identifiers
|
|
141
|
+
users_with_empty = OptimizedUser.load_multi(['opt_user_1', '', 'opt_user_2'])
|
|
142
|
+
[users_with_empty[0]&.name, users_with_empty[1], users_with_empty[2]&.name]
|
|
143
|
+
#=> ['Alice', nil, 'Bob']
|
|
144
|
+
|
|
145
|
+
## find_by_identifier works with suffix as keyword parameter
|
|
146
|
+
OptimizedUser.find_by_identifier('opt_user_1', suffix: :object)&.name
|
|
147
|
+
#=> 'Alice'
|
|
148
|
+
|
|
149
|
+
## find_by_identifier works with both keyword parameters
|
|
150
|
+
OptimizedUser.find_by_identifier('opt_user_1', suffix: :object, check_exists: false)&.name
|
|
151
|
+
#=> 'Alice'
|
|
152
|
+
|
|
153
|
+
# Teardown: Clean up test data
|
|
154
|
+
OptimizedUser.destroy!('opt_user_1')
|
|
155
|
+
OptimizedUser.destroy!('opt_user_2')
|
|
156
|
+
OptimizedUser.destroy!('opt_user_3')
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# try/unit/horreum/relations_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
1
5
|
# try/horreum/relations_try.rb
|
|
2
6
|
# Test Horreum Database type relations functionality
|
|
3
7
|
|
|
@@ -92,15 +96,15 @@ prefs = @test_user.preferences
|
|
|
92
96
|
@test_user.preferences.size
|
|
93
97
|
#=> 2
|
|
94
98
|
|
|
95
|
-
## Clearing a counter returns
|
|
99
|
+
## Clearing a counter returns 0 when not set yet
|
|
96
100
|
@test_product.views.clear
|
|
97
101
|
@test_product.views.clear
|
|
98
|
-
#=>
|
|
102
|
+
#=> 0
|
|
99
103
|
|
|
100
|
-
## Clearing a counter returns
|
|
104
|
+
## Clearing a counter returns 1 when it is set
|
|
101
105
|
@test_product.views.increment
|
|
102
106
|
@test_product.views.clear
|
|
103
|
-
#=>
|
|
107
|
+
#=> 1
|
|
104
108
|
|
|
105
109
|
## Counter Database type works
|
|
106
110
|
@test_product.views.increment
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# try/unit/horreum/serialization_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
1
5
|
# try/horreum/serialization_try.rb
|
|
2
6
|
|
|
3
7
|
require_relative '../../support/helpers/test_helpers'
|
|
@@ -20,10 +24,10 @@ Familia.dbclient.set('debug:starting_save_if_not_exists_tests', Familia.now.to_s
|
|
|
20
24
|
@new_customer.save_if_not_exists
|
|
21
25
|
#=> true
|
|
22
26
|
|
|
23
|
-
## save_if_not_exists raises error when customer already exists
|
|
27
|
+
## save_if_not_exists! raises error when customer already exists
|
|
24
28
|
@duplicate_customer = Customer.new "new-customer-#{@test_id}@test.com"
|
|
25
29
|
@duplicate_customer.name = 'Duplicate Customer'
|
|
26
|
-
@duplicate_customer.save_if_not_exists
|
|
30
|
+
@duplicate_customer.save_if_not_exists!
|
|
27
31
|
#=!> Familia::RecordExistsError
|
|
28
32
|
#==> error.message.include?("Key already exists")
|
|
29
33
|
|