familia 2.0.0.pre18 → 2.0.0.pre21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/claude-code-review.yml +4 -9
- data/.github/workflows/code-smells.yml +64 -3
- data/.pre-commit-config.yaml +8 -6
- data/.reek.yml +10 -9
- data/.rubocop.yml +4 -0
- data/CHANGELOG.rst +205 -88
- data/CLAUDE.md +62 -10
- data/Gemfile +3 -3
- data/Gemfile.lock +27 -62
- data/README.md +39 -0
- data/bin/try +16 -0
- data/bin/tryouts +16 -0
- data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
- data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
- data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
- data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
- data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
- data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -0
- data/docs/1106-participates_in-bidirectional-solution.md +129 -0
- data/docs/guides/encryption.md +486 -0
- data/docs/guides/feature-encrypted-fields.md +123 -7
- data/docs/guides/feature-expiration.md +177 -133
- data/docs/guides/feature-external-identifiers.md +415 -443
- data/docs/guides/feature-object-identifiers.md +400 -269
- data/docs/guides/feature-quantization.md +120 -6
- data/docs/guides/feature-relationships-indexing.md +318 -0
- data/docs/guides/feature-relationships-methods.md +146 -604
- data/docs/guides/feature-relationships-participation.md +263 -0
- data/docs/guides/feature-relationships.md +118 -136
- data/docs/guides/feature-system-devs.md +176 -693
- data/docs/guides/feature-system.md +119 -6
- data/docs/guides/feature-transient-fields.md +81 -0
- data/docs/guides/field-system.md +778 -0
- data/docs/guides/index.md +32 -15
- data/docs/guides/logging.md +187 -0
- data/docs/guides/optimized-loading.md +674 -0
- data/docs/guides/thread-safety-monitoring.md +61 -0
- data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
- data/docs/migrating/v2.0.0-pre19.md +197 -0
- data/docs/migrating/v2.0.0-pre22.md +241 -0
- data/docs/overview.md +7 -9
- data/docs/reference/api-technical.md +267 -320
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
- data/examples/autoloader/mega_customer.rb +2 -0
- data/examples/datatype_standalone.rb +282 -0
- data/examples/encrypted_fields.rb +2 -1
- data/examples/json_usage_patterns.rb +2 -0
- data/examples/relationships.rb +3 -0
- data/examples/safe_dump.rb +2 -1
- data/examples/sampling_demo.rb +53 -0
- data/examples/single_connection_transaction_confusions.rb +2 -1
- data/familia.gemspec +2 -1
- data/lib/familia/base.rb +2 -0
- data/lib/familia/connection/behavior.rb +254 -0
- data/lib/familia/connection/handlers.rb +97 -0
- data/lib/familia/connection/individual_command_proxy.rb +2 -0
- data/lib/familia/connection/middleware.rb +34 -24
- data/lib/familia/connection/operation_core.rb +3 -1
- data/lib/familia/connection/operations.rb +2 -0
- data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +4 -2
- data/lib/familia/connection/transaction_core.rb +75 -9
- data/lib/familia/connection.rb +21 -5
- data/lib/familia/data_type/class_methods.rb +3 -1
- data/lib/familia/data_type/connection.rb +153 -7
- data/lib/familia/data_type/database_commands.rb +9 -4
- data/lib/familia/data_type/serialization.rb +10 -4
- data/lib/familia/data_type/settings.rb +2 -0
- data/lib/familia/data_type/types/counter.rb +2 -0
- data/lib/familia/data_type/types/hashkey.rb +8 -6
- data/lib/familia/data_type/types/listkey.rb +2 -0
- data/lib/familia/data_type/types/lock.rb +2 -0
- data/lib/familia/data_type/types/sorted_set.rb +2 -0
- data/lib/familia/data_type/types/stringkey.rb +2 -0
- data/lib/familia/data_type/types/unsorted_set.rb +2 -0
- data/lib/familia/data_type.rb +2 -0
- data/lib/familia/encryption/encrypted_data.rb +4 -2
- data/lib/familia/encryption/manager.rb +2 -0
- data/lib/familia/encryption/provider.rb +2 -0
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
- data/lib/familia/encryption/registry.rb +2 -0
- data/lib/familia/encryption/request_cache.rb +2 -0
- data/lib/familia/encryption.rb +9 -2
- data/lib/familia/errors.rb +53 -14
- data/lib/familia/features/autoloader.rb +2 -0
- data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
- data/lib/familia/features/encrypted_fields.rb +2 -2
- data/lib/familia/features/expiration/extensions.rb +11 -11
- data/lib/familia/features/expiration.rb +29 -21
- data/lib/familia/features/external_identifier.rb +33 -7
- data/lib/familia/features/object_identifier.rb +2 -0
- data/lib/familia/features/quantization.rb +3 -1
- data/lib/familia/features/relationships/README.md +3 -1
- data/lib/familia/features/relationships/collection_operations.rb +2 -0
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +177 -47
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +203 -63
- data/lib/familia/features/relationships/indexing.rb +40 -42
- data/lib/familia/features/relationships/indexing_relationship.rb +17 -5
- data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
- data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
- data/lib/familia/features/relationships/participation.rb +155 -69
- data/lib/familia/features/relationships/participation_membership.rb +69 -0
- data/lib/familia/features/relationships/participation_relationship.rb +34 -6
- data/lib/familia/features/relationships/score_encoding.rb +2 -0
- data/lib/familia/features/relationships.rb +5 -3
- data/lib/familia/features/safe_dump.rb +2 -0
- data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
- data/lib/familia/features/transient_fields.rb +2 -0
- data/lib/familia/features.rb +2 -0
- data/lib/familia/field_type.rb +5 -2
- data/lib/familia/horreum/connection.rb +28 -36
- data/lib/familia/horreum/database_commands.rb +131 -10
- data/lib/familia/horreum/definition.rb +18 -7
- data/lib/familia/horreum/management.rb +233 -57
- data/lib/familia/horreum/persistence.rb +314 -122
- data/lib/familia/horreum/related_fields.rb +2 -0
- data/lib/familia/horreum/serialization.rb +26 -4
- data/lib/familia/horreum/settings.rb +2 -0
- data/lib/familia/horreum/utils.rb +2 -8
- data/lib/familia/horreum.rb +46 -13
- data/lib/familia/identifier_extractor.rb +2 -0
- data/lib/familia/instrumentation.rb +156 -0
- data/lib/familia/json_serializer.rb +2 -0
- data/lib/familia/logging.rb +94 -37
- data/lib/familia/refinements/dear_json.rb +2 -0
- data/lib/familia/refinements/stylize_words.rb +2 -14
- data/lib/familia/refinements/time_literals.rb +2 -0
- data/lib/familia/refinements.rb +2 -0
- data/lib/familia/secure_identifier.rb +10 -2
- data/lib/familia/settings.rb +9 -7
- data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
- data/lib/familia/thread_safety/monitor.rb +328 -0
- data/lib/familia/utils.rb +13 -0
- data/lib/familia/verifiable_identifier.rb +3 -1
- data/lib/familia/version.rb +3 -1
- data/lib/familia.rb +31 -4
- data/lib/middleware/database_command_counter.rb +152 -0
- data/lib/middleware/database_logger.rb +325 -129
- data/lib/multi_result.rb +2 -0
- data/try/edge_cases/empty_identifiers_try.rb +2 -0
- data/try/edge_cases/hash_symbolization_try.rb +2 -0
- data/try/edge_cases/json_serialization_try.rb +2 -0
- data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
- data/try/edge_cases/race_conditions_try.rb +4 -0
- data/try/edge_cases/reserved_keywords_try.rb +4 -0
- data/try/edge_cases/string_coercion_try.rb +6 -4
- data/try/edge_cases/ttl_side_effects_try.rb +4 -0
- data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
- data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
- data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
- data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
- data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
- data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
- data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
- data/try/features/encrypted_fields/memory_security_try.rb +4 -0
- data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
- data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
- data/try/features/encryption/config_persistence_try.rb +4 -0
- data/try/features/encryption/core_try.rb +4 -0
- data/try/features/encryption/instance_variable_scope_try.rb +4 -0
- data/try/features/encryption/module_loading_try.rb +4 -0
- data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
- data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
- data/try/features/encryption/roundtrip_validation_try.rb +4 -0
- data/try/features/encryption/secure_memory_handling_try.rb +4 -0
- data/try/features/expiration/expiration_try.rb +5 -1
- data/try/features/external_identifier/external_identifier_try.rb +171 -8
- data/try/features/feature_dependencies_try.rb +2 -0
- data/try/features/feature_improvements_try.rb +2 -0
- data/try/features/field_groups_try.rb +2 -0
- data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
- data/try/features/object_identifier/object_identifier_try.rb +2 -0
- data/try/features/quantization/quantization_try.rb +4 -0
- data/try/features/real_feature_integration_try.rb +2 -0
- data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
- data/try/features/relationships/indexing_rebuild_try.rb +600 -0
- data/try/features/relationships/indexing_try.rb +30 -4
- data/try/features/relationships/participation_bidirectional_try.rb +242 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
- data/try/features/relationships/participation_commands_verification_try.rb +2 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
- data/try/features/relationships/participation_reverse_index_try.rb +15 -13
- data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
- data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
- data/try/features/relationships/relationships_api_changes_try.rb +6 -4
- data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
- data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
- data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
- data/try/features/relationships/relationships_performance_try.rb +4 -0
- data/try/features/relationships/relationships_performance_working_try.rb +4 -0
- data/try/features/relationships/relationships_try.rb +6 -4
- data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
- data/try/features/safe_dump/safe_dump_try.rb +4 -0
- data/try/features/transient_fields/redacted_string_try.rb +2 -0
- data/try/features/transient_fields/refresh_reset_try.rb +3 -0
- data/try/features/transient_fields/simple_refresh_test.rb +3 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
- data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
- data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
- data/try/integration/connection/fiber_context_preservation_try.rb +7 -3
- data/try/integration/connection/handler_constraints_try.rb +4 -0
- data/try/integration/connection/isolated_dbclient_try.rb +4 -0
- data/try/integration/connection/middleware_reconnect_try.rb +2 -0
- data/try/integration/connection/operation_mode_guards_try.rb +5 -1
- data/try/integration/connection/pipeline_fallback_integration_try.rb +15 -12
- data/try/integration/connection/pools_try.rb +4 -0
- data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
- data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
- data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
- data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
- data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
- data/try/integration/connection/transaction_modes_try.rb +4 -0
- data/try/integration/conventional_inheritance_try.rb +4 -0
- data/try/integration/create_method_try.rb +26 -22
- data/try/integration/cross_component_try.rb +4 -0
- data/try/integration/data_types/datatype_pipelines_try.rb +108 -0
- data/try/integration/data_types/datatype_transactions_try.rb +251 -0
- data/try/integration/database_consistency_try.rb +4 -0
- data/try/integration/familia_extended_try.rb +4 -0
- data/try/integration/familia_members_methods_try.rb +4 -0
- data/try/integration/models/customer_safe_dump_try.rb +9 -1
- data/try/integration/models/customer_try.rb +4 -0
- data/try/integration/models/datatype_base_try.rb +4 -0
- data/try/integration/models/familia_object_try.rb +5 -1
- data/try/integration/persistence_operations_try.rb +166 -10
- data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
- data/try/integration/save_methods_consistency_try.rb +241 -0
- data/try/integration/scenarios_try.rb +4 -0
- data/try/integration/secure_identifier_try.rb +4 -0
- data/try/integration/transaction_safety_core_try.rb +176 -0
- data/try/integration/transaction_safety_workflow_try.rb +291 -0
- data/try/integration/verifiable_identifier_try.rb +4 -0
- data/try/investigation/pipeline_routing/README.md +228 -0
- data/try/performance/benchmarks_try.rb +4 -0
- data/try/performance/transaction_safety_benchmark_try.rb +238 -0
- data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
- data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
- data/try/support/debugging/cache_behavior_tracer.rb +4 -0
- data/try/support/debugging/debug_aad_process.rb +3 -0
- data/try/support/debugging/debug_concealed_internal.rb +3 -0
- data/try/support/debugging/debug_concealed_reveal.rb +3 -0
- data/try/support/debugging/debug_context_aad.rb +3 -0
- data/try/support/debugging/debug_context_simple.rb +3 -0
- data/try/support/debugging/debug_cross_context.rb +3 -0
- data/try/support/debugging/debug_database_load.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
- data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
- data/try/support/debugging/debug_field_decrypt.rb +3 -0
- data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
- data/try/support/debugging/debug_load_path.rb +3 -0
- data/try/support/debugging/debug_method_definition.rb +3 -0
- data/try/support/debugging/debug_method_resolution.rb +3 -0
- data/try/support/debugging/debug_minimal.rb +3 -0
- data/try/support/debugging/debug_provider.rb +3 -0
- data/try/support/debugging/debug_secure_behavior.rb +3 -0
- data/try/support/debugging/debug_string_class.rb +3 -0
- data/try/support/debugging/debug_test.rb +3 -0
- data/try/support/debugging/debug_test_design.rb +3 -0
- data/try/support/debugging/encryption_method_tracer.rb +4 -0
- data/try/support/debugging/provider_diagnostics.rb +4 -0
- data/try/support/helpers/test_cleanup.rb +4 -0
- data/try/support/helpers/test_helpers.rb +5 -0
- data/try/support/memory/memory_basic_test.rb +4 -0
- data/try/support/memory/memory_detailed_test.rb +4 -0
- data/try/support/memory/memory_search_for_string.rb +4 -0
- data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
- data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
- data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
- data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
- data/try/support/prototypes/atomic_saves_v4.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
- data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
- data/try/support/prototypes/pooling/pool_siege.rb +4 -2
- data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
- data/try/thread_safety/README.md +496 -0
- data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
- data/try/thread_safety/connection_chain_race_try.rb +148 -0
- data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
- data/try/thread_safety/feature_registry_race_try.rb +226 -0
- data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
- data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
- data/try/thread_safety/field_registration_race_try.rb +222 -0
- data/try/thread_safety/logger_initialization_race_try.rb +170 -0
- data/try/thread_safety/middleware_registration_race_try.rb +154 -0
- data/try/thread_safety/module_config_race_try.rb +175 -0
- data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
- data/try/unit/core/autoloader_try.rb +4 -0
- data/try/unit/core/base_enhancements_try.rb +4 -0
- data/try/unit/core/connection_try.rb +4 -0
- data/try/unit/core/errors_try.rb +4 -0
- data/try/unit/core/extensions_try.rb +4 -0
- data/try/unit/core/familia_logger_try.rb +2 -0
- data/try/unit/core/familia_try.rb +4 -0
- data/try/unit/core/middleware_sampling_try.rb +335 -0
- data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
- data/try/unit/core/middleware_thread_safety_try.rb +245 -0
- data/try/unit/core/middleware_try.rb +4 -0
- data/try/unit/core/settings_try.rb +4 -0
- data/try/unit/core/time_utils_try.rb +4 -0
- data/try/unit/core/tools_try.rb +4 -0
- data/try/unit/core/utils_try.rb +37 -0
- data/try/unit/data_types/boolean_try.rb +5 -1
- data/try/unit/data_types/counter_try.rb +4 -0
- data/try/unit/data_types/datatype_base_try.rb +4 -0
- data/try/unit/data_types/hash_try.rb +4 -0
- data/try/unit/data_types/list_try.rb +4 -0
- data/try/unit/data_types/lock_try.rb +4 -0
- data/try/unit/data_types/sorted_set_try.rb +4 -0
- data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
- data/try/unit/data_types/string_try.rb +5 -1
- data/try/unit/data_types/unsortedset_try.rb +4 -0
- data/try/unit/familia_resolve_class_try.rb +116 -0
- data/try/unit/horreum/auto_indexing_on_save_try.rb +36 -16
- data/try/unit/horreum/automatic_index_validation_try.rb +255 -0
- data/try/unit/horreum/base_try.rb +5 -1
- data/try/unit/horreum/class_methods_try.rb +6 -2
- data/try/unit/horreum/commands_try.rb +4 -0
- data/try/unit/horreum/defensive_initialization_try.rb +4 -0
- data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +4 -0
- data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
- data/try/unit/horreum/field_categories_try.rb +4 -0
- data/try/unit/horreum/field_definition_try.rb +4 -0
- data/try/unit/horreum/initialization_try.rb +5 -1
- data/try/unit/horreum/json_type_preservation_try.rb +2 -0
- data/try/unit/horreum/optimized_loading_try.rb +156 -0
- data/try/unit/horreum/relations_try.rb +8 -4
- data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
- data/try/unit/horreum/serialization_try.rb +6 -2
- data/try/unit/horreum/settings_try.rb +4 -0
- data/try/unit/horreum/unique_index_edge_cases_try.rb +380 -0
- data/try/unit/horreum/unique_index_guard_validation_try.rb +283 -0
- data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
- data/try/unit/middleware/database_logger_methods_try.rb +251 -0
- data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
- data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
- data/try/unit/thread_safety_monitor_try.rb +149 -0
- metadata +81 -14
- data/.github/workflows/code-quality.yml +0 -138
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
- data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
- data/docs/archive/FAMILIA_UPDATE.md +0 -226
- data/docs/archive/README.md +0 -64
- data/docs/archive/api-reference.md +0 -333
- data/docs/guides/core-field-system.md +0 -806
- data/docs/guides/implementation.md +0 -276
- data/docs/guides/security-model.md +0 -183
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/features/external_identifier.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
module Features
|
|
@@ -12,8 +14,8 @@ module Familia
|
|
|
12
14
|
base.extend ModelClassMethods
|
|
13
15
|
base.include ModelInstanceMethods
|
|
14
16
|
|
|
15
|
-
# Ensure default
|
|
16
|
-
base.add_feature_options(:external_identifier,
|
|
17
|
+
# Ensure default format is set in feature options
|
|
18
|
+
base.add_feature_options(:external_identifier, format: 'ext_%{id}')
|
|
17
19
|
|
|
18
20
|
# Add class-level mapping for extid -> id lookups
|
|
19
21
|
base.class_hashkey :extid_lookup
|
|
@@ -35,10 +37,10 @@ module Familia
|
|
|
35
37
|
# - Deterministic generation from objid ensures consistency
|
|
36
38
|
# - Shorter than objid (128-bit vs 256-bit) for external use
|
|
37
39
|
# - Base-36 encoding for URL-safe identifiers
|
|
38
|
-
# - 'ext_' prefix
|
|
40
|
+
# - Customizable format template (default: 'ext_' prefix)
|
|
39
41
|
# - Lazy generation preserves values from initialization
|
|
40
42
|
#
|
|
41
|
-
# @example Using external identifier fields
|
|
43
|
+
# @example Using external identifier fields with default format
|
|
42
44
|
# class User < Familia::Horreum
|
|
43
45
|
# feature :object_identifier
|
|
44
46
|
# feature :external_identifier
|
|
@@ -51,6 +53,30 @@ module Familia
|
|
|
51
53
|
# user2 = User.new(objid: user.objid, email: 'user@example.com')
|
|
52
54
|
# user2.extid # => "ext_abc123def456ghi789" (identical to user.extid)
|
|
53
55
|
#
|
|
56
|
+
# @example Using custom format template with hyphen separator
|
|
57
|
+
# class APIKey < Familia::Horreum
|
|
58
|
+
# feature :object_identifier
|
|
59
|
+
# feature :external_identifier, format: 'api-%{id}'
|
|
60
|
+
# end
|
|
61
|
+
# key = APIKey.new
|
|
62
|
+
# key.extid # => "api-abc123def456ghi789"
|
|
63
|
+
#
|
|
64
|
+
# @example Using custom format template with custom prefix
|
|
65
|
+
# class Customer < Familia::Horreum
|
|
66
|
+
# feature :object_identifier
|
|
67
|
+
# feature :external_identifier, format: 'cust_%{id}'
|
|
68
|
+
# end
|
|
69
|
+
# customer = Customer.new
|
|
70
|
+
# customer.extid # => "cust_abc123def456ghi789"
|
|
71
|
+
#
|
|
72
|
+
# @example Using format template without prefix
|
|
73
|
+
# class Resource < Familia::Horreum
|
|
74
|
+
# feature :object_identifier
|
|
75
|
+
# feature :external_identifier, format: 'v2/%{id}'
|
|
76
|
+
# end
|
|
77
|
+
# resource = Resource.new
|
|
78
|
+
# resource.extid # => "v2/abc123def456ghi789"
|
|
79
|
+
#
|
|
54
80
|
class ExternalIdentifierFieldType < Familia::FieldType
|
|
55
81
|
# Override getter to provide lazy generation from objid
|
|
56
82
|
#
|
|
@@ -252,11 +278,11 @@ module Familia
|
|
|
252
278
|
# 128 bits is approximately 25 characters in base36.
|
|
253
279
|
external_part = random_bytes.unpack1('H*').to_i(16).to_s(36).rjust(25, '0')
|
|
254
280
|
|
|
255
|
-
# Get
|
|
281
|
+
# Get format from feature options and interpolate the ID
|
|
256
282
|
options = self.class.feature_options(:external_identifier)
|
|
257
|
-
|
|
283
|
+
format = options[:format] || 'ext_%{id}'
|
|
258
284
|
|
|
259
|
-
|
|
285
|
+
format % { id: external_part }
|
|
260
286
|
end
|
|
261
287
|
|
|
262
288
|
# Full-length alias for extid for clarity when needed
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/familia/features/quantization.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
4
6
|
module Features
|
|
@@ -389,7 +391,7 @@ module Familia
|
|
|
389
391
|
# user.quantized_identifier(1.hour) # => "123:1672531200"
|
|
390
392
|
# user.quantized_identifier(1.hour, pattern: '%Y%m%d%H') # => "123:2023010114"
|
|
391
393
|
#
|
|
392
|
-
def quantized_identifier(quantum, pattern: nil, separator:
|
|
394
|
+
def quantized_identifier(quantum, pattern: nil, separator: Familia.delim)
|
|
393
395
|
timestamp = qstamp(quantum, pattern: pattern)
|
|
394
396
|
base_id = respond_to?(:identifier) ? identifier : object_id
|
|
395
397
|
"#{base_id}#{separator}#{timestamp}"
|
|
@@ -18,6 +18,7 @@ participates_in Organization, :members, score: :joined_at, bidirectional: true
|
|
|
18
18
|
|
|
19
19
|
**unique_index** - Fast unique lookups ("find object by unique field value")
|
|
20
20
|
```ruby
|
|
21
|
+
unique_index :email, :email_index # Class-level: User.find_by_email()
|
|
21
22
|
unique_index :email, :email_index, within: Organization # Scoped: org.find_by_email()
|
|
22
23
|
```
|
|
23
24
|
|
|
@@ -89,7 +90,8 @@ class Customer < Familia::Horreum
|
|
|
89
90
|
feature :relationships
|
|
90
91
|
|
|
91
92
|
participates_in Organization, :members # Customer belongs to org
|
|
92
|
-
unique_index :email, :email_index
|
|
93
|
+
unique_index :email, :email_index # Class-level: Customer.find_by_email()
|
|
94
|
+
multi_index :status, :status_index, within: Organization # Scoped: org.find_all_by_status()
|
|
93
95
|
end
|
|
94
96
|
```
|
|
95
97
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# lib/familia/features/relationships/indexing/multi_index_generators.rb
|
|
2
|
+
#
|
|
1
3
|
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
module Familia
|
|
@@ -32,29 +34,30 @@ module Familia
|
|
|
32
34
|
# @param indexed_class [Class] The class being indexed (e.g., Employee)
|
|
33
35
|
# @param field [Symbol] The field to index
|
|
34
36
|
# @param index_name [Symbol] Name of the index
|
|
35
|
-
# @param within [Class, Symbol]
|
|
37
|
+
# @param within [Class, Symbol] Scope class for instance-scoped index (required)
|
|
36
38
|
# @param query [Boolean] Whether to generate query methods
|
|
37
39
|
def setup(indexed_class:, field:, index_name:, within:, query:)
|
|
38
|
-
# Multi-index always requires a
|
|
39
|
-
|
|
40
|
-
resolved_class = Familia.resolve_class(
|
|
40
|
+
# Multi-index always requires a scope context
|
|
41
|
+
scope_class = within
|
|
42
|
+
resolved_class = Familia.resolve_class(scope_class)
|
|
41
43
|
|
|
42
44
|
# Store metadata for this indexing relationship
|
|
43
45
|
indexed_class.indexing_relationships << IndexingRelationship.new(
|
|
44
46
|
field: field,
|
|
45
|
-
|
|
47
|
+
scope_class: scope_class,
|
|
48
|
+
within: within,
|
|
46
49
|
index_name: index_name,
|
|
47
50
|
query: query,
|
|
48
51
|
cardinality: :multi,
|
|
49
52
|
)
|
|
50
53
|
|
|
51
54
|
# Always generate the factory method - required by mutation methods
|
|
52
|
-
if
|
|
55
|
+
if scope_class.is_a?(Class)
|
|
53
56
|
generate_factory_method(resolved_class, index_name)
|
|
54
57
|
end
|
|
55
58
|
|
|
56
|
-
# Generate query methods on the
|
|
57
|
-
if query &&
|
|
59
|
+
# Generate query methods on the scope class (optional)
|
|
60
|
+
if query && scope_class.is_a?(Class)
|
|
58
61
|
generate_query_methods_destination(indexed_class, field, resolved_class, index_name)
|
|
59
62
|
end
|
|
60
63
|
|
|
@@ -62,42 +65,45 @@ module Familia
|
|
|
62
65
|
generate_mutation_methods_self(indexed_class, field, resolved_class, index_name)
|
|
63
66
|
end
|
|
64
67
|
|
|
65
|
-
# Generates the factory method ON THE
|
|
68
|
+
# Generates the factory method ON THE SCOPE CLASS (Company when within: Company):
|
|
66
69
|
# - company.index_name_for(field_value) - DataType factory (always needed)
|
|
67
70
|
#
|
|
68
71
|
# This method is required by mutation methods even when query: false
|
|
69
72
|
#
|
|
70
|
-
# @param
|
|
73
|
+
# @param scope_class [Class] The scope class providing uniqueness context (e.g., Company)
|
|
71
74
|
# @param index_name [Symbol] Name of the index (e.g., :dept_index)
|
|
72
|
-
def generate_factory_method(
|
|
73
|
-
|
|
75
|
+
def generate_factory_method(scope_class, index_name)
|
|
76
|
+
actual_scope_class = Familia.resolve_class(scope_class)
|
|
74
77
|
|
|
75
|
-
|
|
78
|
+
actual_scope_class.class_eval do
|
|
76
79
|
# Helper method to get index set for a specific field value
|
|
77
80
|
# This acts as a factory for field-value-specific DataTypes
|
|
78
81
|
define_method(:"#{index_name}_for") do |field_value|
|
|
79
82
|
# Return properly managed DataType instance with parameterized key
|
|
80
|
-
index_key =
|
|
83
|
+
index_key = Familia.join(index_name, field_value)
|
|
81
84
|
Familia::UnsortedSet.new(index_key, parent: self)
|
|
82
85
|
end
|
|
83
86
|
end
|
|
84
87
|
end
|
|
85
88
|
|
|
86
|
-
# Generates query methods ON THE
|
|
89
|
+
# Generates query methods ON THE SCOPE CLASS (Company when within: Company):
|
|
87
90
|
# - company.sample_from_department(dept, count=1) - random sampling
|
|
88
91
|
# - company.find_all_by_department(dept) - all objects
|
|
89
92
|
# - company.rebuild_dept_index - rebuild index
|
|
90
93
|
#
|
|
91
94
|
# @param indexed_class [Class] The class being indexed (e.g., Employee)
|
|
92
95
|
# @param field [Symbol] The field to index (e.g., :department)
|
|
93
|
-
# @param
|
|
96
|
+
# @param scope_class [Class] The scope class providing uniqueness context (e.g., Company)
|
|
94
97
|
# @param index_name [Symbol] Name of the index (e.g., :dept_index)
|
|
95
|
-
def generate_query_methods_destination(indexed_class, field,
|
|
96
|
-
# Resolve
|
|
97
|
-
|
|
98
|
+
def generate_query_methods_destination(indexed_class, field, scope_class, index_name)
|
|
99
|
+
# Resolve scope class using Familia pattern
|
|
100
|
+
actual_scope_class = Familia.resolve_class(scope_class)
|
|
101
|
+
|
|
102
|
+
# Get scope_class_config for method naming (needed for rebuild methods)
|
|
103
|
+
scope_class_config = actual_scope_class.config_name
|
|
98
104
|
|
|
99
105
|
# Generate instance sampling method (e.g., company.sample_from_department)
|
|
100
|
-
|
|
106
|
+
actual_scope_class.class_eval do
|
|
101
107
|
|
|
102
108
|
define_method(:"sample_from_#{field}") do |field_value, count = 1|
|
|
103
109
|
index_set = send("#{index_name}_for", field_value) # i.e. UnsortedSet
|
|
@@ -117,11 +123,135 @@ module Familia
|
|
|
117
123
|
index_set.members.map { |id| indexed_class.find_by_identifier(id) }
|
|
118
124
|
end
|
|
119
125
|
|
|
120
|
-
# Generate method to rebuild the index for this parent instance
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
# Generate method to rebuild the multi-value index for this parent instance
|
|
127
|
+
#
|
|
128
|
+
# Multi-indexes create separate sets for each field value, requiring a three-phase approach:
|
|
129
|
+
# 1. Loading: Load all objects once and cache them (discovers field values simultaneously)
|
|
130
|
+
# 2. Clearing: Remove all existing index sets using SCAN
|
|
131
|
+
# 3. Rebuilding: Rebuild index from cached objects (no reload needed)
|
|
132
|
+
#
|
|
133
|
+
# @param batch_size [Integer] Number of identifiers to process per batch
|
|
134
|
+
# @yield [progress] Optional block called with progress updates
|
|
135
|
+
# @yieldparam progress [Hash] Progress information with keys:
|
|
136
|
+
# - :phase [Symbol] Current phase (:loading, :clearing, :rebuilding)
|
|
137
|
+
# - :current [Integer] Current item count
|
|
138
|
+
# - :total [Integer] Total items (when known)
|
|
139
|
+
# - :field_value [String] Current field value being processed
|
|
140
|
+
#
|
|
141
|
+
# @example Basic rebuild
|
|
142
|
+
# company.rebuild_dept_index
|
|
143
|
+
#
|
|
144
|
+
# @example With progress monitoring
|
|
145
|
+
# company.rebuild_dept_index do |progress|
|
|
146
|
+
# puts "#{progress[:phase]}: #{progress[:current]}/#{progress[:total]}"
|
|
147
|
+
# end
|
|
148
|
+
#
|
|
149
|
+
# @example Memory-conscious rebuild for large collections
|
|
150
|
+
# # Process in smaller batches to reduce memory footprint
|
|
151
|
+
# company.rebuild_dept_index(batch_size: 50)
|
|
152
|
+
#
|
|
153
|
+
# @note Memory Considerations:
|
|
154
|
+
# This method caches all objects in memory during rebuild to avoid duplicate
|
|
155
|
+
# database loads. For very large collections (>100k objects), monitor memory usage
|
|
156
|
+
# and consider processing in chunks or using a streaming approach if memory
|
|
157
|
+
# constraints are encountered. The batch_size parameter controls Redis I/O
|
|
158
|
+
# batching but does not affect memory usage since all objects are cached.
|
|
159
|
+
#
|
|
160
|
+
define_method(:"rebuild_#{index_name}") do |batch_size: 100, &progress_block|
|
|
161
|
+
# PHASE 1: Find the collection containing the indexed objects
|
|
162
|
+
# Look for a participation relationship where indexed_class participates in this scope_class
|
|
163
|
+
collection_name = nil
|
|
164
|
+
|
|
165
|
+
# Check if indexed_class has participation to this scope_class
|
|
166
|
+
if indexed_class.respond_to?(:participation_relationships)
|
|
167
|
+
participation = indexed_class.participation_relationships.find do |rel|
|
|
168
|
+
rel.target_class == self.class
|
|
169
|
+
end
|
|
170
|
+
collection_name = participation&.collection_name if participation
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Get the collection DataType if we found a participation relationship
|
|
174
|
+
collection = collection_name ? send(collection_name) : nil
|
|
175
|
+
|
|
176
|
+
if collection
|
|
177
|
+
# PHASE 2: Load objects once and cache them for both discovery and rebuilding
|
|
178
|
+
# This avoids duplicate load_multi calls (previous approach loaded twice)
|
|
179
|
+
progress_block&.call(phase: :loading, current: 0, total: collection.size)
|
|
180
|
+
|
|
181
|
+
field_values = Set.new
|
|
182
|
+
cached_objects = []
|
|
183
|
+
processed = 0
|
|
184
|
+
|
|
185
|
+
collection.members.each_slice(batch_size) do |identifiers|
|
|
186
|
+
# Load objects in batches - SINGLE LOAD for both phases
|
|
187
|
+
objects = indexed_class.load_multi(identifiers).compact
|
|
188
|
+
cached_objects.concat(objects)
|
|
189
|
+
|
|
190
|
+
objects.each do |obj|
|
|
191
|
+
value = obj.send(field)
|
|
192
|
+
# Only track non-nil, non-empty field values
|
|
193
|
+
field_values << value.to_s if value && !value.to_s.strip.empty?
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
processed += identifiers.size
|
|
197
|
+
progress_block&.call(phase: :loading, current: processed, total: collection.size)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# PHASE 3: Clear all existing field-value-specific index sets
|
|
201
|
+
# Use SCAN to find all existing index keys (including orphaned ones from deleted field values)
|
|
202
|
+
progress_block&.call(phase: :clearing, current: 0, total: field_values.size)
|
|
203
|
+
|
|
204
|
+
# Get the base pattern for this index by creating a sample index set
|
|
205
|
+
# The "*" creates a wildcard pattern like "company:123:dept_index:*" for SCAN
|
|
206
|
+
sample_index = send(:"#{index_name}_for", "*")
|
|
207
|
+
index_pattern = sample_index.dbkey
|
|
208
|
+
|
|
209
|
+
# Find all existing index keys using SCAN
|
|
210
|
+
cleared_count = 0
|
|
211
|
+
dbclient.scan_each(match: index_pattern) do |key|
|
|
212
|
+
dbclient.del(key)
|
|
213
|
+
cleared_count += 1
|
|
214
|
+
progress_block&.call(phase: :clearing, current: cleared_count, total: field_values.size, key: key)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# PHASE 4: Rebuild index from cached objects (no reload needed)
|
|
218
|
+
progress_block&.call(phase: :rebuilding, current: 0, total: cached_objects.size)
|
|
219
|
+
|
|
220
|
+
processed = 0
|
|
221
|
+
cached_objects.each_slice(batch_size) do |objects|
|
|
222
|
+
transaction do |_tx|
|
|
223
|
+
objects.each do |obj|
|
|
224
|
+
# Use the generated add_to method to maintain consistency
|
|
225
|
+
# This ensures the same logic is used as during normal operation
|
|
226
|
+
obj.send(:"add_to_#{scope_class_config}_#{index_name}", self)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
processed += objects.size
|
|
231
|
+
progress_block&.call(phase: :rebuilding, current: processed, total: cached_objects.size)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
Familia.info "[Rebuild] Multi-index #{index_name} rebuilt: #{field_values.size} field values, #{processed} objects"
|
|
235
|
+
|
|
236
|
+
processed # Return count of processed objects
|
|
237
|
+
|
|
238
|
+
else
|
|
239
|
+
# No participation relationship found - warn and suggest alternative
|
|
240
|
+
Familia.warn <<~WARNING
|
|
241
|
+
[Rebuild] Cannot rebuild multi-index #{index_name}: no participation relationship found
|
|
242
|
+
|
|
243
|
+
Multi-index rebuild requires a participation relationship to find objects.
|
|
244
|
+
Add a participation relationship to #{indexed_class.name}:
|
|
245
|
+
|
|
246
|
+
class #{indexed_class.name} < Familia::Horreum
|
|
247
|
+
participates_in #{self.class.name}, :collection_name, score: :field
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
Then access the collection via: #{self.class.config_name}.collection_name
|
|
251
|
+
WARNING
|
|
252
|
+
|
|
253
|
+
nil
|
|
254
|
+
end
|
|
125
255
|
end
|
|
126
256
|
end
|
|
127
257
|
end
|
|
@@ -133,62 +263,62 @@ module Familia
|
|
|
133
263
|
#
|
|
134
264
|
# @param indexed_class [Class] The class being indexed (e.g., Employee)
|
|
135
265
|
# @param field [Symbol] The field to index (e.g., :department)
|
|
136
|
-
# @param
|
|
266
|
+
# @param scope_class [Class] The scope class providing uniqueness context (e.g., Company)
|
|
137
267
|
# @param index_name [Symbol] Name of the index (e.g., :dept_index)
|
|
138
|
-
def generate_mutation_methods_self(indexed_class, field,
|
|
139
|
-
|
|
268
|
+
def generate_mutation_methods_self(indexed_class, field, scope_class, index_name)
|
|
269
|
+
scope_class_config = scope_class.config_name
|
|
140
270
|
indexed_class.class_eval do
|
|
141
|
-
method_name = :"add_to_#{
|
|
142
|
-
Familia.
|
|
271
|
+
method_name = :"add_to_#{scope_class_config}_#{index_name}"
|
|
272
|
+
Familia.debug("[MultiIndexGenerators] #{name} method #{method_name}")
|
|
143
273
|
|
|
144
|
-
define_method(method_name) do |
|
|
145
|
-
return unless
|
|
274
|
+
define_method(method_name) do |scope_instance|
|
|
275
|
+
return unless scope_instance
|
|
146
276
|
|
|
147
277
|
field_value = send(field)
|
|
148
278
|
return unless field_value
|
|
149
279
|
|
|
150
|
-
# Use helper method on
|
|
151
|
-
index_set =
|
|
280
|
+
# Use helper method on scope instance instead of manual instantiation
|
|
281
|
+
index_set = scope_instance.send("#{index_name}_for", field_value)
|
|
152
282
|
|
|
153
283
|
# Use UnsortedSet DataType method (no scoring)
|
|
154
284
|
index_set.add(identifier)
|
|
155
285
|
end
|
|
156
286
|
|
|
157
|
-
method_name = :"remove_from_#{
|
|
158
|
-
Familia.
|
|
287
|
+
method_name = :"remove_from_#{scope_class_config}_#{index_name}"
|
|
288
|
+
Familia.debug("[MultiIndexGenerators] #{name} method #{method_name}")
|
|
159
289
|
|
|
160
|
-
define_method(method_name) do |
|
|
161
|
-
return unless
|
|
290
|
+
define_method(method_name) do |scope_instance|
|
|
291
|
+
return unless scope_instance
|
|
162
292
|
|
|
163
293
|
field_value = send(field)
|
|
164
294
|
return unless field_value
|
|
165
295
|
|
|
166
|
-
# Use helper method on
|
|
167
|
-
index_set =
|
|
296
|
+
# Use helper method on scope instance instead of manual instantiation
|
|
297
|
+
index_set = scope_instance.send("#{index_name}_for", field_value)
|
|
168
298
|
|
|
169
299
|
# Remove using UnsortedSet DataType method
|
|
170
300
|
index_set.remove(identifier)
|
|
171
301
|
end
|
|
172
302
|
|
|
173
|
-
method_name = :"update_in_#{
|
|
174
|
-
Familia.
|
|
303
|
+
method_name = :"update_in_#{scope_class_config}_#{index_name}"
|
|
304
|
+
Familia.debug("[MultiIndexGenerators] #{name} method #{method_name}")
|
|
175
305
|
|
|
176
|
-
define_method(method_name) do |
|
|
177
|
-
return unless
|
|
306
|
+
define_method(method_name) do |scope_instance, old_field_value = nil|
|
|
307
|
+
return unless scope_instance
|
|
178
308
|
|
|
179
309
|
new_field_value = send(field)
|
|
180
310
|
|
|
181
311
|
# Use Familia's transaction method for atomicity with DataType abstraction
|
|
182
|
-
|
|
312
|
+
scope_instance.transaction do |_tx|
|
|
183
313
|
# Remove from old index if provided - use helper method
|
|
184
314
|
if old_field_value
|
|
185
|
-
old_index_set =
|
|
315
|
+
old_index_set = scope_instance.send("#{index_name}_for", old_field_value)
|
|
186
316
|
old_index_set.remove(identifier)
|
|
187
317
|
end
|
|
188
318
|
|
|
189
319
|
# Add to new index if present - use helper method
|
|
190
320
|
if new_field_value
|
|
191
|
-
new_index_set =
|
|
321
|
+
new_index_set = scope_instance.send("#{index_name}_for", new_field_value)
|
|
192
322
|
new_index_set.add(identifier)
|
|
193
323
|
end
|
|
194
324
|
end
|