familia 2.0.0.pre16 → 2.0.0.pre18
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/ci.yml +2 -2
- data/.github/workflows/{code-smellage.yml → code-smells.yml} +3 -63
- data/.gitignore +2 -0
- data/.rubocop.yml +6 -0
- data/CHANGELOG.rst +82 -0
- data/CLAUDE.md +47 -2
- data/Gemfile.lock +1 -1
- data/README.md +13 -0
- data/bin/irb +1 -1
- data/docs/archive/FAMILIA_TECHNICAL.md +1 -1
- data/docs/guides/core-field-system.md +48 -26
- data/docs/migrating/v2.0.0-pre18.md +58 -0
- data/docs/overview.md +2 -2
- data/docs/qodo-merge-compliance.md +96 -0
- data/docs/reference/api-technical.md +1 -1
- data/examples/encrypted_fields.rb +1 -1
- data/examples/safe_dump.rb +1 -1
- data/lib/familia/base.rb +6 -6
- data/lib/familia/connection/middleware.rb +58 -4
- data/lib/familia/connection.rb +1 -1
- data/lib/familia/data_type/class_methods.rb +63 -0
- data/lib/familia/data_type/connection.rb +83 -0
- data/lib/familia/data_type/{commands.rb → database_commands.rb} +2 -2
- data/lib/familia/data_type/serialization.rb +5 -5
- data/lib/familia/data_type/settings.rb +96 -0
- data/lib/familia/data_type/types/hashkey.rb +2 -1
- data/lib/familia/data_type/types/sorted_set.rb +113 -10
- data/lib/familia/data_type/types/stringkey.rb +0 -4
- data/lib/familia/data_type.rb +8 -195
- data/lib/familia/encryption/encrypted_data.rb +12 -2
- data/lib/familia/encryption/manager.rb +11 -4
- data/lib/familia/features/autoloader.rb +3 -1
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +11 -3
- data/lib/familia/features/encrypted_fields.rb +5 -2
- data/lib/familia/features/external_identifier.rb +49 -8
- data/lib/familia/features/object_identifier.rb +84 -12
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +9 -9
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +45 -26
- data/lib/familia/features/relationships/indexing.rb +7 -1
- data/lib/familia/features/relationships/participation/participant_methods.rb +6 -2
- data/lib/familia/features/safe_dump.rb +2 -3
- data/lib/familia/features/transient_fields.rb +7 -2
- data/lib/familia/features.rb +6 -1
- data/lib/familia/field_type.rb +0 -18
- data/lib/familia/horreum/{core/connection.rb → connection.rb} +21 -0
- data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +1 -1
- data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +102 -56
- data/lib/familia/horreum/{subclass/management.rb → management.rb} +18 -15
- data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +73 -170
- data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +22 -2
- data/lib/familia/horreum/serialization.rb +190 -0
- data/lib/familia/horreum.rb +39 -14
- data/lib/familia/identifier_extractor.rb +60 -0
- data/lib/familia/logging.rb +271 -112
- data/lib/familia/refinements.rb +0 -1
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +2 -2
- data/lib/middleware/{database_middleware.rb → database_logger.rb} +47 -14
- data/pr_agent.toml +31 -0
- data/pr_compliance_checklist.yaml +45 -0
- data/try/edge_cases/empty_identifiers_try.rb +1 -1
- data/try/edge_cases/hash_symbolization_try.rb +31 -31
- data/try/edge_cases/json_serialization_try.rb +2 -2
- data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +170 -0
- data/try/edge_cases/race_conditions_try.rb +1 -1
- data/try/edge_cases/reserved_keywords_try.rb +1 -1
- data/try/edge_cases/string_coercion_try.rb +1 -1
- data/try/edge_cases/ttl_side_effects_try.rb +1 -1
- data/try/features/encrypted_fields/aad_protection_try.rb +1 -1
- data/try/features/encrypted_fields/concealed_string_core_try.rb +1 -1
- data/try/features/encrypted_fields/context_isolation_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +1 -1
- data/try/features/encrypted_fields/error_conditions_try.rb +1 -1
- data/try/features/encrypted_fields/fresh_key_derivation_try.rb +1 -1
- data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
- data/try/features/encrypted_fields/key_rotation_try.rb +1 -1
- data/try/features/encrypted_fields/memory_security_try.rb +1 -1
- data/try/features/encrypted_fields/missing_current_key_version_try.rb +1 -1
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +1 -1
- data/try/features/encrypted_fields/thread_safety_try.rb +1 -1
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +1 -1
- data/try/{encryption → features/encryption}/config_persistence_try.rb +1 -1
- data/try/{encryption/encryption_core_try.rb → features/encryption/core_try.rb} +2 -2
- data/try/{encryption → features/encryption}/instance_variable_scope_try.rb +1 -1
- data/try/{encryption → features/encryption}/module_loading_try.rb +1 -1
- data/try/{encryption → features/encryption}/providers/aes_gcm_provider_try.rb +1 -1
- data/try/{encryption → features/encryption}/providers/xchacha20_poly1305_provider_try.rb +1 -1
- data/try/{encryption → features/encryption}/roundtrip_validation_try.rb +1 -1
- data/try/{encryption → features/encryption}/secure_memory_handling_try.rb +2 -2
- data/try/features/expiration/expiration_try.rb +1 -1
- data/try/features/external_identifier/external_identifier_try.rb +1 -1
- data/try/features/feature_dependencies_try.rb +1 -1
- data/try/features/feature_improvements_try.rb +1 -1
- data/try/features/field_groups_try.rb +244 -0
- data/try/features/object_identifier/object_identifier_integration_try.rb +1 -1
- data/try/features/object_identifier/object_identifier_try.rb +1 -1
- data/try/features/quantization/quantization_try.rb +1 -1
- data/try/features/real_feature_integration_try.rb +17 -14
- data/try/features/relationships/indexing_commands_verification_try.rb +8 -3
- data/try/features/relationships/indexing_try.rb +16 -1
- data/try/features/relationships/participation_commands_verification_spec.rb +1 -1
- data/try/features/relationships/participation_commands_verification_try.rb +4 -4
- data/try/features/relationships/participation_performance_improvements_try.rb +1 -1
- data/try/features/relationships/participation_reverse_index_try.rb +1 -1
- data/try/features/relationships/relationships_api_changes_try.rb +1 -1
- data/try/features/relationships/relationships_edge_cases_try.rb +3 -3
- data/try/features/relationships/relationships_performance_minimal_try.rb +1 -1
- data/try/features/relationships/relationships_performance_simple_try.rb +1 -1
- data/try/features/relationships/relationships_performance_try.rb +1 -1
- data/try/features/relationships/relationships_performance_working_try.rb +1 -1
- data/try/features/relationships/relationships_try.rb +1 -1
- data/try/features/safe_dump/safe_dump_advanced_try.rb +1 -1
- data/try/features/safe_dump/safe_dump_try.rb +1 -1
- data/try/features/transient_fields/redacted_string_try.rb +1 -1
- data/try/features/transient_fields/refresh_reset_try.rb +3 -1
- data/try/features/transient_fields/single_use_redacted_string_try.rb +1 -1
- data/try/features/transient_fields/transient_fields_core_try.rb +1 -1
- data/try/features/transient_fields/transient_fields_integration_try.rb +1 -1
- data/try/{connection → integration/connection}/fiber_context_preservation_try.rb +1 -1
- data/try/{connection → integration/connection}/handler_constraints_try.rb +1 -1
- data/try/{core → integration/connection}/isolated_dbclient_try.rb +3 -3
- data/try/integration/connection/middleware_reconnect_try.rb +87 -0
- data/try/{connection → integration/connection}/operation_mode_guards_try.rb +1 -1
- data/try/{connection → integration/connection}/pipeline_fallback_integration_try.rb +1 -1
- data/try/{core → integration/connection}/pools_try.rb +1 -1
- data/try/{connection → integration/connection}/responsibility_chain_tracking_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_fallback_integration_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_mode_permissive_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_mode_strict_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_mode_warn_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_modes_try.rb +1 -1
- data/try/{core → integration}/conventional_inheritance_try.rb +1 -1
- data/try/{core → integration}/create_method_try.rb +1 -1
- data/try/integration/cross_component_try.rb +1 -1
- data/try/{core → integration}/database_consistency_try.rb +12 -8
- data/try/{core → integration}/familia_extended_try.rb +1 -1
- data/try/{core → integration}/familia_members_methods_try.rb +1 -1
- data/try/{models → integration/models}/customer_safe_dump_try.rb +1 -1
- data/try/{models → integration/models}/customer_try.rb +6 -6
- data/try/{models → integration/models}/datatype_base_try.rb +1 -1
- data/try/{models → integration/models}/familia_object_try.rb +1 -1
- data/try/{core → integration}/persistence_operations_try.rb +1 -1
- data/try/integration/relationships_persistence_round_trip_try.rb +441 -0
- data/try/{configuration → integration}/scenarios_try.rb +2 -2
- data/try/{core → integration}/secure_identifier_try.rb +1 -1
- data/try/{core → integration}/verifiable_identifier_try.rb +1 -1
- data/try/performance/benchmarks_try.rb +2 -2
- data/try/support/benchmarks/deserialization_benchmark.rb +180 -0
- data/try/support/benchmarks/deserialization_correctness_test.rb +237 -0
- data/try/{helpers → support/helpers}/test_helpers.rb +15 -7
- data/try/{memory → support/memory}/memory_docker_ruby_dump.sh +1 -1
- data/try/{core → unit/core}/autoloader_try.rb +1 -1
- data/try/{core → unit/core}/base_enhancements_try.rb +1 -9
- data/try/{core → unit/core}/connection_try.rb +5 -5
- data/try/{core → unit/core}/errors_try.rb +4 -4
- data/try/{core → unit/core}/extensions_try.rb +1 -1
- data/try/unit/core/familia_logger_try.rb +110 -0
- data/try/{core → unit/core}/familia_try.rb +2 -2
- data/try/{core → unit/core}/middleware_try.rb +41 -1
- data/try/{core → unit/core}/settings_try.rb +1 -1
- data/try/{core → unit/core}/time_utils_try.rb +1 -1
- data/try/{core → unit/core}/tools_try.rb +3 -3
- data/try/{core → unit/core}/utils_try.rb +17 -14
- data/try/{data_types → unit/data_types}/boolean_try.rb +1 -1
- data/try/{data_types → unit/data_types}/counter_try.rb +1 -1
- data/try/{data_types → unit/data_types}/datatype_base_try.rb +1 -1
- data/try/{data_types → unit/data_types}/hash_try.rb +1 -1
- data/try/{data_types → unit/data_types}/list_try.rb +1 -1
- data/try/{data_types → unit/data_types}/lock_try.rb +1 -1
- data/try/{data_types → unit/data_types}/sorted_set_try.rb +1 -1
- data/try/unit/data_types/sorted_set_zadd_options_try.rb +625 -0
- data/try/{data_types → unit/data_types}/string_try.rb +1 -1
- data/try/{data_types → unit/data_types}/unsortedset_try.rb +1 -1
- data/try/unit/horreum/auto_indexing_on_save_try.rb +212 -0
- data/try/{horreum → unit/horreum}/base_try.rb +3 -3
- data/try/{horreum → unit/horreum}/class_methods_try.rb +1 -1
- data/try/{horreum → unit/horreum}/commands_try.rb +3 -1
- data/try/unit/horreum/defensive_initialization_try.rb +86 -0
- data/try/{horreum → unit/horreum}/destroy_related_fields_cleanup_try.rb +3 -1
- data/try/{horreum → unit/horreum}/enhanced_conflict_handling_try.rb +1 -1
- data/try/{horreum → unit/horreum}/field_categories_try.rb +27 -18
- data/try/{horreum → unit/horreum}/field_definition_try.rb +1 -1
- data/try/{horreum → unit/horreum}/initialization_try.rb +2 -2
- data/try/unit/horreum/json_type_preservation_try.rb +248 -0
- data/try/{horreum → unit/horreum}/relations_try.rb +1 -1
- data/try/{horreum → unit/horreum}/serialization_persistent_fields_try.rb +24 -18
- data/try/{horreum → unit/horreum}/serialization_try.rb +4 -4
- data/try/{horreum → unit/horreum}/settings_try.rb +3 -1
- data/try/{refinements → unit/refinements}/dear_json_array_methods_try.rb +1 -1
- data/try/{refinements → unit/refinements}/dear_json_hash_methods_try.rb +1 -1
- data/try/{refinements → unit/refinements}/time_literals_numeric_methods_try.rb +1 -1
- data/try/{refinements → unit/refinements}/time_literals_string_methods_try.rb +1 -1
- data/try/valkey.conf +26 -0
- metadata +149 -132
- data/lib/familia/distinguisher.rb +0 -85
- data/lib/familia/horreum/core.rb +0 -21
- data/lib/familia/refinements/logger_trace.rb +0 -60
- data/try/refinements/logger_trace_methods_try.rb +0 -44
- /data/lib/familia/horreum/{shared/settings.rb → settings.rb} +0 -0
- /data/lib/familia/horreum/{core/utils.rb → utils.rb} +0 -0
- /data/try/{debugging → support/debugging}/README.md +0 -0
- /data/try/{debugging → support/debugging}/cache_behavior_tracer.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_aad_process.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_concealed_internal.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_concealed_reveal.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_context_aad.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_context_simple.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_cross_context.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_database_load.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_encrypted_json_check.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_encrypted_json_step_by_step.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_exists_lifecycle.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_field_decrypt.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_fresh_cross_context.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_load_path.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_method_definition.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_method_resolution.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_minimal.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_provider.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_secure_behavior.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_string_class.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_test.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_test_design.rb +0 -0
- /data/try/{debugging → support/debugging}/encryption_method_tracer.rb +0 -0
- /data/try/{debugging → support/debugging}/provider_diagnostics.rb +0 -0
- /data/try/{helpers → support/helpers}/test_cleanup.rb +0 -0
- /data/try/{memory → support/memory}/memory_basic_test.rb +0 -0
- /data/try/{memory → support/memory}/memory_detailed_test.rb +0 -0
- /data/try/{memory → support/memory}/memory_search_for_string.rb +0 -0
- /data/try/{memory → support/memory}/test_actual_redactedstring_protection.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v1_context_proxy.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v2_connection_switching.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v3_connection_pool.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v4.rb +0 -0
- /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v2_connection_switching_helpers.rb +0 -0
- /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/README.md +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/configurable_stress_test.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_metrics.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_stress_test.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_threading_models.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/visualize_stress_results.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/pool_siege.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/run_stress_tests.rb +0 -0
@@ -0,0 +1,212 @@
|
|
1
|
+
# try/horreum/auto_indexing_on_save_try.rb
|
2
|
+
|
3
|
+
#
|
4
|
+
# Auto-indexing on save functionality tests
|
5
|
+
# Tests automatic index population when Familia::Horreum objects are saved
|
6
|
+
#
|
7
|
+
|
8
|
+
require_relative '../../support/helpers/test_helpers'
|
9
|
+
|
10
|
+
# Test classes for auto-indexing functionality
|
11
|
+
class ::AutoIndexUser < Familia::Horreum
|
12
|
+
feature :relationships
|
13
|
+
|
14
|
+
identifier_field :user_id
|
15
|
+
field :user_id
|
16
|
+
field :email
|
17
|
+
field :username
|
18
|
+
field :department
|
19
|
+
|
20
|
+
# Class-level unique indexes (should auto-populate on save)
|
21
|
+
unique_index :email, :email_index
|
22
|
+
unique_index :username, :username_index
|
23
|
+
end
|
24
|
+
|
25
|
+
class ::AutoIndexCompany < Familia::Horreum
|
26
|
+
feature :relationships
|
27
|
+
|
28
|
+
identifier_field :company_id
|
29
|
+
field :company_id
|
30
|
+
field :name
|
31
|
+
end
|
32
|
+
|
33
|
+
class ::AutoIndexEmployee < Familia::Horreum
|
34
|
+
feature :relationships
|
35
|
+
|
36
|
+
identifier_field :emp_id
|
37
|
+
field :emp_id
|
38
|
+
field :badge_number
|
39
|
+
field :department
|
40
|
+
|
41
|
+
# Instance-scoped indexes (should NOT auto-populate - require parent context)
|
42
|
+
unique_index :badge_number, :badge_index, within: AutoIndexCompany
|
43
|
+
multi_index :department, :dept_index, within: AutoIndexCompany
|
44
|
+
end
|
45
|
+
|
46
|
+
# Setup
|
47
|
+
@user_id = "user_#{rand(1000000)}"
|
48
|
+
@user = AutoIndexUser.new(user_id: @user_id, email: 'test@example.com', username: 'testuser', department: 'engineering')
|
49
|
+
|
50
|
+
@company_id = "comp_#{rand(1000000)}"
|
51
|
+
@company = AutoIndexCompany.new(company_id: @company_id, name: 'Test Corp')
|
52
|
+
|
53
|
+
@emp_id = "emp_#{rand(1000000)}"
|
54
|
+
@employee = AutoIndexEmployee.new(emp_id: @emp_id, badge_number: 'BADGE123', department: 'sales')
|
55
|
+
|
56
|
+
# =============================================
|
57
|
+
# 1. Class-Level Unique Index Auto-Population
|
58
|
+
# =============================================
|
59
|
+
|
60
|
+
## Unique index is empty before save
|
61
|
+
AutoIndexUser.email_index.has_key?('test@example.com')
|
62
|
+
#=> false
|
63
|
+
|
64
|
+
## Save automatically populates unique index
|
65
|
+
@user.save
|
66
|
+
AutoIndexUser.email_index.has_key?('test@example.com')
|
67
|
+
#=> true
|
68
|
+
|
69
|
+
## Auto-populated index maps to correct identifier
|
70
|
+
AutoIndexUser.email_index.get('test@example.com')
|
71
|
+
#=> @user_id
|
72
|
+
|
73
|
+
## Finder method works after auto-indexing
|
74
|
+
found = AutoIndexUser.find_by_email('test@example.com')
|
75
|
+
found&.user_id
|
76
|
+
#=> @user_id
|
77
|
+
|
78
|
+
## Multiple unique indexes auto-populate on same save
|
79
|
+
AutoIndexUser.username_index.get('testuser')
|
80
|
+
#=> @user_id
|
81
|
+
|
82
|
+
## Subsequent saves maintain index (idempotent)
|
83
|
+
@user.save
|
84
|
+
AutoIndexUser.email_index.get('test@example.com')
|
85
|
+
#=> @user_id
|
86
|
+
|
87
|
+
## Changing indexed field and saving adds new entry (old entry remains unless manually removed)
|
88
|
+
# Note: Auto-indexing is idempotent addition only - updates require manual update_in_class_* calls
|
89
|
+
@user.email = 'newemail@example.com'
|
90
|
+
@user.save
|
91
|
+
# New email is indexed, but old email remains (expected behavior - use update_in_class_* for proper updates)
|
92
|
+
[AutoIndexUser.email_index.has_key?('test@example.com'), AutoIndexUser.email_index.get('newemail@example.com') == @user_id]
|
93
|
+
#=> [true, true]
|
94
|
+
|
95
|
+
# =============================================
|
96
|
+
# 2. Instance-Scoped Indexes (Manual Only)
|
97
|
+
# =============================================
|
98
|
+
|
99
|
+
## Instance-scoped indexes do NOT auto-populate on save
|
100
|
+
@employee.save
|
101
|
+
@company.badge_index.has_key?('BADGE123')
|
102
|
+
#=> false
|
103
|
+
|
104
|
+
## Instance-scoped indexes remain manual (require parent context)
|
105
|
+
@employee.add_to_auto_index_company_badge_index(@company)
|
106
|
+
@company.badge_index.has_key?('BADGE123')
|
107
|
+
#=> true
|
108
|
+
|
109
|
+
# =============================================
|
110
|
+
# 3. Edge Cases and Error Handling
|
111
|
+
# =============================================
|
112
|
+
|
113
|
+
## Nil field values handled gracefully
|
114
|
+
@user_nil_id = "user_nil_#{rand(1000000)}"
|
115
|
+
@user_nil = AutoIndexUser.new(user_id: @user_nil_id, email: nil, username: nil, department: nil)
|
116
|
+
@user_nil.save
|
117
|
+
AutoIndexUser.email_index.has_key?('')
|
118
|
+
#=> false
|
119
|
+
|
120
|
+
## Empty string field values handled gracefully
|
121
|
+
@user_empty_id = "user_empty_#{rand(1000000)}"
|
122
|
+
@user_empty = AutoIndexUser.new(user_id: @user_empty_id, email: '', username: '', department: '')
|
123
|
+
@user_empty.save
|
124
|
+
# Empty strings are indexed (they're valid string values, just empty)
|
125
|
+
AutoIndexUser.email_index.has_key?('')
|
126
|
+
#=> true
|
127
|
+
|
128
|
+
## Auto-indexing works with create method
|
129
|
+
@user2_id = "user_#{rand(1000000)}"
|
130
|
+
@user2 = AutoIndexUser.create(user_id: @user2_id, email: 'create@example.com', username: 'createuser', department: 'marketing')
|
131
|
+
AutoIndexUser.find_by_email('create@example.com')&.user_id
|
132
|
+
#=> @user2_id
|
133
|
+
|
134
|
+
## Auto-indexing idempotent with multiple saves
|
135
|
+
@user2.save
|
136
|
+
@user2.save
|
137
|
+
@user2.save
|
138
|
+
AutoIndexUser.email_index.get('create@example.com')
|
139
|
+
#=> @user2_id
|
140
|
+
|
141
|
+
## Field update followed by save adds new entry (use update_in_class_* for proper updates)
|
142
|
+
old_email = @user2.email
|
143
|
+
@user2.email = 'updated@example.com'
|
144
|
+
@user2.save
|
145
|
+
# Both old and new emails are indexed (auto-indexing doesn't remove old values)
|
146
|
+
# For proper updates that remove old values, use: @user2.update_in_class_email_index(old_email)
|
147
|
+
[AutoIndexUser.email_index.has_key?(old_email), AutoIndexUser.email_index.get('updated@example.com') == @user2_id]
|
148
|
+
#=> [true, true]
|
149
|
+
|
150
|
+
# =============================================
|
151
|
+
# 4. Integration with Other Features
|
152
|
+
# =============================================
|
153
|
+
|
154
|
+
## Auto-indexing works with transient fields
|
155
|
+
class ::AutoIndexWithTransient < Familia::Horreum
|
156
|
+
feature :transient_fields
|
157
|
+
feature :relationships
|
158
|
+
|
159
|
+
identifier_field :id
|
160
|
+
field :id
|
161
|
+
field :email
|
162
|
+
transient_field :temp_value
|
163
|
+
|
164
|
+
unique_index :email, :email_index
|
165
|
+
end
|
166
|
+
|
167
|
+
@transient_id = "trans_#{rand(1000000)}"
|
168
|
+
@transient_obj = AutoIndexWithTransient.new(id: @transient_id, email: 'transient@example.com', temp_value: 'ignored')
|
169
|
+
@transient_obj.save
|
170
|
+
AutoIndexWithTransient.find_by_email('transient@example.com')&.id
|
171
|
+
#=> @transient_id
|
172
|
+
|
173
|
+
## Auto-indexing works regardless of other features
|
174
|
+
# Just verify that the feature system doesn't interfere
|
175
|
+
@transient_obj.class.respond_to?(:indexing_relationships)
|
176
|
+
#=> true
|
177
|
+
|
178
|
+
# =============================================
|
179
|
+
# 5. Performance and Behavior Verification
|
180
|
+
# =============================================
|
181
|
+
|
182
|
+
## Auto-indexing has negligible overhead (no existence checks)
|
183
|
+
# This test verifies the design: we use idempotent commands (HSET, SADD)
|
184
|
+
# rather than checking if the index exists before updating
|
185
|
+
@user4_id = "user_#{rand(1000000)}"
|
186
|
+
@user4 = AutoIndexUser.new(user_id: @user4_id, email: 'perf@example.com', username: 'perfuser', department: 'ops')
|
187
|
+
|
188
|
+
# Save multiple times - all should succeed with same result
|
189
|
+
@user4.save
|
190
|
+
@user4.save
|
191
|
+
@user4.save
|
192
|
+
|
193
|
+
AutoIndexUser.email_index.get('perf@example.com')
|
194
|
+
#=> @user4_id
|
195
|
+
|
196
|
+
## Auto-indexing only processes class-level indexes
|
197
|
+
# Verify no errors when instance-scoped indexes present
|
198
|
+
@employee2_id = "emp_#{rand(1000000)}"
|
199
|
+
@employee2 = AutoIndexEmployee.new(emp_id: @employee2_id, badge_number: 'BADGE456', department: 'engineering')
|
200
|
+
@employee2.save # Should not error, just skip instance-scoped indexes
|
201
|
+
@employee2.emp_id
|
202
|
+
#=> @employee2_id
|
203
|
+
|
204
|
+
# Teardown - clean up test objects
|
205
|
+
[@user, @user2, @user4, @user_nil, @user_empty, @company, @employee, @employee2, @transient_obj].each do |obj|
|
206
|
+
obj.destroy! if obj.respond_to?(:destroy!) && obj.respond_to?(:exists?) && obj.exists?
|
207
|
+
end
|
208
|
+
|
209
|
+
# Clean up class-level indexes
|
210
|
+
[AutoIndexUser.email_index, AutoIndexUser.username_index].each do |index|
|
211
|
+
index.delete! if index.respond_to?(:delete!) && index.respond_to?(:exists?) && index.exists?
|
212
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# try/horreum/base_try.rb
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative '../../support/helpers/test_helpers'
|
4
4
|
|
5
5
|
Familia.debug = false
|
6
6
|
|
@@ -203,11 +203,11 @@ end
|
|
203
203
|
@aliased.display_size! 100
|
204
204
|
#=> true
|
205
205
|
|
206
|
-
## Aliased field refresh works correctly
|
206
|
+
## Aliased field refresh works correctly (type preserved)
|
207
207
|
@aliased.width = 50 # unsaved change
|
208
208
|
@aliased.refresh!
|
209
209
|
@aliased.width
|
210
|
-
#=>
|
210
|
+
#=> 100
|
211
211
|
|
212
212
|
## Fast method with custom name
|
213
213
|
class CustomFastMethodTest < Familia::Horreum
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# try/horreum/defensive_initialization_try.rb
|
2
|
+
|
3
|
+
require_relative '../../support/helpers/test_helpers'
|
4
|
+
|
5
|
+
# Test defensive initialization behavior
|
6
|
+
class User < Familia::Horreum
|
7
|
+
field :email
|
8
|
+
list :sessions
|
9
|
+
zset :metrics
|
10
|
+
|
11
|
+
def initialize(email = nil)
|
12
|
+
# This is the common mistake - overriding initialize without calling super
|
13
|
+
@email = email
|
14
|
+
# Missing: super() or initialize_relatives
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class SafeUser < Familia::Horreum
|
19
|
+
field :email
|
20
|
+
list :sessions
|
21
|
+
zset :metrics
|
22
|
+
|
23
|
+
def init
|
24
|
+
# This is the correct way - using the init hook
|
25
|
+
# Fields are already set by initialize, no need to override
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Setup instances for testing
|
30
|
+
@user = User.new("test@example.com")
|
31
|
+
@safe_user = SafeUser.new
|
32
|
+
@safe_user.email = "safe@example.com"
|
33
|
+
|
34
|
+
## Test that accessing relationships after bad initialize triggers lazy initialization
|
35
|
+
@user.email
|
36
|
+
#=> "test@example.com"
|
37
|
+
|
38
|
+
## Test that sessions works with lazy initialization
|
39
|
+
@user.sessions.class
|
40
|
+
#=> Familia::ListKey
|
41
|
+
|
42
|
+
## Test that metrics also works with lazy initialization
|
43
|
+
@user.metrics.class
|
44
|
+
#=> Familia::SortedSet
|
45
|
+
|
46
|
+
## Test that safe user works normally
|
47
|
+
@safe_user.email
|
48
|
+
#=> "safe@example.com"
|
49
|
+
|
50
|
+
## Test that safe user sessions work
|
51
|
+
@safe_user.sessions.class
|
52
|
+
#=> Familia::ListKey
|
53
|
+
|
54
|
+
## Test that relatives_initialized flag prevents double initialization
|
55
|
+
@user.singleton_class.instance_variable_get(:@relatives_initialized)
|
56
|
+
#=> true
|
57
|
+
|
58
|
+
## Test that manual initialize_relatives call is no-op
|
59
|
+
@user.initialize_relatives
|
60
|
+
@user.sessions.class
|
61
|
+
#=> Familia::ListKey
|
62
|
+
|
63
|
+
## Test that the original problem is now fixed - bad override still works
|
64
|
+
class BadUser < Familia::Horreum
|
65
|
+
field :email
|
66
|
+
list :sessions
|
67
|
+
|
68
|
+
def initialize(email)
|
69
|
+
# Bad: overriding initialize without calling super
|
70
|
+
@email = email
|
71
|
+
# Missing: super() or initialize_relatives
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
@bad_user = BadUser.new("bad@example.com")
|
76
|
+
@bad_user.email
|
77
|
+
#=> "bad@example.com"
|
78
|
+
|
79
|
+
## Test that relationships work despite bad initialize (lazy initialization kicks in)
|
80
|
+
@bad_user.sessions.class
|
81
|
+
#=> Familia::ListKey
|
82
|
+
|
83
|
+
## Test that the bad user can actually use the relationships
|
84
|
+
@bad_user.sessions.add("session_123")
|
85
|
+
@bad_user.sessions.size > 0
|
86
|
+
#=> true
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# try/horreum/destroy_related_fields_cleanup_try.rb
|
2
|
+
|
1
3
|
# Horreum destroy! Related Fields Cleanup Tryouts
|
2
4
|
#
|
3
5
|
# Tests that when a Horreum instance is destroyed, all its related fields
|
@@ -7,7 +9,7 @@
|
|
7
9
|
# This addresses the bug where destroy! only deleted the main object key
|
8
10
|
# but left related field keys in the database.
|
9
11
|
|
10
|
-
require_relative '
|
12
|
+
require_relative '../../support/helpers/test_helpers'
|
11
13
|
|
12
14
|
MANY_FIELD_MULTIPLIER = 10
|
13
15
|
|
@@ -1,44 +1,53 @@
|
|
1
1
|
# try/horreum/field_categories_try.rb
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative '../../support/helpers/test_helpers'
|
4
4
|
|
5
5
|
Familia.debug = false
|
6
6
|
|
7
|
-
# Define test class with various field
|
7
|
+
# Define test class with various field types
|
8
8
|
class FieldCategoryTest < Familia::Horreum
|
9
|
+
feature :encrypted_fields
|
10
|
+
feature :transient_fields
|
11
|
+
|
9
12
|
identifier_field :id
|
10
13
|
field :id
|
11
|
-
field :name #
|
12
|
-
|
13
|
-
|
14
|
-
field :description
|
15
|
-
field :settings
|
14
|
+
field :name # regular field
|
15
|
+
encrypted_field :email # encrypted field
|
16
|
+
transient_field :tryouts_cache_data # transient field
|
17
|
+
field :description # regular persistent field
|
18
|
+
field :settings # regular field
|
16
19
|
end
|
17
20
|
|
18
21
|
# Test class with multiple transient fields
|
19
22
|
class MultiTransientTest < Familia::Horreum
|
23
|
+
feature :transient_fields
|
24
|
+
|
20
25
|
identifier_field :id
|
21
26
|
field :id
|
22
27
|
field :permanent_data
|
23
|
-
|
24
|
-
|
25
|
-
|
28
|
+
transient_field :temp1
|
29
|
+
transient_field :temp2
|
30
|
+
transient_field :temp3
|
26
31
|
end
|
27
32
|
|
28
|
-
# Field
|
33
|
+
# Field types work with field aliasing
|
29
34
|
class AliasedCategoryTest < Familia::Horreum
|
35
|
+
feature :transient_fields
|
36
|
+
|
30
37
|
identifier_field :id
|
31
38
|
field :id
|
32
|
-
|
33
|
-
field :internal_perm, as: :perm
|
39
|
+
transient_field :internal_temp, as: :temp
|
40
|
+
field :internal_perm, as: :perm
|
34
41
|
end
|
35
42
|
|
36
43
|
# Test edge case with all transient fields
|
37
44
|
class AllTransientTest < Familia::Horreum
|
45
|
+
feature :transient_fields
|
46
|
+
|
38
47
|
identifier_field :id
|
39
48
|
field :id
|
40
|
-
|
41
|
-
|
49
|
+
transient_field :temp1
|
50
|
+
transient_field :temp2
|
42
51
|
end
|
43
52
|
|
44
53
|
## Field types are stored correctly
|
@@ -58,11 +67,11 @@ FieldCategoryTest.field_types[:email].category
|
|
58
67
|
FieldCategoryTest.field_types[:tryouts_cache_data].category
|
59
68
|
#=> :transient
|
60
69
|
|
61
|
-
##
|
70
|
+
## Regular fields have :field category
|
62
71
|
FieldCategoryTest.field_types[:description].category
|
63
|
-
#=> :
|
72
|
+
#=> :field
|
64
73
|
|
65
|
-
##
|
74
|
+
## Regular fields default to :field category
|
66
75
|
FieldCategoryTest.field_types[:settings].category
|
67
76
|
#=> :field
|
68
77
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# try/horreum/initialization_try.rb
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative '../../support/helpers/test_helpers'
|
4
4
|
|
5
5
|
Familia.debug = false
|
6
6
|
|
@@ -97,7 +97,7 @@ Familia.debug = false
|
|
97
97
|
@complex.save
|
98
98
|
@complex.refresh!
|
99
99
|
[@complex.custid, @complex.name, @complex.role, @complex.verified]
|
100
|
-
#=> ["complex@test.com", "Complex User", "admin",
|
100
|
+
#=> ["complex@test.com", "Complex User", "admin", true]
|
101
101
|
|
102
102
|
## Clean up saved test objects
|
103
103
|
[@customer6, @complex].map(&:delete!)
|