familia 2.0.0.pre15 → 2.0.0.pre16
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/code-quality.yml +138 -0
- data/.github/workflows/code-smellage.yml +145 -0
- data/.github/workflows/docs.yml +31 -8
- data/.gitignore +1 -1
- data/.pre-commit-config.yaml +7 -1
- data/.reek.yml +98 -0
- data/.rubocop.yml +48 -10
- data/.talismanrc +9 -0
- data/.yardopts +18 -13
- data/CHANGELOG.rst +64 -4
- data/CLAUDE.md +1 -1
- data/Gemfile +6 -5
- data/Gemfile.lock +99 -23
- data/LICENSE.txt +1 -1
- data/README.md +285 -85
- data/changelog.d/README.md +2 -2
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
- data/docs/archive/FAMILIA_TECHNICAL.md +41 -41
- data/docs/archive/FAMILIA_UPDATE.md +3 -3
- data/docs/archive/README.md +3 -2
- data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
- data/docs/conf.py +29 -0
- data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
- data/docs/guides/feature-encrypted-fields.md +785 -0
- data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
- data/docs/guides/feature-external-identifiers.md +637 -0
- data/docs/guides/feature-object-identifiers.md +435 -0
- data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
- data/docs/guides/feature-relationships-methods.md +684 -0
- data/docs/guides/feature-relationships.md +200 -0
- data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
- data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
- data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
- data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
- data/docs/guides/index.md +176 -0
- data/docs/guides/{Security-Model.md → security-model.md} +1 -1
- data/docs/migrating/v2.0.0-pre.md +1 -1
- data/docs/migrating/v2.0.0-pre11.md +2 -2
- data/docs/migrating/v2.0.0-pre12.md +2 -2
- data/docs/migrating/v2.0.0-pre5.md +33 -12
- data/docs/migrating/v2.0.0-pre6.md +2 -2
- data/docs/migrating/v2.0.0-pre7.md +8 -8
- data/docs/overview.md +623 -19
- data/docs/reference/api-technical.md +1365 -0
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
- data/examples/autoloader/mega_customer.rb +3 -1
- data/examples/encrypted_fields.rb +378 -0
- data/examples/json_usage_patterns.rb +144 -0
- data/examples/relationships.rb +13 -13
- data/examples/safe_dump.rb +6 -6
- data/examples/single_connection_transaction_confusions.rb +379 -0
- data/lib/familia/base.rb +49 -10
- data/lib/familia/connection/handlers.rb +223 -0
- data/lib/familia/connection/individual_command_proxy.rb +64 -0
- data/lib/familia/connection/middleware.rb +75 -0
- data/lib/familia/connection/operation_core.rb +93 -0
- data/lib/familia/connection/operations.rb +277 -0
- data/lib/familia/connection/pipeline_core.rb +87 -0
- data/lib/familia/connection/transaction_core.rb +100 -0
- data/lib/familia/connection.rb +60 -186
- data/lib/familia/data_type/commands.rb +53 -51
- data/lib/familia/data_type/serialization.rb +108 -107
- data/lib/familia/data_type/types/counter.rb +1 -1
- data/lib/familia/data_type/types/hashkey.rb +13 -10
- data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
- data/lib/familia/data_type/types/lock.rb +3 -2
- data/lib/familia/data_type/types/sorted_set.rb +26 -15
- data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -5
- data/lib/familia/data_type/types/unsorted_set.rb +20 -27
- data/lib/familia/data_type.rb +75 -47
- data/lib/familia/distinguisher.rb +85 -0
- data/lib/familia/encryption/encrypted_data.rb +15 -24
- data/lib/familia/encryption/manager.rb +6 -4
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
- data/lib/familia/encryption/request_cache.rb +7 -7
- data/lib/familia/encryption.rb +2 -3
- data/lib/familia/errors.rb +9 -3
- data/lib/familia/features/autoloader.rb +30 -12
- data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
- data/lib/familia/features/encrypted_fields.rb +66 -64
- data/lib/familia/features/expiration/extensions.rb +1 -1
- data/lib/familia/features/expiration.rb +31 -26
- data/lib/familia/features/external_identifier.rb +9 -12
- data/lib/familia/features/object_identifier.rb +56 -19
- data/lib/familia/features/quantization.rb +16 -21
- data/lib/familia/features/relationships/README.md +97 -0
- data/lib/familia/features/relationships/collection_operations.rb +104 -0
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +301 -0
- data/lib/familia/features/relationships/indexing.rb +176 -256
- data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
- data/lib/familia/features/relationships/participation/participant_methods.rb +160 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
- data/lib/familia/features/relationships/participation.rb +656 -0
- data/lib/familia/features/relationships/participation_relationship.rb +31 -0
- data/lib/familia/features/relationships/score_encoding.rb +20 -20
- data/lib/familia/features/relationships.rb +65 -266
- data/lib/familia/features/safe_dump.rb +127 -130
- data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
- data/lib/familia/features/transient_fields.rb +3 -5
- data/lib/familia/features.rb +4 -13
- data/lib/familia/field_type.rb +24 -4
- data/lib/familia/horreum/core/connection.rb +229 -26
- data/lib/familia/horreum/core/database_commands.rb +27 -17
- data/lib/familia/horreum/core/serialization.rb +40 -20
- data/lib/familia/horreum/core/utils.rb +2 -1
- data/lib/familia/horreum/shared/settings.rb +2 -1
- data/lib/familia/horreum/subclass/definition.rb +33 -45
- data/lib/familia/horreum/subclass/management.rb +72 -24
- data/lib/familia/horreum/subclass/related_fields_management.rb +82 -21
- data/lib/familia/horreum.rb +196 -114
- data/lib/familia/json_serializer.rb +0 -1
- data/lib/familia/logging.rb +11 -114
- data/lib/familia/refinements/dear_json.rb +122 -0
- data/lib/familia/refinements/logger_trace.rb +20 -17
- data/lib/familia/refinements/stylize_words.rb +65 -0
- data/lib/familia/refinements/time_literals.rb +60 -52
- data/lib/familia/refinements.rb +2 -1
- data/lib/familia/secure_identifier.rb +60 -28
- data/lib/familia/settings.rb +83 -7
- data/lib/familia/utils.rb +5 -87
- data/lib/familia/verifiable_identifier.rb +4 -4
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +72 -14
- data/lib/middleware/database_middleware.rb +56 -14
- data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
- data/try/configuration/scenarios_try.rb +1 -1
- data/try/connection/fiber_context_preservation_try.rb +250 -0
- data/try/connection/handler_constraints_try.rb +59 -0
- data/try/connection/operation_mode_guards_try.rb +208 -0
- data/try/connection/pipeline_fallback_integration_try.rb +128 -0
- data/try/connection/responsibility_chain_tracking_try.rb +72 -0
- data/try/connection/transaction_fallback_integration_try.rb +288 -0
- data/try/connection/transaction_mode_permissive_try.rb +153 -0
- data/try/connection/transaction_mode_strict_try.rb +98 -0
- data/try/connection/transaction_mode_warn_try.rb +131 -0
- data/try/connection/transaction_modes_try.rb +249 -0
- data/try/core/autoloader_try.rb +120 -2
- data/try/core/connection_try.rb +7 -7
- data/try/core/conventional_inheritance_try.rb +130 -0
- data/try/core/create_method_try.rb +15 -23
- data/try/core/database_consistency_try.rb +10 -10
- data/try/core/errors_try.rb +8 -11
- data/try/core/familia_extended_try.rb +2 -2
- data/try/core/familia_members_methods_try.rb +76 -0
- data/try/core/isolated_dbclient_try.rb +165 -0
- data/try/core/middleware_try.rb +16 -16
- data/try/core/persistence_operations_try.rb +4 -4
- data/try/core/pools_try.rb +42 -26
- data/try/core/secure_identifier_try.rb +28 -24
- data/try/core/time_utils_try.rb +10 -10
- data/try/core/tools_try.rb +1 -1
- data/try/core/utils_try.rb +2 -2
- data/try/data_types/boolean_try.rb +4 -4
- data/try/data_types/datatype_base_try.rb +0 -2
- data/try/data_types/list_try.rb +10 -10
- data/try/data_types/sorted_set_try.rb +5 -5
- data/try/data_types/string_try.rb +12 -12
- data/try/data_types/unsortedset_try.rb +33 -0
- data/try/debugging/cache_behavior_tracer.rb +7 -7
- data/try/debugging/debug_aad_process.rb +1 -1
- data/try/debugging/debug_concealed_internal.rb +1 -1
- data/try/debugging/debug_cross_context.rb +1 -1
- data/try/debugging/debug_fresh_cross_context.rb +1 -1
- data/try/debugging/encryption_method_tracer.rb +10 -10
- data/try/edge_cases/hash_symbolization_try.rb +1 -1
- data/try/edge_cases/ttl_side_effects_try.rb +1 -1
- data/try/encryption/config_persistence_try.rb +2 -2
- data/try/encryption/encryption_core_try.rb +19 -19
- data/try/encryption/instance_variable_scope_try.rb +1 -1
- data/try/encryption/module_loading_try.rb +2 -2
- data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
- data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
- data/try/encryption/secure_memory_handling_try.rb +1 -1
- data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
- data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
- data/try/features/encrypted_fields/fresh_key_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 +7 -7
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
- data/try/features/external_identifier/external_identifier_try.rb +1 -1
- data/try/features/feature_dependencies_try.rb +3 -3
- data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
- data/try/features/object_identifier/object_identifier_try.rb +10 -0
- data/try/features/quantization/quantization_try.rb +1 -1
- data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
- data/try/features/relationships/indexing_try.rb +433 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
- data/try/features/relationships/participation_commands_verification_try.rb +105 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
- data/try/features/relationships/participation_reverse_index_try.rb +196 -0
- data/try/features/relationships/relationships_api_changes_try.rb +72 -71
- data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
- data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
- data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
- data/try/features/relationships/relationships_performance_try.rb +20 -20
- data/try/features/relationships/relationships_try.rb +27 -38
- data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
- data/try/features/transient_fields/refresh_reset_try.rb +1 -1
- data/try/features/transient_fields/simple_refresh_test.rb +1 -1
- data/try/helpers/test_cleanup.rb +86 -0
- data/try/helpers/test_helpers.rb +3 -3
- data/try/horreum/base_try.rb +3 -2
- data/try/horreum/commands_try.rb +1 -1
- data/try/horreum/destroy_related_fields_cleanup_try.rb +330 -0
- data/try/horreum/initialization_try.rb +11 -7
- data/try/horreum/relations_try.rb +21 -13
- data/try/horreum/serialization_try.rb +12 -11
- data/try/integration/cross_component_try.rb +3 -3
- data/try/memory/memory_basic_test.rb +1 -1
- data/try/memory/memory_docker_ruby_dump.sh +1 -1
- data/try/models/customer_safe_dump_try.rb +1 -1
- data/try/models/customer_try.rb +8 -10
- data/try/models/datatype_base_try.rb +3 -3
- data/try/models/familia_object_try.rb +9 -8
- data/try/performance/benchmarks_try.rb +2 -2
- data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
- data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
- data/try/prototypes/atomic_saves_v4.rb +1 -1
- data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
- data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
- data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
- data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
- data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
- data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
- data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
- data/try/prototypes/pooling/pool_siege.rb +11 -11
- data/try/prototypes/pooling/run_stress_tests.rb +7 -7
- data/try/refinements/dear_json_array_methods_try.rb +53 -0
- data/try/refinements/dear_json_hash_methods_try.rb +54 -0
- data/try/refinements/logger_trace_methods_try.rb +44 -0
- data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
- data/try/refinements/time_literals_string_methods_try.rb +80 -0
- metadata +75 -43
- data/.rubocop_todo.yml +0 -208
- data/docs/connection_pooling.md +0 -192
- data/docs/guides/Connection-Pooling-Guide.md +0 -437
- data/docs/guides/Encrypted-Fields-Overview.md +0 -101
- data/docs/guides/Feature-System-Autoloading.md +0 -198
- data/docs/guides/Home.md +0 -116
- data/docs/guides/Relationships-Guide.md +0 -737
- data/docs/guides/relationships-methods.md +0 -266
- data/docs/reference/auditing_database_commands.rb +0 -228
- data/examples/permissions.rb +0 -240
- data/lib/familia/features/relationships/cascading.rb +0 -437
- data/lib/familia/features/relationships/membership.rb +0 -497
- data/lib/familia/features/relationships/permission_management.rb +0 -264
- data/lib/familia/features/relationships/querying.rb +0 -615
- data/lib/familia/features/relationships/redis_operations.rb +0 -274
- data/lib/familia/features/relationships/tracking.rb +0 -418
- data/lib/familia/refinements/snake_case.rb +0 -40
- data/lib/familia/validation/command_recorder.rb +0 -336
- data/lib/familia/validation/expectations.rb +0 -519
- data/lib/familia/validation/validation_helpers.rb +0 -443
- data/lib/familia/validation/validator.rb +0 -412
- data/lib/familia/validation.rb +0 -140
- data/try/data_types/set_try.rb +0 -33
- data/try/features/relationships/categorical_permissions_try.rb +0 -515
- data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
- data/try/validation/atomic_operations_try.rb.disabled +0 -320
- data/try/validation/command_validation_try.rb.disabled +0 -207
- data/try/validation/performance_validation_try.rb.disabled +0 -324
- data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -24,13 +24,10 @@ class EdgeTestDomain < Familia::Horreum
|
|
24
24
|
field :created_at
|
25
25
|
field :score_value
|
26
26
|
|
27
|
-
# Test different score calculation methods - simplified
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
member_of EdgeTestCustomer, :domains, type: :sorted_set
|
32
|
-
member_of EdgeTestCustomer, :simple_domains, type: :set
|
33
|
-
member_of EdgeTestCustomer, :domain_list, type: :list
|
27
|
+
# Test different score calculation methods and collection types - simplified
|
28
|
+
participates_in EdgeTestCustomer, :domains, score: :created_at, type: :sorted_set
|
29
|
+
participates_in EdgeTestCustomer, :simple_domains, type: :set
|
30
|
+
participates_in EdgeTestCustomer, :domain_list, type: :list
|
34
31
|
|
35
32
|
def calculated_score
|
36
33
|
(score_value || 0) * 2
|
@@ -58,19 +55,19 @@ end
|
|
58
55
|
# Score encoding edge cases
|
59
56
|
|
60
57
|
## Score encoding handles maximum metadata value
|
61
|
-
max_score = @domain1.encode_score(
|
58
|
+
max_score = @domain1.encode_score(Familia.now, 255)
|
62
59
|
decoded = @domain1.decode_score(max_score)
|
63
60
|
decoded[:permissions]
|
64
61
|
#=> 255
|
65
62
|
|
66
63
|
## Score encoding handles zero metadata
|
67
|
-
zero_score = @domain1.encode_score(
|
64
|
+
zero_score = @domain1.encode_score(Familia.now, 0)
|
68
65
|
decoded_zero = @domain1.decode_score(zero_score)
|
69
66
|
decoded_zero[:permissions]
|
70
67
|
#=> 0
|
71
68
|
|
72
69
|
## Permission encoding handles unknown permission levels
|
73
|
-
unknown_perm_score = @domain1.permission_encode(
|
70
|
+
unknown_perm_score = @domain1.permission_encode(Familia.now, :unknown_permission)
|
74
71
|
decoded_unknown = @domain1.permission_decode(unknown_perm_score)
|
75
72
|
decoded_unknown[:permission_list]
|
76
73
|
#=> []
|
@@ -90,7 +87,7 @@ decoded_large[:permissions]
|
|
90
87
|
#=> 123
|
91
88
|
|
92
89
|
## Permission encoding maps correctly
|
93
|
-
read_score = @domain1.permission_encode(
|
90
|
+
read_score = @domain1.permission_encode(Familia.now, :read)
|
94
91
|
decoded_read = @domain1.permission_decode(read_score)
|
95
92
|
decoded_read[:permission_list].include?(:read)
|
96
93
|
#=> true
|
@@ -102,7 +99,7 @@ decoded_epoch[:permissions]
|
|
102
99
|
#=> 42
|
103
100
|
|
104
101
|
## Boundary score values work correctly
|
105
|
-
boundary_score = @domain1.encode_score(
|
102
|
+
boundary_score = @domain1.encode_score(Familia.now, 255)
|
106
103
|
decoded_boundary = @domain1.decode_score(boundary_score)
|
107
104
|
decoded_boundary[:permissions] <= 255
|
108
105
|
#=> true
|
@@ -112,22 +109,22 @@ decoded_boundary[:permissions] <= 255
|
|
112
109
|
## Method score calculation works with saved objects
|
113
110
|
@customer1.save
|
114
111
|
@domain1.save
|
115
|
-
@domain1.
|
116
|
-
method_score = @domain1.
|
112
|
+
@domain1.add_to_edge_test_customer_domains(@customer1)
|
113
|
+
method_score = @domain1.score_in_edge_test_customer_domains(@customer1)
|
117
114
|
method_score.is_a?(Float) && method_score > 0
|
118
115
|
#=> true
|
119
116
|
|
120
117
|
## Sorted set membership works
|
121
|
-
@domain1.
|
118
|
+
@domain1.in_edge_test_customer_domains?(@customer1)
|
122
119
|
#=> true
|
123
120
|
|
124
121
|
## Score methods respond correctly
|
125
|
-
@domain1.respond_to?(:
|
122
|
+
@domain1.respond_to?(:score_in_edge_test_customer_domains)
|
126
123
|
#=> true
|
127
124
|
|
128
125
|
## Basic relationship cleanup works
|
129
|
-
@domain1.
|
130
|
-
@domain1.
|
126
|
+
@domain1.remove_from_edge_test_customer_domains(@customer1)
|
127
|
+
@domain1.in_edge_test_customer_domains?(@customer1)
|
131
128
|
#=> false
|
132
129
|
|
133
130
|
# Clean up test data
|
@@ -44,7 +44,7 @@ save_time = Benchmark.realtime do
|
|
44
44
|
@domains.each do |domain|
|
45
45
|
domain.save
|
46
46
|
# Use numeric scores for sorted sets
|
47
|
-
MinimalDomain.all_domains.add(domain.
|
47
|
+
MinimalDomain.all_domains.add(domain.identifier, domain.priority_score.to_f)
|
48
48
|
MinimalDomain.active_domains.add(domain.identifier)
|
49
49
|
MinimalDomain.domain_history.push(domain.identifier)
|
50
50
|
MinimalDomain.domain_lookup[domain.display_domain] = domain.identifier
|
@@ -105,7 +105,7 @@ end
|
|
105
105
|
large_time = Benchmark.realtime do
|
106
106
|
@large_domains.each do |domain|
|
107
107
|
domain.save
|
108
|
-
MinimalDomain.all_domains.add(domain.
|
108
|
+
MinimalDomain.all_domains.add(domain.identifier, domain.priority_score.to_f)
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
@@ -30,7 +30,7 @@ class SimplePerfDomain < Familia::Horreum
|
|
30
30
|
class_hashkey :domain_lookup
|
31
31
|
|
32
32
|
# Define tracking relationships for testing
|
33
|
-
|
33
|
+
participates_in SimplePerfCustomer, :simple_domains, score: :created_at
|
34
34
|
end
|
35
35
|
|
36
36
|
# =============================================
|
@@ -46,7 +46,7 @@ end
|
|
46
46
|
SimplePerfDomain.new(
|
47
47
|
domain_id: "simple_perf_domain_#{i}",
|
48
48
|
display_domain: "simple#{i}.example.com",
|
49
|
-
created_at:
|
49
|
+
created_at: Familia.now.to_i + i,
|
50
50
|
priority_score: rand(100)
|
51
51
|
)
|
52
52
|
end
|
@@ -58,15 +58,15 @@ save_time = Benchmark.realtime do
|
|
58
58
|
# Manually add to collections for testing (ensure numeric score)
|
59
59
|
# Convert created_at to float, handling nil case
|
60
60
|
score = if domain.created_at.nil?
|
61
|
-
|
61
|
+
Familia.now
|
62
62
|
elsif domain.created_at.is_a?(Time)
|
63
63
|
domain.created_at.to_f
|
64
64
|
else
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
65
|
+
domain.created_at.to_f
|
66
|
+
end
|
67
|
+
SimplePerfDomain.all_domains.add(domain.identifier, score)
|
68
|
+
SimplePerfDomain.domain_lookup[domain.display_domain] = domain.identifier
|
69
|
+
end
|
70
70
|
end
|
71
71
|
|
72
72
|
# Should complete in reasonable time
|
@@ -39,7 +39,7 @@ class PerfDomain < Familia::Horreum
|
|
39
39
|
class_hashkey :id_lookup
|
40
40
|
|
41
41
|
# Define tracking relationships for testing
|
42
|
-
|
42
|
+
participates_in PerfCustomer, :domains, score: :created_at
|
43
43
|
end
|
44
44
|
|
45
45
|
# Integration test with other features
|
@@ -73,7 +73,7 @@ end
|
|
73
73
|
# =============================================
|
74
74
|
|
75
75
|
## Create test objects for performance testing
|
76
|
-
@customer = PerfCustomer.new(custid: 'perf_customer', name: 'Performance Test', created_at:
|
76
|
+
@customer = PerfCustomer.new(custid: 'perf_customer', name: 'Performance Test', created_at: Familia.now.to_i)
|
77
77
|
@customer.save
|
78
78
|
|
79
79
|
# Create multiple domains for bulk operations
|
@@ -81,7 +81,7 @@ end
|
|
81
81
|
PerfDomain.new(
|
82
82
|
domain_id: "perf_domain_#{i}",
|
83
83
|
display_domain: "perf#{i}.example.com",
|
84
|
-
created_at:
|
84
|
+
created_at: Familia.now.to_i + i,
|
85
85
|
priority_score: rand(100),
|
86
86
|
customer_id: @customer.custid
|
87
87
|
)
|
@@ -93,8 +93,8 @@ save_time = Benchmark.realtime do
|
|
93
93
|
domain.save
|
94
94
|
# Manually populate collections for testing
|
95
95
|
score = domain.created_at.is_a?(Time) ? domain.created_at.to_f : domain.created_at.to_f
|
96
|
-
PerfDomain.all_domains.add(
|
97
|
-
PerfDomain.priority_queue.add(domain.
|
96
|
+
PerfDomain.all_domains.add(domain.identifier, score)
|
97
|
+
PerfDomain.priority_queue.add(domain.identifier, domain.priority_score)
|
98
98
|
PerfDomain.active_domains.add(domain.identifier)
|
99
99
|
PerfDomain.domain_history.push(domain.identifier)
|
100
100
|
PerfDomain.domain_lookup[domain.display_domain] = domain.identifier
|
@@ -153,7 +153,7 @@ instances = 100.times.map { |i| PerfDomain.new(domain_id: "mem_test_#{i}") }
|
|
153
153
|
after_instances = ObjectSpace.count_objects[:T_OBJECT]
|
154
154
|
|
155
155
|
# Should not have created excessive objects (relationship metadata is shared)
|
156
|
-
(after_instances - before_instances) <
|
156
|
+
(after_instances - before_instances) < 250 # Allow for reasonable, 2.5x overhead
|
157
157
|
#=> true
|
158
158
|
|
159
159
|
## Class-level relationship metadata should be constant size
|
@@ -203,19 +203,19 @@ operation_threads = 3.times.map do |i|
|
|
203
203
|
domain = PerfDomain.new(
|
204
204
|
domain_id: "thread_test_#{i}",
|
205
205
|
display_domain: "thread#{i}.test.com",
|
206
|
-
created_at:
|
206
|
+
created_at: Familia.now.to_i + i,
|
207
207
|
priority_score: i * 10
|
208
208
|
)
|
209
209
|
domain.save
|
210
210
|
|
211
211
|
# Add to collections manually
|
212
|
-
PerfDomain.all_domains.add(domain.
|
213
|
-
PerfDomain.priority_queue.add(domain.
|
212
|
+
PerfDomain.all_domains.add(domain.identifier, domain.created_at.to_f)
|
213
|
+
PerfDomain.priority_queue.add(domain.identifier, domain.priority_score)
|
214
214
|
domain.save
|
215
215
|
|
216
216
|
# Manually add to collections for thread test
|
217
|
-
PerfDomain.all_domains.add(domain.
|
218
|
-
PerfDomain.priority_queue.add(domain.
|
217
|
+
PerfDomain.all_domains.add(domain.identifier, domain.created_at.to_f)
|
218
|
+
PerfDomain.priority_queue.add(domain.identifier, domain.priority_score)
|
219
219
|
|
220
220
|
# Verify the domain was added to collections
|
221
221
|
in_all = PerfDomain.all_domains.member?(domain.identifier)
|
@@ -244,7 +244,7 @@ thread_results.all? { |result| result == [true, true] }
|
|
244
244
|
# =============================================
|
245
245
|
|
246
246
|
## Integration with safe_dump (if available)
|
247
|
-
integration_model = IntegrationTestModel.new(id: 'integration_test', data: 'test_data', created_at:
|
247
|
+
integration_model = IntegrationTestModel.new(id: 'integration_test', data: 'test_data', created_at: Familia.now.to_i)
|
248
248
|
|
249
249
|
if integration_model.respond_to?(:safe_dump)
|
250
250
|
integration_model.save
|
@@ -260,7 +260,7 @@ end
|
|
260
260
|
#=:> String
|
261
261
|
|
262
262
|
## Integration with expiration (if available)
|
263
|
-
integration_model2 = IntegrationTestModel.new(id: 'integration_test2', data: 'test_data2', created_at:
|
263
|
+
integration_model2 = IntegrationTestModel.new(id: 'integration_test2', data: 'test_data2', created_at: Familia.now.to_i)
|
264
264
|
if integration_model2.respond_to?(:default_expiration)
|
265
265
|
# Test that expiration works with relationship collections
|
266
266
|
if integration_model2.class.respond_to?(:all_items)
|
@@ -315,15 +315,15 @@ stress_time < 0.1
|
|
315
315
|
test_domain = PerfDomain.new(
|
316
316
|
domain_id: 'atomic_test',
|
317
317
|
display_domain: 'atomic.test.com',
|
318
|
-
created_at:
|
318
|
+
created_at: Familia.now.to_i,
|
319
319
|
priority_score: 50
|
320
320
|
)
|
321
321
|
|
322
322
|
# Save should succeed and add to all collections
|
323
323
|
test_domain.save
|
324
324
|
# Manually add to collections
|
325
|
-
PerfDomain.all_domains.add(test_domain.
|
326
|
-
PerfDomain.priority_queue.add(test_domain.
|
325
|
+
PerfDomain.all_domains.add(test_domain.identifier, test_domain.created_at.to_f)
|
326
|
+
PerfDomain.priority_queue.add(test_domain.identifier, test_domain.priority_score)
|
327
327
|
PerfDomain.active_domains.add(test_domain.identifier)
|
328
328
|
PerfDomain.domain_history.push(test_domain.identifier)
|
329
329
|
|
@@ -337,14 +337,14 @@ all_collections_have_domain
|
|
337
337
|
#=> true
|
338
338
|
|
339
339
|
## Partial failures should not leave inconsistent state
|
340
|
-
# This would require more complex testing infrastructure to simulate Redis failures
|
340
|
+
# This would require more complex testing infrastructure to simulate Valkey/Redis failures
|
341
341
|
|
342
|
-
## Recovery from Redis connection issues
|
342
|
+
## Recovery from Valkey/Redis connection issues
|
343
343
|
begin
|
344
|
-
# Test graceful handling of Redis errors during relationship operations
|
344
|
+
# Test graceful handling of Valkey/Redis errors during relationship operations
|
345
345
|
dead_domain = PerfDomain.new(domain_id: 'dead_test')
|
346
346
|
|
347
|
-
# This test would require mocking Redis to fail, which is complex in this context
|
347
|
+
# This test would require mocking Valkey/Redis to fail, which is complex in this context
|
348
348
|
# For now, just verify that normal operations work
|
349
349
|
dead_domain.save
|
350
350
|
dead_domain.destroy!
|
@@ -25,14 +25,11 @@ class TestDomain < Familia::Horreum
|
|
25
25
|
field :created_at
|
26
26
|
field :permission_level
|
27
27
|
|
28
|
-
# Basic
|
29
|
-
|
30
|
-
|
28
|
+
# Basic participation with simplified score
|
29
|
+
participates_in TestCustomer, :domains, score: :created_at
|
30
|
+
class_participates_in :all_domains, score: :created_at
|
31
31
|
|
32
32
|
# Note: Indexing features removed for stability
|
33
|
-
|
34
|
-
# Basic membership
|
35
|
-
member_of TestCustomer, :domains
|
36
33
|
end
|
37
34
|
|
38
35
|
class TestTag < Familia::Horreum
|
@@ -42,8 +39,8 @@ class TestTag < Familia::Horreum
|
|
42
39
|
field :name
|
43
40
|
field :created_at
|
44
41
|
|
45
|
-
# Global
|
46
|
-
|
42
|
+
# Global participation
|
43
|
+
class_participates_in :all_tags, score: :created_at
|
47
44
|
end
|
48
45
|
|
49
46
|
# Setup
|
@@ -51,10 +48,10 @@ end
|
|
51
48
|
@domain = TestDomain.new(
|
52
49
|
domain_id: 'dom_789',
|
53
50
|
display_domain: 'example.com',
|
54
|
-
created_at:
|
51
|
+
created_at: Familia.now.to_i,
|
55
52
|
permission_level: :write
|
56
53
|
)
|
57
|
-
@tag = TestTag.new(name: 'important', created_at:
|
54
|
+
@tag = TestTag.new(name: 'important', created_at: Familia.now.to_i)
|
58
55
|
|
59
56
|
# =============================================
|
60
57
|
# 1. V2 Feature Integration Tests
|
@@ -72,10 +69,6 @@ TestDomain.included_modules.map(&:name).include?('Familia::Features::Relationshi
|
|
72
69
|
@domain.respond_to?(:permission_encode)
|
73
70
|
#=> true
|
74
71
|
|
75
|
-
## Redis operations functionality is available
|
76
|
-
@domain.respond_to?(:atomic_operation)
|
77
|
-
#=> true
|
78
|
-
|
79
72
|
## Identifier method works (wraps identifier_field)
|
80
73
|
TestDomain.identifier_field
|
81
74
|
#=> :domain_id
|
@@ -89,7 +82,7 @@ TestDomain.identifier_field
|
|
89
82
|
# =============================================
|
90
83
|
|
91
84
|
## Permission encoding creates proper score
|
92
|
-
@score = @domain.permission_encode(
|
85
|
+
@score = @domain.permission_encode(Familia.now, :write)
|
93
86
|
@score.to_s.match?(/\d+\.\d+/)
|
94
87
|
#=> true
|
95
88
|
|
@@ -99,16 +92,16 @@ decoded[:permission_list].include?(:write)
|
|
99
92
|
#=> true
|
100
93
|
|
101
94
|
## Score encoding preserves timestamp ordering
|
102
|
-
@early_score = @domain.encode_score(
|
103
|
-
@late_score = @domain.encode_score(
|
95
|
+
@early_score = @domain.encode_score(Familia.now - 3600, 100) # 1 hour ago
|
96
|
+
@late_score = @domain.encode_score(Familia.now, 100)
|
104
97
|
@late_score > @early_score
|
105
98
|
#=> true
|
106
99
|
|
107
100
|
# =============================================
|
108
|
-
# 3.
|
101
|
+
# 3. Participation Relationships (participates_in)
|
109
102
|
# =============================================
|
110
103
|
|
111
|
-
## Save operation manages
|
104
|
+
## Save operation manages participation relationships
|
112
105
|
@customer.save
|
113
106
|
@domain.save
|
114
107
|
|
@@ -129,24 +122,24 @@ decoded[:permission_list].include?(:write)
|
|
129
122
|
#=> true
|
130
123
|
|
131
124
|
## Domain can check membership in customer domains (collision-free naming)
|
132
|
-
@domain.respond_to?(:
|
125
|
+
@domain.respond_to?(:in_test_customer_domains?)
|
133
126
|
#=> true
|
134
127
|
|
135
128
|
## Domain can add itself to customer domains (collision-free naming)
|
136
|
-
@domain.respond_to?(:
|
129
|
+
@domain.respond_to?(:add_to_test_customer_domains)
|
137
130
|
#=> true
|
138
131
|
|
139
132
|
## Domain can remove itself from customer domains (collision-free naming)
|
140
|
-
@domain.respond_to?(:
|
133
|
+
@domain.respond_to?(:remove_from_test_customer_domains)
|
141
134
|
#=> true
|
142
135
|
|
143
136
|
## Add domain to customer collection
|
144
|
-
@domain.
|
145
|
-
@domain.
|
137
|
+
@domain.add_to_test_customer_domains(@customer)
|
138
|
+
@domain.in_test_customer_domains?(@customer)
|
146
139
|
#=> true
|
147
140
|
|
148
141
|
## Score is properly encoded
|
149
|
-
score = @domain.
|
142
|
+
score = @domain.score_in_test_customer_domains(@customer)
|
150
143
|
score.is_a?(Float) && score > 0
|
151
144
|
#=> true
|
152
145
|
|
@@ -154,8 +147,8 @@ score.is_a?(Float) && score > 0
|
|
154
147
|
# 4. Basic Functionality Verification
|
155
148
|
# =============================================
|
156
149
|
|
157
|
-
## Domain
|
158
|
-
@domain.respond_to?(:
|
150
|
+
## Domain participation methods work correctly
|
151
|
+
@domain.respond_to?(:score_in_test_customer_domains)
|
159
152
|
#=> true
|
160
153
|
|
161
154
|
## Score calculation methods are available
|
@@ -163,23 +156,23 @@ score.is_a?(Float) && score > 0
|
|
163
156
|
#=> true
|
164
157
|
|
165
158
|
# =============================================
|
166
|
-
# 5.
|
159
|
+
# 5. Bidirectional Participation Methods
|
167
160
|
# =============================================
|
168
161
|
|
169
|
-
##
|
170
|
-
@domain.respond_to?(:
|
162
|
+
## participates_in generates collision-free bidirectional methods with collection names
|
163
|
+
@domain.respond_to?(:add_to_test_customer_domains)
|
171
164
|
#=> true
|
172
165
|
|
173
|
-
## Basic
|
174
|
-
@domain.
|
175
|
-
@domain.
|
166
|
+
## Basic bidirectional participation operations work
|
167
|
+
@domain.remove_from_test_customer_domains(@customer)
|
168
|
+
@domain.in_test_customer_domains?(@customer)
|
176
169
|
#=> false
|
177
170
|
|
178
171
|
# =============================================
|
179
172
|
# 6. Basic Global Tag Tracking Test
|
180
173
|
# =============================================
|
181
174
|
|
182
|
-
## Tag can be
|
175
|
+
## Tag can be participating globally
|
183
176
|
@tag.save
|
184
177
|
@tag.respond_to?(:add_to_class_all_tags)
|
185
178
|
#=> true
|
@@ -213,10 +206,6 @@ temp_key = @domain.create_temp_key("test_operation", 60)
|
|
213
206
|
temp_key.start_with?("temp:")
|
214
207
|
#=> true
|
215
208
|
|
216
|
-
## Batch operations are available
|
217
|
-
@domain.respond_to?(:batch_zadd)
|
218
|
-
#=> true
|
219
|
-
|
220
209
|
## Score range queries work
|
221
210
|
@domain.respond_to?(:score_range)
|
222
211
|
#=> true
|
@@ -17,8 +17,8 @@ Customer.safe_dump_fields
|
|
17
17
|
@cust.custid = 'test@example.com'
|
18
18
|
@cust.role = 'user'
|
19
19
|
@cust.verified = true
|
20
|
-
@cust.created =
|
21
|
-
@cust.updated =
|
20
|
+
@cust.created = Familia.now.to_i
|
21
|
+
@cust.updated = Familia.now.to_i
|
22
22
|
@safe_dump = @cust.safe_dump
|
23
23
|
@safe_dump.keys.sort
|
24
24
|
#=> [:active, :created, :custid, :role, :secrets_created, :updated, :verified]
|
@@ -94,7 +94,7 @@ SecretService.fields.sort
|
|
94
94
|
@service.endpoint_url
|
95
95
|
#=> "https://api.example.com"
|
96
96
|
|
97
|
-
##
|
97
|
+
## UnsortedSet transient fields again after refresh
|
98
98
|
@service.api_key = 'new-api-key-after-refresh'
|
99
99
|
@service.password = 'new-password-after-refresh'
|
100
100
|
@service.token = 'new-token-after-refresh'
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# Test cleanup helper for managing anonymous classes and test isolation
|
2
|
+
#
|
3
|
+
# This module provides utilities to create and cleanup test classes that
|
4
|
+
# inherit from Familia classes. Without proper cleanup, anonymous classes
|
5
|
+
# pollute the Familia.members registry causing test failures.
|
6
|
+
|
7
|
+
module TestCleanup
|
8
|
+
@test_classes = []
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_reader :test_classes
|
12
|
+
|
13
|
+
# Create a test class that inherits from the given base class.
|
14
|
+
# The created class is automatically tracked for cleanup.
|
15
|
+
#
|
16
|
+
# @param base_class [Class] The class to inherit from (e.g., Familia::Horreum)
|
17
|
+
# @param block [Proc] Block to define the class body
|
18
|
+
# @return [Class] The created test class
|
19
|
+
def create_test_class(base_class, &block)
|
20
|
+
test_class = Class.new(base_class, &block)
|
21
|
+
track_test_class(test_class)
|
22
|
+
test_class
|
23
|
+
end
|
24
|
+
|
25
|
+
# Track a test class for cleanup. Use this when you create test classes
|
26
|
+
# directly with Class.new instead of using create_test_class.
|
27
|
+
#
|
28
|
+
# @param klass [Class] The test class to track
|
29
|
+
# @return [Class] The tracked class
|
30
|
+
def track_test_class(klass)
|
31
|
+
@test_classes << klass unless @test_classes.include?(klass)
|
32
|
+
klass
|
33
|
+
end
|
34
|
+
|
35
|
+
# Remove all tracked test classes from Familia.members and clear
|
36
|
+
# the tracking array. This should be called in test teardown.
|
37
|
+
#
|
38
|
+
# @return [Array<Class>] The classes that were removed
|
39
|
+
def cleanup_test_classes
|
40
|
+
removed_classes = []
|
41
|
+
|
42
|
+
@test_classes.each do |test_class|
|
43
|
+
if Familia.members.include?(test_class)
|
44
|
+
Familia.unload_member(test_class)
|
45
|
+
removed_classes << test_class
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
@test_classes.clear
|
50
|
+
removed_classes
|
51
|
+
end
|
52
|
+
|
53
|
+
# Clean up all anonymous classes from Familia.members.
|
54
|
+
# This is a more aggressive cleanup that removes any class with nil name.
|
55
|
+
#
|
56
|
+
# @return [Array<Class>] The anonymous classes that were removed
|
57
|
+
def cleanup_anonymous_classes
|
58
|
+
Familia.clear_anonymous_members
|
59
|
+
end
|
60
|
+
|
61
|
+
# Perform complete test cleanup - both tracked and anonymous classes
|
62
|
+
#
|
63
|
+
# @return [Hash] Summary of cleanup performed
|
64
|
+
def complete_cleanup
|
65
|
+
tracked_removed = cleanup_test_classes
|
66
|
+
anonymous_removed = cleanup_anonymous_classes
|
67
|
+
|
68
|
+
{
|
69
|
+
tracked_classes_removed: tracked_removed.size,
|
70
|
+
anonymous_classes_removed: anonymous_removed.size,
|
71
|
+
total_removed: tracked_removed.size + anonymous_removed.size
|
72
|
+
}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Automatically perform cleanup if we're in test mode
|
78
|
+
# This ensures cleanup happens even if tests don't explicitly call it
|
79
|
+
at_exit do
|
80
|
+
if Familia.test_mode? && TestCleanup.test_classes.any?
|
81
|
+
cleanup_result = TestCleanup.complete_cleanup
|
82
|
+
if cleanup_result[:total_removed] > 0
|
83
|
+
puts "[TestCleanup] Cleaned up #{cleanup_result[:total_removed]} test classes on exit"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/try/helpers/test_helpers.rb
CHANGED
@@ -65,7 +65,7 @@ class Customer < Familia::Horreum
|
|
65
65
|
# We use a callable here since `:active?` is not a valid method symbol.
|
66
66
|
safe_dump_field :active, ->(cust) { cust.active? }
|
67
67
|
|
68
|
-
class_sorted_set :values, key: '
|
68
|
+
class_sorted_set :values, key: 'familia:customer'
|
69
69
|
class_hashkey :domains
|
70
70
|
|
71
71
|
hashkey :stripe_customer
|
@@ -91,9 +91,9 @@ class Customer < Familia::Horreum
|
|
91
91
|
field :reset_requested #=> Boolean
|
92
92
|
|
93
93
|
hashkey :password_reset #=> Familia::HashKey
|
94
|
-
list :sessions #=> Familia::
|
94
|
+
list :sessions #=> Familia::ListKey
|
95
95
|
|
96
|
-
class_list :
|
96
|
+
class_list :all_customers, suffix: [] # no suffix
|
97
97
|
class_string :message
|
98
98
|
|
99
99
|
class_zset :instances, class: self, reference: true
|
data/try/horreum/base_try.rb
CHANGED
@@ -52,7 +52,7 @@ Familia.debug = false
|
|
52
52
|
#=> true
|
53
53
|
|
54
54
|
## Horreum object fields have a fast attribute method (1 of 2)
|
55
|
-
Familia.trace :LOAD,
|
55
|
+
Familia.trace :LOAD, nil, @customer.uri if Familia.debug?
|
56
56
|
@customer.name! 'Jane Doe'
|
57
57
|
#=> true
|
58
58
|
|
@@ -69,7 +69,8 @@ Familia.trace :LOAD, @customer.dbclient, @customer.uri, caller if Familia.debug?
|
|
69
69
|
|
70
70
|
## Horreum objects can be destroyed
|
71
71
|
@customer.destroy!
|
72
|
-
|
72
|
+
#=:> MultiResult
|
73
|
+
#==> result.successful?
|
73
74
|
|
74
75
|
## All horrerum objects have a key field
|
75
76
|
@customer.identifier
|
data/try/horreum/commands_try.rb
CHANGED