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
@@ -0,0 +1,200 @@
|
|
1
|
+
# Relationships Feature Guide
|
2
|
+
|
3
|
+
The Relationships feature transforms how you manage object associations in Familia applications. Instead of manually maintaining foreign keys and indexes, relationships provide automatic bidirectional links, efficient queries, and Ruby-like collection syntax that makes working with related objects feel natural and intuitive.
|
4
|
+
|
5
|
+
This guide provides a breadth-first introduction to the four core relationship capabilities: **participation**, **indexing**, **querying**, and **cascading operations**.
|
6
|
+
|
7
|
+
> [!TIP]
|
8
|
+
> Enable relationships with `feature :relationships`, define associations with `participates_in`, and use clean Ruby syntax like `customer.domains << domain` to manage relationships.
|
9
|
+
|
10
|
+
## What Are Relationships?
|
11
|
+
|
12
|
+
Relationships automate object associations in Familia, eliminating manual foreign key management:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
# Without relationships - manual and error-prone
|
16
|
+
customer.domain_ids.add(domain.identifier)
|
17
|
+
domain.customer_id = customer.identifier
|
18
|
+
|
19
|
+
# With relationships - automatic and clean
|
20
|
+
customer.domains << domain # Updates both sides automatically
|
21
|
+
```
|
22
|
+
|
23
|
+
**Key Benefits:**
|
24
|
+
- **Automatic bidirectional updates** - no manual synchronization
|
25
|
+
- **Ruby-like syntax** - familiar `<<` and collection operations
|
26
|
+
- **O(1) lookups** - efficient Valkey/Redis-backed indexing
|
27
|
+
- **Lifecycle management** - automatic cleanup and maintenance
|
28
|
+
|
29
|
+
## Core Relationship Capabilities
|
30
|
+
|
31
|
+
### 1. Participation - Bidirectional Object Links
|
32
|
+
|
33
|
+
Connect objects with automatic synchronization:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class User < Familia::Horreum
|
37
|
+
feature :relationships
|
38
|
+
set :teams # Collection holder
|
39
|
+
end
|
40
|
+
|
41
|
+
class Team < Familia::Horreum
|
42
|
+
feature :relationships
|
43
|
+
participates_in User, :teams # Declares participation
|
44
|
+
end
|
45
|
+
|
46
|
+
# Usage - automatic bidirectional updates
|
47
|
+
user.teams << team
|
48
|
+
team.in_user_teams?(user) # => true
|
49
|
+
```
|
50
|
+
|
51
|
+
**Many-to-Many Example:**
|
52
|
+
```ruby
|
53
|
+
class Project < Familia::Horreum
|
54
|
+
set :contributors, :reviewers
|
55
|
+
end
|
56
|
+
|
57
|
+
class Developer < Familia::Horreum
|
58
|
+
participates_in Project, :contributors
|
59
|
+
participates_in Project, :reviewers
|
60
|
+
end
|
61
|
+
|
62
|
+
project.contributors << alice # Alice contributes
|
63
|
+
project.reviewers << bob # Bob reviews
|
64
|
+
```
|
65
|
+
|
66
|
+
### 2. Indexing - Automatic Object Tracking
|
67
|
+
|
68
|
+
Enable O(1) lookups with automatic index management:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
class User < Familia::Horreum
|
72
|
+
feature :relationships
|
73
|
+
field :email, :created_at
|
74
|
+
|
75
|
+
# Global unique lookups
|
76
|
+
class_indexed_by :email, :email_lookup
|
77
|
+
|
78
|
+
# Scored tracking collections
|
79
|
+
class_participates_in :all_users, score: :created_at
|
80
|
+
end
|
81
|
+
|
82
|
+
# Automatic on save/destroy
|
83
|
+
User.find_by_email("alice@example.com") # O(1) lookup
|
84
|
+
User.all_users.range(0, 9) # Most recent 10 users
|
85
|
+
```
|
86
|
+
|
87
|
+
**Relationship-Scoped Indexing:**
|
88
|
+
```ruby
|
89
|
+
class Domain < Familia::Horreum
|
90
|
+
participates_in Customer, :domains
|
91
|
+
indexed_by :name, :domain_index, target: Customer # Unique per customer
|
92
|
+
end
|
93
|
+
|
94
|
+
customer.find_by_name("example.com") # Find domain within this customer
|
95
|
+
```
|
96
|
+
|
97
|
+
### 3. Querying - Ruby-like Collection Operations
|
98
|
+
|
99
|
+
Work with relationships like standard Ruby collections:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
# Standard collection operations
|
103
|
+
org.members << alice # Add relationship
|
104
|
+
org.members.merge([id1, id2, id3]) # Bulk additions
|
105
|
+
org.members.size # Count relationships
|
106
|
+
org.members.empty? # Check if any exist
|
107
|
+
|
108
|
+
# Set operations
|
109
|
+
common = org1.members & org2.members # Intersection
|
110
|
+
all = org1.members | org2.members # Union
|
111
|
+
|
112
|
+
# Load actual objects when needed
|
113
|
+
member_ids = org.members.to_a
|
114
|
+
members = Person.multiget(*member_ids) # Efficient bulk loading
|
115
|
+
```
|
116
|
+
|
117
|
+
> [!WARNING]
|
118
|
+
> Collections store identifiers, not objects. Use `multiget` for efficient bulk loading.
|
119
|
+
|
120
|
+
### 4. Cascading Operations - Scored Relationships
|
121
|
+
|
122
|
+
Use scores for time-based tracking and priority systems:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
class Timeline < Familia::Horreum
|
126
|
+
feature :relationships
|
127
|
+
sorted_set :events # Scored collection
|
128
|
+
end
|
129
|
+
|
130
|
+
class Event < Familia::Horreum
|
131
|
+
feature :relationships
|
132
|
+
field :timestamp, :priority
|
133
|
+
participates_in Timeline, :events, score: :timestamp
|
134
|
+
end
|
135
|
+
|
136
|
+
# Automatic scoring when relationships established
|
137
|
+
timeline.events << event # Uses event.timestamp as score
|
138
|
+
|
139
|
+
# Time-based and priority queries
|
140
|
+
recent = timeline.events.range_by_score((Time.now - 1.hour).to_i, '+inf')
|
141
|
+
top_priority = project.tasks.range(0, 4, order: 'DESC') # Highest priority first
|
142
|
+
```
|
143
|
+
|
144
|
+
**Common Scoring Patterns:**
|
145
|
+
- **Timestamps** for chronological ordering
|
146
|
+
- **Priority levels** for ranking systems
|
147
|
+
- **User ratings** for recommendation systems
|
148
|
+
- **Custom lambdas** for complex scoring logic
|
149
|
+
|
150
|
+
## Best Practices
|
151
|
+
|
152
|
+
**Performance:**
|
153
|
+
- Use `merge([id1, id2, id3])` for bulk additions
|
154
|
+
- Use `multiget(*ids)` for efficient bulk loading
|
155
|
+
- Use pagination: `collection.range(0, 9)` instead of loading all
|
156
|
+
|
157
|
+
**Lifecycle Management:**
|
158
|
+
```ruby
|
159
|
+
def destroy
|
160
|
+
cleanup_relationships # Remove from all relationships first
|
161
|
+
super
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
**Validation:**
|
166
|
+
```ruby
|
167
|
+
def add_member(user_id)
|
168
|
+
raise "Team is full" if members.size >= max_members
|
169
|
+
members << user_id
|
170
|
+
end
|
171
|
+
```
|
172
|
+
|
173
|
+
## Common Patterns
|
174
|
+
|
175
|
+
**Conditional Scoring:**
|
176
|
+
```ruby
|
177
|
+
class_participates_in :active_users,
|
178
|
+
score: ->(user) { user.active? ? user.last_activity : 0 }
|
179
|
+
```
|
180
|
+
|
181
|
+
**Bidirectional Updates:**
|
182
|
+
```ruby
|
183
|
+
# ✅ Automatic bidirectional
|
184
|
+
customer.domains << domain
|
185
|
+
|
186
|
+
# ❌ Manual (avoid)
|
187
|
+
customer.domains.add(domain.identifier)
|
188
|
+
```
|
189
|
+
|
190
|
+
---
|
191
|
+
|
192
|
+
## See Also
|
193
|
+
|
194
|
+
- **[Technical Reference](../reference/api-technical.md#relationships-feature-v200-pre7)** - Implementation details and advanced patterns
|
195
|
+
- **[Relationship Methods Guide](feature-relationships-methods.md)** - Complete method reference
|
196
|
+
- **[Feature System Guide](feature-system.md)** - Understanding Familia's feature architecture
|
197
|
+
- **[Implementation Guide](implementation.md)** - Production deployment and configuration patterns
|
198
|
+
|
199
|
+
> [!NOTE]
|
200
|
+
> **Next Steps:** Once you're comfortable with basic relationships, explore the [Relationship Methods Guide](feature-relationships-methods.md) for detailed method references, or check the [Technical Reference](../reference/api-technical.md#relationships-feature-v200-pre7) for advanced patterns like permission encoding and performance optimization.
|
@@ -776,11 +776,11 @@ module Familia::Features::DebugLogging
|
|
776
776
|
|
777
777
|
base.define_singleton_method(:feature) do |name|
|
778
778
|
Familia.ld "[DEBUG] Loading feature #{name} on #{self}"
|
779
|
-
start_time =
|
779
|
+
start_time = Familia.now
|
780
780
|
|
781
781
|
result = original_feature_method.call(name)
|
782
782
|
|
783
|
-
load_time = (
|
783
|
+
load_time = (Familia.now - start_time) * 1000
|
784
784
|
Familia.ld "[DEBUG] Feature #{name} loaded in #{load_time.round(2)}ms"
|
785
785
|
|
786
786
|
result
|
@@ -792,11 +792,11 @@ module Familia::Features::DebugLogging
|
|
792
792
|
return block.call unless Familia.debug?
|
793
793
|
|
794
794
|
Familia.ld "[DEBUG] Calling #{method_name} on #{self}"
|
795
|
-
start_time =
|
795
|
+
start_time = Familia.now
|
796
796
|
|
797
797
|
result = block.call
|
798
798
|
|
799
|
-
duration = (
|
799
|
+
duration = (Familia.now - start_time) * 1000
|
800
800
|
Familia.ld "[DEBUG] #{method_name} completed in #{duration.round(2)}ms"
|
801
801
|
|
802
802
|
result
|
@@ -113,7 +113,7 @@ class Customer < Familia::Horreum
|
|
113
113
|
field :custid, :name, :email
|
114
114
|
|
115
115
|
# Define relationship collections
|
116
|
-
|
116
|
+
participates_in :active_users, type: :sorted_set
|
117
117
|
indexed_by :email_lookup, field: :email
|
118
118
|
set :domains
|
119
119
|
end
|
@@ -125,7 +125,7 @@ class Domain < Familia::Horreum
|
|
125
125
|
field :domain_id, :name
|
126
126
|
|
127
127
|
# Declare membership in customer collections
|
128
|
-
|
128
|
+
participates_in Customer, :domains, type: :set
|
129
129
|
end
|
130
130
|
|
131
131
|
# Usage
|
@@ -165,7 +165,7 @@ module Familia
|
|
165
165
|
module Features
|
166
166
|
module MyCustomFeature
|
167
167
|
def self.included(base)
|
168
|
-
Familia.trace :LOADED, self, base
|
168
|
+
Familia.trace :LOADED, self, base if Familia.debug?
|
169
169
|
base.extend ClassMethods
|
170
170
|
base.prepend InstanceMethods # Use prepend for method interception
|
171
171
|
end
|
@@ -213,12 +213,12 @@ module Familia
|
|
213
213
|
|
214
214
|
module ClassMethods
|
215
215
|
def enable_audit_for(*field_names)
|
216
|
-
@audited_fields ||= Set.new
|
216
|
+
@audited_fields ||= ::Set.new
|
217
217
|
@audited_fields.merge(field_names.map(&:to_sym))
|
218
218
|
end
|
219
219
|
|
220
220
|
def audited_fields
|
221
|
-
@audited_fields || Set.new
|
221
|
+
@audited_fields || ::Set.new
|
222
222
|
end
|
223
223
|
end
|
224
224
|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
## Overview
|
4
4
|
|
5
|
-
Transient fields provide secure handling of sensitive runtime data that should never be persisted to Redis
|
5
|
+
Transient fields provide secure handling of sensitive runtime data that should never be persisted to Valkey/Redis. Unlike encrypted fields, transient fields exist only in memory and are automatically wrapped in `RedactedString` for security.
|
6
6
|
|
7
7
|
## When to Use Transient Fields
|
8
8
|
|
@@ -270,7 +270,7 @@ end
|
|
270
270
|
|
271
271
|
| Feature | Encrypted Fields | Transient Fields |
|
272
272
|
|---------|------------------|------------------|
|
273
|
-
| **Persistence** | Saved to Redis
|
273
|
+
| **Persistence** | Saved to Valkey/Redis | Memory only |
|
274
274
|
| **Encryption** | AES/XChaCha20 | None (not stored) |
|
275
275
|
| **Use Case** | Long-term secrets | Runtime secrets |
|
276
276
|
| **Access** | Automatic decrypt | RedactedString wrapper |
|
@@ -5,8 +5,8 @@
|
|
5
5
|
The encrypted fields feature uses a modular provider system with field transformation hooks:
|
6
6
|
|
7
7
|
```
|
8
|
-
User Input → Field Setter → Provider Selection → Encryption → Redis
|
9
|
-
Redis
|
8
|
+
User Input → Field Setter → Provider Selection → Encryption → Valkey/Redis
|
9
|
+
Valkey/Redis → Algorithm Detection → Decryption → Field Getter → User Output
|
10
10
|
```
|
11
11
|
|
12
12
|
### Provider Architecture
|
@@ -247,7 +247,7 @@ it "encrypts sensitive fields", :encryption do
|
|
247
247
|
user = User.create(favorite_snack: "leftover pizza")
|
248
248
|
|
249
249
|
# Verify encryption in Redis
|
250
|
-
raw_value = redis.hget(user.
|
250
|
+
raw_value = redis.hget(user.dbkey, "favorite_snack")
|
251
251
|
expect(raw_value).not_to include("leftover pizza")
|
252
252
|
expect(JSON.parse(raw_value)).to have_key("ciphertext")
|
253
253
|
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# Familia v2.0 Documentation
|
2
|
+
|
3
|
+
Welcome to the comprehensive documentation for Familia v2.0. This guide collection provides detailed explanations of all major features including security, connection management, architecture, and object relationships.
|
4
|
+
|
5
|
+
> **📖 Documentation Layers**
|
6
|
+
> - **[Overview](../overview.md)** - Conceptual introduction and getting started
|
7
|
+
> - **[Technical Reference](../reference/api-technical.md)** - Implementation patterns and technical details
|
8
|
+
> - **This Guide Collection** - Deep-dive topic guides with detailed prose and examples
|
9
|
+
|
10
|
+
## 📚 Guide Structure
|
11
|
+
|
12
|
+
### 🏗️ Architecture & System Design
|
13
|
+
|
14
|
+
4. **[Feature System](feature-system.md)** - Modular architecture with dependencies and autoloader patterns
|
15
|
+
5. **[Feature System for Developers](feature-system-devs.md)** - Advanced feature development patterns
|
16
|
+
3. **[Security Model](security-model.md)** - Cryptographic design and Ruby memory considerations
|
17
|
+
6. **[Connection Pooling](config-connection-pooling.md)** - Provider pattern for efficient Redis/Valkey pooling
|
18
|
+
7. **[Core Field System](core-field-system.md)** - Field definitions and data type mappings
|
19
|
+
|
20
|
+
|
21
|
+
### 🔐 Special Field Types
|
22
|
+
|
23
|
+
1. **[Encrypted Fields](feature-encrypted-fields.md)** - Persistent encrypted storage with modular providers
|
24
|
+
2. **[Transient Fields](feature-transient-fields.md)** - Non-persistent secure data handling with RedactedString
|
25
|
+
10. **[Object Identifiers](feature-object-identifiers.md)** - Automatic ID generation with configurable strategies _(new!)_
|
26
|
+
11. **[External Identifiers](feature-external-identifiers.md)** - Integration with external systems and legacy data _(new!)_
|
27
|
+
|
28
|
+
|
29
|
+
### 🔗 Object Relationships & Identifiers
|
30
|
+
|
31
|
+
8. **[Relationships](feature-relationships.md)** - Object relationships and membership system
|
32
|
+
9. **[Relationship Methods](feature-relationships-methods.md)** - Detailed method reference for relationships
|
33
|
+
|
34
|
+
### ⏱️ Time & Analytics Features
|
35
|
+
|
36
|
+
12. **[Expiration](feature-expiration.md)** - TTL management and cascading expiration
|
37
|
+
13. **[Quantization](feature-quantization.md)** - Time-based data bucketing for analytics
|
38
|
+
14. **[Time Utilities](time-utilities.md)** - Time manipulation and formatting utilities
|
39
|
+
|
40
|
+
### 🛠️ Implementation & Usage
|
41
|
+
|
42
|
+
15. **[Implementation Guide](implementation.md)** - Advanced configuration and usage patterns
|
43
|
+
|
44
|
+
## 🚀 Quick Start Examples
|
45
|
+
|
46
|
+
### Encrypted Fields (Persistent)
|
47
|
+
```ruby
|
48
|
+
class User < Familia::Horreum
|
49
|
+
feature :encrypted_fields
|
50
|
+
encrypted_field :secret_recipe
|
51
|
+
end
|
52
|
+
|
53
|
+
# Configure encryption
|
54
|
+
Familia.configure do |config|
|
55
|
+
config.encryption_keys = { v1: ENV['FAMILIA_ENCRYPTION_KEY'] }
|
56
|
+
config.current_key_version = :v1
|
57
|
+
end
|
58
|
+
|
59
|
+
user = User.new(secret_recipe: "donna's cookies")
|
60
|
+
user.save
|
61
|
+
user.secret_recipe # => "donna's cookies" (automatically decrypted)
|
62
|
+
```
|
63
|
+
|
64
|
+
### Feature System (Modular)
|
65
|
+
```ruby
|
66
|
+
class Customer < Familia::Horreum
|
67
|
+
feature :safe_dump # API-safe serialization
|
68
|
+
feature :expiration # TTL support
|
69
|
+
feature :encrypted_fields # Secure storage
|
70
|
+
|
71
|
+
field :name, :email
|
72
|
+
encrypted_field :api_key
|
73
|
+
default_expiration 24.hours
|
74
|
+
safe_dump_fields :name, :email
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
### Connection Pooling (Performance)
|
79
|
+
```ruby
|
80
|
+
# Configure connection provider for multi-database pooling
|
81
|
+
Familia.connection_provider = lambda do |uri|
|
82
|
+
parsed = URI.parse(uri) # => URI::Redis
|
83
|
+
pool_key = "#{parsed.host}:#{parsed.port}/#{parsed.db || 0}"
|
84
|
+
|
85
|
+
@pools[pool_key] ||= ConnectionPool.new(size: 10) do
|
86
|
+
Redis.new(host: parsed.host, port: parsed.port, db: parsed.db || 0)
|
87
|
+
end
|
88
|
+
|
89
|
+
@pools[pool_key].with { |conn| conn }
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
### Object Relationships
|
94
|
+
```ruby
|
95
|
+
class Customer < Familia::Horreum
|
96
|
+
feature :relationships
|
97
|
+
identifier_field :custid
|
98
|
+
field :custid, :name, :email
|
99
|
+
set :domains # Customer collections
|
100
|
+
end
|
101
|
+
|
102
|
+
class Domain < Familia::Horreum
|
103
|
+
feature :relationships
|
104
|
+
identifier_field :domain_id
|
105
|
+
field :domain_id, :name, :dns_zone
|
106
|
+
participates_in Customer, :domains # Bidirectional membership
|
107
|
+
end
|
108
|
+
|
109
|
+
# Create objects and establish relationships
|
110
|
+
customer = Customer.new(custid: "cust123", name: "Acme Corp")
|
111
|
+
domain = Domain.new(domain_id: "dom456", name: "acme.com")
|
112
|
+
|
113
|
+
# Ruby-like syntax for relationships
|
114
|
+
customer.domains << domain # Clean collection syntax
|
115
|
+
|
116
|
+
# Query relationships
|
117
|
+
domain.in_customer_domains?(customer.custid) # => true
|
118
|
+
customer.domains.member?(domain.identifier) # => true
|
119
|
+
```
|
120
|
+
|
121
|
+
### Object Identifiers (Auto-generation)
|
122
|
+
```ruby
|
123
|
+
class Document < Familia::Horreum
|
124
|
+
feature :object_identifier, generator: :uuid_v4
|
125
|
+
field :title, :content
|
126
|
+
end
|
127
|
+
|
128
|
+
class Session < Familia::Horreum
|
129
|
+
feature :object_identifier, generator: :hex
|
130
|
+
field :user_id, :data
|
131
|
+
end
|
132
|
+
|
133
|
+
# Automatic ID generation
|
134
|
+
doc = Document.create(title: "My Document")
|
135
|
+
doc.objid # => "f47ac10b-58cc-4372-a567-0e02b2c3d479"
|
136
|
+
|
137
|
+
session = Session.create(user_id: "123")
|
138
|
+
session.objid # => "a1b2c3d4e5f6"
|
139
|
+
```
|
140
|
+
|
141
|
+
### External Identifiers (Legacy Integration)
|
142
|
+
```ruby
|
143
|
+
class ExternalUser < Familia::Horreum
|
144
|
+
feature :external_identifier
|
145
|
+
field :internal_id, :external_id, :name
|
146
|
+
end
|
147
|
+
|
148
|
+
# Map external system IDs to internal objects
|
149
|
+
user = ExternalUser.create(
|
150
|
+
internal_id: SecureRandom.uuid,
|
151
|
+
external_id: "ext_12345",
|
152
|
+
name: "Legacy User"
|
153
|
+
)
|
154
|
+
|
155
|
+
# Find by external ID
|
156
|
+
found = ExternalUser.find_by_external_id("ext_12345")
|
157
|
+
```
|
158
|
+
|
159
|
+
### Quantization (Analytics)
|
160
|
+
```ruby
|
161
|
+
class MetricsBucket < Familia::Horreum
|
162
|
+
feature :quantization
|
163
|
+
field :metric_key, :value_count
|
164
|
+
string :counter, quantize: [10.minutes, '%H:%M']
|
165
|
+
end
|
166
|
+
|
167
|
+
# Automatic time bucketing for analytics
|
168
|
+
MetricsBucket.record_event("page_view") # Groups into 10-min buckets
|
169
|
+
```
|
170
|
+
|
171
|
+
|
172
|
+
## Related Resources
|
173
|
+
|
174
|
+
- [Familia README](https://github.com/delano/familia) - Main project documentation
|
175
|
+
- [Issue #57](https://github.com/delano/familia/issues/57) - Original feature proposal
|
176
|
+
- [Issue #58](https://github.com/delano/familia/issues/58) - Wiki documentation tracking
|
@@ -138,7 +138,7 @@ Ruby provides **NO** memory safety guarantees for cryptographic secrets. This af
|
|
138
138
|
|
139
139
|
Both providers attempt best-effort memory clearing:
|
140
140
|
- Call `.clear` on sensitive strings after use
|
141
|
-
-
|
141
|
+
- UnsortedSet variables to `nil` when done
|
142
142
|
- Use finalizers for cleanup (no guarantees)
|
143
143
|
|
144
144
|
**Recommendation**: For production systems with high-security requirements, consider:
|
@@ -6,7 +6,7 @@ This guide covers migrating from Familia v1.x to the v2.0.0-pre foundation relea
|
|
6
6
|
|
7
7
|
The v2.0.0-pre series represents a major modernization of Familia with:
|
8
8
|
- Complete API redesign for clarity and consistency
|
9
|
-
- Valkey compatibility alongside Redis support
|
9
|
+
- Valkey compatibility alongside Valkey/Redis support
|
10
10
|
- Ruby 3.4+ modernization with improved thread safety
|
11
11
|
- New connection pooling architecture
|
12
12
|
|
@@ -172,7 +172,7 @@ module CommonFields
|
|
172
172
|
field :version
|
173
173
|
|
174
174
|
def touch_updated
|
175
|
-
self.updated =
|
175
|
+
self.updated = Familia.now.to_i
|
176
176
|
end
|
177
177
|
end
|
178
178
|
|
@@ -202,7 +202,7 @@ safe_dump_field :field2
|
|
202
202
|
safe_dump_field :field3, ->(obj) { ... }
|
203
203
|
```
|
204
204
|
|
205
|
-
### 2.
|
205
|
+
### 2. UnsortedSet Up Auto-loading (Optional)
|
206
206
|
If you have project-specific features, set up auto-loading:
|
207
207
|
|
208
208
|
```ruby
|
@@ -14,7 +14,7 @@ The new `Familia::VerifiableIdentifier` module allows applications to create and
|
|
14
14
|
class Customer < Familia::Horreum
|
15
15
|
feature :verifiable_identifier
|
16
16
|
|
17
|
-
# Required:
|
17
|
+
# Required: UnsortedSet the HMAC secret (do this once in your app initialization)
|
18
18
|
# Generate with: SecureRandom.hex(64)
|
19
19
|
ENV['VERIFIABLE_ID_HMAC_SECRET'] = 'your_64_character_hex_secret'
|
20
20
|
end
|
@@ -85,7 +85,7 @@ puts "VERIFIABLE_ID_HMAC_SECRET=#{secret}"
|
|
85
85
|
#### Environment Configuration
|
86
86
|
```ruby
|
87
87
|
# config/application.rb or equivalent
|
88
|
-
#
|
88
|
+
# UnsortedSet this BEFORE any VerifiableIdentifier usage
|
89
89
|
ENV['VERIFIABLE_ID_HMAC_SECRET'] = Rails.application.credentials.verifiable_id_secret
|
90
90
|
|
91
91
|
# Or configure programmatically
|
@@ -51,7 +51,7 @@ end
|
|
51
51
|
|
52
52
|
### 3. Update Serialization Code
|
53
53
|
|
54
|
-
Handle `
|
54
|
+
Handle `ConcealedString` in serialization:
|
55
55
|
|
56
56
|
**Before:**
|
57
57
|
```ruby
|
@@ -63,16 +63,16 @@ end
|
|
63
63
|
**After:**
|
64
64
|
```ruby
|
65
65
|
def to_json
|
66
|
-
#
|
66
|
+
# ConcealedString automatically excluded from serialization
|
67
67
|
{ name: name }.to_json # password field omitted if transient
|
68
68
|
end
|
69
69
|
```
|
70
70
|
|
71
|
-
**Manual
|
71
|
+
**Manual ConcealedString Handling:**
|
72
72
|
```ruby
|
73
73
|
# Access original value when needed
|
74
74
|
password.reveal # Returns actual string value
|
75
|
-
password.
|
75
|
+
password.cleared? # Returns true if cleared from memory
|
76
76
|
```
|
77
77
|
|
78
78
|
### 4. Implement Key Rotation Procedures
|
@@ -80,19 +80,38 @@ password.redacted? # Returns true if redacted
|
|
80
80
|
**Rotation Process:**
|
81
81
|
1. Add new key version to configuration
|
82
82
|
2. Update `current_key_version`
|
83
|
-
3. Re-encrypt existing data
|
84
|
-
4.
|
83
|
+
3. Re-encrypt existing data using `re_encrypt_fields!`
|
84
|
+
4. Verify migration completion
|
85
|
+
5. Remove old keys after migration complete
|
85
86
|
|
86
87
|
**Example Rotation Script:**
|
87
88
|
```ruby
|
88
|
-
# Add new key version
|
89
|
-
Familia.
|
90
|
-
|
89
|
+
# Step 1: Add new key version
|
90
|
+
Familia.configure do |config|
|
91
|
+
config.encryption_keys = {
|
92
|
+
v2: ENV['OLD_ENCRYPTION_KEY'],
|
93
|
+
v3: ENV['NEW_ENCRYPTION_KEY']
|
94
|
+
}
|
95
|
+
config.current_key_version = :v3
|
96
|
+
end
|
97
|
+
|
98
|
+
# Step 2: Validate configuration
|
99
|
+
Familia::Encryption.validate_configuration!
|
91
100
|
|
92
|
-
# Re-encrypt existing records
|
101
|
+
# Step 3: Re-encrypt existing records
|
93
102
|
Vault.all.each do |vault|
|
94
|
-
vault.
|
103
|
+
vault.re_encrypt_fields! # Re-encrypts with current key
|
104
|
+
vault.save
|
95
105
|
end
|
106
|
+
|
107
|
+
# Step 4: Verify migration
|
108
|
+
Vault.all.each do |vault|
|
109
|
+
status = vault.encrypted_fields_status
|
110
|
+
puts "Vault #{vault.identifier}: #{status}"
|
111
|
+
end
|
112
|
+
|
113
|
+
# Step 5: Remove old key (after verification)
|
114
|
+
Familia.config.encryption_keys.delete(:v2)
|
96
115
|
```
|
97
116
|
|
98
117
|
## Security Best Practices
|
@@ -101,7 +120,9 @@ end
|
|
101
120
|
- **Key Rotation:** Rotate encryption keys regularly (quarterly/annually)
|
102
121
|
- **Field Selection:** Only encrypt fields that truly need protection
|
103
122
|
- **Memory Clearing:** Use transient fields for temporary sensitive data
|
104
|
-
- **Logging:** Verify
|
123
|
+
- **Logging:** Verify ConcealedString prevents accidental logging
|
124
|
+
- **Configuration Validation:** Use `validate_configuration!` before production
|
125
|
+
- **Monitoring:** Use `encrypted_fields_status` to track encryption state
|
105
126
|
|
106
127
|
## Next Steps
|
107
128
|
|
@@ -35,7 +35,7 @@ The Horreum class structure was reorganized for better maintainability:
|
|
35
35
|
- `Familia::Horreum::Core` - Essential functionality
|
36
36
|
- `Familia::Horreum::ClassMethods` - Class-level methods
|
37
37
|
- `Familia::Horreum::Serialization` - Object serialization
|
38
|
-
- `Familia::Horreum::Commands` - Redis command wrappers
|
38
|
+
- `Familia::Horreum::Commands` - Valkey/Redis command wrappers
|
39
39
|
|
40
40
|
**Feature System Improvements:**
|
41
41
|
- Dependency management between features
|
@@ -56,7 +56,7 @@ end
|
|
56
56
|
```
|
57
57
|
|
58
58
|
**Improved Data Consistency:**
|
59
|
-
- Automatic retry for transient Redis connection issues
|
59
|
+
- Automatic retry for transient Valkey/Redis connection issues
|
60
60
|
- Better handling of concurrent modifications
|
61
61
|
- Enhanced validation before persistence operations
|
62
62
|
|