familia 2.0.0.pre19 → 2.0.0.pre22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/claude-code-review.yml +4 -9
- data/.github/workflows/code-smells.yml +64 -3
- data/.pre-commit-config.yaml +8 -6
- data/.reek.yml +10 -9
- data/.rubocop.yml +4 -0
- data/.talismanrc +5 -1
- data/CHANGELOG.rst +220 -112
- data/CLAUDE.md +28 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +20 -17
- data/bin/try +16 -0
- data/bin/tryouts +16 -0
- data/docs/1106-participates_in-bidirectional-solution.md +129 -0
- data/docs/guides/encryption.md +486 -0
- data/docs/guides/feature-encrypted-fields.md +123 -7
- data/docs/guides/feature-expiration.md +161 -117
- data/docs/guides/feature-external-identifiers.md +415 -443
- data/docs/guides/feature-object-identifiers.md +400 -269
- data/docs/guides/feature-quantization.md +120 -6
- data/docs/guides/feature-relationships-indexing.md +318 -0
- data/docs/guides/feature-relationships-methods.md +146 -604
- data/docs/guides/feature-relationships-participation.md +263 -0
- data/docs/guides/feature-relationships.md +118 -136
- data/docs/guides/feature-system-devs.md +176 -693
- data/docs/guides/feature-system.md +119 -6
- data/docs/guides/feature-transient-fields.md +81 -0
- data/docs/guides/field-system.md +778 -0
- data/docs/guides/index.md +32 -15
- data/docs/guides/logging.md +187 -0
- data/docs/guides/optimized-loading.md +674 -0
- data/docs/guides/thread-safety-monitoring.md +61 -0
- data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
- data/docs/migrating/v2.0.0-pre22.md +241 -0
- data/docs/overview.md +7 -9
- data/docs/reference/api-technical.md +267 -320
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
- data/examples/autoloader/mega_customer.rb +2 -0
- data/examples/datatype_standalone.rb +4 -3
- data/examples/encrypted_fields.rb +2 -1
- data/examples/json_usage_patterns.rb +2 -0
- data/examples/relationships.rb +3 -0
- data/examples/safe_dump.rb +2 -1
- data/examples/sampling_demo.rb +53 -0
- data/examples/single_connection_transaction_confusions.rb +2 -1
- data/familia.gemspec +2 -1
- data/lib/familia/base.rb +2 -0
- data/lib/familia/connection/behavior.rb +2 -0
- data/lib/familia/connection/handlers.rb +2 -0
- data/lib/familia/connection/individual_command_proxy.rb +2 -0
- data/lib/familia/connection/middleware.rb +34 -24
- data/lib/familia/connection/operation_core.rb +3 -2
- data/lib/familia/connection/operations.rb +2 -0
- data/lib/familia/connection/pipelined_core.rb +3 -3
- data/lib/familia/connection/transaction_core.rb +69 -2
- data/lib/familia/connection.rb +18 -3
- data/lib/familia/data_type/class_methods.rb +3 -1
- data/lib/familia/data_type/connection.rb +2 -0
- data/lib/familia/data_type/database_commands.rb +2 -0
- data/lib/familia/data_type/serialization.rb +79 -52
- data/lib/familia/data_type/settings.rb +2 -0
- data/lib/familia/data_type/types/counter.rb +2 -0
- data/lib/familia/data_type/types/hashkey.rb +7 -5
- data/lib/familia/data_type/types/listkey.rb +2 -0
- data/lib/familia/data_type/types/lock.rb +2 -0
- data/lib/familia/data_type/types/sorted_set.rb +7 -10
- data/lib/familia/data_type/types/stringkey.rb +24 -0
- data/lib/familia/data_type/types/unsorted_set.rb +2 -0
- data/lib/familia/data_type.rb +2 -0
- data/lib/familia/encryption/encrypted_data.rb +4 -2
- data/lib/familia/encryption/manager.rb +2 -0
- data/lib/familia/encryption/provider.rb +2 -0
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
- data/lib/familia/encryption/registry.rb +2 -0
- data/lib/familia/encryption/request_cache.rb +2 -0
- data/lib/familia/encryption.rb +9 -2
- data/lib/familia/errors.rb +2 -0
- data/lib/familia/features/autoloader.rb +2 -0
- data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
- data/lib/familia/features/encrypted_fields.rb +2 -2
- data/lib/familia/features/expiration/extensions.rb +3 -1
- data/lib/familia/features/expiration.rb +12 -4
- data/lib/familia/features/external_identifier.rb +62 -7
- data/lib/familia/features/object_identifier.rb +49 -0
- data/lib/familia/features/quantization.rb +3 -1
- data/lib/familia/features/relationships/README.md +3 -1
- data/lib/familia/features/relationships/collection_operations.rb +2 -0
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +138 -9
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +97 -21
- data/lib/familia/features/relationships/indexing.rb +3 -0
- data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
- data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
- data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
- data/lib/familia/features/relationships/participation.rb +155 -69
- data/lib/familia/features/relationships/participation_membership.rb +69 -0
- data/lib/familia/features/relationships/participation_relationship.rb +34 -6
- data/lib/familia/features/relationships/score_encoding.rb +2 -0
- data/lib/familia/features/relationships.rb +5 -3
- data/lib/familia/features/safe_dump.rb +2 -0
- data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
- data/lib/familia/features/transient_fields.rb +2 -0
- data/lib/familia/features.rb +2 -0
- data/lib/familia/field_type.rb +3 -1
- data/lib/familia/horreum/connection.rb +17 -1
- data/lib/familia/horreum/database_commands.rb +8 -1
- data/lib/familia/horreum/definition.rb +16 -6
- data/lib/familia/horreum/management.rb +353 -52
- data/lib/familia/horreum/persistence.rb +179 -108
- data/lib/familia/horreum/related_fields.rb +2 -0
- data/lib/familia/horreum/serialization.rb +23 -4
- data/lib/familia/horreum/settings.rb +2 -0
- data/lib/familia/horreum/utils.rb +2 -0
- data/lib/familia/horreum.rb +15 -1
- data/lib/familia/identifier_extractor.rb +3 -1
- data/lib/familia/instrumentation.rb +156 -0
- data/lib/familia/json_serializer.rb +2 -0
- data/lib/familia/logging.rb +92 -32
- data/lib/familia/refinements/dear_json.rb +2 -0
- data/lib/familia/refinements/stylize_words.rb +2 -14
- data/lib/familia/refinements/time_literals.rb +2 -0
- data/lib/familia/refinements.rb +2 -0
- data/lib/familia/secure_identifier.rb +10 -2
- data/lib/familia/settings.rb +2 -0
- data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
- data/lib/familia/thread_safety/monitor.rb +328 -0
- data/lib/familia/utils.rb +13 -0
- data/lib/familia/verifiable_identifier.rb +3 -1
- data/lib/familia/version.rb +3 -1
- data/lib/familia.rb +31 -4
- data/lib/middleware/database_command_counter.rb +152 -0
- data/lib/middleware/database_logger.rb +295 -170
- data/lib/multi_result.rb +61 -31
- data/try/edge_cases/empty_identifiers_try.rb +2 -0
- data/try/edge_cases/hash_symbolization_try.rb +2 -0
- data/try/edge_cases/json_serialization_try.rb +2 -0
- data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
- data/try/edge_cases/race_conditions_try.rb +4 -0
- data/try/edge_cases/reserved_keywords_try.rb +4 -0
- data/try/edge_cases/string_coercion_try.rb +2 -0
- data/try/edge_cases/ttl_side_effects_try.rb +4 -0
- data/try/features/count_any_edge_cases_try.rb +486 -0
- data/try/features/count_any_methods_try.rb +197 -0
- data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
- data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
- data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
- data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
- data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
- data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
- data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
- data/try/features/encrypted_fields/memory_security_try.rb +4 -0
- data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
- data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
- data/try/features/encryption/config_persistence_try.rb +4 -0
- data/try/features/encryption/core_try.rb +4 -0
- data/try/features/encryption/instance_variable_scope_try.rb +4 -0
- data/try/features/encryption/module_loading_try.rb +4 -0
- data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
- data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
- data/try/features/encryption/roundtrip_validation_try.rb +4 -0
- data/try/features/encryption/secure_memory_handling_try.rb +4 -0
- data/try/features/expiration/expiration_try.rb +4 -0
- data/try/features/external_identifier/external_identifier_try.rb +305 -8
- data/try/features/feature_dependencies_try.rb +2 -0
- data/try/features/feature_improvements_try.rb +2 -0
- data/try/features/field_groups_try.rb +2 -0
- data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
- data/try/features/object_identifier/object_identifier_try.rb +140 -0
- data/try/features/quantization/quantization_try.rb +4 -0
- data/try/features/real_feature_integration_try.rb +2 -0
- data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
- data/try/features/relationships/indexing_rebuild_try.rb +606 -0
- data/try/features/relationships/indexing_try.rb +2 -0
- data/try/features/relationships/participation_bidirectional_try.rb +242 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
- data/try/features/relationships/participation_commands_verification_try.rb +2 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
- data/try/features/relationships/participation_reverse_index_try.rb +15 -13
- data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
- data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
- data/try/features/relationships/relationships_api_changes_try.rb +2 -0
- data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
- data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
- data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
- data/try/features/relationships/relationships_performance_try.rb +4 -0
- data/try/features/relationships/relationships_performance_working_try.rb +4 -0
- data/try/features/relationships/relationships_try.rb +6 -4
- data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
- data/try/features/safe_dump/safe_dump_try.rb +4 -0
- data/try/features/transient_fields/redacted_string_try.rb +2 -0
- data/try/features/transient_fields/refresh_reset_try.rb +3 -0
- data/try/features/transient_fields/simple_refresh_test.rb +3 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
- data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
- data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
- data/try/integration/connection/fiber_context_preservation_try.rb +4 -0
- data/try/integration/connection/handler_constraints_try.rb +4 -0
- data/try/integration/connection/isolated_dbclient_try.rb +4 -0
- data/try/integration/connection/middleware_reconnect_try.rb +2 -0
- data/try/integration/connection/operation_mode_guards_try.rb +4 -0
- data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
- data/try/integration/connection/pools_try.rb +4 -0
- data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
- data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
- data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
- data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
- data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
- data/try/integration/connection/transaction_modes_try.rb +4 -0
- data/try/integration/conventional_inheritance_try.rb +4 -0
- data/try/integration/create_method_try.rb +4 -0
- data/try/integration/cross_component_try.rb +4 -0
- data/try/integration/data_types/datatype_pipelines_try.rb +9 -3
- data/try/integration/data_types/datatype_transactions_try.rb +17 -7
- data/try/integration/database_consistency_try.rb +4 -0
- data/try/integration/familia_extended_try.rb +4 -0
- data/try/integration/familia_members_methods_try.rb +4 -0
- data/try/integration/models/customer_safe_dump_try.rb +4 -0
- data/try/integration/models/customer_try.rb +7 -3
- data/try/integration/models/datatype_base_try.rb +4 -0
- data/try/integration/models/familia_object_try.rb +4 -0
- data/try/integration/persistence_operations_try.rb +4 -0
- data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
- data/try/integration/save_methods_consistency_try.rb +241 -0
- data/try/integration/scenarios_try.rb +4 -0
- data/try/integration/secure_identifier_try.rb +4 -0
- data/try/integration/transaction_safety_core_try.rb +176 -0
- data/try/integration/transaction_safety_workflow_try.rb +291 -0
- data/try/integration/verifiable_identifier_try.rb +4 -0
- data/try/investigation/pipeline_routing/README.md +228 -0
- data/try/performance/benchmarks_try.rb +4 -0
- data/try/performance/transaction_safety_benchmark_try.rb +238 -0
- data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
- data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
- data/try/support/debugging/cache_behavior_tracer.rb +4 -0
- data/try/support/debugging/debug_aad_process.rb +3 -0
- data/try/support/debugging/debug_concealed_internal.rb +3 -0
- data/try/support/debugging/debug_concealed_reveal.rb +3 -0
- data/try/support/debugging/debug_context_aad.rb +3 -0
- data/try/support/debugging/debug_context_simple.rb +3 -0
- data/try/support/debugging/debug_cross_context.rb +3 -0
- data/try/support/debugging/debug_database_load.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
- data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
- data/try/support/debugging/debug_field_decrypt.rb +3 -0
- data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
- data/try/support/debugging/debug_load_path.rb +3 -0
- data/try/support/debugging/debug_method_definition.rb +3 -0
- data/try/support/debugging/debug_method_resolution.rb +3 -0
- data/try/support/debugging/debug_minimal.rb +3 -0
- data/try/support/debugging/debug_provider.rb +3 -0
- data/try/support/debugging/debug_secure_behavior.rb +3 -0
- data/try/support/debugging/debug_string_class.rb +3 -0
- data/try/support/debugging/debug_test.rb +3 -0
- data/try/support/debugging/debug_test_design.rb +3 -0
- data/try/support/debugging/encryption_method_tracer.rb +4 -0
- data/try/support/debugging/provider_diagnostics.rb +4 -0
- data/try/support/helpers/test_cleanup.rb +4 -0
- data/try/support/helpers/test_helpers.rb +5 -0
- data/try/support/memory/memory_basic_test.rb +4 -0
- data/try/support/memory/memory_detailed_test.rb +4 -0
- data/try/support/memory/memory_search_for_string.rb +4 -0
- data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
- data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
- data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
- data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
- data/try/support/prototypes/atomic_saves_v4.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
- data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
- data/try/support/prototypes/pooling/pool_siege.rb +4 -2
- data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
- data/try/thread_safety/README.md +496 -0
- data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
- data/try/thread_safety/connection_chain_race_try.rb +148 -0
- data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
- data/try/thread_safety/feature_registry_race_try.rb +226 -0
- data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
- data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
- data/try/thread_safety/field_registration_race_try.rb +222 -0
- data/try/thread_safety/logger_initialization_race_try.rb +170 -0
- data/try/thread_safety/middleware_registration_race_try.rb +154 -0
- data/try/thread_safety/module_config_race_try.rb +175 -0
- data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
- data/try/unit/core/autoloader_try.rb +4 -0
- data/try/unit/core/base_enhancements_try.rb +4 -0
- data/try/unit/core/connection_try.rb +4 -0
- data/try/unit/core/errors_try.rb +4 -0
- data/try/unit/core/extensions_try.rb +4 -0
- data/try/unit/core/familia_logger_try.rb +2 -0
- data/try/unit/core/familia_try.rb +4 -0
- data/try/unit/core/middleware_sampling_try.rb +335 -0
- data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
- data/try/unit/core/middleware_thread_safety_try.rb +245 -0
- data/try/unit/core/middleware_try.rb +4 -0
- data/try/unit/core/settings_try.rb +4 -0
- data/try/unit/core/time_utils_try.rb +4 -0
- data/try/unit/core/tools_try.rb +4 -0
- data/try/unit/core/utils_try.rb +37 -0
- data/try/unit/data_types/boolean_try.rb +39 -22
- data/try/unit/data_types/counter_try.rb +4 -0
- data/try/unit/data_types/datatype_base_try.rb +4 -0
- data/try/unit/data_types/hash_try.rb +6 -2
- data/try/unit/data_types/list_try.rb +4 -0
- data/try/unit/data_types/lock_try.rb +4 -0
- data/try/unit/data_types/serialization_try.rb +386 -0
- data/try/unit/data_types/sorted_set_try.rb +4 -0
- data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
- data/try/unit/data_types/string_try.rb +4 -0
- data/try/unit/data_types/unsortedset_try.rb +4 -0
- data/try/unit/familia_resolve_class_try.rb +116 -0
- data/try/unit/horreum/auto_indexing_on_save_try.rb +5 -1
- data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
- data/try/unit/horreum/base_try.rb +4 -0
- data/try/unit/horreum/class_methods_try.rb +4 -0
- data/try/unit/horreum/commands_try.rb +4 -0
- data/try/unit/horreum/defensive_initialization_try.rb +4 -0
- data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +6 -1
- data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
- data/try/unit/horreum/field_categories_try.rb +4 -0
- data/try/unit/horreum/field_definition_try.rb +4 -0
- data/try/unit/horreum/initialization_try.rb +4 -0
- data/try/unit/horreum/json_type_preservation_try.rb +2 -0
- data/try/unit/horreum/optimized_loading_try.rb +156 -0
- data/try/unit/horreum/relations_try.rb +4 -0
- data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
- data/try/unit/horreum/serialization_try.rb +4 -0
- data/try/unit/horreum/settings_try.rb +4 -0
- data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
- data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
- data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
- data/try/unit/middleware/database_logger_methods_try.rb +251 -0
- data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
- data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
- data/try/unit/thread_safety_monitor_try.rb +149 -0
- metadata +69 -17
- data/.github/workflows/code-quality.yml +0 -138
- data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
- data/changelog.d/20251011_203905_delano_next.rst +0 -30
- data/changelog.d/20251011_212633_delano_next.rst +0 -13
- data/changelog.d/20251011_221253_delano_next.rst +0 -26
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
- data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
- data/docs/archive/FAMILIA_UPDATE.md +0 -226
- data/docs/archive/README.md +0 -64
- data/docs/archive/api-reference.md +0 -333
- data/docs/guides/core-field-system.md +0 -806
- data/docs/guides/implementation.md +0 -276
- data/docs/guides/security-model.md +0 -183
|
@@ -1,684 +1,226 @@
|
|
|
1
1
|
# Relationship Methods Reference
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Complete reference for methods generated by Familia's relationships feature. Each relationship declaration creates predictable, consistently-named methods on both participating classes.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Quick Reference
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
### Basic Participation
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
### Basic Participation Declaration
|
|
9
|
+
When `Domain` declares `participates_in Customer, :domains`:
|
|
12
10
|
|
|
13
11
|
```ruby
|
|
14
|
-
class
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class Customer < Familia::Horreum
|
|
20
|
-
feature :relationships
|
|
21
|
-
set :domains # Must define the collection
|
|
22
|
-
end
|
|
23
|
-
```
|
|
12
|
+
# Target class (Customer) gets:
|
|
13
|
+
customer.domains # Collection accessor
|
|
14
|
+
customer.add_domains_instance(domain) # Add single
|
|
15
|
+
customer.add_domains([d1, d2]) # Add multiple
|
|
16
|
+
customer.remove_domains_instance(domain) # Remove
|
|
24
17
|
|
|
25
|
-
|
|
18
|
+
# Participant class (Domain) gets:
|
|
19
|
+
domain.add_to_customer_domains(customer) # Add self
|
|
20
|
+
domain.remove_from_customer_domains(customer) # Remove self
|
|
21
|
+
domain.in_customer_domains?(customer) # Check membership
|
|
22
|
+
domain.score_in_customer_domains(customer) # Get score (sorted_set)
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
```ruby
|
|
33
|
-
domain = Domain.new(name: "example.com")
|
|
34
|
-
customer = Customer.new(name: "Acme Corp")
|
|
35
|
-
|
|
36
|
-
# Explicit method calls
|
|
37
|
-
domain.add_to_customer_domains(customer)
|
|
38
|
-
domain.in_customer_domains?(customer) # => true
|
|
39
|
-
domain.remove_from_customer_domains(customer)
|
|
40
|
-
domain.in_customer_domains?(customer) # => false
|
|
24
|
+
# Reverse collection methods (Domain):
|
|
25
|
+
domain.customer_instances # Load Customer objects
|
|
26
|
+
domain.customer_ids # Just IDs
|
|
27
|
+
domain.customer? # Has any?
|
|
28
|
+
domain.customer_count # Count
|
|
41
29
|
```
|
|
42
30
|
|
|
43
|
-
|
|
31
|
+
## Participation Methods
|
|
44
32
|
|
|
45
|
-
|
|
46
|
-
```ruby
|
|
47
|
-
customer.domains << domain # Equivalent to domain.add_to_customer_domains(customer)
|
|
48
|
-
customer.domains.delete(domain.identifier) # Removes relationship bidirectionally
|
|
49
|
-
```
|
|
33
|
+
### Target Class Methods
|
|
50
34
|
|
|
51
|
-
|
|
52
|
-
`{action}_{to|from}_{lowercase_class_name}_{collection_name}` or `in_{lowercase_class_name}_{collection_name}?`
|
|
35
|
+
The target class (specified in `participates_in`) receives collection management methods.
|
|
53
36
|
|
|
54
|
-
## Class-Level Tracking Methods (`class_participates_in`)
|
|
55
|
-
|
|
56
|
-
### Declaration
|
|
57
37
|
```ruby
|
|
58
|
-
class
|
|
59
|
-
|
|
60
|
-
class_participates_in :all_customers, score: :created_at
|
|
61
|
-
class_participates_in :active_customers, score: ->(customer) {
|
|
62
|
-
customer.status == 'active' ? customer.last_activity : 0
|
|
63
|
-
}
|
|
38
|
+
class Domain < Familia::Horreum
|
|
39
|
+
participates_in Customer, :domains, score: :priority
|
|
64
40
|
end
|
|
65
41
|
```
|
|
66
42
|
|
|
67
|
-
|
|
43
|
+
| Method | Description | Example |
|
|
44
|
+
|--------|-------------|---------|
|
|
45
|
+
| `domains` | Collection accessor | `customer.domains.size` |
|
|
46
|
+
| `add_domains_instance(item, score)` | Add single item | `customer.add_domains_instance(domain, 100)` |
|
|
47
|
+
| `add_domains([items])` | Bulk add | `customer.add_domains([d1, d2, d3])` |
|
|
48
|
+
| `remove_domains_instance(item)` | Remove item | `customer.remove_domains_instance(domain)` |
|
|
68
49
|
|
|
69
|
-
|
|
70
|
-
- **`Customer.all_customers`** - Returns the sorted set collection directly
|
|
71
|
-
- **`Customer.active_customers`** - Returns the active customers sorted set
|
|
50
|
+
### Participant Class Methods
|
|
72
51
|
|
|
73
|
-
|
|
74
|
-
- **`Customer.add_to_all_customers(customer)`** - Manually add customer to tracking
|
|
75
|
-
- **`Customer.remove_from_all_customers(customer)`** - Manually remove customer from tracking
|
|
52
|
+
The participant class (calling `participates_in`) receives membership management methods.
|
|
76
53
|
|
|
77
|
-
|
|
54
|
+
| Method | Description | Example |
|
|
55
|
+
|--------|-------------|---------|
|
|
56
|
+
| `add_to_customer_domains(target, score)` | Add to target's collection | `domain.add_to_customer_domains(customer)` |
|
|
57
|
+
| `remove_from_customer_domains(target)` | Remove from target's collection | `domain.remove_from_customer_domains(customer)` |
|
|
58
|
+
| `in_customer_domains?(target)` | Check membership | `domain.in_customer_domains?(customer)` |
|
|
59
|
+
| `score_in_customer_domains(target)` | Get score (sorted_set only) | `domain.score_in_customer_domains(customer)` |
|
|
60
|
+
| `position_in_customer_domains(target)` | Get position (list only) | `domain.position_in_customer_domains(customer)` |
|
|
78
61
|
|
|
79
|
-
|
|
80
|
-
# Query operations (delegated to underlying sorted set)
|
|
81
|
-
Customer.all_customers.size # Count of tracked customers
|
|
82
|
-
Customer.all_customers.range(0, 9) # First 10 customers (by score)
|
|
83
|
-
Customer.all_customers.range_by_score(min_score, max_score) # Customers in score range
|
|
84
|
-
|
|
85
|
-
# Score-based queries
|
|
86
|
-
recent_customers = Customer.all_customers.range_by_score(
|
|
87
|
-
(Time.now - 1.week).to_i, '+inf'
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
# Get customer with scores
|
|
91
|
-
customers_with_scores = Customer.all_customers.range(0, -1, with_scores: true)
|
|
92
|
-
# => [["customer_id_1", 1634567890], ["customer_id_2", 1634567920], ...]
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Automatic Behavior
|
|
96
|
-
- Objects are **automatically added** to class-level tracking collections when saved
|
|
97
|
-
- Objects are **automatically removed** when destroyed
|
|
98
|
-
- Scores are **automatically calculated** using the provided field or lambda
|
|
99
|
-
- No manual method calls required for basic lifecycle tracking
|
|
100
|
-
|
|
101
|
-
### Scored Participation in Parent Collections
|
|
62
|
+
### Reverse Collection Methods
|
|
102
63
|
|
|
103
|
-
|
|
104
|
-
class User < Familia::Horreum
|
|
105
|
-
feature :relationships
|
|
106
|
-
participates_in Team, :active_users, score: :last_seen
|
|
107
|
-
participates_in Team, :top_performers, score: ->(user) { user.performance_score }
|
|
108
|
-
end
|
|
64
|
+
Participants also get methods to query all relationships of a given type.
|
|
109
65
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
**Generated methods on Team instances:**
|
|
117
|
-
- **`team.active_users`** - Access the scored collection
|
|
118
|
-
- **`team.top_performers`** - Access the performance-scored collection
|
|
119
|
-
|
|
120
|
-
**Usage:**
|
|
121
|
-
```ruby
|
|
122
|
-
team = Team.new(name: "Development")
|
|
123
|
-
user = User.new(name: "Alice", last_seen: Time.now.to_i)
|
|
124
|
-
|
|
125
|
-
# User automatically added with score when relationship established
|
|
126
|
-
team.active_users << user
|
|
127
|
-
|
|
128
|
-
# Query by score ranges
|
|
129
|
-
recently_active = team.active_users.range_by_score(
|
|
130
|
-
(Time.now - 1.hour).to_i, '+inf'
|
|
131
|
-
)
|
|
132
|
-
```
|
|
66
|
+
| Method | Description | Returns |
|
|
67
|
+
|--------|-------------|---------|
|
|
68
|
+
| `customer_instances` | Load all related objects | `Array<Customer>` |
|
|
69
|
+
| `customer_ids` | Get all related IDs | `Array<String>` |
|
|
70
|
+
| `customer?` | Check if has any relationships | `Boolean` |
|
|
71
|
+
| `customer_count` | Count relationships | `Integer` |
|
|
133
72
|
|
|
134
|
-
##
|
|
73
|
+
## Class-Level Participation
|
|
135
74
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
### Class-Level Indexing (`class_indexed_by`)
|
|
75
|
+
### Declaration
|
|
139
76
|
|
|
140
77
|
```ruby
|
|
141
|
-
class
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
class_indexed_by :email, :email_lookup
|
|
146
|
-
class_indexed_by :username, :username_lookup
|
|
147
|
-
class_indexed_by :api_key, :api_key_lookup
|
|
78
|
+
class User < Familia::Horreum
|
|
79
|
+
class_participates_in :all_users, score: :created_at
|
|
80
|
+
class_participates_in :active_users,
|
|
81
|
+
score: ->(u) { u.active? ? u.last_activity : 0 }
|
|
148
82
|
end
|
|
149
83
|
```
|
|
150
84
|
|
|
151
|
-
### Generated
|
|
85
|
+
### Generated Methods
|
|
152
86
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
87
|
+
| Level | Method | Description |
|
|
88
|
+
|-------|--------|-------------|
|
|
89
|
+
| **Class** | `User.all_users` | Collection accessor |
|
|
90
|
+
| | `User.add_to_all_users(user, score)` | Manual add |
|
|
91
|
+
| | `User.remove_from_all_users(user)` | Manual remove |
|
|
92
|
+
| **Instance** | `user.add_to_class_all_users(score)` | Add self |
|
|
93
|
+
| | `user.remove_from_class_all_users` | Remove self |
|
|
94
|
+
| | `user.in_class_all_users?` | Check membership |
|
|
95
|
+
| | `user.score_in_class_all_users` | Get score |
|
|
157
96
|
|
|
158
|
-
|
|
159
|
-
- **`Customer.find_by_email(email)`** - Find customer ID by email (O(1) lookup)
|
|
160
|
-
- **`Customer.find_by_username(username)`** - Find customer ID by username
|
|
161
|
-
- **`Customer.find_by_api_key(api_key)`** - Find customer ID by API key
|
|
97
|
+
## Indexing Methods
|
|
162
98
|
|
|
163
|
-
###
|
|
164
|
-
|
|
165
|
-
**Index management:**
|
|
166
|
-
- **`customer.add_to_class_email_lookup`** - Manually add to email index
|
|
167
|
-
- **`customer.remove_from_class_email_lookup`** - Manually remove from email index
|
|
168
|
-
- **`customer.update_class_email_lookup(old_email)`** - Update index when email changes
|
|
169
|
-
|
|
170
|
-
### Usage Examples
|
|
99
|
+
### Class-Level Unique Index
|
|
171
100
|
|
|
172
101
|
```ruby
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
username: "alice123",
|
|
177
|
-
api_key: "ak_abcd1234"
|
|
178
|
-
)
|
|
179
|
-
customer.save # Automatically added to all indexes
|
|
180
|
-
|
|
181
|
-
# O(1) lookups
|
|
182
|
-
customer_id = Customer.find_by_email("alice@example.com")
|
|
183
|
-
customer = Customer.load(customer_id) if customer_id
|
|
184
|
-
|
|
185
|
-
# Direct index access
|
|
186
|
-
all_emails = Customer.email_lookup.to_h
|
|
187
|
-
# => {"alice@example.com" => "customer_1", "bob@example.com" => "customer_2"}
|
|
188
|
-
|
|
189
|
-
# Index operations
|
|
190
|
-
Customer.email_lookup.get("alice@example.com") # => "customer_1"
|
|
191
|
-
Customer.email_lookup.set("new@example.com", "customer_3")
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### Automatic Behavior
|
|
195
|
-
- Objects are **automatically indexed** when saved
|
|
196
|
-
- Indexes are **automatically updated** when indexed fields change
|
|
197
|
-
- Objects are **automatically removed** from indexes when destroyed
|
|
198
|
-
- No manual index management required for standard lifecycle operations
|
|
199
|
-
|
|
200
|
-
**Redis key pattern:** `{class_name.downcase}:{index_name}`
|
|
201
|
-
|
|
202
|
-
### Relationship-Scoped Indexing (`indexed_by` with `target:`)
|
|
203
|
-
|
|
204
|
-
```ruby
|
|
205
|
-
class Customer < Familia::Horreum
|
|
206
|
-
feature :relationships
|
|
207
|
-
field :name
|
|
208
|
-
set :domains
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
class Domain < Familia::Horreum
|
|
212
|
-
feature :relationships
|
|
213
|
-
field :name, :subdomain, :port
|
|
214
|
-
participates_in Customer, :domains
|
|
215
|
-
|
|
216
|
-
# Index domains by name within each customer (domains can have same name across customers)
|
|
217
|
-
indexed_by :name, :domain_index, target: Customer
|
|
218
|
-
indexed_by :subdomain, :subdomain_index, target: Customer
|
|
219
|
-
indexed_by :port, :port_index, target: Customer
|
|
102
|
+
class User < Familia::Horreum
|
|
103
|
+
unique_index :email, :email_lookup
|
|
104
|
+
unique_index :api_key, :api_key_lookup, query: false
|
|
220
105
|
end
|
|
221
106
|
```
|
|
222
107
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
**Multiple lookups:**
|
|
231
|
-
- **`customer.find_all_by_name(domain_names)`** - Find multiple domain IDs by names
|
|
232
|
-
- **`customer.find_all_by_subdomain(subdomains)`** - Find multiple domain IDs by subdomains
|
|
233
|
-
- **`customer.find_all_by_port(ports)`** - Find multiple domain IDs by ports
|
|
234
|
-
|
|
235
|
-
**Direct index access:**
|
|
236
|
-
- **`customer.domain_index`** - Access the name index hash directly
|
|
237
|
-
- **`customer.subdomain_index`** - Access the subdomain index hash directly
|
|
238
|
-
- **`customer.port_index`** - Access the port index hash directly
|
|
239
|
-
|
|
240
|
-
### Usage Examples
|
|
241
|
-
|
|
242
|
-
```ruby
|
|
243
|
-
customer = Customer.new(name: "Acme Corp")
|
|
244
|
-
domain1 = Domain.new(name: "example.com", subdomain: "www", port: 443)
|
|
245
|
-
domain2 = Domain.new(name: "api.example.com", subdomain: "api", port: 443)
|
|
246
|
-
|
|
247
|
-
# Establish relationships (automatic indexing)
|
|
248
|
-
customer.domains << domain1
|
|
249
|
-
customer.domains << domain2
|
|
250
|
-
|
|
251
|
-
# O(1) lookups within customer scope
|
|
252
|
-
domain_id = customer.find_by_name("example.com")
|
|
253
|
-
api_domain_id = customer.find_by_subdomain("api")
|
|
254
|
-
|
|
255
|
-
# Multiple lookups
|
|
256
|
-
ssl_domains = customer.find_all_by_port([443, 8443])
|
|
257
|
-
|
|
258
|
-
# Direct index access
|
|
259
|
-
all_domain_names = customer.domain_index.to_h
|
|
260
|
-
# => {"example.com" => "domain_1", "api.example.com" => "domain_2"}
|
|
261
|
-
|
|
262
|
-
# Check if customer has domain with specific name
|
|
263
|
-
has_domain = customer.domain_index.get("example.com").present?
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
### Generated Methods on Indexed Class (Domain)
|
|
108
|
+
| Method | Generated When | Description |
|
|
109
|
+
|--------|---------------|-------------|
|
|
110
|
+
| `User.find_by_email(email)` | `query: true` (default) | O(1) lookup |
|
|
111
|
+
| `User.index_email_for(user)` | Always | Manual index |
|
|
112
|
+
| `User.unindex_email_for(user)` | Always | Remove from index |
|
|
113
|
+
| `User.reindex_email_for(user)` | Always | Update index |
|
|
267
114
|
|
|
268
|
-
|
|
269
|
-
- **`domain.add_to_customer_domain_index(customer)`** - Add to customer's domain name index
|
|
270
|
-
- **`domain.remove_from_customer_domain_index(customer)`** - Remove from customer's domain name index
|
|
271
|
-
- **`domain.update_customer_domain_index(customer, old_name)`** - Update index when name changes
|
|
115
|
+
### Instance-Scoped Unique Index
|
|
272
116
|
|
|
273
|
-
### Automatic Behavior
|
|
274
|
-
- Domain is **automatically indexed** when added to customer's domains collection
|
|
275
|
-
- Index is **automatically updated** when domain's indexed fields change
|
|
276
|
-
- Domain is **automatically removed** from index when relationship is removed
|
|
277
|
-
- All index management happens transparently during relationship operations
|
|
278
|
-
|
|
279
|
-
**Redis key pattern:** `{target_class.downcase}:{target_id}:{index_name}`
|
|
280
|
-
|
|
281
|
-
### When to Use Each Indexing Context
|
|
282
|
-
|
|
283
|
-
**Class-level indexing (`class_indexed_by`):**
|
|
284
|
-
- Use for **system-wide unique** field lookups
|
|
285
|
-
- Examples: email addresses, usernames, API keys, social security numbers
|
|
286
|
-
- Best when field values should be unique across all instances
|
|
287
|
-
|
|
288
|
-
**Relationship-scoped indexing (`indexed_by` with `target:`):**
|
|
289
|
-
- Use for **context-specific** field lookups
|
|
290
|
-
- Examples: domain names per customer, project names per team, usernames per organization
|
|
291
|
-
- Best when field values are unique within a specific parent context but may duplicate across different parents
|
|
292
|
-
|
|
293
|
-
## Complete API Reference
|
|
294
|
-
|
|
295
|
-
### Method Naming Conventions
|
|
296
|
-
|
|
297
|
-
**Participation methods:**
|
|
298
|
-
- `{add_to|remove_from}_{target_class_downcase}_{collection_name}(target)`
|
|
299
|
-
- `in_{target_class_downcase}_{collection_name}?(target)`
|
|
300
|
-
|
|
301
|
-
**Class-level tracking:**
|
|
302
|
-
- `{add_to|remove_from}_{collection_name}(object)` (class methods)
|
|
303
|
-
- `{ClassName}.{collection_name}` (collection accessor)
|
|
304
|
-
|
|
305
|
-
**Class-level indexing:**
|
|
306
|
-
- `{add_to|remove_from}_class_{index_name}` (instance methods)
|
|
307
|
-
- `{ClassName}.{index_name}` (index accessor)
|
|
308
|
-
- `{ClassName}.find_by_{field_name}(value)` (convenience lookup)
|
|
309
|
-
|
|
310
|
-
**Relationship-scoped indexing:**
|
|
311
|
-
- `{target_instance}.find_by_{field_name}(value)` (single lookup)
|
|
312
|
-
- `{target_instance}.find_all_by_{field_name}(values)` (multiple lookup)
|
|
313
|
-
- `{target_instance}.{index_name}` (direct index access)
|
|
314
|
-
|
|
315
|
-
## Key Benefits of the Relationships API
|
|
316
|
-
|
|
317
|
-
- **Automatic Management**: Save operations update indexes and tracking automatically
|
|
318
|
-
- **Ruby-Idiomatic**: Use `<<` operator for natural collection syntax
|
|
319
|
-
- **Consistent Storage**: All indexes stored at class level for architectural simplicity
|
|
320
|
-
- **Predictable Naming**: Method names follow consistent patterns for easy discovery
|
|
321
|
-
- **O(1) Performance**: Hash-based indexes provide constant-time lookups
|
|
322
|
-
- **Bidirectional Sync**: Relationship changes automatically update both sides
|
|
323
|
-
|
|
324
|
-
## Advanced Implementation Patterns
|
|
325
|
-
|
|
326
|
-
### Error Handling and Edge Cases
|
|
327
|
-
|
|
328
|
-
**Handling Missing Objects:**
|
|
329
117
|
```ruby
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
customer.domains << domain
|
|
333
|
-
rescue Familia::Problem => e
|
|
334
|
-
Rails.logger.error "Failed to establish relationship: #{e.message}"
|
|
335
|
-
end
|
|
336
|
-
|
|
337
|
-
# Check if objects exist before relating
|
|
338
|
-
if domain.persisted? && customer.persisted?
|
|
339
|
-
customer.domains << domain
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
# Batch operations with error handling
|
|
343
|
-
domain_ids.each do |id|
|
|
344
|
-
next unless Domain.exists?(id) # Custom existence check
|
|
345
|
-
customer.domains.add(id)
|
|
118
|
+
class Employee < Familia::Horreum
|
|
119
|
+
unique_index :badge_number, :badge_index, within: Company
|
|
346
120
|
end
|
|
347
121
|
```
|
|
348
122
|
|
|
349
|
-
**
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
stored_id = self.class.email_lookup.get(email)
|
|
356
|
-
return true if stored_id == identifier
|
|
123
|
+
**Methods on scope class (Company):**
|
|
124
|
+
| Method | Description |
|
|
125
|
+
|--------|-------------|
|
|
126
|
+
| `company.find_by_badge_number(badge)` | Find employee |
|
|
127
|
+
| `company.index_badge_number_for(employee)` | Add to index |
|
|
128
|
+
| `company.unindex_badge_number_for(employee)` | Remove from index |
|
|
357
129
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
130
|
+
**Methods on indexed class (Employee):**
|
|
131
|
+
| Method | Description |
|
|
132
|
+
|--------|-------------|
|
|
133
|
+
| `employee.add_to_company_badge_index(company)` | Add to company's index |
|
|
134
|
+
| `employee.remove_from_company_badge_index(company)` | Remove from index |
|
|
135
|
+
| `employee.in_company_badge_index?(company)` | Check if indexed |
|
|
363
136
|
|
|
364
|
-
|
|
365
|
-
end
|
|
366
|
-
```
|
|
137
|
+
### Multi-Value Index
|
|
367
138
|
|
|
368
|
-
### Performance Optimization Techniques
|
|
369
|
-
|
|
370
|
-
**Lazy Loading Patterns:**
|
|
371
139
|
```ruby
|
|
372
|
-
class
|
|
373
|
-
|
|
374
|
-
set :domains
|
|
375
|
-
|
|
376
|
-
# Memoized relationship loading
|
|
377
|
-
def domain_objects
|
|
378
|
-
@domain_objects ||= begin
|
|
379
|
-
domain_ids = domains.to_a
|
|
380
|
-
Domain.multiget(*domain_ids).compact
|
|
381
|
-
end
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
# Paginated relationship loading
|
|
385
|
-
def recent_domains(limit = 10)
|
|
386
|
-
recent_ids = domains.range(0, limit - 1)
|
|
387
|
-
Domain.multiget(*recent_ids).compact
|
|
388
|
-
end
|
|
389
|
-
|
|
390
|
-
# Selective field loading
|
|
391
|
-
def domain_names
|
|
392
|
-
domains.to_a.map do |id|
|
|
393
|
-
Domain.new(domain_id: id).name # Load only name field
|
|
394
|
-
end
|
|
395
|
-
end
|
|
140
|
+
class Employee < Familia::Horreum
|
|
141
|
+
multi_index :department, :dept_index, within: Company
|
|
396
142
|
end
|
|
397
143
|
```
|
|
398
144
|
|
|
399
|
-
**
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
def bulk_add_members(user_ids)
|
|
406
|
-
# Validate all IDs exist first
|
|
407
|
-
valid_ids = user_ids.select { |id| User.exists?(id) }
|
|
408
|
-
|
|
409
|
-
# Use Valkey/Redis pipeline for bulk operations
|
|
410
|
-
Familia.redis.pipelined do |pipeline|
|
|
411
|
-
valid_ids.each do |user_id|
|
|
412
|
-
members.add(user_id)
|
|
413
|
-
# Update reverse indexes in same pipeline
|
|
414
|
-
user = User.new(user_id: user_id)
|
|
415
|
-
user.add_to_team_members(self)
|
|
416
|
-
end
|
|
417
|
-
end
|
|
418
|
-
|
|
419
|
-
valid_ids.size
|
|
420
|
-
end
|
|
421
|
-
|
|
422
|
-
def bulk_remove_members(user_ids)
|
|
423
|
-
# Atomic bulk removal
|
|
424
|
-
removed_count = 0
|
|
425
|
-
user_ids.each do |user_id|
|
|
426
|
-
if members.delete(user_id)
|
|
427
|
-
removed_count += 1
|
|
428
|
-
# Clean up reverse relationship
|
|
429
|
-
user = User.new(user_id: user_id)
|
|
430
|
-
user.remove_from_team_members(self)
|
|
431
|
-
end
|
|
432
|
-
end
|
|
433
|
-
removed_count
|
|
434
|
-
end
|
|
435
|
-
end
|
|
436
|
-
```
|
|
145
|
+
**Methods on scope class (Company):**
|
|
146
|
+
| Method | Description |
|
|
147
|
+
|--------|-------------|
|
|
148
|
+
| `company.find_all_by_department(dept)` | Find all in department |
|
|
149
|
+
| `company.sample_from_department(dept, count)` | Random sample |
|
|
437
150
|
|
|
438
|
-
|
|
151
|
+
**Methods on indexed class (Employee):**
|
|
152
|
+
| Method | Description |
|
|
153
|
+
|--------|-------------|
|
|
154
|
+
| `employee.add_to_company_dept_index(company)` | Add to index |
|
|
155
|
+
| `employee.remove_from_company_dept_index(company)` | Remove from index |
|
|
439
156
|
|
|
440
|
-
|
|
441
|
-
```ruby
|
|
442
|
-
class Project < Familia::Horreum
|
|
443
|
-
feature :relationships
|
|
444
|
-
field :priority, :created_at, :deadline
|
|
157
|
+
## Method Naming Patterns
|
|
445
158
|
|
|
446
|
-
|
|
159
|
+
### Participation
|
|
447
160
|
|
|
448
|
-
|
|
161
|
+
- **Target methods**: `{collection}`, `add_{collection}_instance`, `remove_{collection}_instance`
|
|
162
|
+
- **Participant methods**: `{action}_to_{target_config_name}_{collection}`
|
|
163
|
+
- **Reverse methods**: `{target_config_name}_instances`, `{target_config_name}_ids`
|
|
449
164
|
|
|
450
|
-
|
|
451
|
-
base_priority = priority.to_i
|
|
452
|
-
urgency_multiplier = deadline_urgency_factor
|
|
453
|
-
age_factor = project_age_factor
|
|
165
|
+
### Indexing
|
|
454
166
|
|
|
455
|
-
|
|
456
|
-
|
|
167
|
+
- **Class unique**: `find_by_{field}`, `index_{field}_for`, `unindex_{field}_for`
|
|
168
|
+
- **Scoped unique**: `{scope}.find_by_{field}`, `{item}.add_to_{scope}_{index}`
|
|
169
|
+
- **Multi-value**: `{scope}.find_all_by_{field}`, `{scope}.sample_from_{field}`
|
|
457
170
|
|
|
458
|
-
|
|
459
|
-
return 1.0 unless deadline
|
|
171
|
+
## Common Usage Examples
|
|
460
172
|
|
|
461
|
-
|
|
462
|
-
return 3.0 if days_until_deadline <= 1 # Critical
|
|
463
|
-
return 2.0 if days_until_deadline <= 7 # High
|
|
464
|
-
1.0 # Normal
|
|
465
|
-
end
|
|
173
|
+
### Establishing Relationships
|
|
466
174
|
|
|
467
|
-
def project_age_factor
|
|
468
|
-
days_old = (Time.now - created_at.to_time) / 1.day
|
|
469
|
-
[1.0 + (days_old / 30.0), 2.0].min # Cap at 2x multiplier
|
|
470
|
-
end
|
|
471
|
-
end
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
**Multi-Level Relationships:**
|
|
475
|
-
```ruby
|
|
476
|
-
class Organization < Familia::Horreum
|
|
477
|
-
feature :relationships
|
|
478
|
-
set :departments
|
|
479
|
-
|
|
480
|
-
# Find all users across all departments
|
|
481
|
-
def all_users
|
|
482
|
-
department_ids = departments.to_a
|
|
483
|
-
user_ids = []
|
|
484
|
-
|
|
485
|
-
department_ids.each do |dept_id|
|
|
486
|
-
dept = Department.load(dept_id)
|
|
487
|
-
user_ids.concat(dept.users.to_a) if dept
|
|
488
|
-
end
|
|
489
|
-
|
|
490
|
-
User.multiget(*user_ids.uniq).compact
|
|
491
|
-
end
|
|
492
|
-
|
|
493
|
-
# Hierarchical relationship queries
|
|
494
|
-
def users_in_department(department_name)
|
|
495
|
-
dept_id = find_by_name(department_name)
|
|
496
|
-
return [] unless dept_id
|
|
497
|
-
|
|
498
|
-
dept = Department.load(dept_id)
|
|
499
|
-
return [] unless dept
|
|
500
|
-
|
|
501
|
-
user_ids = dept.users.to_a
|
|
502
|
-
User.multiget(*user_ids).compact
|
|
503
|
-
end
|
|
504
|
-
end
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
### Advanced Indexing Patterns
|
|
508
|
-
|
|
509
|
-
**Composite Index Keys:**
|
|
510
175
|
```ruby
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
field :customer_id, :environment, :key_type, :key_hash
|
|
176
|
+
# Single addition with auto-calculated score
|
|
177
|
+
customer.add_domains_instance(domain)
|
|
514
178
|
|
|
515
|
-
|
|
516
|
-
|
|
179
|
+
# Single addition with explicit score
|
|
180
|
+
customer.add_domains_instance(domain, 100.0)
|
|
517
181
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
def environment_and_type
|
|
521
|
-
"#{environment}:#{key_type}" # e.g., "production:read_write"
|
|
522
|
-
end
|
|
523
|
-
end
|
|
182
|
+
# Bulk addition
|
|
183
|
+
customer.add_domains([domain1, domain2, domain3])
|
|
524
184
|
|
|
525
|
-
#
|
|
526
|
-
|
|
527
|
-
customer.find_all_by_environment_and_type(["staging:read_write", "production:read_write"])
|
|
185
|
+
# From participant side
|
|
186
|
+
domain.add_to_customer_domains(customer)
|
|
528
187
|
```
|
|
529
188
|
|
|
530
|
-
|
|
531
|
-
```ruby
|
|
532
|
-
class Event < Familia::Horreum
|
|
533
|
-
feature :relationships
|
|
534
|
-
field :event_type, :timestamp, :user_id
|
|
535
|
-
|
|
536
|
-
participates_in User, :events, score: :timestamp
|
|
537
|
-
indexed_by :daily_partition, :daily_events, target: User
|
|
538
|
-
|
|
539
|
-
private
|
|
189
|
+
### Querying Relationships
|
|
540
190
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
end
|
|
545
|
-
|
|
546
|
-
# Usage - find today's events for user
|
|
547
|
-
today = Time.now.strftime('%Y%m%d')
|
|
548
|
-
todays_event_ids = user.find_all_by_daily_partition([today])
|
|
549
|
-
```
|
|
191
|
+
```ruby
|
|
192
|
+
# Check membership
|
|
193
|
+
domain.in_customer_domains?(customer) # => true/false
|
|
550
194
|
|
|
551
|
-
|
|
195
|
+
# Get all relationships
|
|
196
|
+
domain.customer_instances # => [customer1, customer2]
|
|
197
|
+
domain.customer_ids # => ["cust_123", "cust_456"]
|
|
198
|
+
domain.customer_count # => 2
|
|
552
199
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
#
|
|
556
|
-
|
|
557
|
-
def setup
|
|
558
|
-
@customer = Customer.create(email: "test@example.com", name: "Test Corp")
|
|
559
|
-
@domain = Domain.create(name: "test.com", status: "active")
|
|
560
|
-
end
|
|
561
|
-
|
|
562
|
-
def test_bidirectional_relationship_establishment
|
|
563
|
-
@customer.domains << @domain
|
|
564
|
-
|
|
565
|
-
# Test both sides of relationship
|
|
566
|
-
assert @customer.domains.member?(@domain.identifier)
|
|
567
|
-
assert @domain.in_customer_domains?(@customer.identifier)
|
|
568
|
-
end
|
|
569
|
-
|
|
570
|
-
def test_relationship_removal
|
|
571
|
-
@customer.domains << @domain
|
|
572
|
-
@customer.domains.delete(@domain.identifier)
|
|
573
|
-
|
|
574
|
-
# Verify complete cleanup
|
|
575
|
-
refute @customer.domains.member?(@domain.identifier)
|
|
576
|
-
refute @domain.in_customer_domains?(@customer.identifier)
|
|
577
|
-
end
|
|
578
|
-
|
|
579
|
-
def test_index_automatic_maintenance
|
|
580
|
-
@customer.save # Should trigger indexing
|
|
581
|
-
|
|
582
|
-
found_id = Customer.find_by_email("test@example.com")
|
|
583
|
-
assert_equal @customer.identifier, found_id
|
|
584
|
-
end
|
|
585
|
-
|
|
586
|
-
def test_scoped_index_lookup
|
|
587
|
-
@customer.domains << @domain
|
|
588
|
-
|
|
589
|
-
found_id = @customer.find_by_name("test.com")
|
|
590
|
-
assert_equal @domain.identifier, found_id
|
|
591
|
-
end
|
|
592
|
-
|
|
593
|
-
def test_scored_relationship_ordering
|
|
594
|
-
project = Project.create(name: "Test Project")
|
|
595
|
-
task1 = Task.create(title: "Low priority", priority: 1)
|
|
596
|
-
task2 = Task.create(title: "High priority", priority: 10)
|
|
597
|
-
|
|
598
|
-
project.tasks << task1
|
|
599
|
-
project.tasks << task2
|
|
600
|
-
|
|
601
|
-
# Should be ordered by priority (high to low)
|
|
602
|
-
task_ids = project.tasks.range(0, -1, order: 'DESC')
|
|
603
|
-
assert_equal task2.identifier, task_ids.first
|
|
604
|
-
assert_equal task1.identifier, task_ids.last
|
|
605
|
-
end
|
|
606
|
-
end
|
|
200
|
+
# Direct collection access
|
|
201
|
+
customer.domains.size # Count
|
|
202
|
+
customer.domains.to_a # All IDs
|
|
203
|
+
customer.domains.range(0, 9) # First 10
|
|
607
204
|
```
|
|
608
205
|
|
|
609
|
-
###
|
|
206
|
+
### Working with Indexes
|
|
610
207
|
|
|
611
208
|
```ruby
|
|
612
|
-
#
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
field :email, :username, :tier, :created_at, :last_activity
|
|
616
|
-
|
|
617
|
-
# Global unique lookups
|
|
618
|
-
class_indexed_by :email, :email_lookup
|
|
619
|
-
class_indexed_by :username, :username_lookup
|
|
620
|
-
|
|
621
|
-
# Tiered customer tracking
|
|
622
|
-
class_participates_in :all_customers, score: :created_at
|
|
623
|
-
class_participates_in :active_customers, score: :last_activity
|
|
624
|
-
class_participates_in :premium_customers,
|
|
625
|
-
score: ->(c) { c.tier == 'premium' ? c.last_activity : 0 }
|
|
626
|
-
|
|
627
|
-
# Relationships
|
|
628
|
-
set :orders, :addresses, :payment_methods
|
|
629
|
-
|
|
630
|
-
def recent_orders(limit = 10)
|
|
631
|
-
order_ids = orders.range(0, limit - 1)
|
|
632
|
-
Order.multiget(*order_ids).compact
|
|
633
|
-
end
|
|
634
|
-
|
|
635
|
-
def total_spent
|
|
636
|
-
recent_orders(100).sum(&:total_amount)
|
|
637
|
-
end
|
|
638
|
-
end
|
|
639
|
-
|
|
640
|
-
class Order < Familia::Horreum
|
|
641
|
-
feature :relationships
|
|
642
|
-
field :total_amount, :status, :created_at, :order_number
|
|
643
|
-
|
|
644
|
-
participates_in Customer, :orders, score: :created_at
|
|
645
|
-
indexed_by :order_number, :order_lookup, target: Customer
|
|
646
|
-
indexed_by :status, :status_lookup, target: Customer
|
|
647
|
-
|
|
648
|
-
set :line_items
|
|
649
|
-
end
|
|
650
|
-
|
|
651
|
-
class Address < Familia::Horreum
|
|
652
|
-
feature :relationships
|
|
653
|
-
field :street, :city, :state, :zip_code, :address_type
|
|
654
|
-
|
|
655
|
-
participates_in Customer, :addresses
|
|
656
|
-
indexed_by :address_type, :address_type_lookup, target: Customer
|
|
657
|
-
end
|
|
209
|
+
# Automatic class-level indexing
|
|
210
|
+
user = User.create(email: 'alice@example.com')
|
|
211
|
+
User.find_by_email('alice@example.com') # => user
|
|
658
212
|
|
|
659
|
-
#
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
username: "alice_smith",
|
|
663
|
-
tier: "premium"
|
|
664
|
-
)
|
|
213
|
+
# Manual scoped indexing
|
|
214
|
+
employee.add_to_company_badge_index(company)
|
|
215
|
+
company.find_by_badge_number('12345') # => employee
|
|
665
216
|
|
|
666
|
-
#
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
# Complex relationship queries
|
|
671
|
-
order = Order.create(order_number: "ORD-12345", status: "shipped")
|
|
672
|
-
customer.orders << order
|
|
673
|
-
|
|
674
|
-
customer.find_by_order_number("ORD-12345") # => order.identifier
|
|
675
|
-
shipped_orders = customer.find_all_by_status(["shipped", "delivered"])
|
|
217
|
+
# Multi-value indexing
|
|
218
|
+
employee.add_to_company_dept_index(company)
|
|
219
|
+
engineers = company.find_all_by_department('engineering')
|
|
676
220
|
```
|
|
677
221
|
|
|
678
|
-
---
|
|
679
|
-
|
|
680
222
|
## See Also
|
|
681
223
|
|
|
682
|
-
- **
|
|
683
|
-
- **
|
|
684
|
-
- **
|
|
224
|
+
- [**Relationships Overview**](feature-relationships.md) - Core concepts and patterns
|
|
225
|
+
- [**Participation Guide**](feature-relationships-participation.md) - Detailed participation docs
|
|
226
|
+
- [**Indexing Guide**](feature-relationships-indexing.md) - Detailed indexing docs
|