familia 2.0.0.pre19 → 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 +177 -112
- data/CLAUDE.md +28 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +20 -17
- data/bin/try +16 -0
- data/bin/tryouts +16 -0
- data/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 +161 -117
- data/docs/guides/feature-external-identifiers.md +415 -443
- data/docs/guides/feature-object-identifiers.md +400 -269
- data/docs/guides/feature-quantization.md +120 -6
- data/docs/guides/feature-relationships-indexing.md +318 -0
- data/docs/guides/feature-relationships-methods.md +146 -604
- data/docs/guides/feature-relationships-participation.md +263 -0
- data/docs/guides/feature-relationships.md +118 -136
- data/docs/guides/feature-system-devs.md +176 -693
- data/docs/guides/feature-system.md +119 -6
- data/docs/guides/feature-transient-fields.md +81 -0
- data/docs/guides/field-system.md +778 -0
- data/docs/guides/index.md +32 -15
- data/docs/guides/logging.md +187 -0
- data/docs/guides/optimized-loading.md +674 -0
- data/docs/guides/thread-safety-monitoring.md +61 -0
- data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
- data/docs/migrating/v2.0.0-pre22.md +241 -0
- data/docs/overview.md +7 -9
- data/docs/reference/api-technical.md +267 -320
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
- data/examples/autoloader/mega_customer.rb +2 -0
- data/examples/datatype_standalone.rb +4 -3
- data/examples/encrypted_fields.rb +2 -1
- data/examples/json_usage_patterns.rb +2 -0
- data/examples/relationships.rb +3 -0
- data/examples/safe_dump.rb +2 -1
- data/examples/sampling_demo.rb +53 -0
- data/examples/single_connection_transaction_confusions.rb +2 -1
- data/familia.gemspec +2 -1
- data/lib/familia/base.rb +2 -0
- data/lib/familia/connection/behavior.rb +2 -0
- data/lib/familia/connection/handlers.rb +2 -0
- data/lib/familia/connection/individual_command_proxy.rb +2 -0
- data/lib/familia/connection/middleware.rb +34 -24
- data/lib/familia/connection/operation_core.rb +2 -0
- data/lib/familia/connection/operations.rb +2 -0
- data/lib/familia/connection/pipelined_core.rb +2 -0
- data/lib/familia/connection/transaction_core.rb +68 -0
- data/lib/familia/connection.rb +18 -3
- data/lib/familia/data_type/class_methods.rb +3 -1
- data/lib/familia/data_type/connection.rb +2 -0
- data/lib/familia/data_type/database_commands.rb +2 -0
- data/lib/familia/data_type/serialization.rb +6 -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 +7 -5
- data/lib/familia/data_type/types/listkey.rb +2 -0
- data/lib/familia/data_type/types/lock.rb +2 -0
- data/lib/familia/data_type/types/sorted_set.rb +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 +2 -0
- data/lib/familia/features/autoloader.rb +2 -0
- data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
- data/lib/familia/features/encrypted_fields.rb +2 -2
- data/lib/familia/features/expiration/extensions.rb +3 -1
- data/lib/familia/features/expiration.rb +12 -4
- data/lib/familia/features/external_identifier.rb +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 +138 -9
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +89 -21
- data/lib/familia/features/relationships/indexing.rb +3 -0
- data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
- data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
- data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
- data/lib/familia/features/relationships/participation.rb +155 -69
- data/lib/familia/features/relationships/participation_membership.rb +69 -0
- data/lib/familia/features/relationships/participation_relationship.rb +34 -6
- data/lib/familia/features/relationships/score_encoding.rb +2 -0
- data/lib/familia/features/relationships.rb +5 -3
- data/lib/familia/features/safe_dump.rb +2 -0
- data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
- data/lib/familia/features/transient_fields.rb +2 -0
- data/lib/familia/features.rb +2 -0
- data/lib/familia/field_type.rb +3 -1
- data/lib/familia/horreum/connection.rb +17 -1
- data/lib/familia/horreum/database_commands.rb +2 -0
- data/lib/familia/horreum/definition.rb +16 -6
- data/lib/familia/horreum/management.rb +212 -42
- data/lib/familia/horreum/persistence.rb +176 -108
- data/lib/familia/horreum/related_fields.rb +2 -0
- data/lib/familia/horreum/serialization.rb +23 -4
- data/lib/familia/horreum/settings.rb +2 -0
- data/lib/familia/horreum/utils.rb +2 -0
- data/lib/familia/horreum.rb +15 -1
- data/lib/familia/identifier_extractor.rb +2 -0
- data/lib/familia/instrumentation.rb +156 -0
- data/lib/familia/json_serializer.rb +2 -0
- data/lib/familia/logging.rb +92 -32
- data/lib/familia/refinements/dear_json.rb +2 -0
- data/lib/familia/refinements/stylize_words.rb +2 -14
- data/lib/familia/refinements/time_literals.rb +2 -0
- data/lib/familia/refinements.rb +2 -0
- data/lib/familia/secure_identifier.rb +10 -2
- data/lib/familia/settings.rb +2 -0
- data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
- data/lib/familia/thread_safety/monitor.rb +328 -0
- data/lib/familia/utils.rb +13 -0
- data/lib/familia/verifiable_identifier.rb +3 -1
- data/lib/familia/version.rb +3 -1
- data/lib/familia.rb +31 -4
- data/lib/middleware/database_command_counter.rb +152 -0
- data/lib/middleware/database_logger.rb +295 -170
- data/lib/multi_result.rb +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 +2 -0
- 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 +4 -0
- 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 +2 -0
- data/try/features/relationships/participation_bidirectional_try.rb +242 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
- data/try/features/relationships/participation_commands_verification_try.rb +2 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
- data/try/features/relationships/participation_reverse_index_try.rb +15 -13
- data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
- data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
- data/try/features/relationships/relationships_api_changes_try.rb +2 -0
- data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
- data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
- data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
- data/try/features/relationships/relationships_performance_try.rb +4 -0
- data/try/features/relationships/relationships_performance_working_try.rb +4 -0
- data/try/features/relationships/relationships_try.rb +6 -4
- data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
- data/try/features/safe_dump/safe_dump_try.rb +4 -0
- data/try/features/transient_fields/redacted_string_try.rb +2 -0
- data/try/features/transient_fields/refresh_reset_try.rb +3 -0
- data/try/features/transient_fields/simple_refresh_test.rb +3 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
- data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
- data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
- data/try/integration/connection/fiber_context_preservation_try.rb +4 -0
- data/try/integration/connection/handler_constraints_try.rb +4 -0
- data/try/integration/connection/isolated_dbclient_try.rb +4 -0
- data/try/integration/connection/middleware_reconnect_try.rb +2 -0
- data/try/integration/connection/operation_mode_guards_try.rb +4 -0
- data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
- data/try/integration/connection/pools_try.rb +4 -0
- data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
- data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
- data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
- data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
- data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
- data/try/integration/connection/transaction_modes_try.rb +4 -0
- data/try/integration/conventional_inheritance_try.rb +4 -0
- data/try/integration/create_method_try.rb +4 -0
- data/try/integration/cross_component_try.rb +4 -0
- data/try/integration/data_types/datatype_pipelines_try.rb +4 -0
- data/try/integration/data_types/datatype_transactions_try.rb +4 -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 +4 -0
- 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 +4 -0
- data/try/integration/persistence_operations_try.rb +4 -0
- data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
- data/try/integration/save_methods_consistency_try.rb +241 -0
- data/try/integration/scenarios_try.rb +4 -0
- data/try/integration/secure_identifier_try.rb +4 -0
- data/try/integration/transaction_safety_core_try.rb +176 -0
- data/try/integration/transaction_safety_workflow_try.rb +291 -0
- data/try/integration/verifiable_identifier_try.rb +4 -0
- data/try/investigation/pipeline_routing/README.md +228 -0
- data/try/performance/benchmarks_try.rb +4 -0
- data/try/performance/transaction_safety_benchmark_try.rb +238 -0
- data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
- data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
- data/try/support/debugging/cache_behavior_tracer.rb +4 -0
- data/try/support/debugging/debug_aad_process.rb +3 -0
- data/try/support/debugging/debug_concealed_internal.rb +3 -0
- data/try/support/debugging/debug_concealed_reveal.rb +3 -0
- data/try/support/debugging/debug_context_aad.rb +3 -0
- data/try/support/debugging/debug_context_simple.rb +3 -0
- data/try/support/debugging/debug_cross_context.rb +3 -0
- data/try/support/debugging/debug_database_load.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
- data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
- data/try/support/debugging/debug_field_decrypt.rb +3 -0
- data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
- data/try/support/debugging/debug_load_path.rb +3 -0
- data/try/support/debugging/debug_method_definition.rb +3 -0
- data/try/support/debugging/debug_method_resolution.rb +3 -0
- data/try/support/debugging/debug_minimal.rb +3 -0
- data/try/support/debugging/debug_provider.rb +3 -0
- data/try/support/debugging/debug_secure_behavior.rb +3 -0
- data/try/support/debugging/debug_string_class.rb +3 -0
- data/try/support/debugging/debug_test.rb +3 -0
- data/try/support/debugging/debug_test_design.rb +3 -0
- data/try/support/debugging/encryption_method_tracer.rb +4 -0
- data/try/support/debugging/provider_diagnostics.rb +4 -0
- data/try/support/helpers/test_cleanup.rb +4 -0
- data/try/support/helpers/test_helpers.rb +5 -0
- data/try/support/memory/memory_basic_test.rb +4 -0
- data/try/support/memory/memory_detailed_test.rb +4 -0
- data/try/support/memory/memory_search_for_string.rb +4 -0
- data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
- data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
- data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
- data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
- data/try/support/prototypes/atomic_saves_v4.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
- data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
- data/try/support/prototypes/pooling/pool_siege.rb +4 -2
- data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
- data/try/thread_safety/README.md +496 -0
- data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
- data/try/thread_safety/connection_chain_race_try.rb +148 -0
- data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
- data/try/thread_safety/feature_registry_race_try.rb +226 -0
- data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
- data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
- data/try/thread_safety/field_registration_race_try.rb +222 -0
- data/try/thread_safety/logger_initialization_race_try.rb +170 -0
- data/try/thread_safety/middleware_registration_race_try.rb +154 -0
- data/try/thread_safety/module_config_race_try.rb +175 -0
- data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
- data/try/unit/core/autoloader_try.rb +4 -0
- data/try/unit/core/base_enhancements_try.rb +4 -0
- data/try/unit/core/connection_try.rb +4 -0
- data/try/unit/core/errors_try.rb +4 -0
- data/try/unit/core/extensions_try.rb +4 -0
- data/try/unit/core/familia_logger_try.rb +2 -0
- data/try/unit/core/familia_try.rb +4 -0
- data/try/unit/core/middleware_sampling_try.rb +335 -0
- data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
- data/try/unit/core/middleware_thread_safety_try.rb +245 -0
- data/try/unit/core/middleware_try.rb +4 -0
- data/try/unit/core/settings_try.rb +4 -0
- data/try/unit/core/time_utils_try.rb +4 -0
- data/try/unit/core/tools_try.rb +4 -0
- data/try/unit/core/utils_try.rb +37 -0
- data/try/unit/data_types/boolean_try.rb +4 -0
- 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 +4 -0
- data/try/unit/data_types/unsortedset_try.rb +4 -0
- data/try/unit/familia_resolve_class_try.rb +116 -0
- data/try/unit/horreum/auto_indexing_on_save_try.rb +5 -1
- data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
- data/try/unit/horreum/base_try.rb +4 -0
- data/try/unit/horreum/class_methods_try.rb +4 -0
- data/try/unit/horreum/commands_try.rb +4 -0
- data/try/unit/horreum/defensive_initialization_try.rb +4 -0
- data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +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 +4 -0
- data/try/unit/horreum/json_type_preservation_try.rb +2 -0
- data/try/unit/horreum/optimized_loading_try.rb +156 -0
- data/try/unit/horreum/relations_try.rb +4 -0
- data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
- data/try/unit/horreum/serialization_try.rb +4 -0
- data/try/unit/horreum/settings_try.rb +4 -0
- data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
- data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
- data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
- data/try/unit/middleware/database_logger_methods_try.rb +251 -0
- data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
- data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
- data/try/unit/thread_safety_monitor_try.rb +149 -0
- metadata +72 -17
- data/.github/workflows/code-quality.yml +0 -138
- data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
- data/changelog.d/20251011_203905_delano_next.rst +0 -30
- data/changelog.d/20251011_212633_delano_next.rst +0 -13
- data/changelog.d/20251011_221253_delano_next.rst +0 -26
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
- data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
- data/docs/archive/FAMILIA_UPDATE.md +0 -226
- data/docs/archive/README.md +0 -64
- data/docs/archive/api-reference.md +0 -333
- data/docs/guides/core-field-system.md +0 -806
- data/docs/guides/implementation.md +0 -276
- data/docs/guides/security-model.md +0 -183
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# try/features/relationships/participation_bidirectional_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require_relative '../../../lib/familia'
|
|
6
|
+
|
|
7
|
+
# Test demonstrating true bidirectional participation functionality
|
|
8
|
+
# This shows the improvement from asymmetric to symmetric relationship access
|
|
9
|
+
|
|
10
|
+
# Setup test classes
|
|
11
|
+
class ::ProjectTeam < Familia::Horreum
|
|
12
|
+
feature :relationships
|
|
13
|
+
|
|
14
|
+
identifier_field :team_id
|
|
15
|
+
field :team_id
|
|
16
|
+
field :name
|
|
17
|
+
field :department
|
|
18
|
+
|
|
19
|
+
# Explicitly declare collections for participates_in
|
|
20
|
+
sorted_set :members
|
|
21
|
+
sorted_set :admins
|
|
22
|
+
|
|
23
|
+
def init
|
|
24
|
+
@team_id ||= "team_#{SecureRandom.hex(4)}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class ::ProjectOrganization < Familia::Horreum
|
|
29
|
+
feature :relationships
|
|
30
|
+
|
|
31
|
+
identifier_field :org_id
|
|
32
|
+
field :org_id
|
|
33
|
+
field :name
|
|
34
|
+
field :type
|
|
35
|
+
|
|
36
|
+
# Explicitly declare collections for participates_in
|
|
37
|
+
sorted_set :employees
|
|
38
|
+
sorted_set :contractors
|
|
39
|
+
|
|
40
|
+
def init
|
|
41
|
+
@org_id ||= "org_#{SecureRandom.hex(4)}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class ::ProjectUser < Familia::Horreum
|
|
46
|
+
feature :relationships
|
|
47
|
+
|
|
48
|
+
identifier_field :user_id
|
|
49
|
+
field :user_id
|
|
50
|
+
field :email
|
|
51
|
+
field :name
|
|
52
|
+
field :role
|
|
53
|
+
|
|
54
|
+
# Define bidirectional participation relationships
|
|
55
|
+
# These will auto-generate reverse collection methods with _instances suffix
|
|
56
|
+
participates_in ProjectTeam, :members # Generates: user.project_team_instances
|
|
57
|
+
participates_in ProjectTeam, :admins # Also adds to user.project_team_instances (union)
|
|
58
|
+
participates_in ProjectOrganization, :employees # Generates: user.project_organization_instances
|
|
59
|
+
|
|
60
|
+
# Custom reverse method name (user chooses the base name)
|
|
61
|
+
participates_in ProjectOrganization, :contractors, as: :contracting_orgs
|
|
62
|
+
|
|
63
|
+
def init
|
|
64
|
+
@user_id ||= "user_#{SecureRandom.hex(4)}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Create test data
|
|
69
|
+
@user1 = ProjectUser.new(email: 'alice@example.com', name: 'Alice', role: 'developer')
|
|
70
|
+
@user2 = ProjectUser.new(email: 'bob@example.com', name: 'Bob', role: 'manager')
|
|
71
|
+
|
|
72
|
+
@team1 = ProjectTeam.new(name: 'Frontend Team', department: 'Engineering')
|
|
73
|
+
@team2 = ProjectTeam.new(name: 'Backend Team', department: 'Engineering')
|
|
74
|
+
@team3 = ProjectTeam.new(name: 'Design Team', department: 'Product')
|
|
75
|
+
|
|
76
|
+
@org1 = ProjectOrganization.new(name: 'TechCorp Inc', type: 'employer')
|
|
77
|
+
@org2 = ProjectOrganization.new(name: 'FreelanceCorp', type: 'contractor')
|
|
78
|
+
|
|
79
|
+
# Save all objects
|
|
80
|
+
[@user1, @user2, @team1, @team2, @team3, @org1, @org2].each(&:save)
|
|
81
|
+
|
|
82
|
+
# Set up relationships using generated methods (automatic participation tracking)
|
|
83
|
+
@team1.add_members_instance(@user1)
|
|
84
|
+
@team2.add_members_instance(@user1)
|
|
85
|
+
@team1.add_admins_instance(@user1)
|
|
86
|
+
@org1.add_employees_instance(@user1)
|
|
87
|
+
@org2.add_contractors_instance(@user1)
|
|
88
|
+
|
|
89
|
+
# Set up user2 in fewer relationships
|
|
90
|
+
@team3.add_members_instance(@user2)
|
|
91
|
+
@org1.add_employees_instance(@user2)
|
|
92
|
+
|
|
93
|
+
## Test the OLD way - difficult and verbose
|
|
94
|
+
# Before: Getting teams for a user required manual parsing
|
|
95
|
+
old_way_keys = @user1.participations.members.select { |k| k.start_with?("project_team:") && k.end_with?(":members") }
|
|
96
|
+
old_way_ids = old_way_keys.map { |k| k.split(':')[1] }.uniq
|
|
97
|
+
old_way_teams = ProjectTeam.load_multi(old_way_ids).compact
|
|
98
|
+
old_way_teams.map(&:name).sort
|
|
99
|
+
#=> ["Backend Team", "Frontend Team"]
|
|
100
|
+
|
|
101
|
+
## Debug - Check if methods are defined
|
|
102
|
+
@user1.respond_to?(:project_team_instances)
|
|
103
|
+
#=> true
|
|
104
|
+
|
|
105
|
+
## Debug - Check participations data
|
|
106
|
+
@user1.participations.members
|
|
107
|
+
#=> ["project_team:#{@team1.identifier}:members", "project_team:#{@team2.identifier}:members", "project_team:#{@team1.identifier}:admins", "project_organization:#{@org1.identifier}:employees", "project_organization:#{@org2.identifier}:contractors"]
|
|
108
|
+
|
|
109
|
+
## Debug - Check if participating_ids_for_target works
|
|
110
|
+
ids = @user1.participating_ids_for_target(ProjectTeam)
|
|
111
|
+
ids
|
|
112
|
+
#=> [@team1.identifier, @team2.identifier]
|
|
113
|
+
|
|
114
|
+
## Debug - Test individual team loading
|
|
115
|
+
ProjectTeam.load(@team1.identifier).name
|
|
116
|
+
#=> "Frontend Team"
|
|
117
|
+
|
|
118
|
+
## Debug - Test load_multi with actual IDs
|
|
119
|
+
test_ids = [@team1.identifier, @team2.identifier]
|
|
120
|
+
ProjectTeam.load_multi(test_ids).compact.map(&:name).sort
|
|
121
|
+
#=> ["Backend Team", "Frontend Team"]
|
|
122
|
+
|
|
123
|
+
## Debug - Check what project_teams method returns
|
|
124
|
+
result = @user1.project_team_instances
|
|
125
|
+
puts "project_teams method returns: #{result.inspect}"
|
|
126
|
+
result.map(&:name).sort
|
|
127
|
+
#=> ["Backend Team", "Frontend Team"]
|
|
128
|
+
|
|
129
|
+
## Test NEW way - clean and intuitive reverse collection method
|
|
130
|
+
user_teams = @user1.project_team_instances # Auto-generated pluralized from ProjectTeam class name
|
|
131
|
+
user_teams.map(&:name).sort
|
|
132
|
+
#=> ["Backend Team", "Frontend Team"]
|
|
133
|
+
|
|
134
|
+
## Test that both users and admins collections are included (union behavior)
|
|
135
|
+
all_team_participations = @user1.project_team_instances # Should include both members and admins
|
|
136
|
+
all_team_participations.map(&:name).sort
|
|
137
|
+
#=> ["Backend Team", "Frontend Team"]
|
|
138
|
+
|
|
139
|
+
## Test IDs-only method (efficient, no object loading)
|
|
140
|
+
user_team_ids = @user1.project_team_ids
|
|
141
|
+
user_team_ids.sort
|
|
142
|
+
#=> [@team1.identifier, @team2.identifier].sort
|
|
143
|
+
|
|
144
|
+
## Test boolean check method
|
|
145
|
+
@user1.project_team?
|
|
146
|
+
#=> true
|
|
147
|
+
|
|
148
|
+
## Test count method returns correct number
|
|
149
|
+
@user1.project_team_count
|
|
150
|
+
#=> 2
|
|
151
|
+
|
|
152
|
+
## Test organizations (different target class)
|
|
153
|
+
user_orgs = @user1.project_organization_instances
|
|
154
|
+
user_orgs.map(&:name).sort
|
|
155
|
+
#=> ["FreelanceCorp", "TechCorp Inc"]
|
|
156
|
+
|
|
157
|
+
## Test custom reverse method name
|
|
158
|
+
contracting_orgs_instances = @user1.contracting_orgs_instances
|
|
159
|
+
contracting_orgs_instances.map(&:name)
|
|
160
|
+
#=> ["FreelanceCorp"]
|
|
161
|
+
|
|
162
|
+
## Test user with fewer relationships
|
|
163
|
+
@user2.project_team_instances.map(&:name)
|
|
164
|
+
#=> ["Design Team"]
|
|
165
|
+
|
|
166
|
+
## Test user2 count
|
|
167
|
+
@user2.project_team_count
|
|
168
|
+
#=> 1
|
|
169
|
+
|
|
170
|
+
## Test user2 organizations
|
|
171
|
+
@user2.project_organization_instances.map(&:name)
|
|
172
|
+
#=> ["TechCorp Inc"]
|
|
173
|
+
|
|
174
|
+
## Test create empty user with no memberships
|
|
175
|
+
@user3 = ProjectUser.new(email: 'charlie@example.com', name: 'Charlie')
|
|
176
|
+
@user3.save
|
|
177
|
+
@user3.project_team_instances
|
|
178
|
+
#=> []
|
|
179
|
+
|
|
180
|
+
## Test empty user with IDs, check and count
|
|
181
|
+
@user3.project_team_ids
|
|
182
|
+
#=> []
|
|
183
|
+
|
|
184
|
+
## Test empty user boolean check
|
|
185
|
+
@user3.project_team?
|
|
186
|
+
#=> false
|
|
187
|
+
|
|
188
|
+
## Test empty user count
|
|
189
|
+
@user3.project_team_count
|
|
190
|
+
#=> 0
|
|
191
|
+
|
|
192
|
+
## Test that forward direction still works (backwards compatibility)
|
|
193
|
+
@team1.members.to_a.sort
|
|
194
|
+
#=> [@user1.identifier]
|
|
195
|
+
|
|
196
|
+
## Test admin collection forward direction
|
|
197
|
+
@team1.admins.to_a
|
|
198
|
+
#=> [@user1.identifier]
|
|
199
|
+
|
|
200
|
+
## Test bidirectional consistency - forward direction
|
|
201
|
+
team1_member_ids = @team1.members.to_a
|
|
202
|
+
team1_members = ProjectUser.load_multi(team1_member_ids).compact
|
|
203
|
+
team1_members.map(&:name)
|
|
204
|
+
#=> ["Alice"]
|
|
205
|
+
|
|
206
|
+
## Test bidirectional consistency - reverse direction
|
|
207
|
+
user1_teams = @user1.project_team_instances
|
|
208
|
+
user1_team_ids = user1_teams.map(&:identifier)
|
|
209
|
+
user1_team_ids.include?(@team1.identifier)
|
|
210
|
+
#=> true
|
|
211
|
+
|
|
212
|
+
## Test multiple users in same team - add user2
|
|
213
|
+
@team1.add_members_instance(@user2)
|
|
214
|
+
@team1.members.member?(@user2.identifier)
|
|
215
|
+
#=> true
|
|
216
|
+
|
|
217
|
+
## Test team1 now has two members
|
|
218
|
+
team1_all_members = @team1.members.to_a.sort
|
|
219
|
+
team1_all_members == [@user1.identifier, @user2.identifier].sort
|
|
220
|
+
#=> true
|
|
221
|
+
|
|
222
|
+
## Test both users show up in team1 - user1
|
|
223
|
+
@user1.project_team_instances.map(&:name).include?("Frontend Team")
|
|
224
|
+
#=> true
|
|
225
|
+
|
|
226
|
+
## Test both users show up in team1 - user2
|
|
227
|
+
@user2.project_team_instances.map(&:name).include?("Frontend Team")
|
|
228
|
+
#=> true
|
|
229
|
+
|
|
230
|
+
## Test cleanup - remove relationships using generated method
|
|
231
|
+
@team1.remove_members_instance(@user1)
|
|
232
|
+
|
|
233
|
+
## Test that removal from members is reflected (no caching)
|
|
234
|
+
# User1 was removed from :members but is still in :admins
|
|
235
|
+
# So they should STILL appear in project_team_instances (union behavior)
|
|
236
|
+
updated_teams = @user1.project_team_instances.map(&:name).sort
|
|
237
|
+
updated_teams.include?("Frontend Team")
|
|
238
|
+
#=> true
|
|
239
|
+
|
|
240
|
+
## Test still admin of team1 after member removal (confirms union behavior)
|
|
241
|
+
@user1.project_team_instances.map(&:name).include?("Frontend Team")
|
|
242
|
+
#=> true
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# try/features/relationships/participation_commands_verification_spec.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
1
5
|
# Generated rspec code for /Users/d/Projects/opensource/d/familia/try/features/relationships/participation_commands_verification_try.rb
|
|
2
6
|
# Updated: 2025-09-26 21:27:49 -0700
|
|
3
7
|
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# try/features/relationships/participation_performance_improvements_try.rb
|
|
2
2
|
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
3
5
|
# Tests for performance improvements in participation functionality
|
|
4
6
|
# Verifies reverse index functionality and robust type comparison
|
|
5
7
|
|
|
6
8
|
require_relative '../../support/helpers/test_helpers'
|
|
7
9
|
|
|
8
10
|
# Test classes for performance improvements
|
|
9
|
-
class PerfTestCustomer < Familia::Horreum
|
|
11
|
+
class ::PerfTestCustomer < Familia::Horreum
|
|
10
12
|
feature :relationships
|
|
11
13
|
|
|
12
14
|
identifier_field :customer_id
|
|
@@ -16,7 +18,7 @@ class PerfTestCustomer < Familia::Horreum
|
|
|
16
18
|
sorted_set :domains
|
|
17
19
|
end
|
|
18
20
|
|
|
19
|
-
class PerfTestDomain < Familia::Horreum
|
|
21
|
+
class ::PerfTestDomain < Familia::Horreum
|
|
20
22
|
feature :relationships
|
|
21
23
|
|
|
22
24
|
identifier_field :domain_id
|
|
@@ -51,7 +53,7 @@ end
|
|
|
51
53
|
#=> true
|
|
52
54
|
|
|
53
55
|
## Test add domain creates reverse index tracking 1 of 2
|
|
54
|
-
@customer.
|
|
56
|
+
@customer.add_domains_instance(@domain)
|
|
55
57
|
@reverse_index_key = "#{@domain.dbkey}:participations"
|
|
56
58
|
@tracked_collections = Familia.dbclient.smembers(@reverse_index_key) # TODO: Do not use keys directly
|
|
57
59
|
@tracked_collections.length
|
|
@@ -64,13 +66,13 @@ end
|
|
|
64
66
|
##=> true
|
|
65
67
|
|
|
66
68
|
## Test remove domain cleans up reverse index tracking
|
|
67
|
-
@customer.
|
|
69
|
+
@customer.remove_domains_instance(@domain)
|
|
68
70
|
@tracked_collections_after_remove = Familia.dbclient.smembers(@reverse_index_key) # TODO: Do not use keys directly
|
|
69
71
|
@tracked_collections_after_remove.include?(@collection_key)
|
|
70
72
|
##=> false
|
|
71
73
|
|
|
72
74
|
## Test robust type comparison in score calculation works with Class
|
|
73
|
-
@customer.
|
|
75
|
+
@customer.add_domains_instance(@domain)
|
|
74
76
|
@score_with_class = @domain.calculate_participation_score(PerfTestCustomer, :domains)
|
|
75
77
|
@score_with_class.is_a?(Numeric)
|
|
76
78
|
#=> true
|
|
@@ -93,16 +95,16 @@ end
|
|
|
93
95
|
## Test membership contains expected target class
|
|
94
96
|
@memberships = @domain.current_participations
|
|
95
97
|
@membership = @memberships.first
|
|
96
|
-
@membership
|
|
98
|
+
@membership.target_class == 'PerfTestCustomer'
|
|
97
99
|
#=> true
|
|
98
100
|
|
|
99
101
|
## Test membership contains collection name
|
|
100
102
|
@memberships = @domain.current_participations
|
|
101
|
-
@membership
|
|
103
|
+
@membership.collection_name == :domains
|
|
102
104
|
#=> true
|
|
103
105
|
|
|
104
106
|
## Test membership contains type information
|
|
105
|
-
@membership
|
|
107
|
+
@membership.type == :sorted_set
|
|
106
108
|
#=> true
|
|
107
109
|
|
|
108
110
|
## Test remove from all participation collections works efficiently
|
|
@@ -112,7 +114,7 @@ end
|
|
|
112
114
|
##=> true
|
|
113
115
|
|
|
114
116
|
## Test domain is removed from customer collection
|
|
115
|
-
@customer.
|
|
117
|
+
@customer.remove_domains_instance(@domain)
|
|
116
118
|
@customer.domains.include?(@domain)
|
|
117
119
|
#=> false
|
|
118
120
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# try/features/relationships/participation_reverse_index_try.rb
|
|
2
2
|
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
3
5
|
# Tests for participation reverse index functionality
|
|
4
6
|
# Verifies performance improvements and correct behavior
|
|
5
7
|
|
|
@@ -52,7 +54,7 @@ end
|
|
|
52
54
|
@domain2.save
|
|
53
55
|
|
|
54
56
|
## Test reverse index tracking is created when adding to sorted set
|
|
55
|
-
@customer.
|
|
57
|
+
@customer.add_domains_instance(@domain1)
|
|
56
58
|
@ri_members1 = @domain1.participations.members
|
|
57
59
|
@ri_members1.is_a?(Array)
|
|
58
60
|
#=> true
|
|
@@ -63,7 +65,7 @@ end
|
|
|
63
65
|
#=> true
|
|
64
66
|
|
|
65
67
|
## Test adding to set collection also creates tracking
|
|
66
|
-
@customer.
|
|
68
|
+
@customer.add_preferred_domains_instance(@domain1)
|
|
67
69
|
@ri_members1_updated = @domain1.participations.members
|
|
68
70
|
@ri_members1_updated.length > 1
|
|
69
71
|
#=> true
|
|
@@ -81,7 +83,7 @@ end
|
|
|
81
83
|
#=> true
|
|
82
84
|
|
|
83
85
|
## Test multiple domains can be tracked
|
|
84
|
-
@customer.
|
|
86
|
+
@customer.add_domains_instance(@domain2)
|
|
85
87
|
@ri_members2_updated = @domain2.participations.members
|
|
86
88
|
@ri_members2_updated.length >= 1
|
|
87
89
|
#=> true
|
|
@@ -94,8 +96,8 @@ end
|
|
|
94
96
|
|
|
95
97
|
## Test remove_from_all_participations uses reverse index
|
|
96
98
|
# NOTE: This method was removed - cleanup happens via individual remove operations
|
|
97
|
-
@customer.
|
|
98
|
-
@customer.
|
|
99
|
+
@customer.remove_domains_instance(@domain1)
|
|
100
|
+
@customer.remove_preferred_domains_instance(@domain1)
|
|
99
101
|
@domain1_collections_after = @domain1.participations.members
|
|
100
102
|
@domain1_collections_after.empty?
|
|
101
103
|
#=> true
|
|
@@ -106,7 +108,7 @@ end
|
|
|
106
108
|
#=> false
|
|
107
109
|
|
|
108
110
|
## Test domain was removed from set
|
|
109
|
-
@customer.
|
|
111
|
+
@customer.remove_preferred_domains_instance(@domain1)
|
|
110
112
|
@customer.preferred_domains.include?(@domain1)
|
|
111
113
|
#=> false
|
|
112
114
|
|
|
@@ -120,12 +122,12 @@ end
|
|
|
120
122
|
#=> true
|
|
121
123
|
|
|
122
124
|
## Test membership includes target information
|
|
123
|
-
@customer_membership = @domain2_memberships.find { |m| m
|
|
124
|
-
@customer_membership.is_a?(
|
|
125
|
+
@customer_membership = @domain2_memberships.find { |m| m.target_id == @customer.identifier }
|
|
126
|
+
@customer_membership.is_a?(Familia::Features::Relationships::ParticipationMembership) || @customer_membership.nil?
|
|
125
127
|
#=> true
|
|
126
128
|
|
|
127
129
|
## Test membership includes collection name
|
|
128
|
-
@customer_membership && @customer_membership
|
|
130
|
+
@customer_membership && @customer_membership.collection_name == :domains || true
|
|
129
131
|
#=> true
|
|
130
132
|
|
|
131
133
|
## Test score type comparison works with different types
|
|
@@ -151,7 +153,7 @@ puts "Before add_preferred_domain - domain2 participations: #{@domain2.participa
|
|
|
151
153
|
puts "Before add_preferred_domain - domain2 in domains?: #{@customer.domains.member?(@domain2.identifier)}"
|
|
152
154
|
|
|
153
155
|
# Add domain to multiple collections
|
|
154
|
-
@customer.
|
|
156
|
+
@customer.add_preferred_domains_instance(@domain2)
|
|
155
157
|
|
|
156
158
|
# Debug: Check what participations we actually have after
|
|
157
159
|
puts "After add_preferred_domain - domain2 participations: #{@domain2.participations.members.inspect}"
|
|
@@ -172,14 +174,14 @@ puts "Domain2 participations length: #{@domain2_final_memberships.length}"
|
|
|
172
174
|
## Test cleanup removes all participations
|
|
173
175
|
# Manual cleanup since remove_from_all_participations was removed
|
|
174
176
|
# Remove from all collections domain2 participates in
|
|
175
|
-
@customer.
|
|
176
|
-
@customer.
|
|
177
|
+
@customer.remove_domains_instance(@domain2)
|
|
178
|
+
@customer.remove_preferred_domains_instance(@domain2) if @customer.preferred_domains.member?(@domain2.identifier)
|
|
177
179
|
@ri_members2_final = @domain2.participations.members
|
|
178
180
|
@ri_members2_final.empty?
|
|
179
181
|
##=> true
|
|
180
182
|
|
|
181
183
|
## Test domain2 removed from all collections
|
|
182
|
-
@customer.
|
|
184
|
+
@customer.remove_domains_instance(@domain2)
|
|
183
185
|
@customer.domains.include?(@domain2)
|
|
184
186
|
#=> false
|
|
185
187
|
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# try/features/relationships/participation_target_class_resolution_try.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Regression test for Symbol/String target class resolution in participates_in
|
|
6
|
+
#
|
|
7
|
+
# This test verifies the fix for the NoMethodError that occurred when
|
|
8
|
+
# participates_in was called with a Symbol or String target class instead
|
|
9
|
+
# of a Class object. The error was:
|
|
10
|
+
# "private method 'member_by_config_name' called for module Familia"
|
|
11
|
+
#
|
|
12
|
+
# See commit: Fix NoMethodError when calling private member_by_config_name
|
|
13
|
+
|
|
14
|
+
require_relative '../../support/helpers/test_helpers'
|
|
15
|
+
|
|
16
|
+
# Test classes for target class resolution
|
|
17
|
+
# Define target class FIRST so it's registered in Familia.members
|
|
18
|
+
class SymbolResolutionCustomer < Familia::Horreum
|
|
19
|
+
feature :relationships
|
|
20
|
+
|
|
21
|
+
identifier_field :customer_id
|
|
22
|
+
field :customer_id
|
|
23
|
+
field :name
|
|
24
|
+
|
|
25
|
+
sorted_set :domains
|
|
26
|
+
set :tags
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Participant classes using different target class formats
|
|
30
|
+
class SymbolResolutionDomain < Familia::Horreum
|
|
31
|
+
feature :relationships
|
|
32
|
+
|
|
33
|
+
identifier_field :domain_id
|
|
34
|
+
field :domain_id
|
|
35
|
+
field :name
|
|
36
|
+
field :created_at
|
|
37
|
+
|
|
38
|
+
# TEST CASE 1: Symbol target class (most common case)
|
|
39
|
+
# This was causing: NoMethodError: private method 'member_by_config_name'
|
|
40
|
+
participates_in :SymbolResolutionCustomer, :domains, score: :created_at
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class StringResolutionTag < Familia::Horreum
|
|
44
|
+
feature :relationships
|
|
45
|
+
|
|
46
|
+
identifier_field :tag_id
|
|
47
|
+
field :tag_id
|
|
48
|
+
field :name
|
|
49
|
+
field :created_at
|
|
50
|
+
|
|
51
|
+
# TEST CASE 2: String target class
|
|
52
|
+
# This was also causing the same NoMethodError
|
|
53
|
+
# Note: Customer's tags is a set (not sorted_set), so specify type: :set
|
|
54
|
+
participates_in 'SymbolResolutionCustomer', :tags, type: :set
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class ClassResolutionItem < Familia::Horreum
|
|
58
|
+
feature :relationships
|
|
59
|
+
|
|
60
|
+
identifier_field :item_id
|
|
61
|
+
field :item_id
|
|
62
|
+
field :name
|
|
63
|
+
field :created_at
|
|
64
|
+
|
|
65
|
+
# TEST CASE 3: Class object (this always worked)
|
|
66
|
+
# Including for completeness
|
|
67
|
+
sorted_set :items
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Setup test data
|
|
71
|
+
@customer = SymbolResolutionCustomer.new(
|
|
72
|
+
customer_id: 'symbol_res_cust_1',
|
|
73
|
+
name: 'Symbol Resolution Customer'
|
|
74
|
+
)
|
|
75
|
+
@domain = SymbolResolutionDomain.new(
|
|
76
|
+
domain_id: 'symbol_res_dom_1',
|
|
77
|
+
name: 'example.com',
|
|
78
|
+
created_at: Time.now.to_f
|
|
79
|
+
)
|
|
80
|
+
@tag = StringResolutionTag.new(
|
|
81
|
+
tag_id: 'string_res_tag_1',
|
|
82
|
+
name: 'important',
|
|
83
|
+
created_at: Time.now.to_f
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Ensure clean state
|
|
87
|
+
[@customer, @domain, @tag].each do |obj|
|
|
88
|
+
obj.destroy if obj&.respond_to?(:destroy) && obj&.respond_to?(:exists?) && obj.exists?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
@customer.save
|
|
92
|
+
@domain.save
|
|
93
|
+
@tag.save
|
|
94
|
+
|
|
95
|
+
## Test Symbol target class resolution works
|
|
96
|
+
# This is the primary regression test - it should not raise NoMethodError
|
|
97
|
+
@customer.add_domains_instance(@domain)
|
|
98
|
+
@customer.domains.member?(@domain.identifier)
|
|
99
|
+
#=> true
|
|
100
|
+
|
|
101
|
+
## Test domain bidirectional methods were created correctly
|
|
102
|
+
@domain.respond_to?(:in_symbol_resolution_customer_domains?)
|
|
103
|
+
#=> true
|
|
104
|
+
|
|
105
|
+
## Test domain can check membership using generated method
|
|
106
|
+
@domain.in_symbol_resolution_customer_domains?(@customer)
|
|
107
|
+
#=> true
|
|
108
|
+
|
|
109
|
+
## Test domain can add itself using generated method
|
|
110
|
+
@customer.domains.remove(@domain)
|
|
111
|
+
@domain.add_to_symbol_resolution_customer_domains(@customer)
|
|
112
|
+
@customer.domains.member?(@domain.identifier)
|
|
113
|
+
#=> true
|
|
114
|
+
|
|
115
|
+
## Test domain score calculation works with Symbol target class
|
|
116
|
+
@score = @domain.calculate_participation_score(:SymbolResolutionCustomer, :domains)
|
|
117
|
+
@score.is_a?(Numeric)
|
|
118
|
+
#=> true
|
|
119
|
+
|
|
120
|
+
## Test domain score matches created_at field
|
|
121
|
+
(@score - @domain.created_at).abs < 0.001
|
|
122
|
+
#=> true
|
|
123
|
+
|
|
124
|
+
## Test String target class resolution works
|
|
125
|
+
# This is the secondary regression test
|
|
126
|
+
@customer.add_tags_instance(@tag)
|
|
127
|
+
@customer.tags.member?(@tag.identifier)
|
|
128
|
+
#=> true
|
|
129
|
+
|
|
130
|
+
## Test tag bidirectional methods were created correctly
|
|
131
|
+
@tag.respond_to?(:in_symbol_resolution_customer_tags?)
|
|
132
|
+
#=> true
|
|
133
|
+
|
|
134
|
+
## Test tag can check membership using generated method
|
|
135
|
+
@tag.in_symbol_resolution_customer_tags?(@customer)
|
|
136
|
+
#=> true
|
|
137
|
+
|
|
138
|
+
## Test tag can add itself using generated method
|
|
139
|
+
@customer.tags.remove(@tag)
|
|
140
|
+
@tag.add_to_symbol_resolution_customer_tags(@customer)
|
|
141
|
+
@customer.tags.member?(@tag.identifier)
|
|
142
|
+
#=> true
|
|
143
|
+
|
|
144
|
+
## Test tag membership check works with String target class (sets don't have scores)
|
|
145
|
+
@tag.in_symbol_resolution_customer_tags?(@customer)
|
|
146
|
+
#=> true
|
|
147
|
+
|
|
148
|
+
## Test reverse index tracking works with Symbol target class
|
|
149
|
+
@domain.participations.members.length > 0
|
|
150
|
+
#=> true
|
|
151
|
+
|
|
152
|
+
## Test reverse index contains the correct collection key
|
|
153
|
+
@domains_key = @customer.domains.dbkey
|
|
154
|
+
@domain.participations.members.include?(@domains_key)
|
|
155
|
+
#=> true
|
|
156
|
+
|
|
157
|
+
## Test reverse index tracking works with String target class
|
|
158
|
+
@tag.participations.members.length > 0
|
|
159
|
+
#=> true
|
|
160
|
+
|
|
161
|
+
## Test reverse index contains the correct collection key for tags
|
|
162
|
+
@tags_key = @customer.tags.dbkey
|
|
163
|
+
@tag.participations.members.include?(@tags_key)
|
|
164
|
+
#=> true
|
|
165
|
+
|
|
166
|
+
## Test current_participations works with Symbol target class
|
|
167
|
+
@domain_participations = @domain.current_participations
|
|
168
|
+
@domain_participations.is_a?(Array)
|
|
169
|
+
#=> true
|
|
170
|
+
|
|
171
|
+
## Test participation data includes correct target class
|
|
172
|
+
@domain_participation = @domain_participations.first
|
|
173
|
+
@domain_participation.target_class == 'SymbolResolutionCustomer'
|
|
174
|
+
#=> true
|
|
175
|
+
|
|
176
|
+
## Test current_participations works with String target class
|
|
177
|
+
@tag_participations = @tag.current_participations
|
|
178
|
+
@tag_participations.is_a?(Array)
|
|
179
|
+
#=> true
|
|
180
|
+
|
|
181
|
+
## Test participation data includes correct collection name
|
|
182
|
+
@tag_participation = @tag_participations.first
|
|
183
|
+
@tag_participation.collection_name == :tags
|
|
184
|
+
#=> true
|
|
185
|
+
|
|
186
|
+
## Test removal works correctly with Symbol target class
|
|
187
|
+
@customer.remove_domains_instance(@domain)
|
|
188
|
+
@customer.domains.member?(@domain.identifier)
|
|
189
|
+
#=> false
|
|
190
|
+
|
|
191
|
+
## Test reverse index cleanup with Symbol target class
|
|
192
|
+
@domain.participations.members.empty?
|
|
193
|
+
#=> true
|
|
194
|
+
|
|
195
|
+
## Test removal works correctly with String target class
|
|
196
|
+
@customer.remove_tags_instance(@tag)
|
|
197
|
+
@customer.tags.member?(@tag.identifier)
|
|
198
|
+
#=> false
|
|
199
|
+
|
|
200
|
+
## Test reverse index cleanup with String target class
|
|
201
|
+
@tag.participations.members.empty?
|
|
202
|
+
#=> true
|
|
203
|
+
|
|
204
|
+
## Cleanup
|
|
205
|
+
[@customer, @domain, @tag].each do |obj|
|
|
206
|
+
obj.destroy if obj&.respond_to?(:destroy) && obj&.respond_to?(:exists?) && obj.exists?
|
|
207
|
+
end
|
|
208
|
+
true
|
|
209
|
+
#=> true
|