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
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# try/integration/create_method_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
1
5
|
# try/core/create_method_try.rb
|
|
2
6
|
#
|
|
3
7
|
# Comprehensive test coverage for the create method
|
|
@@ -37,7 +41,7 @@ end
|
|
|
37
41
|
# =============================================
|
|
38
42
|
|
|
39
43
|
## create method successfully creates new object
|
|
40
|
-
@created_obj = CreateTestModel.create(id: @first_test_id, name: 'Created Object', value: 'test_value')
|
|
44
|
+
@created_obj = CreateTestModel.create!(id: @first_test_id, name: 'Created Object', value: 'test_value')
|
|
41
45
|
[@created_obj.class, @created_obj.exists?, @created_obj.name]
|
|
42
46
|
#=> [CreateTestModel, true, 'Created Object']
|
|
43
47
|
|
|
@@ -55,17 +59,17 @@ end
|
|
|
55
59
|
# =============================================
|
|
56
60
|
|
|
57
61
|
## create method raises RecordExistsError for duplicate
|
|
58
|
-
CreateTestModel.create(id: @first_test_id, name: 'Duplicate Attempt')
|
|
62
|
+
CreateTestModel.create!(id: @first_test_id, name: 'Duplicate Attempt')
|
|
59
63
|
#=!> Familia::RecordExistsError
|
|
60
64
|
|
|
61
65
|
## RecordExistsError includes the dbkey in the message
|
|
62
|
-
CreateTestModel.create(id: @first_test_id, name: 'Another Duplicate')
|
|
66
|
+
CreateTestModel.create!(id: @first_test_id, name: 'Another Duplicate')
|
|
63
67
|
#=!> Familia::RecordExistsError
|
|
64
68
|
#==> !!error.message.match(/create_test_model:#{@first_test_id}:object/)
|
|
65
69
|
|
|
66
70
|
## RecordExistsError message follows consistent format
|
|
67
71
|
begin
|
|
68
|
-
CreateTestModel.create(id: @first_test_id, name: 'Yet Another Duplicate')
|
|
72
|
+
CreateTestModel.create!(id: @first_test_id, name: 'Yet Another Duplicate')
|
|
69
73
|
false # Should not reach here
|
|
70
74
|
rescue Familia::RecordExistsError => e
|
|
71
75
|
e.message.start_with?('Key already exists:')
|
|
@@ -74,10 +78,10 @@ end
|
|
|
74
78
|
|
|
75
79
|
## RecordExistsError exposes key property for programmatic access
|
|
76
80
|
@final_test_id = next_test_id
|
|
77
|
-
CreateTestModel.create(id: @final_test_id, name: 'Setup for Key Test')
|
|
81
|
+
CreateTestModel.create!(id: @final_test_id, name: 'Setup for Key Test')
|
|
78
82
|
|
|
79
83
|
begin
|
|
80
|
-
CreateTestModel.create(id: @final_test_id, name: 'Key Test Duplicate')
|
|
84
|
+
CreateTestModel.create!(id: @final_test_id, name: 'Key Test Duplicate')
|
|
81
85
|
false # Should not reach here
|
|
82
86
|
rescue Familia::RecordExistsError => e
|
|
83
87
|
# Key should be accessible and contain the identifier
|
|
@@ -90,22 +94,22 @@ end
|
|
|
90
94
|
# =============================================
|
|
91
95
|
|
|
92
96
|
## create with empty identifier raises NoIdentifier error
|
|
93
|
-
CreateTestModel.create(id: '')
|
|
97
|
+
CreateTestModel.create!(id: '')
|
|
94
98
|
#=!> Familia::NoIdentifier
|
|
95
99
|
|
|
96
100
|
## create with nil identifier raises NoIdentifier error
|
|
97
|
-
CreateTestModel.create(id: nil)
|
|
101
|
+
CreateTestModel.create!(id: nil)
|
|
98
102
|
#=!> Familia::NoIdentifier
|
|
99
103
|
|
|
100
104
|
## create with only some fields set
|
|
101
105
|
@partial_id = next_test_id
|
|
102
|
-
@partial_obj = CreateTestModel.create(id: @partial_id, name: 'Partial Object')
|
|
106
|
+
@partial_obj = CreateTestModel.create!(id: @partial_id, name: 'Partial Object')
|
|
103
107
|
[@partial_obj.exists?, @partial_obj.name, @partial_obj.value]
|
|
104
108
|
#=> [true, 'Partial Object', nil]
|
|
105
109
|
|
|
106
110
|
## create with no additional fields (only identifier)
|
|
107
111
|
@minimal_id = next_test_id
|
|
108
|
-
@minimal_obj = CreateTestModel.create(id: @minimal_id)
|
|
112
|
+
@minimal_obj = CreateTestModel.create!(id: @minimal_id)
|
|
109
113
|
[@minimal_obj.exists?, @minimal_obj.id]
|
|
110
114
|
#=> [true, @minimal_id]
|
|
111
115
|
|
|
@@ -115,14 +119,14 @@ CreateTestModel.create(id: nil)
|
|
|
115
119
|
|
|
116
120
|
## create is atomic - no partial state on failure
|
|
117
121
|
@concurrent_id = next_test_id
|
|
118
|
-
@first_obj = CreateTestModel.create(id: @concurrent_id, name: 'First')
|
|
122
|
+
@first_obj = CreateTestModel.create!(id: @concurrent_id, name: 'First')
|
|
119
123
|
|
|
120
124
|
# Verify first object exists
|
|
121
125
|
first_exists = @first_obj.exists?
|
|
122
126
|
|
|
123
127
|
# Attempt to create duplicate should not affect existing object
|
|
124
128
|
begin
|
|
125
|
-
CreateTestModel.create(id: @concurrent_id, name: 'Concurrent Attempt')
|
|
129
|
+
CreateTestModel.create!(id: @concurrent_id, name: 'Concurrent Attempt')
|
|
126
130
|
false # Should not reach here
|
|
127
131
|
rescue Familia::RecordExistsError
|
|
128
132
|
# Original object should be unchanged
|
|
@@ -134,7 +138,7 @@ end
|
|
|
134
138
|
## create failure doesn't leave partial data
|
|
135
139
|
before_failed_create = Familia.dbclient.keys("create_test_model:#{@concurrent_id}:*").length
|
|
136
140
|
begin
|
|
137
|
-
CreateTestModel.create(id: @concurrent_id, name: 'Should Fail')
|
|
141
|
+
CreateTestModel.create!(id: @concurrent_id, name: 'Should Fail')
|
|
138
142
|
rescue Familia::RecordExistsError
|
|
139
143
|
# Should not create any additional keys
|
|
140
144
|
after_failed_create = Familia.dbclient.keys("create_test_model:#{@concurrent_id}:*").length
|
|
@@ -148,20 +152,20 @@ end
|
|
|
148
152
|
|
|
149
153
|
## Both create and save_if_not_exists raise same error type for duplicates
|
|
150
154
|
@consistency_id = next_test_id
|
|
151
|
-
@consistency_obj = CreateTestModel.create(id: @consistency_id, name: 'Consistency Test')
|
|
155
|
+
@consistency_obj = CreateTestModel.create!(id: @consistency_id, name: 'Consistency Test')
|
|
152
156
|
|
|
153
157
|
# Test create raises RecordExistsError
|
|
154
158
|
create_error_class = begin
|
|
155
|
-
CreateTestModel.create(id: @consistency_id, name: 'Create Duplicate')
|
|
159
|
+
CreateTestModel.create!(id: @consistency_id, name: 'Create Duplicate')
|
|
156
160
|
nil
|
|
157
161
|
rescue => e
|
|
158
162
|
e.class
|
|
159
163
|
end
|
|
160
164
|
|
|
161
|
-
# Test save_if_not_exists raises RecordExistsError
|
|
165
|
+
# Test save_if_not_exists! raises RecordExistsError
|
|
162
166
|
sine_error_class = begin
|
|
163
167
|
duplicate_obj = CreateTestModel.new(id: @consistency_id, name: 'SINE Duplicate')
|
|
164
|
-
duplicate_obj.save_if_not_exists
|
|
168
|
+
duplicate_obj.save_if_not_exists!
|
|
165
169
|
nil
|
|
166
170
|
rescue => e
|
|
167
171
|
e.class
|
|
@@ -172,17 +176,17 @@ end
|
|
|
172
176
|
|
|
173
177
|
## Both methods have similar error message patterns
|
|
174
178
|
@error_comparison_id = next_test_id
|
|
175
|
-
CreateTestModel.create(id: @error_comparison_id, name: 'Error Comparison')
|
|
179
|
+
CreateTestModel.create!(id: @error_comparison_id, name: 'Error Comparison')
|
|
176
180
|
|
|
177
181
|
create_error_msg = begin
|
|
178
|
-
CreateTestModel.create(id: @error_comparison_id, name: 'Create Error')
|
|
182
|
+
CreateTestModel.create!(id: @error_comparison_id, name: 'Create Error')
|
|
179
183
|
nil
|
|
180
184
|
rescue => e
|
|
181
185
|
e.message
|
|
182
186
|
end
|
|
183
187
|
|
|
184
188
|
sine_error_msg = begin
|
|
185
|
-
CreateTestModel.new(id: @error_comparison_id, name: 'SINE Error').save_if_not_exists
|
|
189
|
+
CreateTestModel.new(id: @error_comparison_id, name: 'SINE Error').save_if_not_exists!
|
|
186
190
|
nil
|
|
187
191
|
rescue => e
|
|
188
192
|
e.message
|
|
@@ -198,7 +202,7 @@ end
|
|
|
198
202
|
|
|
199
203
|
## create works with complex field values
|
|
200
204
|
@complex_id = next_test_id
|
|
201
|
-
@complex_obj = CreateTestModel.create(
|
|
205
|
+
@complex_obj = CreateTestModel.create!(
|
|
202
206
|
id: @complex_id,
|
|
203
207
|
name: 'Complex Object',
|
|
204
208
|
value: { nested: 'data', array: [1, 2, 3] }
|
|
@@ -214,7 +218,7 @@ end
|
|
|
214
218
|
@consistency_check_id = next_test_id
|
|
215
219
|
|
|
216
220
|
# Create via class method
|
|
217
|
-
@class_created = CreateTestModel.create(id: @consistency_check_id, name: 'Class Created')
|
|
221
|
+
@class_created = CreateTestModel.create!(id: @consistency_check_id, name: 'Class Created')
|
|
218
222
|
|
|
219
223
|
# Both class and instance methods should see the object as existing
|
|
220
224
|
class_sees_exists = CreateTestModel.exists?(@consistency_check_id)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# try/integration/data_types/datatype_pipelines_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# DataType Pipeline Support Tryouts
|
|
6
|
+
#
|
|
7
|
+
# Tests pipeline support for DataType objects. Pipelines provide performance
|
|
8
|
+
# optimization by batching commands without the atomicity guarantee of transactions.
|
|
9
|
+
|
|
10
|
+
require_relative '../../support/helpers/test_helpers'
|
|
11
|
+
|
|
12
|
+
# Setup
|
|
13
|
+
class PipelineTestUser < Familia::Horreum
|
|
14
|
+
logical_database 4
|
|
15
|
+
identifier_field :userid
|
|
16
|
+
field :userid
|
|
17
|
+
field :name
|
|
18
|
+
|
|
19
|
+
sorted_set :scores
|
|
20
|
+
hashkey :profile
|
|
21
|
+
set :tags
|
|
22
|
+
counter :visits
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@user = PipelineTestUser.new(userid: 'pipe_user_001')
|
|
26
|
+
@user.name = 'Pipeline Tester'
|
|
27
|
+
@user.save
|
|
28
|
+
|
|
29
|
+
## Parent-owned SortedSet can execute pipeline
|
|
30
|
+
result = @user.scores.pipelined do |pipe|
|
|
31
|
+
pipe.zadd(@user.scores.dbkey, 100, 'p1')
|
|
32
|
+
pipe.zadd(@user.scores.dbkey, 200, 'p2')
|
|
33
|
+
pipe.zcard(@user.scores.dbkey)
|
|
34
|
+
end
|
|
35
|
+
[result.is_a?(MultiResult), @user.scores.members.size]
|
|
36
|
+
#=> [true, 2]
|
|
37
|
+
|
|
38
|
+
## Parent-owned HashKey can execute pipeline
|
|
39
|
+
result = @user.profile.pipelined do |pipe|
|
|
40
|
+
pipe.hset(@user.profile.dbkey, 'city', 'NYC')
|
|
41
|
+
pipe.hset(@user.profile.dbkey, 'state', 'NY')
|
|
42
|
+
pipe.hgetall(@user.profile.dbkey)
|
|
43
|
+
end
|
|
44
|
+
[result.is_a?(MultiResult), @user.profile.keys.sort]
|
|
45
|
+
#=> [true, ["city", "state"]]
|
|
46
|
+
|
|
47
|
+
## Standalone SortedSet can execute pipeline
|
|
48
|
+
leaderboard = Familia::SortedSet.new('pipeline:leaderboard')
|
|
49
|
+
leaderboard.delete!
|
|
50
|
+
result = leaderboard.pipelined do |pipe|
|
|
51
|
+
pipe.zadd(leaderboard.dbkey, 100, 'player1')
|
|
52
|
+
pipe.zadd(leaderboard.dbkey, 200, 'player2')
|
|
53
|
+
pipe.zcard(leaderboard.dbkey)
|
|
54
|
+
end
|
|
55
|
+
[result.is_a?(MultiResult), leaderboard.members.size]
|
|
56
|
+
#=> [true, 2]
|
|
57
|
+
|
|
58
|
+
## Pipeline with direct_access works correctly
|
|
59
|
+
result = @user.profile.pipelined do |pipe_conn|
|
|
60
|
+
pipe_conn.hset(@user.profile.dbkey, 'pipeline_test', 'yes')
|
|
61
|
+
|
|
62
|
+
@user.profile.direct_access do |conn, key|
|
|
63
|
+
conn.object_id == pipe_conn.object_id &&
|
|
64
|
+
conn.hset(key, 'direct_test', 'yes')
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
[@user.profile['pipeline_test'], @user.profile['direct_test']]
|
|
68
|
+
#=> ["yes", "yes"]
|
|
69
|
+
|
|
70
|
+
## Pipeline returns MultiResult with correct structure
|
|
71
|
+
result = @user.scores.pipelined do |pipe|
|
|
72
|
+
pipe.zadd(@user.scores.dbkey, 300, 'p3')
|
|
73
|
+
pipe.zadd(@user.scores.dbkey, 400, 'p4')
|
|
74
|
+
end
|
|
75
|
+
[result.is_a?(MultiResult), result.results.is_a?(Array)]
|
|
76
|
+
#=> [true, true]
|
|
77
|
+
|
|
78
|
+
## Empty pipeline returns empty MultiResult
|
|
79
|
+
result = @user.scores.pipelined { |pipe| }
|
|
80
|
+
[result.is_a?(MultiResult), result.results.empty?]
|
|
81
|
+
#=> [true, true]
|
|
82
|
+
|
|
83
|
+
## Multiple DataType operations in single pipeline
|
|
84
|
+
result = @user.scores.pipelined do |pipe|
|
|
85
|
+
pipe.zadd(@user.scores.dbkey, 500, 'multi')
|
|
86
|
+
pipe.hset(@user.profile.dbkey, 'multi', 'pipeline')
|
|
87
|
+
pipe.sadd(@user.tags.dbkey, 'multi_tag')
|
|
88
|
+
end
|
|
89
|
+
[
|
|
90
|
+
result.is_a?(MultiResult),
|
|
91
|
+
@user.scores.member?('multi'),
|
|
92
|
+
@user.profile['multi'],
|
|
93
|
+
@user.tags.member?('multi_tag')
|
|
94
|
+
]
|
|
95
|
+
#=> [true, true, "pipeline", true]
|
|
96
|
+
|
|
97
|
+
## Standalone HashKey with logical_database option
|
|
98
|
+
custom = Familia::HashKey.new('pipeline:custom', logical_database: 5)
|
|
99
|
+
custom.delete!
|
|
100
|
+
result = custom.pipelined do |pipe|
|
|
101
|
+
pipe.hset(custom.dbkey, 'key1', 'value1')
|
|
102
|
+
pipe.hget(custom.dbkey, 'key1')
|
|
103
|
+
end
|
|
104
|
+
result.is_a?(MultiResult)
|
|
105
|
+
#=> true
|
|
106
|
+
|
|
107
|
+
# Cleanup
|
|
108
|
+
@user.destroy!
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# try/integration/data_types/datatype_transactions_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# DataType Transaction Support Tryouts
|
|
6
|
+
#
|
|
7
|
+
# Tests transaction support for DataType objects, covering both parent-owned
|
|
8
|
+
# DataTypes (delegating to parent) and standalone DataTypes (managing their
|
|
9
|
+
# own connections). Validates atomic operations, connection context handling,
|
|
10
|
+
# and integration with the transaction mode system.
|
|
11
|
+
|
|
12
|
+
require_relative '../../support/helpers/test_helpers'
|
|
13
|
+
|
|
14
|
+
# Setup - Create test model with various DataType fields
|
|
15
|
+
class TransactionTestUser < Familia::Horreum
|
|
16
|
+
logical_database 2
|
|
17
|
+
identifier_field :userid
|
|
18
|
+
field :userid
|
|
19
|
+
field :name
|
|
20
|
+
field :email
|
|
21
|
+
|
|
22
|
+
# Instance-level DataTypes
|
|
23
|
+
sorted_set :scores
|
|
24
|
+
hashkey :profile
|
|
25
|
+
set :tags
|
|
26
|
+
list :activity
|
|
27
|
+
counter :visits
|
|
28
|
+
string :bio
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
@user = TransactionTestUser.new(userid: 'txn_user_001')
|
|
32
|
+
@user.name = 'Transaction Tester'
|
|
33
|
+
@user.save
|
|
34
|
+
|
|
35
|
+
## Parent-owned SortedSet can execute transaction
|
|
36
|
+
result = @user.scores.transaction do |conn|
|
|
37
|
+
conn.zadd(@user.scores.dbkey, 100, 'level1')
|
|
38
|
+
conn.zadd(@user.scores.dbkey, 200, 'level2')
|
|
39
|
+
conn.zadd(@user.scores.dbkey, 300, 'level3')
|
|
40
|
+
end
|
|
41
|
+
[result.is_a?(MultiResult), @user.scores.members.sort]
|
|
42
|
+
#=> [true, ["level1", "level2", "level3"]]
|
|
43
|
+
|
|
44
|
+
## Parent-owned HashKey can execute transaction
|
|
45
|
+
result = @user.profile.transaction do |conn|
|
|
46
|
+
conn.hset(@user.profile.dbkey, 'city', 'San Francisco')
|
|
47
|
+
conn.hset(@user.profile.dbkey, 'country', 'USA')
|
|
48
|
+
conn.hget(@user.profile.dbkey, 'city')
|
|
49
|
+
end
|
|
50
|
+
[result.is_a?(MultiResult), result.results.last, @user.profile['country']]
|
|
51
|
+
#=> [true, "San Francisco", "USA"]
|
|
52
|
+
|
|
53
|
+
## Parent-owned UnsortedSet can execute transaction
|
|
54
|
+
result = @user.tags.transaction do |conn|
|
|
55
|
+
conn.sadd(@user.tags.dbkey, 'ruby')
|
|
56
|
+
conn.sadd(@user.tags.dbkey, 'redis')
|
|
57
|
+
conn.scard(@user.tags.dbkey)
|
|
58
|
+
end
|
|
59
|
+
[result.is_a?(MultiResult), result.results.last, @user.tags.members.sort]
|
|
60
|
+
#=> [true, 2, ["redis", "ruby"]]
|
|
61
|
+
|
|
62
|
+
## Parent-owned List can execute transaction
|
|
63
|
+
result = @user.activity.transaction do |conn|
|
|
64
|
+
conn.rpush(@user.activity.dbkey, 'login')
|
|
65
|
+
conn.rpush(@user.activity.dbkey, 'view_profile')
|
|
66
|
+
conn.rpush(@user.activity.dbkey, 'logout')
|
|
67
|
+
conn.llen(@user.activity.dbkey)
|
|
68
|
+
end
|
|
69
|
+
[result.is_a?(MultiResult), result.results.last, @user.activity.members]
|
|
70
|
+
#=> [true, 3, ["login", "view_profile", "logout"]]
|
|
71
|
+
|
|
72
|
+
## Parent-owned Counter can execute transaction
|
|
73
|
+
result = @user.visits.transaction do |conn|
|
|
74
|
+
conn.set(@user.visits.dbkey, 0)
|
|
75
|
+
conn.incr(@user.visits.dbkey)
|
|
76
|
+
conn.incr(@user.visits.dbkey)
|
|
77
|
+
conn.get(@user.visits.dbkey)
|
|
78
|
+
end
|
|
79
|
+
[result.is_a?(MultiResult), result.results.last.to_i, @user.visits.value]
|
|
80
|
+
#=> [true, 2, 2]
|
|
81
|
+
|
|
82
|
+
## Parent-owned StringKey can execute transaction
|
|
83
|
+
result = @user.bio.transaction do |conn|
|
|
84
|
+
conn.set(@user.bio.dbkey, 'Ruby developer')
|
|
85
|
+
conn.append(@user.bio.dbkey, ' and Redis enthusiast')
|
|
86
|
+
conn.get(@user.bio.dbkey)
|
|
87
|
+
end
|
|
88
|
+
[result.is_a?(MultiResult), result.results.last, @user.bio.value]
|
|
89
|
+
#=> [true, "Ruby developer and Redis enthusiast", "Ruby developer and Redis enthusiast"]
|
|
90
|
+
|
|
91
|
+
## Standalone SortedSet can execute transaction
|
|
92
|
+
leaderboard = Familia::SortedSet.new('game:leaderboard')
|
|
93
|
+
leaderboard.delete!
|
|
94
|
+
result = leaderboard.transaction do |conn|
|
|
95
|
+
conn.zadd(leaderboard.dbkey, 500, 'player1')
|
|
96
|
+
conn.zadd(leaderboard.dbkey, 600, 'player2')
|
|
97
|
+
conn.zadd(leaderboard.dbkey, 450, 'player3')
|
|
98
|
+
conn.zcard(leaderboard.dbkey)
|
|
99
|
+
end
|
|
100
|
+
[result.is_a?(MultiResult), result.results.last, leaderboard.members.size]
|
|
101
|
+
#=> [true, 3, 3]
|
|
102
|
+
|
|
103
|
+
## Standalone HashKey can execute transaction
|
|
104
|
+
cache = Familia::HashKey.new('app:cache')
|
|
105
|
+
cache.delete!
|
|
106
|
+
result = cache.transaction do |conn|
|
|
107
|
+
conn.hset(cache.dbkey, 'key1', 'value1')
|
|
108
|
+
conn.hset(cache.dbkey, 'key2', 'value2')
|
|
109
|
+
conn.hkeys(cache.dbkey)
|
|
110
|
+
end
|
|
111
|
+
[result.is_a?(MultiResult), result.results.last.sort, cache.keys.sort]
|
|
112
|
+
#=> [true, ["key1", "key2"], ["key1", "key2"]]
|
|
113
|
+
|
|
114
|
+
## Standalone UnsortedSet can execute transaction
|
|
115
|
+
global_tags = Familia::UnsortedSet.new('app:tags')
|
|
116
|
+
global_tags.delete!
|
|
117
|
+
result = global_tags.transaction do |conn|
|
|
118
|
+
conn.sadd(global_tags.dbkey, 'tag1')
|
|
119
|
+
conn.sadd(global_tags.dbkey, 'tag2')
|
|
120
|
+
conn.smembers(global_tags.dbkey)
|
|
121
|
+
end
|
|
122
|
+
[result.is_a?(MultiResult), result.results.last.sort, global_tags.members.sort]
|
|
123
|
+
#=> [true, ["tag1", "tag2"], ["tag1", "tag2"]]
|
|
124
|
+
|
|
125
|
+
## Standalone StringKey can execute transaction
|
|
126
|
+
session_data = Familia::StringKey.new('session:abc123')
|
|
127
|
+
session_data.delete!
|
|
128
|
+
result = session_data.transaction do |conn|
|
|
129
|
+
conn.set(session_data.dbkey, '{"user_id": 123}')
|
|
130
|
+
conn.expire(session_data.dbkey, 3600)
|
|
131
|
+
conn.get(session_data.dbkey)
|
|
132
|
+
end
|
|
133
|
+
[result.is_a?(MultiResult), result.results.last, session_data.value]
|
|
134
|
+
#=> [true, "{\"user_id\": 123}", "{\"user_id\": 123}"]
|
|
135
|
+
|
|
136
|
+
## Transaction with logical_database option works
|
|
137
|
+
custom_cache = Familia::HashKey.new('custom:cache', logical_database: 3)
|
|
138
|
+
custom_cache.delete!
|
|
139
|
+
result = custom_cache.transaction do |conn|
|
|
140
|
+
conn.hset(custom_cache.dbkey, 'setting', 'enabled')
|
|
141
|
+
conn.hget(custom_cache.dbkey, 'setting')
|
|
142
|
+
end
|
|
143
|
+
[result.is_a?(MultiResult), result.results.last]
|
|
144
|
+
#=> [true, "enabled"]
|
|
145
|
+
|
|
146
|
+
## Transaction provides correct connection object type
|
|
147
|
+
conn_class = nil
|
|
148
|
+
@user.scores.transaction do |conn|
|
|
149
|
+
conn_class = conn.class.name
|
|
150
|
+
end
|
|
151
|
+
conn_class
|
|
152
|
+
#=> "Redis::MultiConnection"
|
|
153
|
+
|
|
154
|
+
## Transaction with direct_access works correctly
|
|
155
|
+
result = @user.profile.transaction do |trans_conn|
|
|
156
|
+
trans_conn.hset(@user.profile.dbkey, 'status', 'active')
|
|
157
|
+
|
|
158
|
+
# direct_access should use the same transaction connection
|
|
159
|
+
@user.profile.direct_access do |conn, key|
|
|
160
|
+
conn.object_id == trans_conn.object_id &&
|
|
161
|
+
conn.hset(key, 'verified', 'true')
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
[@user.profile['status'], @user.profile['verified']]
|
|
165
|
+
#=> ["active", "true"]
|
|
166
|
+
|
|
167
|
+
## Transaction atomicity - all commands succeed or none
|
|
168
|
+
test_zset = Familia::SortedSet.new('atomic:test')
|
|
169
|
+
test_zset.delete!
|
|
170
|
+
test_zset.add('initial', 1)
|
|
171
|
+
|
|
172
|
+
begin
|
|
173
|
+
test_zset.transaction do |conn|
|
|
174
|
+
conn.zadd(test_zset.dbkey, 100, 'member1')
|
|
175
|
+
conn.zadd(test_zset.dbkey, 200, 'member2')
|
|
176
|
+
raise 'Intentional error to test rollback'
|
|
177
|
+
end
|
|
178
|
+
rescue => e
|
|
179
|
+
# Transaction should have rolled back
|
|
180
|
+
test_zset.members
|
|
181
|
+
end
|
|
182
|
+
#=> ["initial"]
|
|
183
|
+
|
|
184
|
+
## Nested transactions with parent-owned DataTypes work
|
|
185
|
+
outer_result = @user.scores.transaction do |outer_conn|
|
|
186
|
+
outer_conn.zadd(@user.scores.dbkey, 999, 'outer_member')
|
|
187
|
+
|
|
188
|
+
inner_result = @user.tags.transaction do |inner_conn|
|
|
189
|
+
inner_conn.sadd(@user.tags.dbkey, 'nested_tag')
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
inner_result.is_a?(MultiResult)
|
|
193
|
+
end
|
|
194
|
+
[outer_result.is_a?(MultiResult), @user.tags.member?('nested_tag')]
|
|
195
|
+
#=> [true, true]
|
|
196
|
+
|
|
197
|
+
## Transaction respects transaction modes (permissive)
|
|
198
|
+
begin
|
|
199
|
+
original_mode = Familia.transaction_mode
|
|
200
|
+
Familia.configure { |config| config.transaction_mode = :permissive }
|
|
201
|
+
|
|
202
|
+
# Force a cached connection to trigger fallback
|
|
203
|
+
@user.class.instance_variable_set(:@dbclient, Familia.create_dbclient)
|
|
204
|
+
|
|
205
|
+
result = @user.scores.transaction do |conn|
|
|
206
|
+
# Should be IndividualCommandProxy in fallback mode
|
|
207
|
+
conn.class == Familia::Connection::IndividualCommandProxy &&
|
|
208
|
+
conn.zadd(@user.scores.dbkey, 888, 'fallback_test')
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
result.is_a?(MultiResult)
|
|
212
|
+
ensure
|
|
213
|
+
@user.class.remove_instance_variable(:@dbclient)
|
|
214
|
+
Familia.configure { |config| config.transaction_mode = original_mode }
|
|
215
|
+
end
|
|
216
|
+
#=> true
|
|
217
|
+
|
|
218
|
+
## Transaction with empty block returns empty MultiResult
|
|
219
|
+
result = @user.scores.transaction { |conn| }
|
|
220
|
+
[result.is_a?(MultiResult), result.results.empty?]
|
|
221
|
+
#=> [true, true]
|
|
222
|
+
|
|
223
|
+
## Transaction connection uses parent's logical_database
|
|
224
|
+
# TransactionTestUser has logical_database 2
|
|
225
|
+
# Parent-owned DataType delegates to parent, verify via class setting
|
|
226
|
+
@user.scores.delete!
|
|
227
|
+
@user.scores.transaction do |conn|
|
|
228
|
+
conn.zadd(@user.scores.dbkey, 1, 'test_member')
|
|
229
|
+
end
|
|
230
|
+
TransactionTestUser.logical_database
|
|
231
|
+
#=> 2
|
|
232
|
+
|
|
233
|
+
## Multiple DataType types in single transaction
|
|
234
|
+
result = @user.scores.transaction do |conn|
|
|
235
|
+
# Can operate on different DataTypes using same connection
|
|
236
|
+
conn.zadd(@user.scores.dbkey, 777, 'multi_test')
|
|
237
|
+
conn.hset(@user.profile.dbkey, 'multi', 'yes')
|
|
238
|
+
conn.sadd(@user.tags.dbkey, 'multi_tag')
|
|
239
|
+
conn.rpush(@user.activity.dbkey, 'multi_action')
|
|
240
|
+
end
|
|
241
|
+
[
|
|
242
|
+
result.is_a?(MultiResult),
|
|
243
|
+
@user.scores.member?('multi_test'),
|
|
244
|
+
@user.profile['multi'],
|
|
245
|
+
@user.tags.member?('multi_tag'),
|
|
246
|
+
@user.activity.members.include?('multi_action')
|
|
247
|
+
]
|
|
248
|
+
#=> [true, true, "yes", true, true]
|
|
249
|
+
|
|
250
|
+
# Cleanup
|
|
251
|
+
@user.destroy!
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# try/integration/models/customer_safe_dump_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
1
5
|
# try/models/customer_safedump_try.rb
|
|
2
6
|
|
|
3
7
|
require_relative '../../support/helpers/test_helpers'
|
|
@@ -39,7 +43,11 @@ require_relative '../../support/helpers/test_helpers'
|
|
|
39
43
|
|
|
40
44
|
## Safe dump includes correct updated timestamp
|
|
41
45
|
@safe_dump[:updated]
|
|
42
|
-
|
|
46
|
+
#=:> Float
|
|
47
|
+
|
|
48
|
+
## Safe dump includes correct updated timestamp
|
|
49
|
+
@safe_dump[:updated].to_i
|
|
50
|
+
#=> @now.to_i
|
|
43
51
|
|
|
44
52
|
## Safe dump includes correct secrets_created count
|
|
45
53
|
@customer.secrets_created.increment
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# try/integration/models/familia_object_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
1
5
|
# try/models/familia_object_try.rb
|
|
2
6
|
|
|
3
7
|
require_relative '../../support/helpers/test_helpers'
|
|
@@ -71,7 +75,7 @@ Customer.all_customers.size
|
|
|
71
75
|
|
|
72
76
|
## Familia class clear
|
|
73
77
|
Customer.all_customers.delete!
|
|
74
|
-
#=>
|
|
78
|
+
#=> 1
|
|
75
79
|
|
|
76
80
|
## Familia class replace 1 of 4
|
|
77
81
|
Customer.message.value = 'msg1'
|