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
@@ -1,266 +0,0 @@
|
|
1
|
-
# Relationship Methods
|
2
|
-
|
3
|
-
Here are the methods automatically generated for each relationship type in the new clean API:
|
4
|
-
|
5
|
-
## member_of Relationships
|
6
|
-
|
7
|
-
When you declare:
|
8
|
-
```ruby
|
9
|
-
class Domain < Familia::Horreum
|
10
|
-
member_of Customer, :domains
|
11
|
-
end
|
12
|
-
```
|
13
|
-
|
14
|
-
**Generated methods on Domain instances:**
|
15
|
-
- `add_to_customer_domains(customer)` - Add this domain to customer's domains collection
|
16
|
-
- `remove_from_customer_domains(customer)` - Remove this domain from customer's domains collection
|
17
|
-
- `in_customer_domains?(customer)` - Check if this domain is in customer's domains collection
|
18
|
-
|
19
|
-
**Collection << operator support:**
|
20
|
-
```ruby
|
21
|
-
customer.domains << domain # Clean Ruby-like syntax (equivalent to domain.add_to_customer_domains(customer))
|
22
|
-
```
|
23
|
-
|
24
|
-
The method names follow the pattern: `{action}_to_{lowercase_class_name}_{collection_name}`
|
25
|
-
|
26
|
-
## tracked_in Relationships
|
27
|
-
|
28
|
-
### Class-Level Tracking (class_tracked_in)
|
29
|
-
When you declare:
|
30
|
-
```ruby
|
31
|
-
class Customer < Familia::Horreum
|
32
|
-
class_tracked_in :all_customers, score: :created_at
|
33
|
-
end
|
34
|
-
```
|
35
|
-
|
36
|
-
**Generated class methods:**
|
37
|
-
- `Customer.add_to_all_customers(customer)` - Add customer to class-level tracking
|
38
|
-
- `Customer.remove_from_all_customers(customer)` - Remove customer from class-level tracking
|
39
|
-
- `Customer.all_customers` - Access the sorted set collection directly
|
40
|
-
|
41
|
-
**Automatic behavior:**
|
42
|
-
- Objects are automatically added to class-level tracking collections when saved
|
43
|
-
- No manual calls required for basic tracking
|
44
|
-
|
45
|
-
### Relationship Tracking (tracked_in with parent class)
|
46
|
-
When you declare:
|
47
|
-
```ruby
|
48
|
-
class User < Familia::Horreum
|
49
|
-
tracked_in Team, :active_users, score: :last_seen
|
50
|
-
end
|
51
|
-
```
|
52
|
-
|
53
|
-
**Generated methods:**
|
54
|
-
- Team instance methods for managing the active_users collection
|
55
|
-
- Automatic score calculation based on the provided lambda or field
|
56
|
-
|
57
|
-
## indexed_by Relationships
|
58
|
-
|
59
|
-
The `indexed_by` method creates Redis hash-based indexes for O(1) field lookups with automatic management.
|
60
|
-
|
61
|
-
### Class-Level Indexing (class_indexed_by)
|
62
|
-
When you declare:
|
63
|
-
```ruby
|
64
|
-
class Customer < Familia::Horreum
|
65
|
-
class_indexed_by :email, :email_lookup
|
66
|
-
end
|
67
|
-
```
|
68
|
-
|
69
|
-
**Generated methods:**
|
70
|
-
- **Instance methods**: `customer.add_to_class_email_lookup`, `customer.remove_from_class_email_lookup`
|
71
|
-
- **Class methods**: `Customer.email_lookup` (returns hash), `Customer.find_by_email(email)`
|
72
|
-
|
73
|
-
**Automatic behavior:**
|
74
|
-
- Objects are automatically added to class-level indexes when saved
|
75
|
-
- Index updates happen transparently on field changes
|
76
|
-
|
77
|
-
Redis key pattern: `customer:email_lookup`
|
78
|
-
|
79
|
-
### Relationship-Scoped Indexing (indexed_by with parent:)
|
80
|
-
When you declare:
|
81
|
-
```ruby
|
82
|
-
class Domain < Familia::Horreum
|
83
|
-
indexed_by :name, :domain_index, parent: Customer
|
84
|
-
end
|
85
|
-
```
|
86
|
-
|
87
|
-
**Generated class methods on Customer:**
|
88
|
-
- `Customer#find_by_name(domain_name)` - Find domain by name within this customer
|
89
|
-
- `Customer#find_all_by_name(domain_names)` - Find multiple domains by names
|
90
|
-
|
91
|
-
Redis key pattern: `domain:domain_index` (all stored at class level for consistency)
|
92
|
-
|
93
|
-
### When to Use Each Context
|
94
|
-
- **Class-level context (`class_indexed_by`)**: Use for system-wide lookups where the field value should be unique across all instances
|
95
|
-
- Examples: email addresses, usernames, API keys
|
96
|
-
- **Relationship context (`parent:` parameter)**: Use for relationship-scoped lookups where the field value is unique within a specific context
|
97
|
-
- Examples: domain names per customer, project names per team
|
98
|
-
|
99
|
-
## Complete Example
|
100
|
-
|
101
|
-
From the relationships example file, you can see the new clean API in action:
|
102
|
-
|
103
|
-
```ruby
|
104
|
-
# Domain declares membership in Customer collections
|
105
|
-
class Domain < Familia::Horreum
|
106
|
-
member_of Customer, :domains
|
107
|
-
class_tracked_in :active_domains, score: -> { status == 'active' ? Time.now.to_i : 0 }
|
108
|
-
end
|
109
|
-
|
110
|
-
class Customer < Familia::Horreum
|
111
|
-
class_indexed_by :email, :email_lookup
|
112
|
-
class_tracked_in :all_customers, score: :created_at
|
113
|
-
end
|
114
|
-
```
|
115
|
-
|
116
|
-
**Usage with automatic behavior:**
|
117
|
-
```ruby
|
118
|
-
# Create and save objects (automatic indexing and tracking)
|
119
|
-
customer = Customer.new(email: "admin@acme.com", name: "Acme Corp")
|
120
|
-
customer.save # Automatically added to email_lookup and all_customers
|
121
|
-
|
122
|
-
domain = Domain.new(name: "acme.com", status: "active")
|
123
|
-
domain.save # Automatically added to active_domains
|
124
|
-
|
125
|
-
# Clean relationship syntax
|
126
|
-
customer.domains << domain # Ruby-like collection syntax
|
127
|
-
|
128
|
-
# Query relationships
|
129
|
-
domain.in_customer_domains?(customer) # => true
|
130
|
-
customer.domains.member?(domain.identifier) # => true
|
131
|
-
|
132
|
-
# O(1) lookups with automatic management
|
133
|
-
found_id = Customer.email_lookup.get("admin@acme.com")
|
134
|
-
```
|
135
|
-
|
136
|
-
## Method Naming Conventions
|
137
|
-
|
138
|
-
The relationship system uses consistent naming patterns:
|
139
|
-
- **member_of**: `{add_to|remove_from|in}_#{parent_class.downcase}_#{collection_name}`
|
140
|
-
- **class_tracked_in**: `{add_to|remove_from}_#{collection_name}` (class methods)
|
141
|
-
- **class_indexed_by**: `{add_to|remove_from}_class_#{index_name}` (instance methods)
|
142
|
-
- **indexed_by with parent**: `{add_to|remove_from}_#{parent_class.downcase}_#{index_name}` (instance methods)
|
143
|
-
|
144
|
-
## Key Benefits
|
145
|
-
|
146
|
-
- **Automatic management**: Save operations update indexes and tracking automatically
|
147
|
-
- **Ruby-idiomatic**: Use `<<` operator for natural collection syntax
|
148
|
-
- **Consistent storage**: All indexes stored at class level for architectural simplicity
|
149
|
-
- **Clean API**: Removed complex global vs parent conditionals for simpler method generation
|
150
|
-
|
151
|
-
|
152
|
-
## Context Parameter Usage Patterns
|
153
|
-
|
154
|
-
The `context` parameter in `indexed_by` is a fundamental architectural decision that determines index scope and ownership. Here are practical patterns for when to use each approach:
|
155
|
-
|
156
|
-
### Global Context Pattern
|
157
|
-
Use `class_indexed_by` when field values should be unique system-wide:
|
158
|
-
|
159
|
-
```ruby
|
160
|
-
class User < Familia::Horreum
|
161
|
-
feature :relationships
|
162
|
-
|
163
|
-
identifier_field :user_id
|
164
|
-
field :user_id, :email, :username
|
165
|
-
|
166
|
-
# System-wide unique email lookup
|
167
|
-
class_indexed_by :email, :email_lookup
|
168
|
-
class_indexed_by :username, :username_lookup
|
169
|
-
end
|
170
|
-
|
171
|
-
# Usage:
|
172
|
-
user.add_to_global_email_lookup
|
173
|
-
found_user_id = User.email_lookup.get("john@example.com")
|
174
|
-
```
|
175
|
-
|
176
|
-
**Redis keys generated**: `global:email_lookup`, `global:username_lookup`
|
177
|
-
|
178
|
-
### Parent Context Pattern
|
179
|
-
Use `parent: SomeClass` when field values are unique within a specific parent context:
|
180
|
-
|
181
|
-
```ruby
|
182
|
-
class Customer < Familia::Horreum
|
183
|
-
feature :relationships
|
184
|
-
|
185
|
-
identifier_field :custid
|
186
|
-
field :custid, :name
|
187
|
-
sorted_set :domains
|
188
|
-
end
|
189
|
-
|
190
|
-
class Domain < Familia::Horreum
|
191
|
-
feature :relationships
|
192
|
-
|
193
|
-
identifier_field :domain_id
|
194
|
-
field :domain_id, :name, :subdomain
|
195
|
-
|
196
|
-
# Domains are unique per customer (customer can't have duplicate domain names)
|
197
|
-
indexed_by :name, :domain_index, parent: Customer
|
198
|
-
indexed_by :subdomain, :subdomain_index, parent: Customer
|
199
|
-
end
|
200
|
-
|
201
|
-
# Usage:
|
202
|
-
customer = Customer.new(custid: "cust_123")
|
203
|
-
customer.find_by_name("example.com") # Find domain within this customer
|
204
|
-
customer.find_all_by_subdomain(["www", "api"]) # Find multiple subdomains
|
205
|
-
```
|
206
|
-
|
207
|
-
**Redis keys generated**: `customer:cust_123:domain_index`, `customer:cust_123:subdomain_index`
|
208
|
-
|
209
|
-
### Mixed Pattern Example
|
210
|
-
A real-world example showing both patterns:
|
211
|
-
|
212
|
-
```ruby
|
213
|
-
class ApiKey < Familia::Horreum
|
214
|
-
feature :relationships
|
215
|
-
|
216
|
-
identifier_field :key_id
|
217
|
-
field :key_id, :key_hash, :name, :scope
|
218
|
-
|
219
|
-
# API key hashes must be globally unique
|
220
|
-
class_indexed_by :key_hash, :global_key_lookup
|
221
|
-
|
222
|
-
# But key names can be reused across different customers
|
223
|
-
indexed_by :name, :customer_key_lookup, parent: Customer
|
224
|
-
indexed_by :scope, :scope_lookup, parent: Customer
|
225
|
-
end
|
226
|
-
|
227
|
-
# Usage examples:
|
228
|
-
# Global lookup (system-wide unique)
|
229
|
-
ApiKey.key_lookup.get("sha256:abc123...")
|
230
|
-
|
231
|
-
# Scoped lookup (unique per customer)
|
232
|
-
customer = Customer.new(custid: "cust_456")
|
233
|
-
customer.find_by_name("production-api-key")
|
234
|
-
customer.find_all_by_scope(["read", "write"])
|
235
|
-
```
|
236
|
-
|
237
|
-
### Migrating Guide
|
238
|
-
If you have existing code with old syntax, here's how to update it:
|
239
|
-
|
240
|
-
```ruby
|
241
|
-
# ❌ Old syntax (pre-refactoring)
|
242
|
-
indexed_by :email_lookup, field: :email
|
243
|
-
indexed_by :email, :email_lookup, context: :global
|
244
|
-
tracked_in :global, :all_users, score: :created_at
|
245
|
-
|
246
|
-
# ✅ New syntax - Class-level scope
|
247
|
-
class_indexed_by :email, :email_lookup
|
248
|
-
class_tracked_in :all_users, score: :created_at
|
249
|
-
|
250
|
-
# ✅ New syntax - Relationship scope
|
251
|
-
indexed_by :email, :customer_email_lookup, parent: Customer
|
252
|
-
tracked_in Customer, :user_activity, score: :last_seen
|
253
|
-
```
|
254
|
-
|
255
|
-
**Key Changes**:
|
256
|
-
1. **Class-level relationships**: Use `class_` prefix (`class_tracked_in`, `class_indexed_by`)
|
257
|
-
2. **Relationship-scoped**: Use `parent:` parameter instead of `:global` symbol
|
258
|
-
3. **Automatic management**: Objects automatically added to class-level collections on save
|
259
|
-
4. **Clean syntax**: Collections support `<<` operator for Ruby-like relationship building
|
260
|
-
5. **Simplified storage**: All indexes stored at class level (parent is conceptual only)
|
261
|
-
|
262
|
-
**Behavioral Changes**:
|
263
|
-
- Save operations now automatically update indexes and class-level tracking
|
264
|
-
- No more manual `add_to_*` calls required for basic functionality
|
265
|
-
- `<<` operator works naturally with all collection types
|
266
|
-
- Method generation simplified without complex global/parent conditionals
|
@@ -1,228 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
# examples/redis_command_validation_example.rb
|
4
|
-
#
|
5
|
-
# Comprehensive example demonstrating Redis command validation for Familia
|
6
|
-
# This example shows how to validate that Redis operations execute exactly
|
7
|
-
# as expected, with particular focus on atomic operations.
|
8
|
-
|
9
|
-
require_relative '../lib/familia'
|
10
|
-
require_relative '../lib/familia/validation'
|
11
|
-
|
12
|
-
# Enable database logging for visibility
|
13
|
-
Familia.enable_database_logging = true
|
14
|
-
Familia.enable_database_counter = true
|
15
|
-
|
16
|
-
# Example models for validation demonstration
|
17
|
-
class Account < Familia::Horreum
|
18
|
-
identifier_field :account_id
|
19
|
-
field :account_id
|
20
|
-
field :balance
|
21
|
-
field :status
|
22
|
-
field :last_updated
|
23
|
-
end
|
24
|
-
|
25
|
-
class TransferService
|
26
|
-
def self.atomic_transfer(from_account, to_account, amount)
|
27
|
-
# Proper atomic implementation using Familia transaction
|
28
|
-
from_balance = from_account.balance.to_i - amount
|
29
|
-
to_balance = to_account.balance.to_i + amount
|
30
|
-
|
31
|
-
Familia.transaction do |conn|
|
32
|
-
conn.hset(from_account.dbkey, 'balance', from_balance.to_s)
|
33
|
-
conn.hset(to_account.dbkey, 'balance', to_balance.to_s)
|
34
|
-
conn.hset(from_account.dbkey, 'last_updated', Time.now.to_i.to_s)
|
35
|
-
conn.hset(to_account.dbkey, 'last_updated', Time.now.to_i.to_s)
|
36
|
-
end
|
37
|
-
|
38
|
-
# Update local state
|
39
|
-
from_account.balance = from_balance.to_s
|
40
|
-
to_account.balance = to_balance.to_s
|
41
|
-
end
|
42
|
-
|
43
|
-
def self.non_atomic_transfer(from_account, to_account, amount)
|
44
|
-
# Non-atomic implementation (BAD - for demonstration)
|
45
|
-
from_account.balance = (from_account.balance.to_i - amount).to_s
|
46
|
-
to_account.balance = (to_account.balance.to_i + amount).to_s
|
47
|
-
|
48
|
-
from_account.save
|
49
|
-
to_account.save
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
puts '🧪 Redis Command Validation Framework Demo'
|
54
|
-
puts '=' * 50
|
55
|
-
|
56
|
-
# Clean up any existing test data
|
57
|
-
cleanup_keys = Familia.dbclient.keys('account:*')
|
58
|
-
Familia.dbclient.del(*cleanup_keys) if cleanup_keys.any?
|
59
|
-
|
60
|
-
# Example 1: Basic Command Recording
|
61
|
-
puts "\n1. Basic Command Recording"
|
62
|
-
puts '-' * 30
|
63
|
-
|
64
|
-
CommandRecorder = Familia::Validation::CommandRecorder
|
65
|
-
CommandRecorder.start_recording
|
66
|
-
|
67
|
-
account = Account.new(account_id: 'acc001', balance: '1000', status: 'active')
|
68
|
-
account.save
|
69
|
-
|
70
|
-
commands = CommandRecorder.stop_recording
|
71
|
-
puts "Recorded #{commands.command_count} commands:"
|
72
|
-
commands.commands.each { |cmd| puts " #{cmd}" }
|
73
|
-
|
74
|
-
# Example 2: Transaction Detection
|
75
|
-
puts "\n2. Transaction Detection"
|
76
|
-
puts '-' * 30
|
77
|
-
|
78
|
-
CommandRecorder.start_recording
|
79
|
-
|
80
|
-
acc1 = Account.new(account_id: 'acc002', balance: '2000')
|
81
|
-
acc2 = Account.new(account_id: 'acc003', balance: '500')
|
82
|
-
acc1.save
|
83
|
-
acc2.save
|
84
|
-
|
85
|
-
TransferService.atomic_transfer(acc1, acc2, 500)
|
86
|
-
|
87
|
-
commands = CommandRecorder.stop_recording
|
88
|
-
puts "Commands executed: #{commands.command_count}"
|
89
|
-
puts "Transactions detected: #{commands.transaction_count}"
|
90
|
-
|
91
|
-
if commands.transaction_blocks.any?
|
92
|
-
tx = commands.transaction_blocks.first
|
93
|
-
puts "Transaction commands: #{tx.command_count}"
|
94
|
-
tx.commands.each { |cmd| puts " [TX] #{cmd}" }
|
95
|
-
end
|
96
|
-
|
97
|
-
# Example 3: Validation with Expectations DSL
|
98
|
-
puts "\n3. Command Validation with Expectations"
|
99
|
-
puts '-' * 30
|
100
|
-
|
101
|
-
begin
|
102
|
-
validator = Familia::Validation::Validator.new
|
103
|
-
|
104
|
-
# This should pass - we expect the exact Redis commands
|
105
|
-
result = validator.validate do |expect|
|
106
|
-
expect.transaction do |tx|
|
107
|
-
tx.hset('account:acc004:object', 'balance', '1500')
|
108
|
-
.hset('account:acc005:object', 'balance', '1000')
|
109
|
-
.hset('account:acc004:object', 'last_updated', Familia::Validation::ArgumentMatcher.new(:any_string))
|
110
|
-
.hset('account:acc005:object', 'last_updated', Familia::Validation::ArgumentMatcher.new(:any_string))
|
111
|
-
end
|
112
|
-
|
113
|
-
# Execute the operation
|
114
|
-
acc4 = Account.new(account_id: 'acc004', balance: '2000')
|
115
|
-
acc5 = Account.new(account_id: 'acc005', balance: '500')
|
116
|
-
acc4.save
|
117
|
-
acc5.save
|
118
|
-
|
119
|
-
TransferService.atomic_transfer(acc4, acc5, 500)
|
120
|
-
end
|
121
|
-
|
122
|
-
puts "Validation result: #{result.valid? ? 'PASS ✅' : 'FAIL ❌'}"
|
123
|
-
puts "Summary: #{result.summary}"
|
124
|
-
rescue StandardError => e
|
125
|
-
puts "Validation demo encountered error: #{e.message}"
|
126
|
-
puts 'This is expected as the framework needs Redis middleware integration'
|
127
|
-
end
|
128
|
-
|
129
|
-
# Example 4: Performance Analysis
|
130
|
-
puts "\n4. Performance Analysis"
|
131
|
-
puts '-' * 30
|
132
|
-
|
133
|
-
begin
|
134
|
-
commands = Familia::Validation.capture_commands do
|
135
|
-
# Create multiple accounts
|
136
|
-
accounts = []
|
137
|
-
(1..5).each do |i|
|
138
|
-
account = Account.new(account_id: "perf#{i}", balance: '1000')
|
139
|
-
account.save
|
140
|
-
accounts << account
|
141
|
-
end
|
142
|
-
|
143
|
-
# Perform operations
|
144
|
-
accounts[0].balance = '1100'
|
145
|
-
accounts[0].save
|
146
|
-
end
|
147
|
-
|
148
|
-
analyzer = Familia::Validation::PerformanceAnalyzer.new(commands)
|
149
|
-
analysis = analyzer.analyze
|
150
|
-
|
151
|
-
puts 'Performance Analysis:'
|
152
|
-
puts " Total Commands: #{analysis[:total_commands]}"
|
153
|
-
puts " Command Types: #{analysis[:command_type_breakdown].keys.join(', ')}"
|
154
|
-
puts " Efficiency Score: #{analysis[:efficiency_score]}/100"
|
155
|
-
rescue StandardError => e
|
156
|
-
puts "Performance analysis encountered error: #{e.message}"
|
157
|
-
end
|
158
|
-
|
159
|
-
# Example 5: Atomicity Validation
|
160
|
-
puts "\n5. Atomicity Validation"
|
161
|
-
puts '-' * 30
|
162
|
-
|
163
|
-
begin
|
164
|
-
# Test atomic vs non-atomic operations
|
165
|
-
acc6 = Account.new(account_id: 'acc006', balance: '3000')
|
166
|
-
acc7 = Account.new(account_id: 'acc007', balance: '1000')
|
167
|
-
acc6.save
|
168
|
-
acc7.save
|
169
|
-
|
170
|
-
# This should detect that atomic operations are properly used
|
171
|
-
validator = Familia::Validation::Validator.new(strict_atomicity: true)
|
172
|
-
|
173
|
-
commands = validator.capture_redis_commands do
|
174
|
-
TransferService.atomic_transfer(acc6, acc7, 1000)
|
175
|
-
end
|
176
|
-
|
177
|
-
atomicity_validator = Familia::Validation::AtomicityValidator.new(commands)
|
178
|
-
result = atomicity_validator.validate
|
179
|
-
|
180
|
-
puts "Atomicity validation: #{result.valid? ? 'PASS ✅' : 'FAIL ❌'}"
|
181
|
-
rescue StandardError => e
|
182
|
-
puts "Atomicity validation encountered error: #{e.message}"
|
183
|
-
end
|
184
|
-
|
185
|
-
puts "\n6. Framework Architecture Overview"
|
186
|
-
puts '-' * 30
|
187
|
-
puts "
|
188
|
-
The Redis Command Validation Framework provides:
|
189
|
-
|
190
|
-
🔍 Command Recording
|
191
|
-
- Captures all Redis commands with full context
|
192
|
-
- Tracks transaction boundaries (MULTI/EXEC)
|
193
|
-
- Records timing and performance metrics
|
194
|
-
|
195
|
-
📝 Expectations DSL
|
196
|
-
- Fluent API for defining expected command sequences
|
197
|
-
- Support for pattern matching and flexible ordering
|
198
|
-
- Transaction and pipeline validation
|
199
|
-
|
200
|
-
✅ Validation Engine
|
201
|
-
- Compares actual vs expected commands
|
202
|
-
- Validates atomicity of operations
|
203
|
-
- Provides detailed mismatch reports
|
204
|
-
|
205
|
-
🧪 Test Helpers
|
206
|
-
- Integration with tryouts framework
|
207
|
-
- Methods like assert_redis_commands, assert_atomic_operation
|
208
|
-
- Automatic setup and cleanup
|
209
|
-
|
210
|
-
⚡ Performance Analysis
|
211
|
-
- Command efficiency scoring
|
212
|
-
- N+1 pattern detection
|
213
|
-
- Transaction overhead analysis
|
214
|
-
|
215
|
-
Key Benefits:
|
216
|
-
• Brass-tacks Redis command validation
|
217
|
-
• Atomic operation verification
|
218
|
-
• Performance optimization insights
|
219
|
-
• Clear diagnostic messages
|
220
|
-
• Thread-safe operation
|
221
|
-
"
|
222
|
-
|
223
|
-
# Cleanup
|
224
|
-
cleanup_keys = Familia.dbclient.keys('account:*')
|
225
|
-
Familia.dbclient.del(*cleanup_keys) if cleanup_keys.any?
|
226
|
-
|
227
|
-
puts "\n🎉 Demo complete! The validation framework is ready for use."
|
228
|
-
puts ' See try/validation/ for comprehensive test examples.'
|