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
data/CLAUDE.md
CHANGED
|
@@ -53,7 +53,7 @@ Add changelog fragment with each user-facing or documented change (optional but
|
|
|
53
53
|
### Known Issues & Quirks
|
|
54
54
|
- **Reserved Keywords**: Cannot use `ttl`, `db`, `valkey`, `redis` as field names - use prefixed alternatives
|
|
55
55
|
- **Empty Identifiers**: Cause stack overflow in key generation - validate before operations
|
|
56
|
-
- **Connection
|
|
56
|
+
- **Lazy Initialization Races**: Connection chains and field collections use lazy initialization without synchronization (generally safe due to Ruby GIL, but not guaranteed)
|
|
57
57
|
|
|
58
58
|
### Debugging
|
|
59
59
|
- **Database command logging**: You can request real-time Database command monitoring from the user
|
|
@@ -105,30 +105,55 @@ class User < Familia::Horreum
|
|
|
105
105
|
end
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
**Good - use the `init` hook
|
|
108
|
+
**Good - use the `init` hook to apply defaults (use `||=` not `=`):**
|
|
109
109
|
```ruby
|
|
110
110
|
class User < Familia::Horreum
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
field :objid
|
|
112
|
+
field :email
|
|
113
|
+
|
|
114
|
+
# Called after Horreum sets fields from kwargs
|
|
115
|
+
# IMPORTANT: Use ||= to apply defaults, not = to override
|
|
116
|
+
def init
|
|
117
|
+
@objid ||= SecureRandom.uuid # Apply default only if not already set
|
|
118
|
+
_run_post_init_hooks # Additional setup logic
|
|
113
119
|
end
|
|
114
120
|
end
|
|
121
|
+
|
|
122
|
+
# This works correctly:
|
|
123
|
+
user = User.new(email: 'test@example.com')
|
|
124
|
+
user.objid # → generated UUID (applied by init)
|
|
125
|
+
user.email # → 'test@example.com' (set by Horreum from kwargs)
|
|
115
126
|
```
|
|
116
127
|
|
|
117
|
-
**
|
|
128
|
+
**Okay - if absolutely necessary, override and call super explicitly:**
|
|
118
129
|
```ruby
|
|
119
130
|
class User < Familia::Horreum
|
|
120
131
|
def initialize(email = nil, **kwargs)
|
|
121
|
-
super
|
|
122
|
-
@email
|
|
132
|
+
super # Initializes related fields here and also calls init
|
|
133
|
+
@email ||= generate_email if email.nil?
|
|
123
134
|
end
|
|
124
135
|
end
|
|
125
136
|
```
|
|
126
137
|
|
|
127
|
-
**Why this matters**: Familia's `initialize` method calls `initialize_relatives`
|
|
138
|
+
**Why this matters**: Familia's `initialize` method processes kwargs FIRST (setting fields), then calls `initialize_relatives` (setting up DataType objects), then calls your `init` hook. By the time `init` runs, kwargs have already been consumed and fields are set.
|
|
139
|
+
|
|
140
|
+
**The ||= Pattern Explained**:
|
|
141
|
+
```ruby
|
|
142
|
+
# WRONG - overwrites what Horreum already set
|
|
143
|
+
def init
|
|
144
|
+
@email = generate_email # Overwrites the correct value
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# RIGHT - applies default only if not already set
|
|
148
|
+
def init
|
|
149
|
+
@email ||= email # Preserves value Horreum set from kwargs
|
|
150
|
+
@email ||= 'default@example.com' # Apply fallback default if still nil
|
|
151
|
+
end
|
|
152
|
+
```
|
|
128
153
|
|
|
129
154
|
**When to use each approach:**
|
|
130
|
-
- **Use `init` hook
|
|
131
|
-
- **Use explicit `super`**:
|
|
155
|
+
- **Use `init` hook with `||=`** (preferred): Apply defaults, run validations, setup callbacks - any logic that should run after field initialization. Follows standard ORM lifecycle hook patterns.
|
|
156
|
+
- **Use explicit `super`**: Only when you need to intercept or transform arguments before Horreum processes them (rare).
|
|
132
157
|
|
|
133
158
|
**DataType Definition**: Use class methods to define keystore database-backed attributes:
|
|
134
159
|
```ruby
|
|
@@ -166,3 +191,30 @@ end
|
|
|
166
191
|
**Memory Efficiency**: Only non-nil values are stored in keystore database to optimize memory usage.
|
|
167
192
|
|
|
168
193
|
**Thread Safety**: Data types are frozen after instantiation to ensure immutability.
|
|
194
|
+
|
|
195
|
+
## Thread Safety Considerations
|
|
196
|
+
|
|
197
|
+
### Current Thread Safety Status (as of 2025-10-21)
|
|
198
|
+
|
|
199
|
+
Familia has **good thread safety** for standard multi-threaded environments:
|
|
200
|
+
|
|
201
|
+
### Testing Thread Safety
|
|
202
|
+
|
|
203
|
+
Thread safety tests are available in `try/thread_safety/`:
|
|
204
|
+
- **100% passing** (56/56 tests)
|
|
205
|
+
- **CyclicBarrier pattern** for maximum contention testing
|
|
206
|
+
- **Test execution**: ~300ms for full suite with 1,000+ concurrent operations
|
|
207
|
+
- **Production monitoring**: 10/10 monitoring tests passing
|
|
208
|
+
|
|
209
|
+
Run thread safety tests:
|
|
210
|
+
```bash
|
|
211
|
+
bundle exec try --agent try/thread_safety/
|
|
212
|
+
bundle exec try --agent try/unit/thread_safety_monitor_try.rb
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Best Practices for Thread-Safe Usage
|
|
216
|
+
|
|
217
|
+
1. **Configure Once at Startup**: Module-level configuration should be set before threads spawn
|
|
218
|
+
2. **Use Immutable DataTypes**: Leverage the fact that DataType instances are frozen
|
|
219
|
+
3. **Test Under Concurrency**: Use the patterns in `try/thread_safety/` to verify thread safety
|
|
220
|
+
4. **Enable Production Monitoring**: Use `Familia.start_monitoring!` to track contention in production
|
data/Gemfile
CHANGED
|
@@ -9,7 +9,7 @@ group :test do
|
|
|
9
9
|
gem 'ruby-prof'
|
|
10
10
|
gem 'stackprof'
|
|
11
11
|
gem 'timecop', require: false
|
|
12
|
-
gem 'tryouts', '~> 3.
|
|
12
|
+
gem 'tryouts', '~> 3.7.1', require: false
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
group :development, :test do
|
|
@@ -17,10 +17,10 @@ group :development, :test do
|
|
|
17
17
|
gem 'irb', '~> 1.15.2', require: false
|
|
18
18
|
gem 'redcarpet', require: false
|
|
19
19
|
gem 'reek', require: false
|
|
20
|
-
gem 'rubocop', require: false
|
|
20
|
+
gem 'rubocop', '~> 1.81.1', require: false
|
|
21
21
|
gem 'rubocop-performance', require: false
|
|
22
22
|
gem 'rubocop-thread_safety', require: false
|
|
23
|
-
gem '
|
|
23
|
+
gem 'ruby-lsp', require: false
|
|
24
24
|
gem 'yard', '~> 0.9', require: false
|
|
25
25
|
end
|
|
26
26
|
|
data/Gemfile.lock
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
familia (2.0.0.
|
|
4
|
+
familia (2.0.0.pre21)
|
|
5
5
|
benchmark (~> 0.4)
|
|
6
|
+
concurrent-ruby (~> 1.3)
|
|
6
7
|
connection_pool (~> 2.5)
|
|
7
8
|
csv (~> 3.3)
|
|
8
9
|
logger (~> 1.7)
|
|
@@ -15,14 +16,13 @@ GEM
|
|
|
15
16
|
remote: https://rubygems.org/
|
|
16
17
|
specs:
|
|
17
18
|
ast (2.4.3)
|
|
18
|
-
backport (1.2.0)
|
|
19
19
|
base64 (0.3.0)
|
|
20
20
|
benchmark (0.4.1)
|
|
21
21
|
bigdecimal (3.2.3)
|
|
22
22
|
concurrent-ruby (1.3.5)
|
|
23
23
|
connection_pool (2.5.3)
|
|
24
24
|
csv (3.3.5)
|
|
25
|
-
date (3.
|
|
25
|
+
date (3.5.0)
|
|
26
26
|
debug (1.11.0)
|
|
27
27
|
irb (~> 1.10)
|
|
28
28
|
reline (>= 0.3.8)
|
|
@@ -56,31 +56,19 @@ GEM
|
|
|
56
56
|
dry-inflector (~> 1.0)
|
|
57
57
|
dry-logic (~> 1.4)
|
|
58
58
|
zeitwerk (~> 2.6)
|
|
59
|
-
erb (5.
|
|
59
|
+
erb (5.1.3)
|
|
60
60
|
ffi (1.17.2)
|
|
61
61
|
ffi (1.17.2-arm64-darwin)
|
|
62
62
|
io-console (0.8.1)
|
|
63
|
-
irb (1.15.
|
|
63
|
+
irb (1.15.3)
|
|
64
64
|
pp (>= 0.6.0)
|
|
65
65
|
rdoc (>= 4.0.0)
|
|
66
66
|
reline (>= 0.4.2)
|
|
67
|
-
|
|
68
|
-
json (2.15.0)
|
|
69
|
-
kramdown (2.5.1)
|
|
70
|
-
rexml (>= 3.3.9)
|
|
71
|
-
kramdown-parser-gfm (1.1.0)
|
|
72
|
-
kramdown (~> 2.0)
|
|
67
|
+
json (2.15.1)
|
|
73
68
|
language_server-protocol (3.17.0.5)
|
|
74
69
|
lint_roller (1.1.0)
|
|
75
70
|
logger (1.7.0)
|
|
76
|
-
|
|
77
|
-
minitest (5.25.5)
|
|
78
|
-
nokogiri (1.18.10)
|
|
79
|
-
mini_portile2 (~> 2.8.2)
|
|
80
|
-
racc (~> 1.4)
|
|
81
|
-
nokogiri (1.18.10-arm64-darwin)
|
|
82
|
-
racc (~> 1.4)
|
|
83
|
-
observer (0.1.2)
|
|
71
|
+
minitest (5.26.0)
|
|
84
72
|
oj (3.16.11)
|
|
85
73
|
bigdecimal (>= 3.0)
|
|
86
74
|
ostruct (>= 0.2)
|
|
@@ -91,10 +79,10 @@ GEM
|
|
|
91
79
|
racc
|
|
92
80
|
pastel (0.8.0)
|
|
93
81
|
tty-color (~> 0.5)
|
|
94
|
-
pp (0.6.
|
|
82
|
+
pp (0.6.3)
|
|
95
83
|
prettyprint
|
|
96
84
|
prettyprint (0.2.0)
|
|
97
|
-
prism (1.
|
|
85
|
+
prism (1.6.0)
|
|
98
86
|
psych (5.2.6)
|
|
99
87
|
date
|
|
100
88
|
stringio
|
|
@@ -104,9 +92,10 @@ GEM
|
|
|
104
92
|
ffi (~> 1)
|
|
105
93
|
rbs (3.9.5)
|
|
106
94
|
logger
|
|
107
|
-
rdoc (6.
|
|
95
|
+
rdoc (6.15.1)
|
|
108
96
|
erb
|
|
109
97
|
psych (>= 4.0.0)
|
|
98
|
+
tsort
|
|
110
99
|
redcarpet (3.6.1)
|
|
111
100
|
redis (5.4.1)
|
|
112
101
|
redis-client (>= 0.22.0)
|
|
@@ -121,22 +110,20 @@ GEM
|
|
|
121
110
|
regexp_parser (2.11.3)
|
|
122
111
|
reline (0.6.2)
|
|
123
112
|
io-console (~> 0.5)
|
|
124
|
-
reverse_markdown (3.0.0)
|
|
125
|
-
nokogiri
|
|
126
113
|
rexml (3.4.1)
|
|
127
|
-
rspec (3.13.
|
|
114
|
+
rspec (3.13.2)
|
|
128
115
|
rspec-core (~> 3.13.0)
|
|
129
116
|
rspec-expectations (~> 3.13.0)
|
|
130
117
|
rspec-mocks (~> 3.13.0)
|
|
131
|
-
rspec-core (3.13.
|
|
118
|
+
rspec-core (3.13.6)
|
|
132
119
|
rspec-support (~> 3.13.0)
|
|
133
120
|
rspec-expectations (3.13.5)
|
|
134
121
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
135
122
|
rspec-support (~> 3.13.0)
|
|
136
|
-
rspec-mocks (3.13.
|
|
123
|
+
rspec-mocks (3.13.7)
|
|
137
124
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
138
125
|
rspec-support (~> 3.13.0)
|
|
139
|
-
rspec-support (3.13.
|
|
126
|
+
rspec-support (3.13.6)
|
|
140
127
|
rubocop (1.81.1)
|
|
141
128
|
json (~> 2.3)
|
|
142
129
|
language_server-protocol (~> 3.17.0.2)
|
|
@@ -159,44 +146,26 @@ GEM
|
|
|
159
146
|
lint_roller (~> 1.1)
|
|
160
147
|
rubocop (~> 1.72, >= 1.72.1)
|
|
161
148
|
rubocop-ast (>= 1.44.0, < 2.0)
|
|
149
|
+
ruby-lsp (0.26.1)
|
|
150
|
+
language_server-protocol (~> 3.17.0)
|
|
151
|
+
prism (>= 1.2, < 2.0)
|
|
152
|
+
rbs (>= 3, < 5)
|
|
162
153
|
ruby-prof (1.7.2)
|
|
163
154
|
base64
|
|
164
155
|
ruby-progressbar (1.13.0)
|
|
165
|
-
solargraph (0.57.0)
|
|
166
|
-
backport (~> 1.2)
|
|
167
|
-
benchmark (~> 0.4)
|
|
168
|
-
bundler (~> 2.0)
|
|
169
|
-
diff-lcs (~> 1.4)
|
|
170
|
-
jaro_winkler (~> 1.6, >= 1.6.1)
|
|
171
|
-
kramdown (~> 2.3)
|
|
172
|
-
kramdown-parser-gfm (~> 1.1)
|
|
173
|
-
logger (~> 1.6)
|
|
174
|
-
observer (~> 0.1)
|
|
175
|
-
ostruct (~> 0.6)
|
|
176
|
-
parser (~> 3.0)
|
|
177
|
-
prism (~> 1.4)
|
|
178
|
-
rbs (>= 3.6.1, <= 4.0.0.dev.4)
|
|
179
|
-
reverse_markdown (~> 3.0)
|
|
180
|
-
rubocop (~> 1.76)
|
|
181
|
-
thor (~> 1.0)
|
|
182
|
-
tilt (~> 2.0)
|
|
183
|
-
yard (~> 0.9, >= 0.9.24)
|
|
184
|
-
yard-activesupport-concern (~> 0.0)
|
|
185
|
-
yard-solargraph (~> 0.1)
|
|
186
156
|
stackprof (0.2.27)
|
|
187
157
|
stringio (3.1.7)
|
|
188
|
-
thor (1.4.0)
|
|
189
|
-
tilt (2.6.1)
|
|
190
158
|
timecop (0.9.10)
|
|
191
|
-
tryouts (3.
|
|
192
|
-
concurrent-ruby (~> 1.0)
|
|
159
|
+
tryouts (3.7.1)
|
|
160
|
+
concurrent-ruby (~> 1.0, < 2)
|
|
193
161
|
irb
|
|
194
162
|
minitest (~> 5.0)
|
|
195
163
|
pastel (~> 0.8)
|
|
196
164
|
prism (~> 1.0)
|
|
197
|
-
rspec (
|
|
165
|
+
rspec (>= 3.0, < 5.0)
|
|
198
166
|
tty-cursor (~> 0.7)
|
|
199
167
|
tty-screen (~> 0.8)
|
|
168
|
+
tsort (0.2.0)
|
|
200
169
|
tty-color (0.6.0)
|
|
201
170
|
tty-cursor (0.7.1)
|
|
202
171
|
tty-screen (0.8.2)
|
|
@@ -205,10 +174,6 @@ GEM
|
|
|
205
174
|
unicode-emoji (4.1.0)
|
|
206
175
|
uri-valkey (1.4.0)
|
|
207
176
|
yard (0.9.37)
|
|
208
|
-
yard-activesupport-concern (0.0.1)
|
|
209
|
-
yard (>= 0.8)
|
|
210
|
-
yard-solargraph (0.1.0)
|
|
211
|
-
yard (~> 0.9)
|
|
212
177
|
zeitwerk (2.7.3)
|
|
213
178
|
|
|
214
179
|
PLATFORMS
|
|
@@ -223,15 +188,15 @@ DEPENDENCIES
|
|
|
223
188
|
rbnacl (~> 7.1, >= 7.1.1)
|
|
224
189
|
redcarpet
|
|
225
190
|
reek
|
|
226
|
-
rubocop
|
|
191
|
+
rubocop (~> 1.81.1)
|
|
227
192
|
rubocop-performance
|
|
228
193
|
rubocop-thread_safety
|
|
194
|
+
ruby-lsp
|
|
229
195
|
ruby-prof
|
|
230
|
-
solargraph
|
|
231
196
|
stackprof
|
|
232
197
|
timecop
|
|
233
|
-
tryouts (~> 3.
|
|
198
|
+
tryouts (~> 3.7.1)
|
|
234
199
|
yard (~> 0.9)
|
|
235
200
|
|
|
236
201
|
BUNDLED WITH
|
|
237
|
-
2.
|
|
202
|
+
2.7.2
|
data/README.md
CHANGED
|
@@ -280,6 +280,7 @@ Flower.multiget("prose", "tulip", "daisy")
|
|
|
280
280
|
|
|
281
281
|
### Transactional Operations
|
|
282
282
|
|
|
283
|
+
**Horreum Model Transactions:**
|
|
283
284
|
```ruby
|
|
284
285
|
user.transaction do |conn|
|
|
285
286
|
conn.set("user:#{user.id}:status", "active")
|
|
@@ -287,6 +288,44 @@ user.transaction do |conn|
|
|
|
287
288
|
end
|
|
288
289
|
```
|
|
289
290
|
|
|
291
|
+
**DataType Transactions** (standalone or parent-owned):
|
|
292
|
+
```ruby
|
|
293
|
+
# Recommended: Use DataType methods for clean, automatic key handling
|
|
294
|
+
user.scores.transaction do
|
|
295
|
+
user.scores.add('level1', 100)
|
|
296
|
+
user.scores.add('level2', 200)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Standalone DataType transaction (e.g., session storage)
|
|
300
|
+
session_key = Familia::StringKey.new('session:abc123')
|
|
301
|
+
session_key.transaction do
|
|
302
|
+
session_key.set(session_data)
|
|
303
|
+
session_key.expire(3600) # Atomic: both succeed or both fail
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Advanced: Connection available for low-level Redis commands
|
|
307
|
+
user.scores.transaction do |conn|
|
|
308
|
+
conn.zadd(user.scores.dbkey, 100, 'level1')
|
|
309
|
+
conn.hset(user.profile.dbkey, 'status', 'active')
|
|
310
|
+
end
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Pipeline Operations** (batch commands for performance):
|
|
314
|
+
```ruby
|
|
315
|
+
# Recommended: Use DataType methods
|
|
316
|
+
leaderboard.pipelined do
|
|
317
|
+
leaderboard.add('player1', 500)
|
|
318
|
+
leaderboard.add('player2', 600)
|
|
319
|
+
leaderboard.size
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Advanced: Raw Redis commands for fine-grained control
|
|
323
|
+
leaderboard.pipelined do |conn|
|
|
324
|
+
conn.zadd(leaderboard.dbkey, 500, 'player1')
|
|
325
|
+
conn.zadd(leaderboard.dbkey, 600, 'player2')
|
|
326
|
+
end
|
|
327
|
+
```
|
|
328
|
+
|
|
290
329
|
### Advanced Patterns
|
|
291
330
|
|
|
292
331
|
**Time-based Expiration:**
|
data/bin/try
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application 'try' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
|
12
|
+
|
|
13
|
+
require "rubygems"
|
|
14
|
+
require "bundler/setup"
|
|
15
|
+
|
|
16
|
+
load Gem.bin_path("tryouts", "try")
|
data/bin/tryouts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application 'tryouts' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
|
12
|
+
|
|
13
|
+
require "rubygems"
|
|
14
|
+
require "bundler/setup"
|
|
15
|
+
|
|
16
|
+
load Gem.bin_path("tryouts", "tryouts")
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
.. Added
|
|
2
|
+
.. -----
|
|
3
|
+
|
|
4
|
+
.. Changed
|
|
5
|
+
.. -------
|
|
6
|
+
|
|
7
|
+
- **ExternalIdentifier Format Flexibility**: The `external_identifier` feature now supports customizable format templates via the `format` option. This allows you to control the entire format of generated external IDs, including the prefix, separator, and overall structure.
|
|
8
|
+
|
|
9
|
+
**Default format** (unchanged behavior):
|
|
10
|
+
|
|
11
|
+
.. code-block:: ruby
|
|
12
|
+
|
|
13
|
+
class User < Familia::Horreum
|
|
14
|
+
feature :external_identifier
|
|
15
|
+
end
|
|
16
|
+
user.extid # => "ext_abc123def456ghi789"
|
|
17
|
+
|
|
18
|
+
**Custom format with different prefix**:
|
|
19
|
+
|
|
20
|
+
.. code-block:: ruby
|
|
21
|
+
|
|
22
|
+
class Customer < Familia::Horreum
|
|
23
|
+
feature :external_identifier, format: 'cust_%{id}'
|
|
24
|
+
end
|
|
25
|
+
customer.extid # => "cust_abc123def456ghi789"
|
|
26
|
+
|
|
27
|
+
**Custom format with different separator**:
|
|
28
|
+
|
|
29
|
+
.. code-block:: ruby
|
|
30
|
+
|
|
31
|
+
class APIKey < Familia::Horreum
|
|
32
|
+
feature :external_identifier, format: 'api-%{id}'
|
|
33
|
+
end
|
|
34
|
+
key.extid # => "api-abc123def456ghi789"
|
|
35
|
+
|
|
36
|
+
**Custom format without traditional prefix**:
|
|
37
|
+
|
|
38
|
+
.. code-block:: ruby
|
|
39
|
+
|
|
40
|
+
class Resource < Familia::Horreum
|
|
41
|
+
feature :external_identifier, format: 'v2/%{id}'
|
|
42
|
+
end
|
|
43
|
+
resource.extid # => "v2/abc123def456ghi789"
|
|
44
|
+
|
|
45
|
+
The `format` option accepts a Ruby format string with the `%{id}` placeholder for the generated identifier. The default format is `'ext_%{id}'`. This provides complete flexibility for various ID formatting needs including different prefixes, separators (underscore, hyphen, slash), URL paths, or no prefix at all.
|
|
46
|
+
|
|
47
|
+
.. Deprecated
|
|
48
|
+
.. ----------
|
|
49
|
+
|
|
50
|
+
.. Removed
|
|
51
|
+
.. -------
|
|
52
|
+
|
|
53
|
+
.. Fixed
|
|
54
|
+
.. -----
|
|
55
|
+
|
|
56
|
+
.. Security
|
|
57
|
+
.. --------
|
|
58
|
+
|
|
59
|
+
.. Documentation
|
|
60
|
+
.. -------------
|
|
61
|
+
|
|
62
|
+
.. AI Assistance
|
|
63
|
+
.. -------------
|
|
64
|
+
|
|
65
|
+
- **Design Review**: Claude Code provided analysis of the current implementation and recommended several idiomatic Ruby approaches for format flexibility, ultimately suggesting the format template pattern using Ruby's native string formatting.
|
|
66
|
+
- **Implementation**: Claude Code implemented the format template feature including code changes, test cases, and documentation updates.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
.. A new scriv changelog fragment.
|
|
2
|
+
..
|
|
3
|
+
.. Uncomment the section that is right (remove the leading dots).
|
|
4
|
+
.. For top level release notes, leave all the headers commented out.
|
|
5
|
+
..
|
|
6
|
+
Added
|
|
7
|
+
-----
|
|
8
|
+
|
|
9
|
+
- Bidirectional reverse collection methods for ``participates_in`` with ``_instances`` suffix (e.g., ``user.project_team_instances``, ``user.project_team_ids``). Supports union behavior for multiple collections and custom naming via ``as:`` parameter. Closes #179.
|
|
10
|
+
|
|
11
|
+
.. Changed
|
|
12
|
+
.. -------
|
|
13
|
+
..
|
|
14
|
+
.. - A bullet item for the Changed category.
|
|
15
|
+
..
|
|
16
|
+
.. Deprecated
|
|
17
|
+
.. ----------
|
|
18
|
+
..
|
|
19
|
+
.. - A bullet item for the Deprecated category.
|
|
20
|
+
..
|
|
21
|
+
.. Removed
|
|
22
|
+
.. -------
|
|
23
|
+
..
|
|
24
|
+
.. - A bullet item for the Removed category.
|
|
25
|
+
..
|
|
26
|
+
.. Fixed
|
|
27
|
+
.. -----
|
|
28
|
+
..
|
|
29
|
+
.. - A bullet item for the Fixed category.
|
|
30
|
+
..
|
|
31
|
+
.. Security
|
|
32
|
+
.. --------
|
|
33
|
+
..
|
|
34
|
+
.. - A bullet item for the Security category.
|
|
35
|
+
..
|
|
36
|
+
.. Documentation
|
|
37
|
+
.. -------------
|
|
38
|
+
..
|
|
39
|
+
.. - A bullet item for the Documentation category.
|
|
40
|
+
..
|
|
41
|
+
AI Assistance
|
|
42
|
+
-------------
|
|
43
|
+
|
|
44
|
+
- Claude Opus 4 assisted with implementation of bidirectional participation relationships using ``_instances`` suffix pattern. Pivoted from initial dry-inflector pluralization approach based on feedback.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.. Fixed mutex race conditions in thread safety implementation
|
|
2
|
+
..
|
|
3
|
+
|
|
4
|
+
Fixed
|
|
5
|
+
-----
|
|
6
|
+
|
|
7
|
+
- Fixed critical race condition in mutex initialization for connection chain lazy loading. The mutex itself was being lazily initialized with ``||=``, which is not atomic and could result in multiple threads creating different mutex instances, defeating synchronization. Changed to eager initialization via ``Connection.included`` hook. (`lib/familia/horreum/connection.rb`)
|
|
8
|
+
|
|
9
|
+
- Fixed critical race condition in mutex initialization for logger lazy loading. Similar to connection chain issue, the logger mutex was lazily initialized with ``||=``. Changed to eager initialization at module definition time. (`lib/familia/logging.rb`)
|
|
10
|
+
|
|
11
|
+
- Fixed logger assignment atomicity issue where ``Familia.logger=`` set ``DatabaseLogger.logger`` outside the mutex synchronization block, potentially causing ``Familia.logger`` and ``DatabaseLogger.logger`` to be temporarily out of sync during concurrent access. Moved ``DatabaseLogger.logger`` assignment inside the synchronization block. (`lib/familia/logging.rb`)
|
|
12
|
+
|
|
13
|
+
- Added explicit return statement to ``Familia.logger`` method for robustness against future refactoring. (`lib/familia/logging.rb`)
|
|
14
|
+
|
|
15
|
+
AI Assistance
|
|
16
|
+
-------------
|
|
17
|
+
|
|
18
|
+
- Code review analysis to identify critical race conditions in mutex initialization
|
|
19
|
+
- Implementation of proper eager mutex initialization patterns
|
|
20
|
+
- Test file updates to reflect new initialization approach
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
.. Added
|
|
2
|
+
.. -----
|
|
3
|
+
|
|
4
|
+
.. Changed
|
|
5
|
+
.. -------
|
|
6
|
+
|
|
7
|
+
.. Deprecated
|
|
8
|
+
.. ----------
|
|
9
|
+
|
|
10
|
+
.. Removed
|
|
11
|
+
.. -------
|
|
12
|
+
|
|
13
|
+
.. Fixed
|
|
14
|
+
.. -----
|
|
15
|
+
|
|
16
|
+
- **Participation Relationships with Symbol/String Target Classes**: Fixed four bugs that occurred when calling `participates_in` with a Symbol or String target class instead of a Class object.
|
|
17
|
+
|
|
18
|
+
**Bug 1 - NoMethodError during relationship definition**:
|
|
19
|
+
|
|
20
|
+
The error was: ``private method 'member_by_config_name' called for module Familia``.
|
|
21
|
+
|
|
22
|
+
**Background**: The `participates_in` method supports flexible target class specifications:
|
|
23
|
+
|
|
24
|
+
.. code-block:: ruby
|
|
25
|
+
|
|
26
|
+
class Domain < Familia::Horreum
|
|
27
|
+
# All three forms should work:
|
|
28
|
+
participates_in Customer, :domains # Class object (always worked)
|
|
29
|
+
participates_in :Customer, :domains # Symbol (was broken)
|
|
30
|
+
participates_in 'Customer', :domains # String (was broken)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
**Root Cause**: The method had redundant class resolution code that directly called the private `Familia.member_by_config_name` method instead of using the public `Familia.resolve_class` API.
|
|
34
|
+
|
|
35
|
+
**Solution**: Removed the redundant resolution code and now uses the already-resolved class from the public API, simplifying the implementation and fixing the visibility issue.
|
|
36
|
+
|
|
37
|
+
**Bug 2 - NoMethodError in current_participations**:
|
|
38
|
+
|
|
39
|
+
When calling `current_participations` on objects that used Symbol/String target classes, it would fail with ``undefined method 'familia_name' for Symbol``.
|
|
40
|
+
|
|
41
|
+
**Root Cause**: The `current_participations` method was calling `.familia_name` on `config.target_class`, which stores the original Symbol/String value passed to `participates_in`.
|
|
42
|
+
|
|
43
|
+
**Solution**: Use the resolved `target_class` variable instead of the stored config value. The resolved class is already available from the `Familia.resolve_class` call earlier in the method.
|
|
44
|
+
|
|
45
|
+
**Bug 3 - NoMethodError in target_class_config_name**:
|
|
46
|
+
|
|
47
|
+
When calling `current_participations`, the internal `target_class_config_name` method would fail with ``undefined method 'config_name' for Symbol``.
|
|
48
|
+
|
|
49
|
+
**Root Cause**: The `ParticipationRelationship.target_class_config_name` method was calling `.config_name` directly on the stored `target_class` value, which could be a Symbol or String.
|
|
50
|
+
|
|
51
|
+
**Solution**: Resolve the target class before calling `config_name` by using `Familia.resolve_class(target_class)`, which handles all input types (Class, Symbol, String) correctly.
|
|
52
|
+
|
|
53
|
+
**Bug 4 - Confusing error when target class not loaded**:
|
|
54
|
+
|
|
55
|
+
When the target class hasn't been loaded yet (load order issue), the error was: ``undefined method 'method_defined?' for nil``.
|
|
56
|
+
|
|
57
|
+
**Root Cause**: When `Familia.resolve_class` returns `nil` (because the target class isn't registered in `Familia.members` yet), the code would pass `nil` to `TargetMethods::Builder.build`, which then failed with a confusing error message that didn't explain the actual problem.
|
|
58
|
+
|
|
59
|
+
**Solution**: Added explicit nil check after `resolve_class` with a detailed ArgumentError that:
|
|
60
|
+
|
|
61
|
+
- Clearly states which target class couldn't be resolved
|
|
62
|
+
- Lists the three most common causes (load order, typo, not inheriting from Horreum)
|
|
63
|
+
- Shows all currently registered Familia classes for debugging
|
|
64
|
+
- Provides a clear solution for fixing the load order
|
|
65
|
+
|
|
66
|
+
**Impact**: Projects using Symbol or String target classes in `participates_in` declarations will now work correctly throughout the entire lifecycle, including relationship definition, method generation, and participation queries. When there's a load order issue or typo, developers get a clear, actionable error message instead of a confusing nil error. This pattern is common when avoiding circular dependencies or when target classes are defined in different files.
|
|
67
|
+
|
|
68
|
+
.. Security
|
|
69
|
+
.. --------
|
|
70
|
+
|
|
71
|
+
.. Documentation
|
|
72
|
+
.. -------------
|
|
73
|
+
|
|
74
|
+
.. AI Assistance
|
|
75
|
+
.. -------------
|
|
76
|
+
|
|
77
|
+
- **Root Cause Analysis**: Claude Code analyzed the error stack trace from the implementing project and identified that a private method was being called as a public method from outside the Familia module.
|
|
78
|
+
- **Fix Implementation**: Claude Code identified redundant class resolution code and simplified it to use the already-resolved class from the public API.
|
|
79
|
+
- **Test Coverage**: Claude Code created comprehensive regression tests including:
|
|
80
|
+
|
|
81
|
+
- Feature-level tests for Symbol/String target class resolution in participation relationships
|
|
82
|
+
- Unit tests for the `Familia.resolve_class` public API
|
|
83
|
+
- Edge case coverage for case-insensitive resolution and modularized classes
|
|
84
|
+
|
|
85
|
+
- **Second Bug Discovery**: During test execution, Claude Code discovered a related bug in `current_participations` that was also failing with Symbol/String target classes. The test coverage revealed that `.familia_name` was being called on the unresolved config value instead of the resolved class instance.
|
|
86
|
+
|
|
87
|
+
- **Third Bug Discovery**: Further test execution revealed another Symbol/String bug in `target_class_config_name`, where `.config_name` was being called directly on Symbol/String values. This was fixed by resolving the class first using `Familia.resolve_class`.
|
|
88
|
+
|
|
89
|
+
- **Test Coverage Refinement**: Claude Code identified and removed unrealistic test cases (all-uppercase, all-lowercase class names) that don't occur in real Ruby code and don't work with the `snake_case` method's design. Updated tests to focus on realistic naming conventions: PascalCase and snake_case, with clear documentation explaining why certain formats aren't supported.
|
|
90
|
+
|
|
91
|
+
- **Fourth Bug Discovery**: After merging to main, the implementing project revealed a load order issue where `Familia.resolve_class` returned `nil`, causing a confusing "undefined method for nil" error. Claude Code added explicit error handling with a detailed, actionable error message that helps developers quickly identify and fix load order issues, typos, or inheritance problems.
|