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,30 +1,30 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Feature System Developer Guide
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The Familia Feature system provides a simple, modular way to add optional functionality to Familia classes. Features are Ruby modules that get included into classes, extending them with additional methods and capabilities.
|
|
6
6
|
|
|
7
|
-
## Architecture
|
|
7
|
+
## Architecture
|
|
8
8
|
|
|
9
9
|
### Core Components
|
|
10
10
|
|
|
11
11
|
#### 1. Feature Registration (`Familia::Base`)
|
|
12
12
|
|
|
13
|
+
Features are registered using the `add_feature` method:
|
|
14
|
+
|
|
13
15
|
```ruby
|
|
14
|
-
# lib/familia/base.rb
|
|
15
16
|
module Familia::Base
|
|
16
|
-
|
|
17
|
-
@feature_definitions = {} # Feature metadata and dependencies
|
|
18
|
-
|
|
19
|
-
def self.add_feature(klass, feature_name, depends_on: [])
|
|
17
|
+
def self.add_feature(klass, feature_name, depends_on: [], field_group: nil)
|
|
20
18
|
@features_available ||= {}
|
|
21
19
|
|
|
22
|
-
# Create feature definition
|
|
20
|
+
# Create simple feature definition
|
|
23
21
|
feature_def = FeatureDefinition.new(
|
|
24
22
|
name: feature_name,
|
|
25
23
|
depends_on: depends_on,
|
|
24
|
+
field_group: field_group
|
|
26
25
|
)
|
|
27
26
|
|
|
27
|
+
# Track feature definitions and availability
|
|
28
28
|
@feature_definitions ||= {}
|
|
29
29
|
@feature_definitions[feature_name] = feature_def
|
|
30
30
|
features_available[feature_name] = klass
|
|
@@ -34,859 +34,342 @@ end
|
|
|
34
34
|
|
|
35
35
|
#### 2. Feature Activation (Horreum Classes)
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
# When a class declares `feature :name`
|
|
39
|
-
module Familia::Horreum::ClassMethods
|
|
40
|
-
def feature(name)
|
|
41
|
-
# 1. Validate feature exists
|
|
42
|
-
feature_klass = Familia::Base.features_available[name]
|
|
43
|
-
raise Familia::Problem, "Unknown feature: #{name}" unless feature_klass
|
|
44
|
-
|
|
45
|
-
# 2. Check dependencies
|
|
46
|
-
validate_feature_dependencies(name)
|
|
47
|
-
|
|
48
|
-
# 3. Include the feature module
|
|
49
|
-
include feature_klass
|
|
50
|
-
|
|
51
|
-
# 4. Track enabled features
|
|
52
|
-
@features_enabled ||= Set.new
|
|
53
|
-
@features_enabled.add(name)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
private
|
|
37
|
+
Features are activated using the `feature` method:
|
|
57
38
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if missing_deps.any?
|
|
64
|
-
raise Familia::Problem,
|
|
65
|
-
"Feature #{feature_name} requires: #{missing_deps.join(', ')}"
|
|
66
|
-
end
|
|
67
|
-
end
|
|
39
|
+
```ruby
|
|
40
|
+
class MyModel < Familia::Horreum
|
|
41
|
+
feature :expiration
|
|
42
|
+
feature :encrypted_fields
|
|
43
|
+
feature :safe_dump
|
|
68
44
|
end
|
|
69
45
|
```
|
|
70
46
|
|
|
71
47
|
#### 3. Feature Definition Structure
|
|
72
48
|
|
|
73
|
-
|
|
74
|
-
class FeatureDefinition
|
|
75
|
-
attr_reader :name, :depends_on, :conflicts_with, :provides
|
|
76
|
-
|
|
77
|
-
def initialize(name:, depends_on: [], conflicts_with: [], provides: [])
|
|
78
|
-
@name = name.to_sym
|
|
79
|
-
@depends_on = Array(depends_on).map(&:to_sym)
|
|
80
|
-
@conflicts_with = Array(conflicts_with).map(&:to_sym)
|
|
81
|
-
@provides = Array(provides).map(&:to_sym)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def compatible_with?(other_feature)
|
|
85
|
-
!conflicts_with.include?(other_feature.name)
|
|
86
|
-
end
|
|
49
|
+
Features are defined using a simple Data class:
|
|
87
50
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
end
|
|
91
|
-
end
|
|
51
|
+
```ruby
|
|
52
|
+
FeatureDefinition = Data.define(:name, :depends_on, :field_group)
|
|
92
53
|
```
|
|
93
54
|
|
|
94
55
|
### Feature Loading Lifecycle
|
|
95
56
|
|
|
96
|
-
#### 1.
|
|
57
|
+
#### 1. Feature Self-Registration
|
|
97
58
|
|
|
98
|
-
|
|
99
|
-
# lib/familia/features.rb - loads all features automatically
|
|
100
|
-
features_dir = File.join(__dir__, 'features')
|
|
101
|
-
Dir.glob(File.join(features_dir, '*.rb')).sort.each do |feature_file|
|
|
102
|
-
begin
|
|
103
|
-
require_relative feature_file
|
|
104
|
-
rescue LoadError => e
|
|
105
|
-
Familia.logger.warn "Failed to load feature #{feature_file}: #{e.message}"
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
#### 2. Feature Self-Registration
|
|
59
|
+
Each feature module registers itself:
|
|
111
60
|
|
|
112
61
|
```ruby
|
|
113
|
-
# Each feature registers itself when loaded
|
|
114
62
|
module Familia::Features::MyFeature
|
|
115
63
|
def self.included(base)
|
|
116
64
|
base.extend ClassMethods
|
|
117
|
-
base.
|
|
65
|
+
base.include InstanceMethods
|
|
118
66
|
end
|
|
119
67
|
|
|
120
68
|
module ClassMethods
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
module InstanceMethods
|
|
125
|
-
# Instance-level functionality
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
# Self-registration at module definition time
|
|
129
|
-
Familia::Base.add_feature self, :my_feature, depends_on: [:other_feature]
|
|
130
|
-
end
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
#### 3. Runtime Inclusion
|
|
134
|
-
|
|
135
|
-
```ruby
|
|
136
|
-
# When a class declares a feature
|
|
137
|
-
class MyModel < Familia::Horreum
|
|
138
|
-
feature :expiration # 1. Validation and dependency check
|
|
139
|
-
feature :encrypted_fields # 2. Module inclusion
|
|
140
|
-
feature :safe_dump # 3. Method definition and setup
|
|
141
|
-
end
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## Advanced Feature Patterns
|
|
145
|
-
|
|
146
|
-
### Conditional Feature Loading
|
|
147
|
-
|
|
148
|
-
```ruby
|
|
149
|
-
module Familia::Features::ConditionalFeature
|
|
150
|
-
def self.included(base)
|
|
151
|
-
# Only add functionality if conditions are met
|
|
152
|
-
if defined?(Rails) && Rails.env.production?
|
|
153
|
-
base.extend ProductionMethods
|
|
154
|
-
else
|
|
155
|
-
base.extend DevelopmentMethods
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# Conditional method definitions based on available libraries
|
|
159
|
-
if defined?(Sidekiq)
|
|
160
|
-
base.include BackgroundJobIntegration
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
if defined?(ActiveRecord)
|
|
164
|
-
base.include ActiveRecordCompatibility
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
module ProductionMethods
|
|
169
|
-
def production_only_method
|
|
170
|
-
# Implementation only available in production
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
module DevelopmentMethods
|
|
175
|
-
def debug_helper_method
|
|
176
|
-
# Development and test helper methods
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Register with environment-specific dependencies
|
|
181
|
-
dependencies = []
|
|
182
|
-
dependencies << :logging if defined?(Rails)
|
|
183
|
-
dependencies << :metrics if ENV['ENABLE_METRICS']
|
|
184
|
-
|
|
185
|
-
Familia::Base.add_feature self, :conditional_feature, depends_on: dependencies
|
|
186
|
-
end
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### Feature Conflicts and Compatibility
|
|
190
|
-
|
|
191
|
-
```ruby
|
|
192
|
-
# Feature that conflicts with others
|
|
193
|
-
module Familia::Features::AlternativeImplementation
|
|
194
|
-
def self.included(base)
|
|
195
|
-
# Check for conflicting features
|
|
196
|
-
conflicting_features = [:original_implementation, :legacy_mode]
|
|
197
|
-
enabled_conflicts = conflicting_features & base.features_enabled.to_a
|
|
198
|
-
|
|
199
|
-
if enabled_conflicts.any?
|
|
200
|
-
raise Familia::Problem,
|
|
201
|
-
"#{self} conflicts with: #{enabled_conflicts.join(', ')}"
|
|
69
|
+
def my_feature_config
|
|
70
|
+
# Class-level functionality
|
|
202
71
|
end
|
|
203
|
-
|
|
204
|
-
base.extend ClassMethods
|
|
205
72
|
end
|
|
206
73
|
|
|
207
|
-
module
|
|
208
|
-
def
|
|
209
|
-
#
|
|
74
|
+
module InstanceMethods
|
|
75
|
+
def my_feature_method
|
|
76
|
+
# Instance-level functionality
|
|
210
77
|
end
|
|
211
78
|
end
|
|
212
79
|
|
|
213
|
-
|
|
214
|
-
|
|
80
|
+
# Self-register with the feature system
|
|
81
|
+
Familia::Base.add_feature self, :my_feature, depends_on: []
|
|
215
82
|
end
|
|
216
83
|
```
|
|
217
84
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
```ruby
|
|
221
|
-
module Familia::Features::CapabilityProvider
|
|
222
|
-
def self.included(base)
|
|
223
|
-
base.extend ClassMethods
|
|
224
|
-
|
|
225
|
-
# Add capability flags to the class
|
|
226
|
-
base.instance_variable_set(:@capabilities, Set.new)
|
|
227
|
-
base.capabilities.merge([:search, :indexing, :full_text])
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
module ClassMethods
|
|
231
|
-
attr_reader :capabilities
|
|
232
|
-
|
|
233
|
-
def has_capability?(capability)
|
|
234
|
-
capabilities.include?(capability.to_sym)
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
def requires_capability(capability)
|
|
238
|
-
unless has_capability?(capability)
|
|
239
|
-
raise Familia::Problem,
|
|
240
|
-
"#{self} requires #{capability} capability"
|
|
241
|
-
end
|
|
242
|
-
end
|
|
243
|
-
end
|
|
85
|
+
#### 2. Runtime Inclusion
|
|
244
86
|
|
|
245
|
-
|
|
246
|
-
Familia::Base.add_feature self, :capability_provider, provides: [:search, :indexing]
|
|
247
|
-
end
|
|
87
|
+
When `feature` is called, the system:
|
|
248
88
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
base.requires_capability(:search)
|
|
254
|
-
base.requires_capability(:indexing)
|
|
255
|
-
|
|
256
|
-
base.extend ClassMethods
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
module ClassMethods
|
|
260
|
-
def search_by_field(field, query)
|
|
261
|
-
# Implementation that uses search capabilities
|
|
262
|
-
end
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
Familia::Base.add_feature self, :search_dependent,
|
|
266
|
-
depends_on: [:capability_provider]
|
|
267
|
-
end
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
### Dynamic Feature Configuration
|
|
89
|
+
1. Validates the feature exists
|
|
90
|
+
2. Checks dependencies are satisfied
|
|
91
|
+
3. Includes the feature module into the class
|
|
92
|
+
4. Stores feature options if provided
|
|
271
93
|
|
|
272
94
|
```ruby
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
base.extend ClassMethods
|
|
276
|
-
|
|
277
|
-
# Initialize configuration
|
|
278
|
-
config = base.feature_config(:configurable_feature)
|
|
279
|
-
|
|
280
|
-
if config[:enable_caching]
|
|
281
|
-
base.include CachingMethods
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
if config[:enable_logging]
|
|
285
|
-
base.include LoggingMethods
|
|
286
|
-
end
|
|
95
|
+
def feature(feature_name = nil, **options)
|
|
96
|
+
@features_enabled ||= []
|
|
287
97
|
|
|
288
|
-
|
|
289
|
-
base.instance_variable_set(:@batch_size, config[:batch_size] || 100)
|
|
290
|
-
end
|
|
98
|
+
return features_enabled if feature_name.nil?
|
|
291
99
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
@feature_configs[feature_name] ||= load_feature_config(feature_name)
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
private
|
|
100
|
+
feature_name = feature_name.to_sym
|
|
101
|
+
feature_module = Familia::Base.find_feature(feature_name, self)
|
|
102
|
+
raise Familia::Problem, "Unsupported feature: #{feature_name}" unless feature_module
|
|
299
103
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
# 2. Environment variables
|
|
308
|
-
env_config = ENV.select { |k, v| k.start_with?("FAMILIA_#{feature_name.upcase}_") }
|
|
309
|
-
env_config.each { |k, v| config[k.split('_').last.downcase.to_sym] = v }
|
|
310
|
-
|
|
311
|
-
# 3. Configuration files
|
|
312
|
-
if defined?(Rails)
|
|
313
|
-
rails_config = Rails.application.config.familia&.features&.dig(feature_name)
|
|
314
|
-
config.merge!(rails_config) if rails_config
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
config
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
def default_config_for(feature_name)
|
|
321
|
-
case feature_name
|
|
322
|
-
when :configurable_feature
|
|
323
|
-
{
|
|
324
|
-
enable_caching: true,
|
|
325
|
-
enable_logging: Rails.env.development?,
|
|
326
|
-
batch_size: 100,
|
|
327
|
-
timeout: 30
|
|
328
|
-
}
|
|
329
|
-
else
|
|
330
|
-
{}
|
|
331
|
-
end
|
|
332
|
-
end
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
module CachingMethods
|
|
336
|
-
def cached_operation(&block)
|
|
337
|
-
# Caching implementation
|
|
338
|
-
end
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
module LoggingMethods
|
|
342
|
-
def log_operation(operation, &block)
|
|
343
|
-
# Logging implementation
|
|
104
|
+
# Check dependencies
|
|
105
|
+
feature_def = Familia::Base.feature_definitions[feature_name]
|
|
106
|
+
if feature_def&.depends_on&.any?
|
|
107
|
+
missing = feature_def.depends_on - features_enabled
|
|
108
|
+
if missing.any?
|
|
109
|
+
raise Familia::Problem,
|
|
110
|
+
"Feature #{feature_name} requires missing dependencies: #{missing.join(', ')}"
|
|
344
111
|
end
|
|
345
112
|
end
|
|
346
113
|
|
|
347
|
-
|
|
114
|
+
features_enabled << feature_name
|
|
115
|
+
include feature_module
|
|
348
116
|
end
|
|
349
117
|
```
|
|
350
118
|
|
|
351
|
-
## Feature Development
|
|
119
|
+
## Basic Feature Development
|
|
352
120
|
|
|
353
121
|
### 1. Feature Structure Template
|
|
354
122
|
|
|
355
123
|
```ruby
|
|
356
|
-
# lib/familia/features/my_feature.rb
|
|
357
124
|
module Familia
|
|
358
125
|
module Features
|
|
359
126
|
module MyFeature
|
|
360
|
-
# Feature metadata
|
|
361
|
-
FEATURE_VERSION = '1.0.0'
|
|
362
|
-
REQUIRED_FAMILIA_VERSION = '>= 2.0.0'
|
|
363
|
-
|
|
364
127
|
def self.included(base)
|
|
365
|
-
# Validation and setup
|
|
366
|
-
validate_environment!(base)
|
|
367
|
-
|
|
368
|
-
Familia.ld "[#{base}] Loading #{self} v#{FEATURE_VERSION}"
|
|
369
|
-
|
|
370
|
-
# Module inclusion
|
|
371
128
|
base.extend ClassMethods
|
|
372
|
-
base.
|
|
373
|
-
base.include HelperMethods # Use include for utility methods
|
|
374
|
-
|
|
375
|
-
# Post-inclusion setup
|
|
376
|
-
configure_feature(base)
|
|
377
|
-
end
|
|
378
|
-
|
|
379
|
-
def self.validate_environment!(base)
|
|
380
|
-
# Check Familia version compatibility
|
|
381
|
-
familia_version = Gem::Version.new(Familia::VERSION)
|
|
382
|
-
required_version = Gem::Requirement.new(REQUIRED_FAMILIA_VERSION)
|
|
383
|
-
|
|
384
|
-
unless required_version.satisfied_by?(familia_version)
|
|
385
|
-
raise Familia::Problem,
|
|
386
|
-
"#{self} requires Familia #{REQUIRED_FAMILIA_VERSION}, " \
|
|
387
|
-
"got #{familia_version}"
|
|
388
|
-
end
|
|
389
|
-
|
|
390
|
-
# Check for required methods/capabilities on the base class
|
|
391
|
-
required_methods = [:identifier_field, :field]
|
|
392
|
-
missing_methods = required_methods.reject { |m| base.respond_to?(m) }
|
|
393
|
-
|
|
394
|
-
if missing_methods.any?
|
|
395
|
-
raise Familia::Problem,
|
|
396
|
-
"#{base} missing required methods: #{missing_methods.join(', ')}"
|
|
397
|
-
end
|
|
398
|
-
end
|
|
399
|
-
|
|
400
|
-
def self.configure_feature(base)
|
|
401
|
-
# Feature-specific initialization
|
|
402
|
-
base.instance_variable_set(:@my_feature_config, {
|
|
403
|
-
enabled: true,
|
|
404
|
-
options: {}
|
|
405
|
-
})
|
|
406
|
-
|
|
407
|
-
# Set up feature-specific data structures
|
|
408
|
-
base.class_eval do
|
|
409
|
-
@my_feature_data ||= {}
|
|
410
|
-
end
|
|
129
|
+
base.include InstanceMethods
|
|
411
130
|
end
|
|
412
131
|
|
|
413
|
-
# Class-level methods added to including class
|
|
414
132
|
module ClassMethods
|
|
415
133
|
def my_feature_config
|
|
416
|
-
@my_feature_config ||= {
|
|
134
|
+
@my_feature_config ||= {}
|
|
417
135
|
end
|
|
418
136
|
|
|
419
137
|
def configure_my_feature(**options)
|
|
420
|
-
my_feature_config
|
|
138
|
+
my_feature_config.merge!(options)
|
|
421
139
|
end
|
|
422
140
|
|
|
423
141
|
def my_feature_enabled?
|
|
424
|
-
|
|
142
|
+
features_enabled.include?(:my_feature)
|
|
425
143
|
end
|
|
426
144
|
end
|
|
427
145
|
|
|
428
|
-
# Instance methods that intercept/override existing methods
|
|
429
146
|
module InstanceMethods
|
|
430
147
|
def save
|
|
431
|
-
# Pre-processing
|
|
432
148
|
before_my_feature_save if respond_to?(:before_my_feature_save, true)
|
|
433
|
-
|
|
434
|
-
# Call original save
|
|
435
149
|
result = super
|
|
436
|
-
|
|
437
|
-
# Post-processing
|
|
438
150
|
after_my_feature_save if respond_to?(:after_my_feature_save, true)
|
|
439
|
-
|
|
440
151
|
result
|
|
441
152
|
end
|
|
442
153
|
|
|
443
154
|
private
|
|
444
155
|
|
|
445
156
|
def before_my_feature_save
|
|
446
|
-
#
|
|
157
|
+
# Pre-save logic
|
|
447
158
|
end
|
|
448
159
|
|
|
449
160
|
def after_my_feature_save
|
|
450
|
-
#
|
|
451
|
-
end
|
|
452
|
-
end
|
|
453
|
-
|
|
454
|
-
# Utility methods that don't override existing functionality
|
|
455
|
-
module HelperMethods
|
|
456
|
-
def my_feature_helper
|
|
457
|
-
return unless self.class.my_feature_enabled?
|
|
458
|
-
# Helper implementation
|
|
161
|
+
# Post-save logic
|
|
459
162
|
end
|
|
460
163
|
end
|
|
461
164
|
|
|
462
165
|
# Register the feature
|
|
463
|
-
Familia::Base.add_feature self, :my_feature
|
|
166
|
+
Familia::Base.add_feature self, :my_feature
|
|
464
167
|
end
|
|
465
168
|
end
|
|
466
169
|
end
|
|
467
170
|
```
|
|
468
171
|
|
|
469
|
-
### 2.
|
|
172
|
+
### 2. Feature with Dependencies
|
|
470
173
|
|
|
471
174
|
```ruby
|
|
472
|
-
module Familia::Features::
|
|
473
|
-
class FeatureError < Familia::Problem; end
|
|
474
|
-
class ConfigurationError < FeatureError; end
|
|
475
|
-
class DependencyError < FeatureError; end
|
|
476
|
-
|
|
175
|
+
module Familia::Features::AdvancedFeature
|
|
477
176
|
def self.included(base)
|
|
478
|
-
|
|
479
|
-
validate_dependencies!(base)
|
|
480
|
-
configure_feature_safely(base)
|
|
481
|
-
rescue => e
|
|
482
|
-
handle_inclusion_error(base, e)
|
|
483
|
-
end
|
|
484
|
-
end
|
|
485
|
-
|
|
486
|
-
def self.validate_dependencies!(base)
|
|
487
|
-
# Check external dependencies
|
|
488
|
-
unless defined?(SomeGem)
|
|
489
|
-
raise DependencyError, "#{self} requires 'some_gem' gem"
|
|
490
|
-
end
|
|
491
|
-
|
|
492
|
-
# Check feature dependencies
|
|
493
|
-
required_features = [:base_feature]
|
|
494
|
-
missing_features = required_features - base.features_enabled.to_a
|
|
495
|
-
|
|
496
|
-
if missing_features.any?
|
|
497
|
-
raise DependencyError,
|
|
498
|
-
"#{self} requires features: #{missing_features.join(', ')}"
|
|
499
|
-
end
|
|
500
|
-
end
|
|
501
|
-
|
|
502
|
-
def self.configure_feature_safely(base)
|
|
503
|
-
# Safely configure with fallbacks
|
|
504
|
-
config = load_configuration
|
|
505
|
-
apply_configuration(base, config)
|
|
506
|
-
rescue => e
|
|
507
|
-
Familia.logger.warn "Feature configuration failed: #{e.message}"
|
|
508
|
-
apply_default_configuration(base)
|
|
509
|
-
end
|
|
510
|
-
|
|
511
|
-
def self.handle_inclusion_error(base, error)
|
|
512
|
-
case error
|
|
513
|
-
when DependencyError
|
|
514
|
-
# Log dependency issues and disable feature
|
|
515
|
-
Familia.logger.error "Feature #{self} disabled: #{error.message}"
|
|
516
|
-
base.instance_variable_set(:@robust_feature_disabled, true)
|
|
517
|
-
when ConfigurationError
|
|
518
|
-
# Try default configuration
|
|
519
|
-
Familia.logger.warn "Using default configuration: #{error.message}"
|
|
520
|
-
apply_default_configuration(base)
|
|
521
|
-
else
|
|
522
|
-
# Re-raise unexpected errors
|
|
523
|
-
raise
|
|
524
|
-
end
|
|
177
|
+
base.extend ClassMethods
|
|
525
178
|
end
|
|
526
179
|
|
|
527
180
|
module ClassMethods
|
|
528
|
-
def
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
def with_robust_feature(&block)
|
|
533
|
-
return unless robust_feature_enabled?
|
|
534
|
-
block.call
|
|
535
|
-
rescue => e
|
|
536
|
-
Familia.logger.error "Robust feature operation failed: #{e.message}"
|
|
537
|
-
nil
|
|
181
|
+
def advanced_method
|
|
182
|
+
# This feature requires :basic_feature to be enabled
|
|
183
|
+
raise "Basic feature required" unless features_enabled.include?(:basic_feature)
|
|
184
|
+
# Advanced functionality here
|
|
538
185
|
end
|
|
539
186
|
end
|
|
540
187
|
|
|
541
|
-
|
|
188
|
+
# Register with dependency
|
|
189
|
+
Familia::Base.add_feature self, :advanced_feature, depends_on: [:basic_feature]
|
|
542
190
|
end
|
|
543
191
|
```
|
|
544
192
|
|
|
545
|
-
### 3. Feature
|
|
193
|
+
### 3. Feature with Field Groups
|
|
546
194
|
|
|
547
195
|
```ruby
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
# Create temporary test class with feature
|
|
552
|
-
test_class = Class.new(Familia::Horreum) do
|
|
553
|
-
def self.name
|
|
554
|
-
'FeatureTestClass'
|
|
555
|
-
end
|
|
556
|
-
|
|
557
|
-
identifier_field :test_id
|
|
558
|
-
field :test_id
|
|
559
|
-
end
|
|
560
|
-
|
|
561
|
-
# Configure feature if needed
|
|
562
|
-
if config.any?
|
|
563
|
-
test_class.define_singleton_method(:feature_config) do |name|
|
|
564
|
-
config
|
|
565
|
-
end
|
|
566
|
-
end
|
|
567
|
-
|
|
568
|
-
# Enable the feature
|
|
569
|
-
test_class.feature feature_name
|
|
570
|
-
|
|
571
|
-
yield test_class
|
|
572
|
-
end
|
|
573
|
-
|
|
574
|
-
def feature_enabled?(klass, feature_name)
|
|
575
|
-
klass.features_enabled.include?(feature_name)
|
|
196
|
+
module Familia::Features::FieldGroupFeature
|
|
197
|
+
def self.included(base)
|
|
198
|
+
base.extend ClassMethods
|
|
576
199
|
end
|
|
577
200
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
201
|
+
module ClassMethods
|
|
202
|
+
def special_field(name, **options)
|
|
203
|
+
# Define fields that belong to this feature
|
|
204
|
+
field(name, **options)
|
|
582
205
|
end
|
|
583
206
|
end
|
|
584
|
-
end
|
|
585
207
|
|
|
586
|
-
#
|
|
587
|
-
|
|
588
|
-
config.include FeatureTestHelpers
|
|
208
|
+
# Register with field group
|
|
209
|
+
Familia::Base.add_feature self, :field_group_feature, field_group: :special_fields
|
|
589
210
|
end
|
|
211
|
+
```
|
|
590
212
|
|
|
591
|
-
|
|
592
|
-
RSpec.describe Familia::Features::MyFeature do
|
|
593
|
-
it "adds expected methods to class" do
|
|
594
|
-
with_feature(:my_feature) do |test_class|
|
|
595
|
-
expect(test_class).to respond_to(:my_feature_config)
|
|
596
|
-
expect(test_class.new).to respond_to(:my_feature_helper)
|
|
597
|
-
end
|
|
598
|
-
end
|
|
599
|
-
|
|
600
|
-
it "respects feature configuration" do
|
|
601
|
-
config = { enabled: false }
|
|
602
|
-
|
|
603
|
-
with_feature(:my_feature, config) do |test_class|
|
|
604
|
-
expect(test_class.my_feature_enabled?).to be false
|
|
605
|
-
end
|
|
606
|
-
end
|
|
213
|
+
## Feature Development Best Practices
|
|
607
214
|
|
|
608
|
-
|
|
609
|
-
expect {
|
|
610
|
-
Class.new(Familia::Horreum) do
|
|
611
|
-
feature :my_feature # Missing :required_feature dependency
|
|
612
|
-
end
|
|
613
|
-
}.to raise_error(Familia::Problem, /requires.*required_feature/)
|
|
614
|
-
end
|
|
615
|
-
end
|
|
616
|
-
```
|
|
215
|
+
### 1. Naming Conventions
|
|
617
216
|
|
|
618
|
-
|
|
217
|
+
- Feature names should be symbols (`:my_feature`)
|
|
218
|
+
- Module names should match: `Familia::Features::MyFeature`
|
|
219
|
+
- Method names should be prefixed with feature name to avoid conflicts
|
|
619
220
|
|
|
620
|
-
###
|
|
221
|
+
### 2. Error Handling
|
|
621
222
|
|
|
622
223
|
```ruby
|
|
623
|
-
module Familia::Features::
|
|
224
|
+
module Familia::Features::RobustFeature
|
|
225
|
+
class FeatureError < StandardError; end
|
|
226
|
+
|
|
624
227
|
def self.included(base)
|
|
625
|
-
|
|
228
|
+
validate_environment!
|
|
626
229
|
base.extend ClassMethods
|
|
230
|
+
end
|
|
627
231
|
|
|
628
|
-
|
|
629
|
-
|
|
232
|
+
def self.validate_environment!
|
|
233
|
+
raise FeatureError, "Ruby 3.0+ required" if RUBY_VERSION < "3.0"
|
|
630
234
|
end
|
|
631
235
|
|
|
632
236
|
module ClassMethods
|
|
633
|
-
def
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
# Expensive setup operations
|
|
637
|
-
perform_expensive_setup
|
|
638
|
-
@setup_complete = true
|
|
639
|
-
end
|
|
640
|
-
|
|
641
|
-
def lazy_feature_method
|
|
642
|
-
ensure_lazy_feature_setup!
|
|
643
|
-
# Method implementation
|
|
237
|
+
def robust_feature_method
|
|
238
|
+
raise FeatureError, "Feature not properly configured" unless configured?
|
|
239
|
+
# Feature logic here
|
|
644
240
|
end
|
|
645
241
|
|
|
646
242
|
private
|
|
647
243
|
|
|
648
|
-
def
|
|
649
|
-
#
|
|
650
|
-
|
|
651
|
-
@compiled_templates = compile_templates
|
|
244
|
+
def configured?
|
|
245
|
+
# Check if feature is properly set up
|
|
246
|
+
true
|
|
652
247
|
end
|
|
653
248
|
end
|
|
654
249
|
|
|
655
|
-
Familia::Base.add_feature self, :
|
|
250
|
+
Familia::Base.add_feature self, :robust_feature
|
|
656
251
|
end
|
|
657
252
|
```
|
|
658
253
|
|
|
659
|
-
###
|
|
254
|
+
### 3. Feature Options
|
|
255
|
+
|
|
256
|
+
Features can accept configuration options:
|
|
660
257
|
|
|
661
258
|
```ruby
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
end
|
|
259
|
+
class MyModel < Familia::Horreum
|
|
260
|
+
feature :my_feature, timeout: 30, retries: 3
|
|
261
|
+
end
|
|
666
262
|
|
|
263
|
+
# Access options in the feature
|
|
264
|
+
module Familia::Features::MyFeature
|
|
667
265
|
module ClassMethods
|
|
668
|
-
def
|
|
669
|
-
|
|
670
|
-
@method_cache[key] ||= expensive_computation(key)
|
|
671
|
-
end
|
|
672
|
-
|
|
673
|
-
def clear_feature_cache!
|
|
674
|
-
@method_cache = {}
|
|
675
|
-
end
|
|
676
|
-
|
|
677
|
-
private
|
|
678
|
-
|
|
679
|
-
def expensive_computation(key)
|
|
680
|
-
# Expensive operation
|
|
681
|
-
sleep 0.1 # Simulate work
|
|
682
|
-
"computed_#{key}"
|
|
266
|
+
def my_feature_timeout
|
|
267
|
+
feature_options(:my_feature)[:timeout] || 60
|
|
683
268
|
end
|
|
684
269
|
end
|
|
685
|
-
|
|
686
|
-
Familia::Base.add_feature self, :cached_feature
|
|
687
270
|
end
|
|
688
271
|
```
|
|
689
272
|
|
|
690
|
-
##
|
|
273
|
+
## Testing Features
|
|
691
274
|
|
|
692
|
-
###
|
|
275
|
+
### Feature Testing Helpers
|
|
693
276
|
|
|
694
277
|
```ruby
|
|
695
|
-
module
|
|
696
|
-
def
|
|
697
|
-
|
|
698
|
-
end
|
|
699
|
-
|
|
700
|
-
module ClassMethods
|
|
701
|
-
def feature_info
|
|
702
|
-
{
|
|
703
|
-
enabled_features: features_enabled.to_a,
|
|
704
|
-
feature_dependencies: feature_dependency_graph,
|
|
705
|
-
feature_conflicts: feature_conflict_map,
|
|
706
|
-
feature_load_order: feature_load_order
|
|
707
|
-
}
|
|
708
|
-
end
|
|
709
|
-
|
|
710
|
-
def feature_dependency_graph
|
|
711
|
-
graph = {}
|
|
712
|
-
features_enabled.each do |feature|
|
|
713
|
-
definition = Familia::Base.feature_definitions[feature]
|
|
714
|
-
graph[feature] = definition&.depends_on || []
|
|
715
|
-
end
|
|
716
|
-
graph
|
|
717
|
-
end
|
|
718
|
-
|
|
719
|
-
def feature_conflict_map
|
|
720
|
-
conflicts = {}
|
|
721
|
-
features_enabled.each do |feature|
|
|
722
|
-
definition = Familia::Base.feature_definitions[feature]
|
|
723
|
-
conflicts[feature] = definition&.conflicts_with || []
|
|
724
|
-
end
|
|
725
|
-
conflicts
|
|
726
|
-
end
|
|
727
|
-
|
|
728
|
-
def feature_load_order
|
|
729
|
-
# Return the order features were loaded
|
|
730
|
-
@feature_load_order ||= []
|
|
731
|
-
end
|
|
732
|
-
|
|
733
|
-
def debug_feature_issues
|
|
734
|
-
issues = []
|
|
735
|
-
|
|
736
|
-
# Check for circular dependencies
|
|
737
|
-
issues.concat(detect_circular_dependencies)
|
|
738
|
-
|
|
739
|
-
# Check for method conflicts
|
|
740
|
-
issues.concat(detect_method_conflicts)
|
|
741
|
-
|
|
742
|
-
# Check for missing dependencies
|
|
743
|
-
issues.concat(detect_missing_dependencies)
|
|
744
|
-
|
|
745
|
-
issues
|
|
746
|
-
end
|
|
747
|
-
|
|
748
|
-
private
|
|
749
|
-
|
|
750
|
-
def detect_circular_dependencies
|
|
751
|
-
# Implementation for circular dependency detection
|
|
752
|
-
end
|
|
753
|
-
|
|
754
|
-
def detect_method_conflicts
|
|
755
|
-
# Implementation for method conflict detection
|
|
756
|
-
end
|
|
278
|
+
module FeatureTestHelpers
|
|
279
|
+
def with_feature(klass, feature_name, **options)
|
|
280
|
+
original_features = klass.features_enabled.dup
|
|
757
281
|
|
|
758
|
-
|
|
759
|
-
|
|
282
|
+
begin
|
|
283
|
+
klass.feature(feature_name, **options)
|
|
284
|
+
yield
|
|
285
|
+
ensure
|
|
286
|
+
# Reset features (note: this is simplified - actual reset is more complex)
|
|
287
|
+
klass.instance_variable_set(:@features_enabled, original_features)
|
|
760
288
|
end
|
|
761
289
|
end
|
|
762
290
|
|
|
763
|
-
|
|
291
|
+
def feature_enabled?(klass, feature_name)
|
|
292
|
+
klass.features_enabled.include?(feature_name)
|
|
293
|
+
end
|
|
764
294
|
end
|
|
765
|
-
```
|
|
766
|
-
|
|
767
|
-
### 2. Feature Debug Logging
|
|
768
|
-
|
|
769
|
-
```ruby
|
|
770
|
-
module Familia::Features::DebugLogging
|
|
771
|
-
def self.included(base)
|
|
772
|
-
return unless Familia.debug?
|
|
773
|
-
|
|
774
|
-
base.extend ClassMethods
|
|
775
|
-
original_feature_method = base.method(:feature)
|
|
776
|
-
|
|
777
|
-
base.define_singleton_method(:feature) do |name|
|
|
778
|
-
Familia.ld "[DEBUG] Loading feature #{name} on #{self}"
|
|
779
|
-
start_time = Familia.now
|
|
780
295
|
|
|
781
|
-
|
|
296
|
+
# Test example
|
|
297
|
+
describe Familia::Features::MyFeature do
|
|
298
|
+
include FeatureTestHelpers
|
|
782
299
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
300
|
+
it "adds expected methods to class" do
|
|
301
|
+
with_feature(MyModel, :my_feature) do
|
|
302
|
+
expect(MyModel).to respond_to(:my_feature_config)
|
|
303
|
+
expect(MyModel.new).to respond_to(:my_feature_method)
|
|
787
304
|
end
|
|
788
305
|
end
|
|
789
306
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
Familia.ld "[DEBUG] Calling #{method_name} on #{self}"
|
|
795
|
-
start_time = Familia.now
|
|
796
|
-
|
|
797
|
-
result = block.call
|
|
798
|
-
|
|
799
|
-
duration = (Familia.now - start_time) * 1000
|
|
800
|
-
Familia.ld "[DEBUG] #{method_name} completed in #{duration.round(2)}ms"
|
|
801
|
-
|
|
802
|
-
result
|
|
803
|
-
end
|
|
307
|
+
it "validates dependencies" do
|
|
308
|
+
expect {
|
|
309
|
+
MyModel.feature(:advanced_feature) # requires :basic_feature
|
|
310
|
+
}.to raise_error(Familia::Problem, /requires missing dependencies/)
|
|
804
311
|
end
|
|
805
|
-
|
|
806
|
-
Familia::Base.add_feature self, :debug_logging
|
|
807
312
|
end
|
|
808
313
|
```
|
|
809
314
|
|
|
810
|
-
##
|
|
315
|
+
## Existing Features Overview
|
|
811
316
|
|
|
812
|
-
###
|
|
317
|
+
### Core Features
|
|
813
318
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
319
|
+
- **`:expiration`** - TTL management for objects and fields
|
|
320
|
+
- **`:encrypted_fields`** - Encrypt sensitive fields before storage
|
|
321
|
+
- **`:safe_dump`** - API-safe serialization excluding sensitive fields
|
|
322
|
+
- **`:relationships`** - Object associations and indexing
|
|
323
|
+
- **`:transient_fields`** - Runtime-only fields that aren't persisted
|
|
324
|
+
- **`:quantization`** - Score quantization for sorted sets
|
|
325
|
+
- **`:object_identifier`** - Flexible object identification strategies
|
|
326
|
+
- **`:external_identifier`** - External system ID management
|
|
822
327
|
|
|
823
|
-
|
|
824
|
-
check_and_migrate_version(base)
|
|
825
|
-
base.extend ClassMethods
|
|
826
|
-
end
|
|
827
|
-
|
|
828
|
-
def self.check_and_migrate_version(base)
|
|
829
|
-
current_version = get_current_version(base)
|
|
830
|
-
return if current_version == VERSION
|
|
831
|
-
|
|
832
|
-
if current_version.nil?
|
|
833
|
-
# First installation
|
|
834
|
-
set_version(base, VERSION)
|
|
835
|
-
return
|
|
836
|
-
end
|
|
328
|
+
### Feature Dependencies
|
|
837
329
|
|
|
838
|
-
|
|
839
|
-
migrate_from_version(base, current_version, VERSION)
|
|
840
|
-
end
|
|
330
|
+
Most features are independent, but some have dependencies:
|
|
841
331
|
|
|
842
|
-
|
|
843
|
-
|
|
332
|
+
```ruby
|
|
333
|
+
# relationships feature has no dependencies
|
|
334
|
+
Familia::Base.add_feature Relationships, :relationships
|
|
844
335
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
send(step[:migration], base)
|
|
848
|
-
end
|
|
336
|
+
# No complex dependency chains in current implementation
|
|
337
|
+
```
|
|
849
338
|
|
|
850
|
-
|
|
851
|
-
end
|
|
339
|
+
## Debugging Features
|
|
852
340
|
|
|
853
|
-
|
|
854
|
-
# Find path through migration steps
|
|
855
|
-
current = from
|
|
856
|
-
path = []
|
|
341
|
+
### Feature Introspection
|
|
857
342
|
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
343
|
+
```ruby
|
|
344
|
+
# Check what features are available
|
|
345
|
+
Familia::Base.features_available.keys
|
|
346
|
+
# => [:expiration, :encrypted_fields, :safe_dump, :relationships, ...]
|
|
861
347
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
348
|
+
# Check what features are enabled on a class
|
|
349
|
+
MyModel.features_enabled
|
|
350
|
+
# => [:expiration, :safe_dump]
|
|
865
351
|
|
|
866
|
-
|
|
867
|
-
|
|
352
|
+
# Check feature definitions
|
|
353
|
+
Familia::Base.feature_definitions[:expiration]
|
|
354
|
+
# => #<data FeatureDefinition name=:expiration, depends_on=[], field_group=nil>
|
|
868
355
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
356
|
+
# Check if a specific feature is enabled
|
|
357
|
+
MyModel.features_enabled.include?(:expiration)
|
|
358
|
+
# => true
|
|
359
|
+
```
|
|
873
360
|
|
|
874
|
-
|
|
875
|
-
# Migration logic for 1.1 -> 2.0
|
|
876
|
-
end
|
|
361
|
+
### Common Issues
|
|
877
362
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
363
|
+
1. **Feature not found**: Ensure the feature module is loaded and registered
|
|
364
|
+
2. **Dependency errors**: Check that required features are enabled first
|
|
365
|
+
3. **Method conflicts**: Features that define the same methods will override each other
|
|
881
366
|
|
|
882
|
-
|
|
883
|
-
def feature_version
|
|
884
|
-
self.class.instance_variable_get(:@versioned_feature_version) || VERSION
|
|
885
|
-
end
|
|
886
|
-
end
|
|
367
|
+
## Migration Notes
|
|
887
368
|
|
|
888
|
-
|
|
889
|
-
end
|
|
890
|
-
```
|
|
369
|
+
The feature system is intentionally simple in the current implementation. More complex features like conflict resolution, versioning, and capability flags are not currently implemented but could be added in future versions if needed.
|
|
891
370
|
|
|
892
|
-
|
|
371
|
+
For now, feature developers should:
|
|
372
|
+
- Keep features focused and independent
|
|
373
|
+
- Use clear naming to avoid method conflicts
|
|
374
|
+
- Test features thoroughly in isolation
|
|
375
|
+
- Document any dependencies clearly
|