familia 2.0.0.pre18 → 2.0.0.pre21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/claude-code-review.yml +4 -9
- data/.github/workflows/code-smells.yml +64 -3
- data/.pre-commit-config.yaml +8 -6
- data/.reek.yml +10 -9
- data/.rubocop.yml +4 -0
- data/CHANGELOG.rst +205 -88
- data/CLAUDE.md +62 -10
- data/Gemfile +3 -3
- data/Gemfile.lock +27 -62
- data/README.md +39 -0
- data/bin/try +16 -0
- data/bin/tryouts +16 -0
- data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
- data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
- data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
- data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
- data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
- data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -0
- data/docs/1106-participates_in-bidirectional-solution.md +129 -0
- data/docs/guides/encryption.md +486 -0
- data/docs/guides/feature-encrypted-fields.md +123 -7
- data/docs/guides/feature-expiration.md +177 -133
- data/docs/guides/feature-external-identifiers.md +415 -443
- data/docs/guides/feature-object-identifiers.md +400 -269
- data/docs/guides/feature-quantization.md +120 -6
- data/docs/guides/feature-relationships-indexing.md +318 -0
- data/docs/guides/feature-relationships-methods.md +146 -604
- data/docs/guides/feature-relationships-participation.md +263 -0
- data/docs/guides/feature-relationships.md +118 -136
- data/docs/guides/feature-system-devs.md +176 -693
- data/docs/guides/feature-system.md +119 -6
- data/docs/guides/feature-transient-fields.md +81 -0
- data/docs/guides/field-system.md +778 -0
- data/docs/guides/index.md +32 -15
- data/docs/guides/logging.md +187 -0
- data/docs/guides/optimized-loading.md +674 -0
- data/docs/guides/thread-safety-monitoring.md +61 -0
- data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
- data/docs/migrating/v2.0.0-pre19.md +197 -0
- data/docs/migrating/v2.0.0-pre22.md +241 -0
- data/docs/overview.md +7 -9
- data/docs/reference/api-technical.md +267 -320
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
- data/examples/autoloader/mega_customer.rb +2 -0
- data/examples/datatype_standalone.rb +282 -0
- data/examples/encrypted_fields.rb +2 -1
- data/examples/json_usage_patterns.rb +2 -0
- data/examples/relationships.rb +3 -0
- data/examples/safe_dump.rb +2 -1
- data/examples/sampling_demo.rb +53 -0
- data/examples/single_connection_transaction_confusions.rb +2 -1
- data/familia.gemspec +2 -1
- data/lib/familia/base.rb +2 -0
- data/lib/familia/connection/behavior.rb +254 -0
- data/lib/familia/connection/handlers.rb +97 -0
- data/lib/familia/connection/individual_command_proxy.rb +2 -0
- data/lib/familia/connection/middleware.rb +34 -24
- data/lib/familia/connection/operation_core.rb +3 -1
- data/lib/familia/connection/operations.rb +2 -0
- data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +4 -2
- data/lib/familia/connection/transaction_core.rb +75 -9
- data/lib/familia/connection.rb +21 -5
- data/lib/familia/data_type/class_methods.rb +3 -1
- data/lib/familia/data_type/connection.rb +153 -7
- data/lib/familia/data_type/database_commands.rb +9 -4
- data/lib/familia/data_type/serialization.rb +10 -4
- data/lib/familia/data_type/settings.rb +2 -0
- data/lib/familia/data_type/types/counter.rb +2 -0
- data/lib/familia/data_type/types/hashkey.rb +8 -6
- data/lib/familia/data_type/types/listkey.rb +2 -0
- data/lib/familia/data_type/types/lock.rb +2 -0
- data/lib/familia/data_type/types/sorted_set.rb +2 -0
- data/lib/familia/data_type/types/stringkey.rb +2 -0
- data/lib/familia/data_type/types/unsorted_set.rb +2 -0
- data/lib/familia/data_type.rb +2 -0
- data/lib/familia/encryption/encrypted_data.rb +4 -2
- data/lib/familia/encryption/manager.rb +2 -0
- data/lib/familia/encryption/provider.rb +2 -0
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
- data/lib/familia/encryption/registry.rb +2 -0
- data/lib/familia/encryption/request_cache.rb +2 -0
- data/lib/familia/encryption.rb +9 -2
- data/lib/familia/errors.rb +53 -14
- data/lib/familia/features/autoloader.rb +2 -0
- data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
- data/lib/familia/features/encrypted_fields.rb +2 -2
- data/lib/familia/features/expiration/extensions.rb +11 -11
- data/lib/familia/features/expiration.rb +29 -21
- data/lib/familia/features/external_identifier.rb +33 -7
- data/lib/familia/features/object_identifier.rb +2 -0
- data/lib/familia/features/quantization.rb +3 -1
- data/lib/familia/features/relationships/README.md +3 -1
- data/lib/familia/features/relationships/collection_operations.rb +2 -0
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +177 -47
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +203 -63
- data/lib/familia/features/relationships/indexing.rb +40 -42
- data/lib/familia/features/relationships/indexing_relationship.rb +17 -5
- data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
- data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
- data/lib/familia/features/relationships/participation.rb +155 -69
- data/lib/familia/features/relationships/participation_membership.rb +69 -0
- data/lib/familia/features/relationships/participation_relationship.rb +34 -6
- data/lib/familia/features/relationships/score_encoding.rb +2 -0
- data/lib/familia/features/relationships.rb +5 -3
- data/lib/familia/features/safe_dump.rb +2 -0
- data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
- data/lib/familia/features/transient_fields.rb +2 -0
- data/lib/familia/features.rb +2 -0
- data/lib/familia/field_type.rb +5 -2
- data/lib/familia/horreum/connection.rb +28 -36
- data/lib/familia/horreum/database_commands.rb +131 -10
- data/lib/familia/horreum/definition.rb +18 -7
- data/lib/familia/horreum/management.rb +233 -57
- data/lib/familia/horreum/persistence.rb +314 -122
- data/lib/familia/horreum/related_fields.rb +2 -0
- data/lib/familia/horreum/serialization.rb +26 -4
- data/lib/familia/horreum/settings.rb +2 -0
- data/lib/familia/horreum/utils.rb +2 -8
- data/lib/familia/horreum.rb +46 -13
- data/lib/familia/identifier_extractor.rb +2 -0
- data/lib/familia/instrumentation.rb +156 -0
- data/lib/familia/json_serializer.rb +2 -0
- data/lib/familia/logging.rb +94 -37
- data/lib/familia/refinements/dear_json.rb +2 -0
- data/lib/familia/refinements/stylize_words.rb +2 -14
- data/lib/familia/refinements/time_literals.rb +2 -0
- data/lib/familia/refinements.rb +2 -0
- data/lib/familia/secure_identifier.rb +10 -2
- data/lib/familia/settings.rb +9 -7
- data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
- data/lib/familia/thread_safety/monitor.rb +328 -0
- data/lib/familia/utils.rb +13 -0
- data/lib/familia/verifiable_identifier.rb +3 -1
- data/lib/familia/version.rb +3 -1
- data/lib/familia.rb +31 -4
- data/lib/middleware/database_command_counter.rb +152 -0
- data/lib/middleware/database_logger.rb +325 -129
- data/lib/multi_result.rb +2 -0
- data/try/edge_cases/empty_identifiers_try.rb +2 -0
- data/try/edge_cases/hash_symbolization_try.rb +2 -0
- data/try/edge_cases/json_serialization_try.rb +2 -0
- data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
- data/try/edge_cases/race_conditions_try.rb +4 -0
- data/try/edge_cases/reserved_keywords_try.rb +4 -0
- data/try/edge_cases/string_coercion_try.rb +6 -4
- data/try/edge_cases/ttl_side_effects_try.rb +4 -0
- data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
- data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
- data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
- data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
- data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
- data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
- data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
- data/try/features/encrypted_fields/memory_security_try.rb +4 -0
- data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
- data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
- data/try/features/encryption/config_persistence_try.rb +4 -0
- data/try/features/encryption/core_try.rb +4 -0
- data/try/features/encryption/instance_variable_scope_try.rb +4 -0
- data/try/features/encryption/module_loading_try.rb +4 -0
- data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
- data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
- data/try/features/encryption/roundtrip_validation_try.rb +4 -0
- data/try/features/encryption/secure_memory_handling_try.rb +4 -0
- data/try/features/expiration/expiration_try.rb +5 -1
- data/try/features/external_identifier/external_identifier_try.rb +171 -8
- data/try/features/feature_dependencies_try.rb +2 -0
- data/try/features/feature_improvements_try.rb +2 -0
- data/try/features/field_groups_try.rb +2 -0
- data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
- data/try/features/object_identifier/object_identifier_try.rb +2 -0
- data/try/features/quantization/quantization_try.rb +4 -0
- data/try/features/real_feature_integration_try.rb +2 -0
- data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
- data/try/features/relationships/indexing_rebuild_try.rb +600 -0
- data/try/features/relationships/indexing_try.rb +30 -4
- data/try/features/relationships/participation_bidirectional_try.rb +242 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
- data/try/features/relationships/participation_commands_verification_try.rb +2 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
- data/try/features/relationships/participation_reverse_index_try.rb +15 -13
- data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
- data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
- data/try/features/relationships/relationships_api_changes_try.rb +6 -4
- data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
- data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
- data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
- data/try/features/relationships/relationships_performance_try.rb +4 -0
- data/try/features/relationships/relationships_performance_working_try.rb +4 -0
- data/try/features/relationships/relationships_try.rb +6 -4
- data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
- data/try/features/safe_dump/safe_dump_try.rb +4 -0
- data/try/features/transient_fields/redacted_string_try.rb +2 -0
- data/try/features/transient_fields/refresh_reset_try.rb +3 -0
- data/try/features/transient_fields/simple_refresh_test.rb +3 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
- data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
- data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
- data/try/integration/connection/fiber_context_preservation_try.rb +7 -3
- data/try/integration/connection/handler_constraints_try.rb +4 -0
- data/try/integration/connection/isolated_dbclient_try.rb +4 -0
- data/try/integration/connection/middleware_reconnect_try.rb +2 -0
- data/try/integration/connection/operation_mode_guards_try.rb +5 -1
- data/try/integration/connection/pipeline_fallback_integration_try.rb +15 -12
- data/try/integration/connection/pools_try.rb +4 -0
- data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
- data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
- data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
- data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
- data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
- data/try/integration/connection/transaction_modes_try.rb +4 -0
- data/try/integration/conventional_inheritance_try.rb +4 -0
- data/try/integration/create_method_try.rb +26 -22
- data/try/integration/cross_component_try.rb +4 -0
- data/try/integration/data_types/datatype_pipelines_try.rb +108 -0
- data/try/integration/data_types/datatype_transactions_try.rb +251 -0
- data/try/integration/database_consistency_try.rb +4 -0
- data/try/integration/familia_extended_try.rb +4 -0
- data/try/integration/familia_members_methods_try.rb +4 -0
- data/try/integration/models/customer_safe_dump_try.rb +9 -1
- data/try/integration/models/customer_try.rb +4 -0
- data/try/integration/models/datatype_base_try.rb +4 -0
- data/try/integration/models/familia_object_try.rb +5 -1
- data/try/integration/persistence_operations_try.rb +166 -10
- data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
- data/try/integration/save_methods_consistency_try.rb +241 -0
- data/try/integration/scenarios_try.rb +4 -0
- data/try/integration/secure_identifier_try.rb +4 -0
- data/try/integration/transaction_safety_core_try.rb +176 -0
- data/try/integration/transaction_safety_workflow_try.rb +291 -0
- data/try/integration/verifiable_identifier_try.rb +4 -0
- data/try/investigation/pipeline_routing/README.md +228 -0
- data/try/performance/benchmarks_try.rb +4 -0
- data/try/performance/transaction_safety_benchmark_try.rb +238 -0
- data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
- data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
- data/try/support/debugging/cache_behavior_tracer.rb +4 -0
- data/try/support/debugging/debug_aad_process.rb +3 -0
- data/try/support/debugging/debug_concealed_internal.rb +3 -0
- data/try/support/debugging/debug_concealed_reveal.rb +3 -0
- data/try/support/debugging/debug_context_aad.rb +3 -0
- data/try/support/debugging/debug_context_simple.rb +3 -0
- data/try/support/debugging/debug_cross_context.rb +3 -0
- data/try/support/debugging/debug_database_load.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
- data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
- data/try/support/debugging/debug_field_decrypt.rb +3 -0
- data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
- data/try/support/debugging/debug_load_path.rb +3 -0
- data/try/support/debugging/debug_method_definition.rb +3 -0
- data/try/support/debugging/debug_method_resolution.rb +3 -0
- data/try/support/debugging/debug_minimal.rb +3 -0
- data/try/support/debugging/debug_provider.rb +3 -0
- data/try/support/debugging/debug_secure_behavior.rb +3 -0
- data/try/support/debugging/debug_string_class.rb +3 -0
- data/try/support/debugging/debug_test.rb +3 -0
- data/try/support/debugging/debug_test_design.rb +3 -0
- data/try/support/debugging/encryption_method_tracer.rb +4 -0
- data/try/support/debugging/provider_diagnostics.rb +4 -0
- data/try/support/helpers/test_cleanup.rb +4 -0
- data/try/support/helpers/test_helpers.rb +5 -0
- data/try/support/memory/memory_basic_test.rb +4 -0
- data/try/support/memory/memory_detailed_test.rb +4 -0
- data/try/support/memory/memory_search_for_string.rb +4 -0
- data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
- data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
- data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
- data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
- data/try/support/prototypes/atomic_saves_v4.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
- data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
- data/try/support/prototypes/pooling/pool_siege.rb +4 -2
- data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
- data/try/thread_safety/README.md +496 -0
- data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
- data/try/thread_safety/connection_chain_race_try.rb +148 -0
- data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
- data/try/thread_safety/feature_registry_race_try.rb +226 -0
- data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
- data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
- data/try/thread_safety/field_registration_race_try.rb +222 -0
- data/try/thread_safety/logger_initialization_race_try.rb +170 -0
- data/try/thread_safety/middleware_registration_race_try.rb +154 -0
- data/try/thread_safety/module_config_race_try.rb +175 -0
- data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
- data/try/unit/core/autoloader_try.rb +4 -0
- data/try/unit/core/base_enhancements_try.rb +4 -0
- data/try/unit/core/connection_try.rb +4 -0
- data/try/unit/core/errors_try.rb +4 -0
- data/try/unit/core/extensions_try.rb +4 -0
- data/try/unit/core/familia_logger_try.rb +2 -0
- data/try/unit/core/familia_try.rb +4 -0
- data/try/unit/core/middleware_sampling_try.rb +335 -0
- data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
- data/try/unit/core/middleware_thread_safety_try.rb +245 -0
- data/try/unit/core/middleware_try.rb +4 -0
- data/try/unit/core/settings_try.rb +4 -0
- data/try/unit/core/time_utils_try.rb +4 -0
- data/try/unit/core/tools_try.rb +4 -0
- data/try/unit/core/utils_try.rb +37 -0
- data/try/unit/data_types/boolean_try.rb +5 -1
- data/try/unit/data_types/counter_try.rb +4 -0
- data/try/unit/data_types/datatype_base_try.rb +4 -0
- data/try/unit/data_types/hash_try.rb +4 -0
- data/try/unit/data_types/list_try.rb +4 -0
- data/try/unit/data_types/lock_try.rb +4 -0
- data/try/unit/data_types/sorted_set_try.rb +4 -0
- data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
- data/try/unit/data_types/string_try.rb +5 -1
- data/try/unit/data_types/unsortedset_try.rb +4 -0
- data/try/unit/familia_resolve_class_try.rb +116 -0
- data/try/unit/horreum/auto_indexing_on_save_try.rb +36 -16
- data/try/unit/horreum/automatic_index_validation_try.rb +255 -0
- data/try/unit/horreum/base_try.rb +5 -1
- data/try/unit/horreum/class_methods_try.rb +6 -2
- data/try/unit/horreum/commands_try.rb +4 -0
- data/try/unit/horreum/defensive_initialization_try.rb +4 -0
- data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +4 -0
- data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
- data/try/unit/horreum/field_categories_try.rb +4 -0
- data/try/unit/horreum/field_definition_try.rb +4 -0
- data/try/unit/horreum/initialization_try.rb +5 -1
- data/try/unit/horreum/json_type_preservation_try.rb +2 -0
- data/try/unit/horreum/optimized_loading_try.rb +156 -0
- data/try/unit/horreum/relations_try.rb +8 -4
- data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
- data/try/unit/horreum/serialization_try.rb +6 -2
- data/try/unit/horreum/settings_try.rb +4 -0
- data/try/unit/horreum/unique_index_edge_cases_try.rb +380 -0
- data/try/unit/horreum/unique_index_guard_validation_try.rb +283 -0
- data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
- data/try/unit/middleware/database_logger_methods_try.rb +251 -0
- data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
- data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
- data/try/unit/thread_safety_monitor_try.rb +149 -0
- metadata +81 -14
- data/.github/workflows/code-quality.yml +0 -138
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
- data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
- data/docs/archive/FAMILIA_UPDATE.md +0 -226
- data/docs/archive/README.md +0 -64
- data/docs/archive/api-reference.md +0 -333
- data/docs/guides/core-field-system.md +0 -806
- data/docs/guides/implementation.md +0 -276
- data/docs/guides/security-model.md +0 -183
|
@@ -87,7 +87,7 @@ The quantization feature integrates seamlessly with your existing Familia models
|
|
|
87
87
|
```ruby
|
|
88
88
|
class AnalyticsEvent < Familia::Horreum
|
|
89
89
|
feature :quantization
|
|
90
|
-
default_expiration
|
|
90
|
+
default_expiration 1.hour # Used as default quantum
|
|
91
91
|
|
|
92
92
|
identifier_field :event_id
|
|
93
93
|
field :event_id, :event_type, :user_id, :data, :timestamp
|
|
@@ -95,7 +95,7 @@ end
|
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
> [!TIP]
|
|
98
|
-
> If you set `default_expiration`, it automatically becomes your default quantum for `qstamp` calls without an explicit interval.
|
|
98
|
+
> If you set `default_expiration`, it automatically becomes your default quantum for `qstamp` calls without an explicit interval. If no `default_expiration` is set, the fallback is 10 minutes.
|
|
99
99
|
|
|
100
100
|
### Your First Quantized Timestamps
|
|
101
101
|
|
|
@@ -104,9 +104,9 @@ The `qstamp` method is your main tool for creating consistent time buckets:
|
|
|
104
104
|
```ruby
|
|
105
105
|
event = AnalyticsEvent.new
|
|
106
106
|
|
|
107
|
-
# Using default quantum (from default_expiration:
|
|
107
|
+
# Using default quantum (from default_expiration: 1 hour)
|
|
108
108
|
timestamp = event.qstamp
|
|
109
|
-
# => 1687276800 (Unix timestamp rounded to
|
|
109
|
+
# => 1687276800 (Unix timestamp rounded to hour boundary)
|
|
110
110
|
|
|
111
111
|
# Using custom quantum
|
|
112
112
|
hourly_timestamp = event.qstamp(1.hour)
|
|
@@ -132,6 +132,10 @@ daily_key = AnalyticsEvent.qstamp(1.day, pattern: '%Y-%m-%d')
|
|
|
132
132
|
|
|
133
133
|
weekly_key = AnalyticsEvent.qstamp(1.week, pattern: '%Y-W%U')
|
|
134
134
|
# => "2023-W24" (Year-Week format)
|
|
135
|
+
|
|
136
|
+
# Array syntax for convenience
|
|
137
|
+
compact_key = AnalyticsEvent.qstamp([1.hour, '%Y%m%d%H'])
|
|
138
|
+
# => "2023061514" (same as above)
|
|
135
139
|
```
|
|
136
140
|
|
|
137
141
|
### Specifying Custom Time
|
|
@@ -654,7 +658,7 @@ RSpec.describe AnalyticsEvent do
|
|
|
654
658
|
end
|
|
655
659
|
|
|
656
660
|
it "uses default quantum from default_expiration" do
|
|
657
|
-
allow(described_class).to receive(:default_expiration).and_return(
|
|
661
|
+
allow(described_class).to receive(:default_expiration).and_return(3600)
|
|
658
662
|
|
|
659
663
|
stamp = described_class.qstamp
|
|
660
664
|
|
|
@@ -740,7 +744,7 @@ class RobustQuantization < Familia::Horreum
|
|
|
740
744
|
rescue => e
|
|
741
745
|
# Fallback to current time with default quantum
|
|
742
746
|
Rails.logger.warn "Quantization failed: #{e.message}"
|
|
743
|
-
qstamp(
|
|
747
|
+
qstamp(10.minutes) # 10-minute fallback
|
|
744
748
|
end
|
|
745
749
|
end
|
|
746
750
|
```
|
|
@@ -773,6 +777,116 @@ class QuantizationMonitor
|
|
|
773
777
|
end
|
|
774
778
|
```
|
|
775
779
|
|
|
780
|
+
## API Reference
|
|
781
|
+
|
|
782
|
+
### Class Methods
|
|
783
|
+
|
|
784
|
+
#### `qstamp(quantum = nil, pattern: nil, time: nil)`
|
|
785
|
+
Generate a quantized timestamp for the current or specified time.
|
|
786
|
+
|
|
787
|
+
**Parameters:**
|
|
788
|
+
- `quantum` (Numeric, Array, nil) - Time quantum in seconds or `[quantum, pattern]` array
|
|
789
|
+
- `pattern` (String, nil) - Optional strftime format pattern
|
|
790
|
+
- `time` (Time, nil) - Reference time (defaults to current time)
|
|
791
|
+
|
|
792
|
+
**Returns:** Integer timestamp or formatted string
|
|
793
|
+
|
|
794
|
+
**Examples:**
|
|
795
|
+
```ruby
|
|
796
|
+
User.qstamp(1.hour) # => 1672531200
|
|
797
|
+
User.qstamp(1.hour, pattern: '%Y%m%d%H') # => "2023010114"
|
|
798
|
+
User.qstamp([1.hour, '%Y%m%d%H']) # => "2023010114" (array syntax)
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
#### `qstamp_range(start_time, end_time, quantum, pattern: nil)`
|
|
802
|
+
Generate multiple quantized timestamps for a time range.
|
|
803
|
+
|
|
804
|
+
**Parameters:**
|
|
805
|
+
- `start_time` (Time) - Start of time range
|
|
806
|
+
- `end_time` (Time) - End of time range
|
|
807
|
+
- `quantum` (Numeric) - Time quantum in seconds
|
|
808
|
+
- `pattern` (String, nil) - Optional strftime format pattern
|
|
809
|
+
|
|
810
|
+
**Returns:** Array of quantized timestamps
|
|
811
|
+
|
|
812
|
+
**Example:**
|
|
813
|
+
```ruby
|
|
814
|
+
start_time = Time.parse('2023-01-01 00:00:00')
|
|
815
|
+
end_time = Time.parse('2023-01-01 23:59:59')
|
|
816
|
+
User.qstamp_range(start_time, end_time, 1.hour)
|
|
817
|
+
# => [1672531200, 1672534800, 1672538400, ...] (24 hourly buckets)
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
#### `in_bucket?(timestamp, quantum, bucket_time)`
|
|
821
|
+
Check if a timestamp falls within a quantized bucket.
|
|
822
|
+
|
|
823
|
+
**Parameters:**
|
|
824
|
+
- `timestamp` (Time, Integer) - The timestamp to check
|
|
825
|
+
- `quantum` (Numeric) - The quantum interval
|
|
826
|
+
- `bucket_time` (Time, Integer) - The bucket reference time
|
|
827
|
+
|
|
828
|
+
**Returns:** Boolean
|
|
829
|
+
|
|
830
|
+
**Example:**
|
|
831
|
+
```ruby
|
|
832
|
+
event_time = Time.parse('2023-01-01 14:37:42')
|
|
833
|
+
bucket_time = Time.parse('2023-01-01 14:00:00')
|
|
834
|
+
User.in_bucket?(event_time, 1.hour, bucket_time) # => true
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
### Instance Methods
|
|
838
|
+
|
|
839
|
+
#### `qstamp(quantum = nil, pattern: nil, time: nil)`
|
|
840
|
+
Instance version of qstamp that can use instance-specific default expiration.
|
|
841
|
+
|
|
842
|
+
#### `quantized_identifier(quantum, pattern: nil, separator: ':')`
|
|
843
|
+
Generate a quantized identifier combining instance identifier with timestamp.
|
|
844
|
+
|
|
845
|
+
**Parameters:**
|
|
846
|
+
- `quantum` (Numeric) - Time quantum in seconds
|
|
847
|
+
- `pattern` (String, nil) - Optional strftime format pattern
|
|
848
|
+
- `separator` (String) - Separator between identifier and timestamp
|
|
849
|
+
|
|
850
|
+
**Returns:** String identifier with quantized timestamp
|
|
851
|
+
|
|
852
|
+
**Example:**
|
|
853
|
+
```ruby
|
|
854
|
+
user = User.new(id: 123)
|
|
855
|
+
user.quantized_identifier(1.hour) # => "123:1672531200"
|
|
856
|
+
user.quantized_identifier(1.hour, pattern: '%Y%m%d%H') # => "123:2023010114"
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
### Error Handling
|
|
860
|
+
|
|
861
|
+
The feature validates quantum values:
|
|
862
|
+
|
|
863
|
+
```ruby
|
|
864
|
+
User.qstamp(0) # => ArgumentError: Quantum must be positive
|
|
865
|
+
User.qstamp(-5) # => ArgumentError: Quantum must be positive
|
|
866
|
+
User.qstamp("invalid") # => ArgumentError: Quantum must be positive
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
### Default Quantum Behavior
|
|
870
|
+
|
|
871
|
+
When no quantum is specified:
|
|
872
|
+
1. Uses class `default_expiration` if available
|
|
873
|
+
2. Falls back to `10.minutes` if no default expiration set
|
|
874
|
+
|
|
875
|
+
```ruby
|
|
876
|
+
class TimedModel < Familia::Horreum
|
|
877
|
+
feature :quantization
|
|
878
|
+
default_expiration 1.hour
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
TimedModel.qstamp() # Uses 1.hour as quantum
|
|
882
|
+
|
|
883
|
+
class BasicModel < Familia::Horreum
|
|
884
|
+
feature :quantization
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
BasicModel.qstamp() # Uses 10.minutes fallback (600 seconds)
|
|
888
|
+
```
|
|
889
|
+
|
|
776
890
|
---
|
|
777
891
|
|
|
778
892
|
## See Also
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# Relationships Indexing Guide
|
|
2
|
+
|
|
3
|
+
Indexing provides O(1) field-to-object lookups using Redis data structures, enabling fast attribute-based queries without relationship semantics.
|
|
4
|
+
|
|
5
|
+
## Core Concepts
|
|
6
|
+
|
|
7
|
+
Indexing creates fast lookups for finding objects by field values:
|
|
8
|
+
- **O(1) performance** - Hash/Set-based constant-time access
|
|
9
|
+
- **Automatic management** - Class indexes update on save/destroy
|
|
10
|
+
- **Flexible scoping** - Global or parent-scoped uniqueness
|
|
11
|
+
- **Query generation** - Automatic `find_by_*` methods
|
|
12
|
+
|
|
13
|
+
## Index Types
|
|
14
|
+
|
|
15
|
+
| Type | Scope | Use Case | Structure |
|
|
16
|
+
|------|-------|----------|-----------|
|
|
17
|
+
| `unique_index` | Class | Global unique fields | Redis HashKey |
|
|
18
|
+
| `unique_index` | Instance | Parent-scoped unique | Redis HashKey |
|
|
19
|
+
| `multi_index` | Instance | Non-unique groupings | Redis Set |
|
|
20
|
+
|
|
21
|
+
## Class-Level Unique Indexing
|
|
22
|
+
|
|
23
|
+
Global unique field lookups with automatic management:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
class User < Familia::Horreum
|
|
27
|
+
feature :relationships
|
|
28
|
+
field :email, :username
|
|
29
|
+
|
|
30
|
+
unique_index :email, :email_lookup
|
|
31
|
+
unique_index :username, :username_lookup
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Automatic indexing on save
|
|
35
|
+
user = User.create(email: 'alice@example.com')
|
|
36
|
+
User.find_by_email('alice@example.com') # => user (O(1) lookup)
|
|
37
|
+
|
|
38
|
+
# Automatic update on field change
|
|
39
|
+
user.update(email: 'alice.smith@example.com')
|
|
40
|
+
User.find_by_email('alice.smith@example.com') # => user
|
|
41
|
+
|
|
42
|
+
# Automatic cleanup on destroy
|
|
43
|
+
user.destroy
|
|
44
|
+
User.find_by_email('alice.smith@example.com') # => nil
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Generated Methods
|
|
48
|
+
|
|
49
|
+
| Method | Description |
|
|
50
|
+
|--------|-------------|
|
|
51
|
+
| `User.find_by_email(email)` | O(1) lookup |
|
|
52
|
+
| `User.index_email_for(user)` | Manual index |
|
|
53
|
+
| `User.unindex_email_for(user)` | Remove from index |
|
|
54
|
+
| `User.reindex_email_for(user)` | Update index |
|
|
55
|
+
|
|
56
|
+
## Instance-Scoped Unique Indexing
|
|
57
|
+
|
|
58
|
+
Unique within parent context, allowing duplicates across parents:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
class Employee < Familia::Horreum
|
|
62
|
+
feature :relationships
|
|
63
|
+
field :badge_number
|
|
64
|
+
|
|
65
|
+
unique_index :badge_number, :badge_index, within: Company
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Manual indexing required (needs parent context)
|
|
69
|
+
company1 = Company.create(name: 'Acme Corp')
|
|
70
|
+
company2 = Company.create(name: 'Beta Inc')
|
|
71
|
+
|
|
72
|
+
emp1 = Employee.create(badge_number: '12345')
|
|
73
|
+
emp1.add_to_company_badge_index(company1)
|
|
74
|
+
|
|
75
|
+
emp2 = Employee.create(badge_number: '12345') # Same badge OK
|
|
76
|
+
emp2.add_to_company_badge_index(company2)
|
|
77
|
+
|
|
78
|
+
# Scoped lookups
|
|
79
|
+
company1.find_by_badge_number('12345') # => emp1
|
|
80
|
+
company2.find_by_badge_number('12345') # => emp2
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Generated Methods
|
|
84
|
+
|
|
85
|
+
**On scope class (Company):**
|
|
86
|
+
| Method | Description |
|
|
87
|
+
|--------|-------------|
|
|
88
|
+
| `find_by_badge_number(badge)` | Find within scope |
|
|
89
|
+
| `index_badge_number_for(emp)` | Add to index |
|
|
90
|
+
| `unindex_badge_number_for(emp)` | Remove from index |
|
|
91
|
+
|
|
92
|
+
**On indexed class (Employee):**
|
|
93
|
+
| Method | Description |
|
|
94
|
+
|--------|-------------|
|
|
95
|
+
| `add_to_company_badge_index(company)` | Add to company's index |
|
|
96
|
+
| `remove_from_company_badge_index(company)` | Remove from index |
|
|
97
|
+
| `in_company_badge_index?(company)` | Check if indexed |
|
|
98
|
+
|
|
99
|
+
## Multi-Value Indexing
|
|
100
|
+
|
|
101
|
+
One-to-many mappings for non-unique field values:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
class Employee < Familia::Horreum
|
|
105
|
+
feature :relationships
|
|
106
|
+
field :department
|
|
107
|
+
|
|
108
|
+
multi_index :department, :dept_index, within: Company
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
company = Company.create(name: 'TechCorp')
|
|
112
|
+
|
|
113
|
+
# Multiple employees in same department
|
|
114
|
+
[
|
|
115
|
+
Employee.create(department: 'engineering'),
|
|
116
|
+
Employee.create(department: 'engineering'),
|
|
117
|
+
Employee.create(department: 'sales')
|
|
118
|
+
].each { |emp| emp.add_to_company_dept_index(company) }
|
|
119
|
+
|
|
120
|
+
# Query all in department
|
|
121
|
+
engineers = company.find_all_by_department('engineering') # => [emp1, emp2]
|
|
122
|
+
sales_team = company.find_all_by_department('sales') # => [emp3]
|
|
123
|
+
|
|
124
|
+
# Random sampling
|
|
125
|
+
sample = company.sample_from_department('engineering', 1) # => [random engineer]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Generated Methods
|
|
129
|
+
|
|
130
|
+
**On scope class:**
|
|
131
|
+
| Method | Description |
|
|
132
|
+
|--------|-------------|
|
|
133
|
+
| `find_all_by_department(dept)` | Find all in department |
|
|
134
|
+
| `sample_from_department(dept, count)` | Random sample |
|
|
135
|
+
|
|
136
|
+
## Advanced Patterns
|
|
137
|
+
|
|
138
|
+
### Composite Keys
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
class ApiKey < Familia::Horreum
|
|
142
|
+
field :environment, :key_type
|
|
143
|
+
|
|
144
|
+
unique_index :environment_and_type, :env_type_index, within: Customer
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
def environment_and_type
|
|
148
|
+
"#{environment}:#{key_type}" # e.g., "production:read_write"
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
customer.find_by_environment_and_type("production:read_only")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Conditional Indexing
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
class Document < Familia::Horreum
|
|
159
|
+
field :status, :slug
|
|
160
|
+
|
|
161
|
+
unique_index :slug, :slug_index, within: Project
|
|
162
|
+
|
|
163
|
+
def add_to_project_slug_index(project)
|
|
164
|
+
return unless status == 'published' # Only index published
|
|
165
|
+
super
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Time Partitioning
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
class Event < Familia::Horreum
|
|
174
|
+
field :timestamp
|
|
175
|
+
|
|
176
|
+
multi_index :daily_partition, :daily_events, within: User
|
|
177
|
+
|
|
178
|
+
private
|
|
179
|
+
def daily_partition
|
|
180
|
+
Time.at(timestamp).strftime('%Y%m%d') # e.g., "20241215"
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
today = Time.now.strftime('%Y%m%d')
|
|
185
|
+
todays_events = user.find_all_by_daily_partition(today)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Key Differences
|
|
189
|
+
|
|
190
|
+
### Class vs Instance Scoping
|
|
191
|
+
|
|
192
|
+
**Class-level (`unique_index :email, :email_lookup`):**
|
|
193
|
+
- Automatic indexing on save/destroy
|
|
194
|
+
- System-wide uniqueness
|
|
195
|
+
- No parent context needed
|
|
196
|
+
- Examples: emails, usernames, API keys
|
|
197
|
+
|
|
198
|
+
**Instance-scoped (`unique_index :badge, :badge_index, within: Company`):**
|
|
199
|
+
- Manual indexing required
|
|
200
|
+
- Unique within parent only
|
|
201
|
+
- Requires parent context
|
|
202
|
+
- Examples: employee IDs, project names per team
|
|
203
|
+
|
|
204
|
+
### Unique vs Multi Indexing
|
|
205
|
+
|
|
206
|
+
**Unique index (`unique_index`):**
|
|
207
|
+
- 1:1 field-to-object mapping
|
|
208
|
+
- Returns single object or nil
|
|
209
|
+
- Enforces uniqueness within scope
|
|
210
|
+
|
|
211
|
+
**Multi index (`multi_index`):**
|
|
212
|
+
- 1:many field-to-objects mapping
|
|
213
|
+
- Returns array of objects
|
|
214
|
+
- Allows duplicate values
|
|
215
|
+
|
|
216
|
+
## Rebuilding Indexes
|
|
217
|
+
|
|
218
|
+
Indexes can be automatically rebuilt from source data using auto-generated rebuild methods:
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
# Class-level indexes
|
|
222
|
+
User.rebuild_email_lookup # Rebuilds from all User.email values
|
|
223
|
+
User.rebuild_username_lookup # Rebuilds from all User.username values
|
|
224
|
+
|
|
225
|
+
# Instance-scoped indexes
|
|
226
|
+
company.rebuild_badge_index # Rebuilds from all Employee.badge_number values
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
These methods work because **indexes are derived data** - they're computed from object field values.
|
|
230
|
+
|
|
231
|
+
> **Important:** Participation data (like `@team.members`) cannot be rebuilt automatically because participations represent business decisions, not derived data. See [Why Participations Can't Be Rebuilt](../../lib/familia/features/relationships/participation/rebuild_strategies.md) for the critical distinction between indexes and participations.
|
|
232
|
+
|
|
233
|
+
**When to rebuild indexes:**
|
|
234
|
+
- After data migrations or bulk imports
|
|
235
|
+
- Recovering from index corruption
|
|
236
|
+
- Adding indexes to existing data
|
|
237
|
+
|
|
238
|
+
## Performance Tips
|
|
239
|
+
|
|
240
|
+
### Bulk Operations
|
|
241
|
+
|
|
242
|
+
```ruby
|
|
243
|
+
# Efficient bulk indexing
|
|
244
|
+
employees.each_slice(100) do |batch|
|
|
245
|
+
company.transaction do
|
|
246
|
+
batch.each { |emp| emp.add_to_company_dept_index(company) }
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Index Monitoring
|
|
252
|
+
|
|
253
|
+
```ruby
|
|
254
|
+
# Check index sizes
|
|
255
|
+
company.dept_index_engineering.size # Count in engineering
|
|
256
|
+
User.email_lookup.size # Total indexed emails
|
|
257
|
+
|
|
258
|
+
# Index distribution
|
|
259
|
+
%w[engineering sales marketing].map { |dept|
|
|
260
|
+
[dept, company.send("dept_index_#{dept}").size]
|
|
261
|
+
}.to_h
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Cleanup
|
|
265
|
+
|
|
266
|
+
```ruby
|
|
267
|
+
# Remove orphaned entries
|
|
268
|
+
company.badge_index.to_h.each do |badge, emp_id|
|
|
269
|
+
unless Employee.exists?(emp_id)
|
|
270
|
+
company.badge_index.delete(badge)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Redis Key Patterns
|
|
276
|
+
|
|
277
|
+
| Type | Pattern | Example |
|
|
278
|
+
|------|---------|---------|
|
|
279
|
+
| Class unique | `{class}:{index_name}` | `user:email_lookup` |
|
|
280
|
+
| Instance unique | `{scope}:{id}:{index_name}` | `company:123:badge_index` |
|
|
281
|
+
| Multi-value | `{scope}:{id}:{index_name}:{value}` | `company:123:dept_index:engineering` |
|
|
282
|
+
|
|
283
|
+
## Troubleshooting
|
|
284
|
+
|
|
285
|
+
### Common Issues
|
|
286
|
+
|
|
287
|
+
**Query methods not generated:**
|
|
288
|
+
- Check `query: true` (default) or explicitly set
|
|
289
|
+
- Verify `feature :relationships` declared
|
|
290
|
+
|
|
291
|
+
**Index not updating:**
|
|
292
|
+
- Class indexes: automatic on save/destroy
|
|
293
|
+
- Instance indexes: require manual `add_to_*` calls
|
|
294
|
+
|
|
295
|
+
**Duplicate key errors:**
|
|
296
|
+
- Use `multi_index` for non-unique values
|
|
297
|
+
- Consider instance-scoped for contextual uniqueness
|
|
298
|
+
|
|
299
|
+
### Debugging
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
# Check configuration
|
|
303
|
+
User.indexing_relationships
|
|
304
|
+
# => [{ field: :email, index_name: :email_lookup, ... }]
|
|
305
|
+
|
|
306
|
+
# Inspect index contents
|
|
307
|
+
User.email_lookup.to_h
|
|
308
|
+
# => {"alice@example.com" => "user_123", ...}
|
|
309
|
+
|
|
310
|
+
# Verify membership
|
|
311
|
+
employee.in_company_badge_index?(company) # => true/false
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## See Also
|
|
315
|
+
|
|
316
|
+
- [**Relationships Overview**](feature-relationships.md) - Core concepts
|
|
317
|
+
- [**Methods Reference**](feature-relationships-methods.md) - Complete API
|
|
318
|
+
- [**Participation Guide**](feature-relationships-participation.md) - Associations
|