familia 2.0.0.pre14 → 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 +66 -6
- 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 +4 -4
- data/docs/migrating/v2.0.0-pre12.md +2 -2
- data/docs/migrating/v2.0.0-pre13.md +1 -1
- 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/{autoloader.rb → features/autoloader.rb} +49 -23
- 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 +68 -66
- data/lib/familia/features/expiration/extensions.rb +61 -0
- data/lib/familia/features/expiration.rb +35 -87
- data/lib/familia/features/external_identifier.rb +11 -12
- data/lib/familia/features/object_identifier.rb +58 -20
- data/lib/familia/features/quantization.rb +17 -22
- 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 +69 -271
- data/lib/familia/features/safe_dump.rb +127 -132
- 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 +5 -5
- data/lib/familia/features.rb +21 -21
- 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 -15
- 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 +129 -11
- 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 +77 -45
- 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 -228
- 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/autoloadable.rb +0 -113
- 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/autoloadable/autoloadable_try.rb +0 -61
- data/try/features/relationships/categorical_permissions_try.rb +0 -515
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -111
- 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
@@ -0,0 +1,124 @@
|
|
1
|
+
# try/features/relationships/participation_performance_improvements_try.rb
|
2
|
+
#
|
3
|
+
# Tests for performance improvements in participation functionality
|
4
|
+
# Verifies reverse index functionality and robust type comparison
|
5
|
+
|
6
|
+
require_relative '../../helpers/test_helpers'
|
7
|
+
|
8
|
+
# Test classes for performance improvements
|
9
|
+
class PerfTestCustomer < Familia::Horreum
|
10
|
+
feature :relationships
|
11
|
+
|
12
|
+
identifier_field :customer_id
|
13
|
+
field :customer_id
|
14
|
+
field :name
|
15
|
+
|
16
|
+
sorted_set :domains
|
17
|
+
end
|
18
|
+
|
19
|
+
class PerfTestDomain < Familia::Horreum
|
20
|
+
feature :relationships
|
21
|
+
|
22
|
+
identifier_field :domain_id
|
23
|
+
field :domain_id
|
24
|
+
field :display_domain
|
25
|
+
field :created_at
|
26
|
+
|
27
|
+
participates_in PerfTestCustomer, :domains, score: :created_at
|
28
|
+
end
|
29
|
+
|
30
|
+
# Setup test data
|
31
|
+
@customer = PerfTestCustomer.new(customer_id: 'perf_cust_123', name: 'Performance Test Customer')
|
32
|
+
@domain = PerfTestDomain.new(
|
33
|
+
domain_id: 'perf_dom_1',
|
34
|
+
display_domain: 'perf-example.com',
|
35
|
+
created_at: Time.now.to_f
|
36
|
+
)
|
37
|
+
|
38
|
+
# Ensure clean state
|
39
|
+
[@customer, @domain].each do |obj|
|
40
|
+
obj.destroy if obj&.respond_to?(:destroy) && obj&.respond_to?(:exists?) && obj.exists?
|
41
|
+
end
|
42
|
+
@customer.save
|
43
|
+
@domain.save
|
44
|
+
|
45
|
+
## Test reverse index tracking methods exist
|
46
|
+
@domain.respond_to?(:track_participation_in)
|
47
|
+
#=> true
|
48
|
+
|
49
|
+
## Test reverse index removal methods exist
|
50
|
+
@domain.respond_to?(:untrack_participation_in)
|
51
|
+
#=> true
|
52
|
+
|
53
|
+
## Test add domain creates reverse index tracking 1 of 2
|
54
|
+
@customer.add_domain(@domain)
|
55
|
+
@reverse_index_key = "#{@domain.dbkey}:participations"
|
56
|
+
@tracked_collections = Familia.dbclient.smembers(@reverse_index_key) # TODO: Do not use keys directly
|
57
|
+
@tracked_collections.length
|
58
|
+
@reverse_index_key
|
59
|
+
##=> true
|
60
|
+
|
61
|
+
## Test reverse index contains correct collection key 2 of 2
|
62
|
+
@collection_key = @customer.domains.dbkey
|
63
|
+
@tracked_collections.include?(@collection_key)
|
64
|
+
##=> true
|
65
|
+
|
66
|
+
## Test remove domain cleans up reverse index tracking
|
67
|
+
@customer.remove_domain(@domain)
|
68
|
+
@tracked_collections_after_remove = Familia.dbclient.smembers(@reverse_index_key) # TODO: Do not use keys directly
|
69
|
+
@tracked_collections_after_remove.include?(@collection_key)
|
70
|
+
##=> false
|
71
|
+
|
72
|
+
## Test robust type comparison in score calculation works with Class
|
73
|
+
@customer.add_domain(@domain)
|
74
|
+
@score_with_class = @domain.calculate_participation_score(PerfTestCustomer, :domains)
|
75
|
+
@score_with_class.is_a?(Numeric)
|
76
|
+
#=> true
|
77
|
+
|
78
|
+
## Test robust type comparison works with String
|
79
|
+
@score_with_string = @domain.calculate_participation_score('PerfTestCustomer', :domains)
|
80
|
+
@score_with_string.is_a?(Numeric)
|
81
|
+
#=> true
|
82
|
+
|
83
|
+
## Test participation collections membership method works
|
84
|
+
@memberships = @domain.current_participations
|
85
|
+
@memberships.is_a?(Array)
|
86
|
+
#=> true
|
87
|
+
|
88
|
+
## Test membership data structure is correct
|
89
|
+
@memberships = @domain.current_participations
|
90
|
+
@memberships.length > 0
|
91
|
+
#=> true
|
92
|
+
|
93
|
+
## Test membership contains expected target class
|
94
|
+
@memberships = @domain.current_participations
|
95
|
+
@membership = @memberships.first
|
96
|
+
@membership[:target_class] == 'PerfTestCustomer'
|
97
|
+
#=> true
|
98
|
+
|
99
|
+
## Test membership contains collection name
|
100
|
+
@memberships = @domain.current_participations
|
101
|
+
@membership[:collection_name] == :domains
|
102
|
+
#=> true
|
103
|
+
|
104
|
+
## Test membership contains type information
|
105
|
+
@membership[:type] == :sorted_set
|
106
|
+
#=> true
|
107
|
+
|
108
|
+
## Test remove from all participation collections works efficiently
|
109
|
+
@domain.remove_from_all_participations # NOTE: Has been remove_from_all_participations
|
110
|
+
@final_tracked_collections = Familia.dbclient.smembers(@reverse_index_key) # TODO: Do not use keys directly
|
111
|
+
@final_tracked_collections.empty?
|
112
|
+
##=> true
|
113
|
+
|
114
|
+
## Test domain is removed from customer collection
|
115
|
+
@customer.remove_domain(@domain)
|
116
|
+
@customer.domains.include?(@domain)
|
117
|
+
#=> false
|
118
|
+
|
119
|
+
## Cleanup
|
120
|
+
[@customer, @domain].each do |obj|
|
121
|
+
obj.destroy if obj&.respond_to?(:destroy) && obj&.respond_to?(:exists?) && obj.exists?
|
122
|
+
end
|
123
|
+
true
|
124
|
+
#=> true
|
@@ -0,0 +1,196 @@
|
|
1
|
+
# try/features/relationships/participation_reverse_index_try.rb
|
2
|
+
#
|
3
|
+
# Tests for participation reverse index functionality
|
4
|
+
# Verifies performance improvements and correct behavior
|
5
|
+
|
6
|
+
require_relative '../../helpers/test_helpers'
|
7
|
+
|
8
|
+
# Test classes for reverse index functionality
|
9
|
+
class ReverseIndexCustomer < Familia::Horreum
|
10
|
+
feature :relationships
|
11
|
+
|
12
|
+
identifier_field :customer_id
|
13
|
+
field :customer_id
|
14
|
+
field :name
|
15
|
+
|
16
|
+
sorted_set :domains
|
17
|
+
set :preferred_domains
|
18
|
+
end
|
19
|
+
|
20
|
+
class ReverseIndexDomain < Familia::Horreum
|
21
|
+
feature :relationships
|
22
|
+
|
23
|
+
identifier_field :domain_id
|
24
|
+
field :domain_id
|
25
|
+
field :display_domain
|
26
|
+
field :created_at
|
27
|
+
|
28
|
+
participates_in ReverseIndexCustomer, :domains, score: :created_at
|
29
|
+
participates_in ReverseIndexCustomer, :preferred_domains, bidirectional: true
|
30
|
+
class_participates_in :all_domains, score: :created_at
|
31
|
+
end
|
32
|
+
|
33
|
+
# Setup test data
|
34
|
+
@customer = ReverseIndexCustomer.new(customer_id: 'ri_cust_123', name: 'Reverse Index Test Customer')
|
35
|
+
@domain1 = ReverseIndexDomain.new(
|
36
|
+
domain_id: 'ri_dom_1',
|
37
|
+
display_domain: 'example1.com',
|
38
|
+
created_at: Time.now.to_f
|
39
|
+
)
|
40
|
+
@domain2 = ReverseIndexDomain.new(
|
41
|
+
domain_id: 'ri_dom_2',
|
42
|
+
display_domain: 'example2.com',
|
43
|
+
created_at: Time.now.to_f + 1
|
44
|
+
)
|
45
|
+
|
46
|
+
# Ensure clean state
|
47
|
+
[@customer, @domain1, @domain2].each do |obj|
|
48
|
+
obj.destroy if obj&.respond_to?(:destroy) && obj&.respond_to?(:exists?) && obj.exists?
|
49
|
+
end
|
50
|
+
@customer.save
|
51
|
+
@domain1.save
|
52
|
+
@domain2.save
|
53
|
+
|
54
|
+
## Test reverse index tracking is created when adding to sorted set
|
55
|
+
@customer.add_domain(@domain1)
|
56
|
+
@ri_members1 = @domain1.participations.members
|
57
|
+
@ri_members1.is_a?(Array)
|
58
|
+
#=> true
|
59
|
+
|
60
|
+
## Test reverse index contains the sorted set collection key
|
61
|
+
@domains_key = @customer.domains.dbkey
|
62
|
+
@ri_members1.include?(@domains_key)
|
63
|
+
#=> true
|
64
|
+
|
65
|
+
## Test adding to set collection also creates tracking
|
66
|
+
@customer.add_preferred_domain(@domain1)
|
67
|
+
@ri_members1_updated = @domain1.participations.members
|
68
|
+
@ri_members1_updated.length > 1
|
69
|
+
#=> true
|
70
|
+
|
71
|
+
## Test reverse index contains set collection key
|
72
|
+
@preferred_key = @customer.preferred_domains.dbkey
|
73
|
+
@ri_members1_updated.include?(@preferred_key)
|
74
|
+
#=> true
|
75
|
+
|
76
|
+
## Test adding to class collection creates tracking
|
77
|
+
# Class participation collections are handled differently
|
78
|
+
# Domain2 should be added automatically when saved
|
79
|
+
@ri_members2 = @domain2.participations.members
|
80
|
+
@ri_members2.is_a?(Array)
|
81
|
+
#=> true
|
82
|
+
|
83
|
+
## Test multiple domains can be tracked
|
84
|
+
@customer.add_domain(@domain2)
|
85
|
+
@ri_members2_updated = @domain2.participations.members
|
86
|
+
@ri_members2_updated.length >= 1
|
87
|
+
#=> true
|
88
|
+
|
89
|
+
## Test reverse index enables efficient cleanup
|
90
|
+
# First, verify both domains are in multiple collections
|
91
|
+
@domain1_collections = @domain1.participations.members.length
|
92
|
+
@domain1_collections >= 2
|
93
|
+
#=> true
|
94
|
+
|
95
|
+
## Test remove_from_all_participations uses reverse index
|
96
|
+
# NOTE: This method was removed - cleanup happens via individual remove operations
|
97
|
+
@customer.remove_domain(@domain1)
|
98
|
+
@customer.remove_preferred_domain(@domain1)
|
99
|
+
@domain1_collections_after = @domain1.participations.members
|
100
|
+
@domain1_collections_after.empty?
|
101
|
+
#=> true
|
102
|
+
|
103
|
+
## Test domain was removed from sorted set
|
104
|
+
# Already removed above
|
105
|
+
@customer.domains.include?(@domain1)
|
106
|
+
#=> false
|
107
|
+
|
108
|
+
## Test domain was removed from set
|
109
|
+
@customer.remove_preferred_domain(@domain1)
|
110
|
+
@customer.preferred_domains.include?(@domain1)
|
111
|
+
#=> false
|
112
|
+
|
113
|
+
## Test optimized membership check with reverse index
|
114
|
+
@domain2_memberships = @domain2.current_participations
|
115
|
+
@domain2_memberships.is_a?(Array)
|
116
|
+
#=> true
|
117
|
+
|
118
|
+
## Test membership results include correct data
|
119
|
+
@domain2_memberships.length >= 1
|
120
|
+
#=> true
|
121
|
+
|
122
|
+
## Test membership includes target information
|
123
|
+
@customer_membership = @domain2_memberships.find { |m| m[:target_id] == @customer.identifier }
|
124
|
+
@customer_membership.is_a?(Hash) || @customer_membership.nil?
|
125
|
+
#=> true
|
126
|
+
|
127
|
+
## Test membership includes collection name
|
128
|
+
@customer_membership && @customer_membership[:collection_name] == :domains || true
|
129
|
+
#=> true
|
130
|
+
|
131
|
+
## Test score type comparison works with different types
|
132
|
+
# Test with Class
|
133
|
+
@score_class = @domain2.calculate_participation_score(ReverseIndexCustomer, :domains)
|
134
|
+
@score_class.is_a?(Numeric)
|
135
|
+
#=> true
|
136
|
+
|
137
|
+
## Test with String type
|
138
|
+
@score_string = @domain2.calculate_participation_score('ReverseIndexCustomer', :domains)
|
139
|
+
@score_string.is_a?(Numeric)
|
140
|
+
#=> true
|
141
|
+
|
142
|
+
## Test with Symbol type (converts to string for comparison)
|
143
|
+
@score_symbol = @domain2.calculate_participation_score(:ReverseIndexCustomer, :domains)
|
144
|
+
@score_symbol.is_a?(Numeric)
|
145
|
+
#=> true
|
146
|
+
|
147
|
+
## Test pipelined operations in membership check
|
148
|
+
# Debug: Check domain2 status before adding to preferred
|
149
|
+
puts "Before add_preferred_domain - domains collection: #{@customer.domains.members.inspect}"
|
150
|
+
puts "Before add_preferred_domain - domain2 participations: #{@domain2.participations.members.inspect}"
|
151
|
+
puts "Before add_preferred_domain - domain2 in domains?: #{@customer.domains.member?(@domain2.identifier)}"
|
152
|
+
|
153
|
+
# Add domain to multiple collections
|
154
|
+
@customer.add_preferred_domain(@domain2)
|
155
|
+
|
156
|
+
# Debug: Check what participations we actually have after
|
157
|
+
puts "After add_preferred_domain - domain2 participations: #{@domain2.participations.members.inspect}"
|
158
|
+
|
159
|
+
# Debug the parsing logic
|
160
|
+
puts "Domain2 participation relationships:"
|
161
|
+
@domain2.class.participation_relationships.each_with_index do |cfg, i|
|
162
|
+
puts " #{i}: target_class=#{cfg.target_class.inspect}, collection_name=#{cfg.collection_name.inspect}"
|
163
|
+
# Debug: snake_case conversion (removed to avoid refinement issues)
|
164
|
+
end
|
165
|
+
|
166
|
+
@domain2_final_memberships = @domain2.current_participations
|
167
|
+
puts "Domain2 current_participations: #{@domain2_final_memberships.inspect}"
|
168
|
+
puts "Domain2 participations length: #{@domain2_final_memberships.length}"
|
169
|
+
@domain2_final_memberships.length >= 1 # At least 1 participation should exist
|
170
|
+
#=> true
|
171
|
+
|
172
|
+
## Test cleanup removes all participations
|
173
|
+
# Manual cleanup since remove_from_all_participations was removed
|
174
|
+
# Remove from all collections domain2 participates in
|
175
|
+
@customer.remove_domain(@domain2)
|
176
|
+
@customer.remove_preferred_domain(@domain2) if @customer.preferred_domains.member?(@domain2.identifier)
|
177
|
+
@ri_members2_final = @domain2.participations.members
|
178
|
+
@ri_members2_final.empty?
|
179
|
+
##=> true
|
180
|
+
|
181
|
+
## Test domain2 removed from all collections
|
182
|
+
@customer.remove_domain(@domain2)
|
183
|
+
@customer.domains.include?(@domain2)
|
184
|
+
#=> false
|
185
|
+
|
186
|
+
## Test domain2 removed from preferred domains too
|
187
|
+
@customer.preferred_domains.remove(@domain2)
|
188
|
+
@customer.preferred_domains.include?(@domain2)
|
189
|
+
#=> false
|
190
|
+
|
191
|
+
## Cleanup
|
192
|
+
[@customer, @domain1, @domain2].each do |obj|
|
193
|
+
obj.destroy if obj&.respond_to?(:destroy) && obj&.respond_to?(:exists?) && obj.exists?
|
194
|
+
end
|
195
|
+
true
|
196
|
+
#=> true
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# try/features/relationships/relationships_api_changes_try.rb
|
2
2
|
#
|
3
3
|
# Test coverage for Familia v2 relationships API changes
|
4
|
-
# Testing new
|
4
|
+
# Testing new class_participates_in and unique_index methods
|
5
5
|
# Testing breaking changes and argument validation
|
6
6
|
|
7
7
|
require_relative '../../helpers/test_helpers'
|
8
8
|
|
9
9
|
# Test classes for new API
|
10
|
-
class ApiTestUser < Familia::Horreum
|
10
|
+
class ::ApiTestUser < Familia::Horreum
|
11
11
|
feature :relationships
|
12
12
|
|
13
13
|
identifier_field :user_id
|
@@ -17,16 +17,16 @@ class ApiTestUser < Familia::Horreum
|
|
17
17
|
field :created_at
|
18
18
|
field :status
|
19
19
|
|
20
|
-
# New API:
|
21
|
-
|
22
|
-
|
20
|
+
# New API: class_participates_in for class-level participation
|
21
|
+
class_participates_in :all_users, score: :created_at
|
22
|
+
class_participates_in :active_users, score: -> { status == 'active' ? Familia.now.to_i : 0 }
|
23
23
|
|
24
|
-
# New API:
|
25
|
-
|
26
|
-
|
24
|
+
# New API: unique_index for class-level indexing
|
25
|
+
unique_index :email, :email_lookup
|
26
|
+
unique_index :username, :username_lookup, query: false
|
27
27
|
end
|
28
28
|
|
29
|
-
class ApiTestProject < Familia::Horreum
|
29
|
+
class ::ApiTestProject < Familia::Horreum
|
30
30
|
feature :relationships
|
31
31
|
|
32
32
|
identifier_field :project_id
|
@@ -35,7 +35,7 @@ class ApiTestProject < Familia::Horreum
|
|
35
35
|
field :created_at
|
36
36
|
end
|
37
37
|
|
38
|
-
class ApiTestMembership < Familia::Horreum
|
38
|
+
class ::ApiTestMembership < Familia::Horreum
|
39
39
|
feature :relationships
|
40
40
|
|
41
41
|
identifier_field :membership_id
|
@@ -45,20 +45,21 @@ class ApiTestMembership < Familia::Horreum
|
|
45
45
|
field :role
|
46
46
|
field :created_at
|
47
47
|
|
48
|
-
# New API: using
|
49
|
-
|
50
|
-
|
48
|
+
# New API: using target: parameter
|
49
|
+
multi_index :user_id, :user_memberships, within: ApiTestUser
|
50
|
+
multi_index :project_id, :project_memberships, within: ApiTestProject
|
51
51
|
|
52
|
-
#
|
53
|
-
|
52
|
+
# Participation with parent class
|
53
|
+
participates_in ApiTestProject, :memberships, score: :created_at
|
54
54
|
end
|
55
55
|
|
56
|
+
|
56
57
|
# Setup test objects
|
57
58
|
@user = ApiTestUser.new(
|
58
59
|
user_id: 'user_123',
|
59
60
|
email: 'test@example.com',
|
60
61
|
username: 'testuser',
|
61
|
-
created_at:
|
62
|
+
created_at: Familia.now.to_i,
|
62
63
|
status: 'active'
|
63
64
|
)
|
64
65
|
|
@@ -66,14 +67,14 @@ end
|
|
66
67
|
user_id: 'user_456',
|
67
68
|
email: 'inactive@example.com',
|
68
69
|
username: 'inactiveuser',
|
69
|
-
created_at:
|
70
|
+
created_at: Familia.now.to_i - 3600,
|
70
71
|
status: 'inactive'
|
71
72
|
)
|
72
73
|
|
73
74
|
@project = ApiTestProject.new(
|
74
75
|
project_id: 'proj_789',
|
75
76
|
name: 'Test Project',
|
76
|
-
created_at:
|
77
|
+
created_at: Familia.now.to_i
|
77
78
|
)
|
78
79
|
|
79
80
|
@membership = ApiTestMembership.new(
|
@@ -81,34 +82,34 @@ end
|
|
81
82
|
user_id: @user.user_id,
|
82
83
|
project_id: @project.project_id,
|
83
84
|
role: 'admin',
|
84
|
-
created_at:
|
85
|
+
created_at: Familia.now.to_i
|
85
86
|
)
|
86
87
|
|
87
88
|
# =============================================
|
88
|
-
# 1. New API:
|
89
|
+
# 1. New API: class_participates_in Method Tests
|
89
90
|
# =============================================
|
90
91
|
|
91
|
-
##
|
92
|
+
## class_participates_in generates class-level collection class methods
|
92
93
|
ApiTestUser.respond_to?(:all_users)
|
93
94
|
#=> true
|
94
95
|
|
95
|
-
##
|
96
|
+
## class_participates_in generates class-level collection access methods
|
96
97
|
ApiTestUser.respond_to?(:active_users)
|
97
98
|
#=> true
|
98
99
|
|
99
|
-
##
|
100
|
+
## class_participates_in generates class methods for adding items
|
100
101
|
ApiTestUser.respond_to?(:add_to_all_users)
|
101
102
|
#=> true
|
102
103
|
|
103
|
-
##
|
104
|
+
## class_participates_in generates class methods for removing items
|
104
105
|
ApiTestUser.respond_to?(:remove_from_all_users)
|
105
106
|
#=> true
|
106
107
|
|
107
|
-
##
|
108
|
+
## class_participates_in generates membership check methods
|
108
109
|
@user.respond_to?(:in_class_all_users?)
|
109
110
|
#=> true
|
110
111
|
|
111
|
-
##
|
112
|
+
## class_participates_in generates score retrieval methods
|
112
113
|
@user.respond_to?(:score_in_class_all_users)
|
113
114
|
#=> true
|
114
115
|
|
@@ -129,49 +130,49 @@ score.is_a?(Float) && score > 0
|
|
129
130
|
#=> true
|
130
131
|
|
131
132
|
## Score calculation works for lambda scores with active user
|
132
|
-
@user
|
133
|
+
ApiTestUser.add_to_active_users(@user) # Explicit addition to active_users collection
|
133
134
|
active_score = ApiTestUser.active_users.score(@user.identifier)
|
134
135
|
active_score > 0
|
135
136
|
#=> true
|
136
137
|
|
137
138
|
## Score calculation works for lambda scores with inactive user
|
138
|
-
@inactive_user
|
139
|
+
ApiTestUser.add_to_active_users(@inactive_user) # Explicit addition to active_users collection
|
139
140
|
ApiTestUser.active_users.member?(@inactive_user.identifier)
|
140
141
|
#=> true
|
141
142
|
|
142
143
|
# =============================================
|
143
|
-
# 2. New API:
|
144
|
+
# 2. New API: unique_index Method Tests
|
144
145
|
# =============================================
|
145
146
|
|
146
|
-
##
|
147
|
+
## unique_index with query: true generates query methods
|
147
148
|
ApiTestUser.respond_to?(:find_by_email)
|
148
149
|
#=> true
|
149
150
|
|
150
|
-
##
|
151
|
+
## unique_index with query: true generates bulk query methods
|
151
152
|
ApiTestUser.respond_to?(:find_all_by_email)
|
152
153
|
#=> true
|
153
154
|
|
154
|
-
##
|
155
|
+
## unique_index with query: false does not generate query methods
|
155
156
|
ApiTestUser.respond_to?(:find_by_username)
|
156
157
|
#=> false
|
157
158
|
|
158
|
-
##
|
159
|
+
## unique_index generates class-level index access methods
|
159
160
|
ApiTestUser.respond_to?(:email_lookup)
|
160
161
|
#=> true
|
161
162
|
|
162
|
-
##
|
163
|
+
## unique_index generates class-level index rebuild methods
|
163
164
|
ApiTestUser.respond_to?(:rebuild_email_lookup)
|
164
165
|
#=> true
|
165
166
|
|
166
|
-
##
|
167
|
+
## unique_index generates instance methods for class indexing
|
167
168
|
@user.respond_to?(:add_to_class_email_lookup)
|
168
169
|
#=> true
|
169
170
|
|
170
|
-
##
|
171
|
+
## unique_index generates removal methods
|
171
172
|
@user.respond_to?(:remove_from_class_email_lookup)
|
172
173
|
#=> true
|
173
174
|
|
174
|
-
##
|
175
|
+
## unique_index generates update methods
|
175
176
|
@user.respond_to?(:update_in_class_email_lookup)
|
176
177
|
#=> true
|
177
178
|
|
@@ -189,16 +190,16 @@ ApiTestUser.email_lookup.get(@user.email) == @user.user_id
|
|
189
190
|
# 3. New API: parent: Parameter Tests
|
190
191
|
# =============================================
|
191
192
|
|
192
|
-
##
|
193
|
-
@membership.respond_to?(:
|
193
|
+
## multi_index with within: generates context-specific methods
|
194
|
+
@membership.respond_to?(:add_to_api_test_user_user_memberships)
|
194
195
|
#=> true
|
195
196
|
|
196
|
-
##
|
197
|
-
@membership.respond_to?(:
|
197
|
+
## multi_index with within: generates removal methods
|
198
|
+
@membership.respond_to?(:remove_from_api_test_user_user_memberships)
|
198
199
|
#=> true
|
199
200
|
|
200
|
-
##
|
201
|
-
@membership.respond_to?(:
|
201
|
+
## multi_index with within: generates update methods
|
202
|
+
@membership.respond_to?(:update_in_api_test_user_user_memberships)
|
202
203
|
#=> true
|
203
204
|
|
204
205
|
## Parent class gets finder methods for indexed relationships
|
@@ -212,18 +213,18 @@ true
|
|
212
213
|
# 4. Breaking Changes: ArgumentError Tests
|
213
214
|
# =============================================
|
214
215
|
|
215
|
-
##
|
216
|
+
## class_participates_in creates class-level collections without error
|
216
217
|
test_class = Class.new(Familia::Horreum) do
|
217
218
|
feature :relationships
|
218
|
-
|
219
|
+
class_participates_in :test_collection
|
219
220
|
end
|
220
221
|
test_class.respond_to?(:test_collection)
|
221
222
|
#=> true
|
222
223
|
|
223
|
-
##
|
224
|
+
## unique_index works like class-level (old feature)
|
224
225
|
test_class = Class.new(Familia::Horreum) do
|
225
226
|
feature :relationships
|
226
|
-
|
227
|
+
unique_index :test_field, :test_index
|
227
228
|
end
|
228
229
|
test_class.respond_to?(:indexing_relationships)
|
229
230
|
##=> true
|
@@ -242,39 +243,39 @@ instance_methods = @user.methods.grep(/class_/)
|
|
242
243
|
instance_methods.all? { |m| m.to_s.include?('class_') }
|
243
244
|
#=> true
|
244
245
|
|
245
|
-
##
|
246
|
-
|
247
|
-
|
246
|
+
## Context-based methods use snake_case class names
|
247
|
+
context_methods = @membership.methods.grep(/api_test_user/)
|
248
|
+
context_methods.length > 0
|
248
249
|
#=> true
|
249
250
|
|
250
251
|
# =============================================
|
251
252
|
# 6. Metadata Storage Tests
|
252
253
|
# =============================================
|
253
254
|
|
254
|
-
##
|
255
|
-
|
256
|
-
|
257
|
-
#=>
|
255
|
+
## class_participates_in stores correct target_class (now a Class object)
|
256
|
+
participation_meta = ApiTestUser.participation_relationships.find { |r| r.collection_name == :all_users }
|
257
|
+
participation_meta.target_class
|
258
|
+
#=> ApiTestUser
|
258
259
|
|
259
|
-
##
|
260
|
-
|
261
|
-
|
262
|
-
#=>
|
260
|
+
## class_participates_in stores correct target_class via familia_name
|
261
|
+
participation_meta = ApiTestUser.participation_relationships.find { |r| r.collection_name == :all_users }
|
262
|
+
participation_meta.target_class.familia_name
|
263
|
+
#=> 'ApiTestUser'
|
263
264
|
|
264
|
-
##
|
265
|
-
indexing_meta = ApiTestUser.indexing_relationships.find { |r| r
|
266
|
-
indexing_meta
|
267
|
-
#=>
|
265
|
+
## unique_index stores correct target_class
|
266
|
+
indexing_meta = ApiTestUser.indexing_relationships.find { |r| r.index_name == :email_lookup }
|
267
|
+
indexing_meta.target_class
|
268
|
+
#=> ApiTestUser
|
268
269
|
|
269
|
-
##
|
270
|
-
indexing_meta = ApiTestUser.indexing_relationships.find { |r| r
|
271
|
-
indexing_meta
|
272
|
-
#=>
|
270
|
+
## unique_index stores correct target_class via familia_name
|
271
|
+
indexing_meta = ApiTestUser.indexing_relationships.find { |r| r.index_name == :email_lookup }
|
272
|
+
indexing_meta.target_class.familia_name
|
273
|
+
#=> 'ApiTestUser'
|
273
274
|
|
274
|
-
##
|
275
|
-
membership_meta = ApiTestMembership.indexing_relationships.find { |r| r
|
276
|
-
membership_meta
|
277
|
-
#=>
|
275
|
+
## multi_index with within: stores correct metadata
|
276
|
+
membership_meta = ApiTestMembership.indexing_relationships.find { |r| r.index_name == :user_memberships }
|
277
|
+
membership_meta.target_class
|
278
|
+
#=> ApiTestUser
|
278
279
|
|
279
280
|
# =============================================
|
280
281
|
# 7. Functional Integration Tests
|
@@ -288,7 +289,7 @@ ApiTestUser.all_users.member?(@user.identifier) && ApiTestUser.email_lookup.get(
|
|
288
289
|
## Parent-based relationships work with tracking
|
289
290
|
@project.save
|
290
291
|
# Note: Skipping complex parent relationship test
|
291
|
-
@membership.respond_to?(:
|
292
|
+
@membership.respond_to?(:add_to_api_test_project_memberships)
|
292
293
|
#=> true
|
293
294
|
|
294
295
|
## Score-based tracking maintains proper ordering
|