familia 2.0.0.pre15 → 2.0.0.pre17

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.
Files changed (288) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -2
  3. data/.github/workflows/code-quality.yml +138 -0
  4. data/.github/workflows/code-smells.yml +85 -0
  5. data/.github/workflows/docs.yml +31 -8
  6. data/.gitignore +3 -1
  7. data/.pre-commit-config.yaml +7 -1
  8. data/.reek.yml +98 -0
  9. data/.rubocop.yml +54 -10
  10. data/.talismanrc +9 -0
  11. data/.yardopts +18 -13
  12. data/CHANGELOG.rst +86 -4
  13. data/CLAUDE.md +39 -1
  14. data/Gemfile +6 -5
  15. data/Gemfile.lock +99 -23
  16. data/LICENSE.txt +1 -1
  17. data/README.md +285 -85
  18. data/changelog.d/README.md +2 -2
  19. data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
  20. data/docs/archive/FAMILIA_TECHNICAL.md +42 -42
  21. data/docs/archive/FAMILIA_UPDATE.md +3 -3
  22. data/docs/archive/README.md +3 -2
  23. data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
  24. data/docs/conf.py +29 -0
  25. data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
  26. data/docs/guides/feature-encrypted-fields.md +785 -0
  27. data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
  28. data/docs/guides/feature-external-identifiers.md +637 -0
  29. data/docs/guides/feature-object-identifiers.md +435 -0
  30. data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
  31. data/docs/guides/feature-relationships-methods.md +684 -0
  32. data/docs/guides/feature-relationships.md +200 -0
  33. data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
  34. data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
  35. data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
  36. data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
  37. data/docs/guides/index.md +176 -0
  38. data/docs/guides/{Security-Model.md → security-model.md} +1 -1
  39. data/docs/migrating/v2.0.0-pre.md +1 -1
  40. data/docs/migrating/v2.0.0-pre11.md +2 -2
  41. data/docs/migrating/v2.0.0-pre12.md +2 -2
  42. data/docs/migrating/v2.0.0-pre5.md +33 -12
  43. data/docs/migrating/v2.0.0-pre6.md +2 -2
  44. data/docs/migrating/v2.0.0-pre7.md +8 -8
  45. data/docs/overview.md +624 -20
  46. data/docs/reference/api-technical.md +1365 -0
  47. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
  48. data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
  49. data/examples/autoloader/mega_customer.rb +3 -1
  50. data/examples/encrypted_fields.rb +378 -0
  51. data/examples/json_usage_patterns.rb +144 -0
  52. data/examples/relationships.rb +13 -13
  53. data/examples/safe_dump.rb +7 -7
  54. data/examples/single_connection_transaction_confusions.rb +379 -0
  55. data/lib/familia/base.rb +51 -10
  56. data/lib/familia/connection/handlers.rb +223 -0
  57. data/lib/familia/connection/individual_command_proxy.rb +64 -0
  58. data/lib/familia/connection/middleware.rb +75 -0
  59. data/lib/familia/connection/operation_core.rb +93 -0
  60. data/lib/familia/connection/operations.rb +277 -0
  61. data/lib/familia/connection/pipeline_core.rb +87 -0
  62. data/lib/familia/connection/transaction_core.rb +100 -0
  63. data/lib/familia/connection.rb +60 -186
  64. data/lib/familia/data_type/class_methods.rb +63 -0
  65. data/lib/familia/data_type/commands.rb +53 -51
  66. data/lib/familia/data_type/connection.rb +83 -0
  67. data/lib/familia/data_type/serialization.rb +108 -107
  68. data/lib/familia/data_type/settings.rb +96 -0
  69. data/lib/familia/data_type/types/counter.rb +1 -1
  70. data/lib/familia/data_type/types/hashkey.rb +15 -11
  71. data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
  72. data/lib/familia/data_type/types/lock.rb +3 -2
  73. data/lib/familia/data_type/types/sorted_set.rb +128 -14
  74. data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -9
  75. data/lib/familia/data_type/types/unsorted_set.rb +20 -27
  76. data/lib/familia/data_type.rb +12 -171
  77. data/lib/familia/distinguisher.rb +85 -0
  78. data/lib/familia/encryption/encrypted_data.rb +15 -24
  79. data/lib/familia/encryption/manager.rb +6 -4
  80. data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
  81. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
  82. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
  83. data/lib/familia/encryption/request_cache.rb +7 -7
  84. data/lib/familia/encryption.rb +2 -3
  85. data/lib/familia/errors.rb +9 -3
  86. data/lib/familia/features/autoloader.rb +30 -12
  87. data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
  88. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
  89. data/lib/familia/features/encrypted_fields.rb +71 -66
  90. data/lib/familia/features/expiration/extensions.rb +1 -1
  91. data/lib/familia/features/expiration.rb +31 -26
  92. data/lib/familia/features/external_identifier.rb +57 -19
  93. data/lib/familia/features/object_identifier.rb +134 -25
  94. data/lib/familia/features/quantization.rb +16 -21
  95. data/lib/familia/features/relationships/README.md +97 -0
  96. data/lib/familia/features/relationships/collection_operations.rb +104 -0
  97. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
  98. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +306 -0
  99. data/lib/familia/features/relationships/indexing.rb +182 -256
  100. data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
  101. data/lib/familia/features/relationships/participation/participant_methods.rb +164 -0
  102. data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
  103. data/lib/familia/features/relationships/participation.rb +656 -0
  104. data/lib/familia/features/relationships/participation_relationship.rb +31 -0
  105. data/lib/familia/features/relationships/score_encoding.rb +20 -20
  106. data/lib/familia/features/relationships.rb +65 -266
  107. data/lib/familia/features/safe_dump.rb +127 -130
  108. data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
  109. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
  110. data/lib/familia/features/transient_fields.rb +10 -7
  111. data/lib/familia/features.rb +10 -14
  112. data/lib/familia/field_type.rb +6 -4
  113. data/lib/familia/horreum/connection.rb +297 -0
  114. data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +27 -17
  115. data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +139 -74
  116. data/lib/familia/horreum/{subclass/management.rb → management.rb} +73 -27
  117. data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +108 -185
  118. data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +104 -23
  119. data/lib/familia/horreum/serialization.rb +172 -0
  120. data/lib/familia/horreum/{shared/settings.rb → settings.rb} +2 -1
  121. data/lib/familia/horreum/{core/utils.rb → utils.rb} +2 -1
  122. data/lib/familia/horreum.rb +222 -119
  123. data/lib/familia/json_serializer.rb +0 -1
  124. data/lib/familia/logging.rb +11 -114
  125. data/lib/familia/refinements/dear_json.rb +122 -0
  126. data/lib/familia/refinements/logger_trace.rb +20 -17
  127. data/lib/familia/refinements/stylize_words.rb +65 -0
  128. data/lib/familia/refinements/time_literals.rb +60 -52
  129. data/lib/familia/refinements.rb +2 -1
  130. data/lib/familia/secure_identifier.rb +60 -28
  131. data/lib/familia/settings.rb +83 -7
  132. data/lib/familia/utils.rb +5 -87
  133. data/lib/familia/verifiable_identifier.rb +4 -4
  134. data/lib/familia/version.rb +1 -1
  135. data/lib/familia.rb +72 -14
  136. data/lib/middleware/database_middleware.rb +56 -14
  137. data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
  138. data/try/configuration/scenarios_try.rb +2 -2
  139. data/try/connection/fiber_context_preservation_try.rb +250 -0
  140. data/try/connection/handler_constraints_try.rb +59 -0
  141. data/try/connection/operation_mode_guards_try.rb +208 -0
  142. data/try/connection/pipeline_fallback_integration_try.rb +128 -0
  143. data/try/connection/responsibility_chain_tracking_try.rb +72 -0
  144. data/try/connection/transaction_fallback_integration_try.rb +288 -0
  145. data/try/connection/transaction_mode_permissive_try.rb +153 -0
  146. data/try/connection/transaction_mode_strict_try.rb +98 -0
  147. data/try/connection/transaction_mode_warn_try.rb +131 -0
  148. data/try/connection/transaction_modes_try.rb +249 -0
  149. data/try/core/autoloader_try.rb +120 -2
  150. data/try/core/connection_try.rb +10 -10
  151. data/try/core/conventional_inheritance_try.rb +130 -0
  152. data/try/core/create_method_try.rb +15 -23
  153. data/try/core/database_consistency_try.rb +11 -10
  154. data/try/core/errors_try.rb +11 -14
  155. data/try/core/familia_extended_try.rb +2 -2
  156. data/try/core/familia_members_methods_try.rb +76 -0
  157. data/try/core/familia_try.rb +1 -1
  158. data/try/core/isolated_dbclient_try.rb +165 -0
  159. data/try/core/middleware_try.rb +16 -16
  160. data/try/core/persistence_operations_try.rb +4 -4
  161. data/try/core/pools_try.rb +42 -26
  162. data/try/core/secure_identifier_try.rb +28 -24
  163. data/try/core/time_utils_try.rb +10 -10
  164. data/try/core/tools_try.rb +3 -3
  165. data/try/core/utils_try.rb +2 -2
  166. data/try/data_types/boolean_try.rb +4 -4
  167. data/try/data_types/datatype_base_try.rb +0 -2
  168. data/try/data_types/list_try.rb +10 -10
  169. data/try/data_types/sorted_set_try.rb +5 -5
  170. data/try/data_types/sorted_set_zadd_options_try.rb +625 -0
  171. data/try/data_types/string_try.rb +12 -12
  172. data/try/data_types/unsortedset_try.rb +33 -0
  173. data/try/debugging/cache_behavior_tracer.rb +7 -7
  174. data/try/debugging/debug_aad_process.rb +1 -1
  175. data/try/debugging/debug_concealed_internal.rb +1 -1
  176. data/try/debugging/debug_cross_context.rb +1 -1
  177. data/try/debugging/debug_fresh_cross_context.rb +1 -1
  178. data/try/debugging/encryption_method_tracer.rb +10 -10
  179. data/try/edge_cases/hash_symbolization_try.rb +1 -1
  180. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  181. data/try/encryption/config_persistence_try.rb +2 -2
  182. data/try/encryption/encryption_core_try.rb +19 -19
  183. data/try/encryption/instance_variable_scope_try.rb +1 -1
  184. data/try/encryption/module_loading_try.rb +2 -2
  185. data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
  186. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
  187. data/try/encryption/secure_memory_handling_try.rb +1 -1
  188. data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
  189. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  190. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
  191. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
  192. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
  193. data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
  194. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  195. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  196. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
  197. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
  198. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  199. data/try/features/feature_dependencies_try.rb +3 -3
  200. data/try/features/field_groups_try.rb +244 -0
  201. data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
  202. data/try/features/object_identifier/object_identifier_try.rb +10 -0
  203. data/try/features/quantization/quantization_try.rb +1 -1
  204. data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
  205. data/try/features/relationships/indexing_try.rb +443 -0
  206. data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
  207. data/try/features/relationships/participation_commands_verification_try.rb +105 -0
  208. data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
  209. data/try/features/relationships/participation_reverse_index_try.rb +196 -0
  210. data/try/features/relationships/relationships_api_changes_try.rb +72 -71
  211. data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
  212. data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
  213. data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
  214. data/try/features/relationships/relationships_performance_try.rb +20 -20
  215. data/try/features/relationships/relationships_try.rb +27 -38
  216. data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
  217. data/try/features/transient_fields/refresh_reset_try.rb +3 -1
  218. data/try/features/transient_fields/simple_refresh_test.rb +1 -1
  219. data/try/helpers/test_cleanup.rb +86 -0
  220. data/try/helpers/test_helpers.rb +6 -7
  221. data/try/horreum/auto_indexing_on_save_try.rb +212 -0
  222. data/try/horreum/base_try.rb +3 -2
  223. data/try/horreum/commands_try.rb +3 -1
  224. data/try/horreum/defensive_initialization_try.rb +86 -0
  225. data/try/horreum/destroy_related_fields_cleanup_try.rb +332 -0
  226. data/try/horreum/initialization_try.rb +11 -7
  227. data/try/horreum/relations_try.rb +21 -13
  228. data/try/horreum/serialization_try.rb +12 -11
  229. data/try/horreum/settings_try.rb +2 -0
  230. data/try/integration/cross_component_try.rb +3 -3
  231. data/try/memory/memory_basic_test.rb +1 -1
  232. data/try/memory/memory_docker_ruby_dump.sh +2 -2
  233. data/try/models/customer_safe_dump_try.rb +1 -1
  234. data/try/models/customer_try.rb +13 -15
  235. data/try/models/datatype_base_try.rb +3 -3
  236. data/try/models/familia_object_try.rb +9 -8
  237. data/try/performance/benchmarks_try.rb +2 -2
  238. data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
  239. data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
  240. data/try/prototypes/atomic_saves_v4.rb +1 -1
  241. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
  242. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  243. data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  244. data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
  245. data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
  246. data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
  247. data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
  248. data/try/prototypes/pooling/pool_siege.rb +11 -11
  249. data/try/prototypes/pooling/run_stress_tests.rb +7 -7
  250. data/try/refinements/dear_json_array_methods_try.rb +53 -0
  251. data/try/refinements/dear_json_hash_methods_try.rb +54 -0
  252. data/try/refinements/logger_trace_methods_try.rb +44 -0
  253. data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
  254. data/try/refinements/time_literals_string_methods_try.rb +80 -0
  255. data/try/valkey.conf +26 -0
  256. metadata +92 -52
  257. data/.rubocop_todo.yml +0 -208
  258. data/docs/connection_pooling.md +0 -192
  259. data/docs/guides/Connection-Pooling-Guide.md +0 -437
  260. data/docs/guides/Encrypted-Fields-Overview.md +0 -101
  261. data/docs/guides/Feature-System-Autoloading.md +0 -198
  262. data/docs/guides/Home.md +0 -116
  263. data/docs/guides/Relationships-Guide.md +0 -737
  264. data/docs/guides/relationships-methods.md +0 -266
  265. data/docs/reference/auditing_database_commands.rb +0 -228
  266. data/examples/permissions.rb +0 -240
  267. data/lib/familia/features/relationships/cascading.rb +0 -437
  268. data/lib/familia/features/relationships/membership.rb +0 -497
  269. data/lib/familia/features/relationships/permission_management.rb +0 -264
  270. data/lib/familia/features/relationships/querying.rb +0 -615
  271. data/lib/familia/features/relationships/redis_operations.rb +0 -274
  272. data/lib/familia/features/relationships/tracking.rb +0 -418
  273. data/lib/familia/horreum/core/connection.rb +0 -73
  274. data/lib/familia/horreum/core.rb +0 -21
  275. data/lib/familia/refinements/snake_case.rb +0 -40
  276. data/lib/familia/validation/command_recorder.rb +0 -336
  277. data/lib/familia/validation/expectations.rb +0 -519
  278. data/lib/familia/validation/validation_helpers.rb +0 -443
  279. data/lib/familia/validation/validator.rb +0 -412
  280. data/lib/familia/validation.rb +0 -140
  281. data/try/data_types/set_try.rb +0 -33
  282. data/try/features/relationships/categorical_permissions_try.rb +0 -515
  283. data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
  284. data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
  285. data/try/validation/atomic_operations_try.rb.disabled +0 -320
  286. data/try/validation/command_validation_try.rb.disabled +0 -207
  287. data/try/validation/performance_validation_try.rb.disabled +0 -324
  288. data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -0,0 +1,1365 @@
1
+ # Familia v2.0.0-pre Series Technical Reference
2
+
3
+ **Familia** is a Ruby ORM for Valkey/Redis providing object mapping, relationships, and advanced features like encryption, connection pooling, and permission systems. This technical reference covers the major classes, methods, and usage patterns introduced in the v2.0.0-pre series.
4
+
5
+ > For conceptual understanding and getting started with Familia, see the [Overview Guide](../overview.md). This document provides detailed implementation patterns and advanced configuration options.
6
+
7
+ ---
8
+
9
+ ## Core Architecture
10
+
11
+ ### Base Classes
12
+
13
+ #### `Familia::Horreum` - Primary ORM Base Class
14
+ The main base class for Valkey/Redis-backed objects, similar to ActiveRecord models.
15
+
16
+ ```ruby
17
+ class User < Familia::Horreum
18
+ # Basic field definitions
19
+ field :name, :email, :created_at
20
+
21
+ # Valkey/Redis data types as instance variables
22
+ list :sessions # Valkey/Redis list
23
+ set :tags # Valkey/Redis set
24
+ zset :scores # Valkey/Redis sorted set
25
+ hashkey :settings # Valkey/Redis hash
26
+ end
27
+ ```
28
+
29
+ **Key Methods:**
30
+ - `save` - Persist object to Valkey/Redis
31
+ - `save_if_not_exists` - Conditional persistence (v2.0.0-pre6)
32
+ - `load` - Load object from Valkey/Redis
33
+ - `exists?` - Check if object exists in Valkey/Redis
34
+ - `destroy` - Remove object from Valkey/Redis
35
+
36
+ #### `Familia::DataType` - Valkey/Redis Data Type Wrapper
37
+ Base class for Valkey/Redis data type implementations.
38
+
39
+ **Registered Types:**
40
+ - `String` - Valkey/Redis strings
41
+ - `List` - Valkey/Redis lists
42
+ - `Set` - Valkey/Redis sets
43
+ - `SortedSet` - Valkey/Redis sorted sets
44
+ - `HashKey` - Valkey/Redis hashes
45
+ - `Counter` - Atomic counters
46
+ - `Lock` - Distributed locks
47
+
48
+ ---
49
+
50
+ ## Feature System (v2.0.0-pre5+)
51
+
52
+ ### Feature Architecture
53
+ Modular system for extending Horreum classes with reusable functionality.
54
+
55
+ ```ruby
56
+ class Customer < Familia::Horreum
57
+ feature :expiration # TTL management
58
+ feature :safe_dump # API-safe serialization
59
+ feature :encrypted_fields # Field encryption
60
+ feature :transient_fields # Non-persistent fields
61
+ feature :relationships # Object relationships
62
+
63
+ field :name, :email
64
+ encrypted_field :api_key
65
+ transient_field :password
66
+ end
67
+ ```
68
+
69
+ ### Built-in Features
70
+
71
+ #### 1. Expiration Feature
72
+ TTL (Time To Live) management with cascading expiration.
73
+
74
+ ```ruby
75
+ class Session < Familia::Horreum
76
+ feature :expiration
77
+
78
+ field :user_id, :token
79
+ default_expiration 24.hours
80
+
81
+ # Cascade expiration to related objects
82
+ cascade_expiration_to :user_activity
83
+ end
84
+
85
+ session = Session.new(user_id: 123, token: "abc123")
86
+ session.save
87
+ session.expire_in(1.hour) # UnsortedSet custom expiration
88
+ session.ttl # Check remaining time
89
+ ```
90
+
91
+ #### 2. SafeDump Feature
92
+ API-safe serialization excluding sensitive fields.
93
+
94
+ ```ruby
95
+ class User < Familia::Horreum
96
+ feature :safe_dump
97
+
98
+ field :name, :email, :password_hash
99
+ safe_dump_field :name, :email # Only these fields in safe_dump
100
+ end
101
+
102
+ user = User.new(name: "Alice", email: "alice@example.com", password_hash: "secret")
103
+ user.safe_dump # => {"name" => "Alice", "email" => "alice@example.com"}
104
+ ```
105
+
106
+ #### 3. Encrypted Fields Feature (v2.0.0-pre5)
107
+ Transparent field-level encryption with multiple providers.
108
+
109
+ ```ruby
110
+ # Configuration
111
+ Familia.configure do |config|
112
+ config.encryption_keys = {
113
+ v1: ENV['FAMILIA_ENCRYPTION_KEY'],
114
+ v2: ENV['FAMILIA_ENCRYPTION_KEY_V2']
115
+ }
116
+ config.current_key_version = :v2
117
+ end
118
+
119
+ class Vault < Familia::Horreum
120
+ feature :encrypted_fields
121
+
122
+ field :name # Plaintext
123
+ encrypted_field :secret_key # Encrypted with XChaCha20-Poly1305
124
+ encrypted_field :api_token # Field-specific key derivation
125
+ encrypted_field :private_data # Transparent access
126
+ end
127
+
128
+ vault = Vault.new(
129
+ name: "Production Secrets",
130
+ secret_key: "super-secret-123",
131
+ api_token: "sk-1234567890"
132
+ )
133
+ vault.save
134
+
135
+ # Transparent access - automatically encrypted/decrypted
136
+ vault.secret_key # => "super-secret-123" (decrypted on access)
137
+ ```
138
+
139
+ **Encryption Providers:**
140
+ - **XChaCha20-Poly1305** (preferred) - Requires `rbnacl` gem
141
+ - **AES-256-GCM** (fallback) - Uses OpenSSL, no dependencies
142
+
143
+ **Advanced Security Implementation:**
144
+ ```ruby
145
+ # Multiple encryption providers with fallback
146
+ class CriticalData < Familia::Horreum
147
+ feature :encrypted_fields
148
+
149
+ # Configure encryption provider preference
150
+ set_encryption_provider :xchacha20_poly1305 # Preferred
151
+ set_fallback_provider :aes_gcm # Fallback
152
+
153
+ encrypted_field :credit_card, aad_fields: [:user_id, :created_at]
154
+ encrypted_field :ssn, provider: :xchacha20_poly1305 # Force specific provider
155
+ encrypted_field :notes # Uses default provider
156
+ end
157
+
158
+ # Key versioning and rotation
159
+ Familia.configure do |config|
160
+ config.encryption_keys = {
161
+ v1: ENV['OLD_KEY'], # Legacy key
162
+ v2: ENV['CURRENT_KEY'], # Current key
163
+ v3: ENV['NEW_KEY'] # New key for rotation
164
+ }
165
+ config.current_key_version = :v2
166
+
167
+ # Provider configuration
168
+ config.encryption_providers = {
169
+ xchacha20_poly1305: {
170
+ key_size: 32,
171
+ nonce_size: 24,
172
+ require_gem: 'rbnacl'
173
+ },
174
+ aes_gcm: {
175
+ key_size: 32,
176
+ iv_size: 12,
177
+ tag_size: 16
178
+ }
179
+ }
180
+ end
181
+
182
+ # Request-level key caching for performance
183
+ Familia::Encryption.with_request_cache do
184
+ 1000.times do |i|
185
+ record = CriticalData.new(
186
+ credit_card: "4111-1111-1111-#{i.to_s.rjust(4, '0')}",
187
+ ssn: "123-45-#{i.to_s.rjust(4, '0')}",
188
+ notes: "Customer record #{i}"
189
+ )
190
+ record.save # Reuses derived keys for performance
191
+ end
192
+ end
193
+
194
+ # Key rotation procedures
195
+ CriticalData.all.each do |record|
196
+ # Re-encrypt with current key version
197
+ record.re_encrypt_fields!
198
+
199
+ # Verify encryption status
200
+ status = record.encrypted_fields_status
201
+ puts "Record #{record.identifier}: #{status}"
202
+ # => {credit_card: {encrypted: true, key_version: :v2, provider: :xchacha20_poly1305}}
203
+ end
204
+ ```
205
+
206
+ **ConcealedString Security Features:**
207
+ ```ruby
208
+ # Automatic protection against accidental exposure
209
+ user = CriticalData.load("user123")
210
+
211
+ # Safe operations
212
+ user.credit_card.class # => ConcealedString
213
+ user.credit_card.to_s # => "[CONCEALED]"
214
+ user.credit_card.inspect # => "[CONCEALED]"
215
+ user.credit_card.to_json # => "\"[CONCEALED]\""
216
+
217
+ # Explicit access when needed
218
+ actual_card = user.credit_card.reveal # => "4111-1111-1111-1234"
219
+
220
+ # Logging safety
221
+ Rails.logger.info "Processing card: #{user.credit_card}"
222
+ # => "Processing card: [CONCEALED]" (safe for logs)
223
+
224
+ # JSON serialization safety
225
+ user_data = user.to_json
226
+ # All encrypted fields show as "[CONCEALED]" in JSON
227
+ ```
228
+
229
+ #### 4. Transient Fields Feature (v2.0.0-pre5)
230
+ Non-persistent fields with memory-safe handling.
231
+
232
+ ```ruby
233
+ class LoginForm < Familia::Horreum
234
+ feature :transient_fields
235
+
236
+ field :username # Persistent
237
+ transient_field :password # Never stored in Redis
238
+ transient_field :csrf_token # Runtime only
239
+ end
240
+
241
+ form = LoginForm.new(username: "alice", password: "secret123")
242
+ form.save # Only saves 'username', password is transient
243
+
244
+ form.password.class # => Familia::Features::TransientFields::RedactedString
245
+ form.password.to_s # => "[REDACTED]" (safe for logging)
246
+ form.password.reveal # => "secret123" (explicit access)
247
+ ```
248
+
249
+ **RedactedString** - Security wrapper preventing accidental exposure:
250
+ - `to_s` returns "[REDACTED]"
251
+ - `inspect` returns "[REDACTED]"
252
+ - `reveal` method for explicit access
253
+ - Safe for logging and serialization
254
+
255
+ #### 5. Relationships Feature (v2.0.0-pre7)
256
+ Comprehensive object relationship system with automatic management, clean Ruby-idiomatic syntax, and simplified method generation.
257
+
258
+ ```ruby
259
+ class Customer < Familia::Horreum
260
+ feature :relationships
261
+
262
+ identifier_field :custid
263
+ field :custid, :name, :email
264
+
265
+ # Collections for storing related object IDs
266
+ set :domains # Simple set
267
+ zset :activity # Scored/sorted collection
268
+
269
+ # Class-level indexed lookups (automatically managed on save/destroy)
270
+ class_indexed_by :email, :email_lookup
271
+
272
+ # Class-level tracking with scoring (automatically managed on save/destroy)
273
+ class_participates_in :all_customers, score: :created_at
274
+ end
275
+
276
+ class Domain < Familia::Horreum
277
+ feature :relationships
278
+
279
+ identifier_field :domain_id
280
+ field :domain_id, :name, :status
281
+
282
+ # Bidirectional membership with clean << operator support
283
+ participates_in Customer, :domains
284
+
285
+ # Relationship-scoped indexing (per-customer domain lookups)
286
+ indexed_by :name, :domain_index, target: Customer
287
+
288
+ # Class-level conditional tracking with lambda scoring
289
+ class_participates_in :active_domains,
290
+ score: -> { status == 'active' ? Familia.now.to_i : 0 }
291
+ end
292
+ ```
293
+
294
+ **Relationship Operations with Automatic Management:**
295
+
296
+ ```ruby
297
+ # Create and save objects (automatic indexing and tracking)
298
+ customer = Customer.new(custid: "cust123", name: "Acme Corp", email: "admin@acme.com")
299
+ customer.save # Automatically adds to email_lookup and all_customers
300
+
301
+ domain = Domain.new(domain_id: "dom456", name: "acme.com", status: "active")
302
+ domain.save # Automatically adds to active_domains
303
+
304
+ # Establish relationships using clean Ruby-like << operator
305
+ customer.domains << domain # Clean, Ruby-idiomatic collection syntax
306
+
307
+ # Query relationships
308
+ domain.in_customer_domains?(customer.custid) # => true
309
+ customer.domains.member?(domain.identifier) # => true
310
+
311
+ # O(1) indexed lookups (automatic management - no manual calls needed)
312
+ found_id = Customer.email_lookup.get("admin@acme.com")
313
+ found_customer = Customer.find_by_email("admin@acme.com") # Convenience method
314
+
315
+ # Relationship-scoped lookups
316
+ customer_domain = customer.find_by_name("acme.com") # Find within customer
317
+
318
+ # Class-level tracking queries
319
+ recent_customers = Customer.all_customers.range_by_score(
320
+ (Familia.now - 24.hours).to_i, '+inf'
321
+ )
322
+ active_domains = Domain.active_domains.members
323
+ ```
324
+
325
+ **Key Features:**
326
+ - **Automatic Management**: Objects automatically added/removed from class-level collections on save/destroy
327
+ - **Clean Syntax**: Collections support Ruby-like `customer.domains << domain` syntax
328
+ - **Simplified Methods**: No complex "global" terminology - uses clear `class_` prefixes
329
+ - **Performance**: O(1) hash lookups and efficient sorted set operations
330
+ - **Flexibility**: Supports class-level and relationship-scoped indexing patterns
331
+
332
+ #### 6. Object Identifier Feature (v2.0.0-pre7)
333
+ Automatic generation of unique object identifiers with configurable strategies.
334
+
335
+ ```ruby
336
+ class Document < Familia::Horreum
337
+ feature :object_identifier, generator: :uuid_v4
338
+
339
+ field :title, :content, :created_at
340
+ end
341
+
342
+ class Session < Familia::Horreum
343
+ feature :object_identifier, generator: :hex, length: 16
344
+
345
+ field :user_id, :data, :expires_at
346
+ end
347
+
348
+ class ApiKey < Familia::Horreum
349
+ feature :object_identifier, generator: :custom
350
+
351
+ field :name, :permissions, :created_at
352
+
353
+ # Custom generator implementation
354
+ def self.generate_identifier
355
+ "ak_#{SecureRandom.alphanumeric(32)}"
356
+ end
357
+ end
358
+ ```
359
+
360
+ **Generator Types:**
361
+ - `:uuid_v4` - Standard UUID v4 format (36 characters)
362
+ - `:hex` - Hexadecimal strings (configurable length, default 12)
363
+ - `:custom` - User-defined generator method
364
+
365
+ **Technical Implementation:**
366
+ ```ruby
367
+ # Auto-generated on object creation
368
+ doc = Document.create(title: "My Document")
369
+ doc.objid # => "f47ac10b-58cc-4372-a567-0e02b2c3d479"
370
+
371
+ session = Session.create(user_id: "123")
372
+ session.objid # => "a1b2c3d4e5f67890"
373
+
374
+ # Custom identifier validation
375
+ api_key = ApiKey.create(name: "Production API")
376
+ api_key.objid # => "ak_Xy9ZaBcD3fG8HjKlMnOpQrStUvWxYz12"
377
+
378
+ # Collision detection and retry logic
379
+ Document.feature_options(:object_identifier)
380
+ #=> {generator: :uuid_v4, max_retries: 3, collision_check: true}
381
+ ```
382
+
383
+ #### 7. External Identifier Feature (v2.0.0-pre7)
384
+ Integration patterns for external system identifiers with validation and mapping.
385
+
386
+ ```ruby
387
+ class ExternalUser < Familia::Horreum
388
+ feature :external_identifier
389
+
390
+ identifier_field :internal_id
391
+ field :internal_id, :external_id, :name, :sync_status, :last_sync_at
392
+
393
+ # External system validation (custom implementation)
394
+ def valid_external_id?
395
+ external_id.present? && external_id.match?(/^ext_\d{6,}$/)
396
+ end
397
+ end
398
+
399
+ class LegacyAccount < Familia::Horreum
400
+ feature :external_identifier, prefix: "legacy"
401
+
402
+ field :legacy_account_id, :migrated_at, :migration_status
403
+
404
+ # Custom validation logic
405
+ def valid_external_id?
406
+ legacy_account_id.present? &&
407
+ legacy_account_id.match?(/^LAC[A-Z]{2}\d{8}$/)
408
+ end
409
+
410
+ # Bidirectional mapping
411
+ def self.find_by_legacy_id(legacy_id)
412
+ mapping = external_id_mapping.get(legacy_id)
413
+ mapping ? load(mapping) : nil
414
+ end
415
+ end
416
+ ```
417
+
418
+ **External ID Management:**
419
+ ```ruby
420
+ # Create with external mapping
421
+ user = ExternalUser.new(
422
+ internal_id: SecureRandom.uuid,
423
+ external_id: "ext_123456",
424
+ name: "John Doe"
425
+ )
426
+ user.save # Automatically creates bidirectional mapping
427
+
428
+ # Lookup by external ID (custom implementation)
429
+ found_user = nil
430
+ ExternalUser.all.each do |user|
431
+ if user.external_id == "ext_123456"
432
+ found_user = user
433
+ break
434
+ end
435
+ end
436
+
437
+ # Sync status tracking (custom implementation)
438
+ user.sync_status = "pending"
439
+ user.save
440
+ user.sync_status = "completed"
441
+ user.save
442
+ ```
443
+
444
+ #### 8. Quantization Feature (v2.0.0-pre7)
445
+ Advanced time-based data bucketing with configurable strategies and analytics integration.
446
+
447
+ ```ruby
448
+ class DailyMetric < Familia::Horreum
449
+ feature :quantization
450
+
451
+ identifier_field :metric_key
452
+ field :metric_key, :bucket_timestamp, :value_count, :sum_value
453
+
454
+ # Example: Basic time quantization
455
+ string :counter, default_expiration: 1.day, quantize: [10.minutes, '%H:%M']
456
+ end
457
+ ```
458
+
459
+ **Basic Usage:**
460
+ ```ruby
461
+ # Create metric with quantized timestamp
462
+ metric = DailyMetric.new(metric_key: "page_views")
463
+ metric.counter.increment
464
+
465
+ # Time-based data grouping occurs automatically
466
+ # All data within the same 10-minute window shares the same key
467
+ ```
468
+
469
+ **Key Benefits:**
470
+ - **Time Bucketing**: Group time-based data into configurable intervals
471
+ - **Reduced Storage**: Aggregate similar data points to optimize memory usage
472
+ - **Analytics Ready**: Perfect for dashboards and time-series data visualization
473
+
474
+ ---
475
+
476
+ ## Advanced Feature System Architecture (v2.0.0-pre7)
477
+
478
+ ### Feature Autoloader for Complex Projects
479
+ Organize features into modular files for large applications.
480
+
481
+ ```ruby
482
+ # app/models/customer.rb - Main model file
483
+ class Customer < Familia::Horreum
484
+ module Features
485
+ include Familia::Features::Autoloader
486
+ # Automatically loads all .rb files from app/models/customer/features/
487
+ end
488
+
489
+ # Core model definition
490
+ identifier_field :custid
491
+ field :custid, :name, :email, :created_at
492
+ end
493
+
494
+ # app/models/customer/features/notifications.rb
495
+ module Customer::Features::Notifications
496
+ def send_welcome_email
497
+ NotificationService.send_template(
498
+ email: email,
499
+ template: 'customer_welcome',
500
+ variables: { name: name, custid: custid }
501
+ )
502
+ end
503
+
504
+ def send_invoice_reminder(invoice_id)
505
+ NotificationService.send_template(
506
+ email: email,
507
+ template: 'invoice_reminder',
508
+ variables: { invoice_id: invoice_id }
509
+ )
510
+ end
511
+ end
512
+
513
+ # app/models/customer/features/analytics.rb
514
+ module Customer::Features::Analytics
515
+ extend ActiveSupport::Concern
516
+
517
+ included do
518
+ # Add analytics tracking to core model
519
+ feature :relationships
520
+ class_participates_in :customer_analytics, score: :created_at
521
+ end
522
+
523
+ def track_activity(activity_type, metadata = {})
524
+ activity_data = {
525
+ custid: custid,
526
+ activity: activity_type,
527
+ timestamp: Familia.now.to_i,
528
+ metadata: metadata
529
+ }
530
+
531
+ # Store in customer's activity stream
532
+ activities.unshift(activity_data.to_json)
533
+ activities.trim(0, 999) # Keep last 1000 activities
534
+ end
535
+
536
+ def recent_activities(limit = 10)
537
+ activities.range(0, limit - 1).map { |json| JSON.parse(json) }
538
+ end
539
+ end
540
+ ```
541
+
542
+ ### Feature Dependencies and Loading Order
543
+ Control feature loading sequence with dependency declarations.
544
+
545
+ ```ruby
546
+ # lib/features/advanced_encryption.rb
547
+ module AdvancedEncryption
548
+ extend Familia::Features::Autoloadable
549
+
550
+ def self.depends_on
551
+ [:encrypted_fields, :safe_dump] # Required features
552
+ end
553
+
554
+ def self.included(base)
555
+ base.extend ClassMethods
556
+ end
557
+
558
+ module ClassMethods
559
+ def encrypt_all_fields!
560
+ # Batch encrypt all existing records
561
+ all_records.each(&:re_encrypt_fields!)
562
+ end
563
+
564
+ def encryption_health_check
565
+ # Validate encryption across all records
566
+ failed_records = []
567
+ all_records.each do |record|
568
+ unless record.encrypted_fields_status.all? { |_, status| status[:encrypted] }
569
+ failed_records << record.identifier
570
+ end
571
+ end
572
+ failed_records
573
+ end
574
+ end
575
+
576
+ def secure_export
577
+ # Combine safe_dump with additional security
578
+ exported = safe_dump
579
+ exported[:export_timestamp] = Familia.now.to_i
580
+ exported[:checksum] = Digest::SHA256.hexdigest(exported.to_json)
581
+ exported
582
+ end
583
+ end
584
+
585
+ # Usage with automatic dependency resolution
586
+ class SecureCustomer < Familia::Horreum
587
+ feature :advanced_encryption # Automatically includes dependencies
588
+
589
+ field :name, :email
590
+ encrypted_field :api_key, :private_notes
591
+ safe_dump_field :name, :email
592
+ end
593
+ ```
594
+
595
+ ### Per-Class Feature Configuration Isolation
596
+ Each class maintains independent feature options.
597
+
598
+ ```ruby
599
+ class PrimaryCache < Familia::Horreum
600
+ feature :expiration, cascade_to: [:secondary_cache]
601
+ feature :quantization, time_buckets: [1.hour, 6.hours, 1.day]
602
+
603
+ field :cache_key, :value, :hit_count
604
+ default_expiration 24.hours
605
+ end
606
+
607
+ class SecondaryCache < Familia::Horreum
608
+ feature :expiration, cascade_to: [] # No further cascading
609
+ feature :quantization, time_buckets: [1.day, 1.week] # Different buckets
610
+
611
+ field :cache_key, :backup_value, :backup_timestamp
612
+ default_expiration 7.days
613
+ end
614
+
615
+ # Feature options are completely isolated
616
+ PrimaryCache.feature_options(:expiration)
617
+ #=> {cascade_to: [:secondary_cache]}
618
+
619
+ SecondaryCache.feature_options(:expiration)
620
+ #=> {cascade_to: []}
621
+
622
+ PrimaryCache.feature_options(:quantization)
623
+ #=> {time_buckets: [3600, 21600, 86400]}
624
+
625
+ SecondaryCache.feature_options(:quantization)
626
+ #=> {time_buckets: [86400, 604800]}
627
+ ```
628
+
629
+ ### Runtime Feature Management
630
+ Add, remove, and configure features dynamically.
631
+
632
+ ```ruby
633
+ class DynamicModel < Familia::Horreum
634
+ field :name, :status
635
+
636
+ def self.enable_feature_set(feature_set)
637
+ case feature_set
638
+ when :basic
639
+ feature :expiration
640
+ feature :safe_dump
641
+ when :secure
642
+ feature :expiration
643
+ feature :encrypted_fields
644
+ feature :safe_dump
645
+ when :analytics
646
+ feature :expiration
647
+ feature :relationships
648
+ feature :quantization
649
+ end
650
+ end
651
+
652
+ def self.feature_enabled?(feature_name)
653
+ features_enabled.include?(feature_name.to_sym)
654
+ end
655
+
656
+ def self.disable_feature(feature_name)
657
+ # Remove feature from enabled list (affects new instances)
658
+ features_enabled.delete(feature_name.to_sym)
659
+ remove_feature_options(feature_name)
660
+ end
661
+ end
662
+
663
+ # Runtime configuration
664
+ DynamicModel.enable_feature_set(:analytics)
665
+ DynamicModel.feature_enabled?(:relationships) # => true
666
+
667
+ # Conditional feature usage
668
+ if DynamicModel.feature_enabled?(:encrypted_fields)
669
+ DynamicModel.encrypted_field :sensitive_data
670
+ end
671
+ ```
672
+
673
+ ---
674
+
675
+ ## Connection Management (v2.0.0-pre+)
676
+
677
+ ### Connection Provider Pattern
678
+ Flexible connection pooling with provider-based architecture.
679
+
680
+ ```ruby
681
+ # Basic Valkey/Redis connection
682
+ Familia.configure do |config|
683
+ config.redis_uri = "redis://localhost:6379/0"
684
+ end
685
+
686
+ # Connection pooling with ConnectionPool gem
687
+ require 'connection_pool'
688
+
689
+ Familia.connection_provider = lambda do |uri|
690
+ parsed = URI.parse(uri)
691
+ pool_key = "#{parsed.host}:#{parsed.port}/#{parsed.db || 0}"
692
+
693
+ @pools ||= {}
694
+ @pools[pool_key] ||= ConnectionPool.new(size: 10, timeout: 5) do
695
+ Redis.new(
696
+ host: parsed.host,
697
+ port: parsed.port,
698
+ db: parsed.db || 0,
699
+ connect_timeout: 1,
700
+ read_timeout: 1,
701
+ write_timeout: 1
702
+ )
703
+ end
704
+
705
+ @pools[pool_key].with { |conn| yield conn if block_given?; conn }
706
+ end
707
+ ```
708
+
709
+ ### Multi-Database Support
710
+ Configure different logical databases for different models.
711
+
712
+ ```ruby
713
+ class User < Familia::Horreum
714
+ logical_database 0 # Use database 0
715
+ field :name, :email
716
+ end
717
+
718
+ class Session < Familia::Horreum
719
+ logical_database 1 # Use database 1
720
+ field :user_id, :token
721
+ end
722
+
723
+ class Cache < Familia::Horreum
724
+ logical_database 2 # Use database 2
725
+ field :key, :value
726
+ end
727
+ ```
728
+
729
+ ---
730
+
731
+ ## Advanced Relationship Patterns
732
+
733
+ ### Permission-Encoded Relationships (v2.0.0-pre7)
734
+ Combine timestamps with permission bits for access control.
735
+
736
+ ```ruby
737
+ class Document < Familia::Horreum
738
+ feature :relationships
739
+
740
+ identifier_field :doc_id
741
+ field :doc_id, :title, :content
742
+
743
+ # Permission constants (bit flags)
744
+ READ = 1 # 001
745
+ WRITE = 2 # 010
746
+ DELETE = 4 # 100
747
+ ADMIN = 8 # 1000
748
+ end
749
+
750
+ class UserDocumentAccess
751
+ # Encode timestamp + permissions into sorted set score
752
+ def self.encode_score(timestamp, permissions)
753
+ "#{timestamp}.#{permissions}".to_f
754
+ end
755
+
756
+ def self.decode_score(score)
757
+ parts = score.to_s.split('.')
758
+ timestamp = parts[0].to_i
759
+ permissions = parts[1] ? parts[1].to_i : 0
760
+ [timestamp, permissions]
761
+ end
762
+
763
+ # Check if user has specific permission
764
+ def self.has_permission?(permissions, required)
765
+ (permissions & required) != 0
766
+ end
767
+ end
768
+
769
+ # Usage example
770
+ user_id = "user123"
771
+ doc_id = "doc456"
772
+ timestamp = Familia.now.to_i
773
+
774
+ # Grant read + write permissions
775
+ permissions = Document::READ | Document::WRITE # 3
776
+ score = UserDocumentAccess.encode_score(timestamp, permissions)
777
+
778
+ # Store in sorted set (user_id -> score with permissions)
779
+ user_documents = Familia::DataType::SortedSet.new("user:#{user_id}:documents")
780
+ user_documents.add(doc_id, score)
781
+
782
+ # Query with permission filtering
783
+ docs_with_write = user_documents.select do |doc_id, score|
784
+ _, permissions = UserDocumentAccess.decode_score(score)
785
+ UserDocumentAccess.has_permission?(permissions, Document::WRITE)
786
+ end
787
+ ```
788
+
789
+ ### Time-Series Relationships with Automatic Management
790
+ Track relationships over time with timestamp-based scoring and automatic updates.
791
+
792
+ ```ruby
793
+ class ActivityTracker < Familia::Horreum
794
+ feature :relationships
795
+
796
+ identifier_field :activity_id
797
+ field :activity_id, :user_id, :activity_type, :data, :created_at
798
+
799
+ # Class-level tracking with automatic management
800
+ class_participates_in :user_activities, score: :created_at
801
+ class_participates_in :activity_by_type,
802
+ score: ->(activity) { "#{activity.activity_type}:#{activity.created_at}".hash }
803
+ end
804
+
805
+ # Create and save activity (automatic tracking)
806
+ activity = ActivityTracker.new(
807
+ activity_id: 'act123',
808
+ user_id: 'user456',
809
+ activity_type: 'login',
810
+ created_at: Familia.now.to_i
811
+ )
812
+ activity.save # Automatically added to both tracking collections
813
+
814
+ # Query recent activities (last hour)
815
+ hour_ago = (Familia.now - 1.hour).to_i
816
+ recent_activities = ActivityTracker.user_activities.range_by_score(
817
+ hour_ago, '+inf', limit: [0, 50]
818
+ )
819
+
820
+ # Get activities by type in time range
821
+ login_activities = ActivityTracker.activity_by_type.range_by_score(
822
+ "login:#{hour_ago}".hash, "login:#{Familia.now.to_i}".hash
823
+ )
824
+ ```
825
+
826
+ ---
827
+
828
+ ## Data Type Usage Patterns
829
+
830
+ ### Advanced Sorted UnsortedSet Operations
831
+ Leverage Valkey/Redis sorted sets for rankings, time series, and scored data.
832
+
833
+ ```ruby
834
+ class Leaderboard < Familia::Horreum
835
+ identifier_field :game_id
836
+ field :game_id, :name
837
+ sorted_set :scores
838
+ end
839
+
840
+ leaderboard = Leaderboard.new(game_id: "game1", name: "Daily Challenge")
841
+
842
+ # Add player scores
843
+ leaderboard.scores.add("player1", 1500)
844
+ leaderboard.scores.add("player2", 2300)
845
+ leaderboard.scores.add("player3", 1800)
846
+
847
+ # Get top 10 players
848
+ top_players = leaderboard.scores.range(0, 9, with_scores: true, order: 'DESC')
849
+ # => [["player2", 2300.0], ["player3", 1800.0], ["player1", 1500.0]]
850
+
851
+ # Get player rank
852
+ rank = leaderboard.scores.rank("player1", order: 'DESC') # => 2 (0-indexed)
853
+
854
+ # Get score range
855
+ mid_tier = leaderboard.scores.range_by_score(1000, 2000, with_scores: true)
856
+
857
+ # Increment score atomically
858
+ leaderboard.scores.increment("player1", 100) # Add 100 to existing score
859
+ ```
860
+
861
+ ### List-Based Queues and Feeds
862
+ Use Valkey/Redis lists for queues, feeds, and ordered data.
863
+
864
+ ```ruby
865
+ class TaskQueue < Familia::Horreum
866
+ identifier_field :queue_name
867
+ field :queue_name
868
+ list :tasks
869
+ end
870
+
871
+ class ActivityFeed < Familia::Horreum
872
+ identifier_field :user_id
873
+ field :user_id
874
+ list :activities
875
+ end
876
+
877
+ # Task queue operations
878
+ queue = TaskQueue.new(queue_name: "email_processing")
879
+
880
+ # Add tasks to queue (right push)
881
+ queue.tasks.push({
882
+ type: "send_email",
883
+ recipient: "user@example.com",
884
+ template: "welcome"
885
+ }.to_json)
886
+
887
+ # Process tasks (left pop - FIFO)
888
+ next_task = queue.tasks.pop # Atomic pop from left
889
+ task_data = JSON.parse(next_task) if next_task
890
+
891
+ # Activity feed with size limit
892
+ feed = ActivityFeed.new(user_id: "user123")
893
+
894
+ # Add activity (keep last 100)
895
+ feed.activities.unshift("User logged in at #{Familia.now}")
896
+ feed.activities.trim(0, 99) # Keep only last 100 items
897
+
898
+ # Get recent activities
899
+ recent = feed.activities.range(0, 9) # Get 10 most recent
900
+ ```
901
+
902
+ ### Hash-Based Configuration Storage
903
+ Store structured configuration and key-value data.
904
+
905
+ ```ruby
906
+ class UserPreferences < Familia::Horreum
907
+ identifier_field :user_id
908
+ field :user_id
909
+ hash :settings
910
+ hash :feature_flags
911
+ end
912
+
913
+ prefs = UserPreferences.new(user_id: "user123")
914
+
915
+ # UnsortedSet individual preferences
916
+ prefs.settings.set("theme", "dark")
917
+ prefs.settings.set("notifications", "true")
918
+ prefs.settings.set("timezone", "UTC-5")
919
+
920
+ # Batch set multiple values
921
+ prefs.feature_flags.update({
922
+ "beta_ui" => "true",
923
+ "new_dashboard" => "false",
924
+ "advanced_features" => "true"
925
+ })
926
+
927
+ # Get preferences
928
+ theme = prefs.settings.get("theme") # => "dark"
929
+ all_settings = prefs.settings.all # => Hash of all settings
930
+
931
+ # Check feature flags
932
+ beta_enabled = prefs.feature_flags.get("beta_ui") == "true"
933
+ ```
934
+
935
+ ---
936
+
937
+ ## Error Handling and Validation
938
+
939
+ ### Connection Error Handling
940
+ Robust error handling for Valkey/Redis connection issues.
941
+
942
+ ```ruby
943
+ class ResilientService < Familia::Horreum
944
+ field :name, :data
945
+
946
+ def self.with_fallback(&block)
947
+ retries = 3
948
+ begin
949
+ yield
950
+ rescue Redis::ConnectionError, Redis::TimeoutError => e
951
+ retries -= 1
952
+ if retries > 0
953
+ sleep(0.1 * (4 - retries)) # Exponential backoff
954
+ retry
955
+ else
956
+ Familia.warn "Redis operation failed after retries: #{e.message}"
957
+ nil # Return nil or handle gracefully
958
+ end
959
+ end
960
+ end
961
+
962
+ def save_with_fallback
963
+ self.class.with_fallback { save }
964
+ end
965
+ end
966
+ ```
967
+
968
+ ### Data Validation Patterns
969
+ Implement validation in model classes.
970
+
971
+ ```ruby
972
+ class User < Familia::Horreum
973
+ field :email, :username, :age
974
+
975
+ def valid?
976
+ errors.clear
977
+ validate_email
978
+ validate_username
979
+ validate_age
980
+ errors.empty?
981
+ end
982
+
983
+ def errors
984
+ @errors ||= []
985
+ end
986
+
987
+ private
988
+
989
+ def validate_email
990
+ unless email&.include?('@')
991
+ errors << "Email must be valid"
992
+ end
993
+ end
994
+
995
+ def validate_username
996
+ if username.nil? || username.length < 3
997
+ errors << "Username must be at least 3 characters"
998
+ end
999
+ end
1000
+
1001
+ def validate_age
1002
+ unless age.is_a?(Integer) && age > 0
1003
+ errors << "Age must be a positive integer"
1004
+ end
1005
+ end
1006
+ end
1007
+
1008
+ # Usage
1009
+ user = User.new(email: "invalid", username: "ab", age: -5)
1010
+ if user.valid?
1011
+ user.save
1012
+ else
1013
+ puts "Validation errors: #{user.errors.join(', ')}"
1014
+ end
1015
+ ```
1016
+
1017
+ ---
1018
+
1019
+ ## Performance Optimization
1020
+
1021
+ ### Batch Operations
1022
+ Minimize Valkey/Redis round trips with batch operations.
1023
+
1024
+ ```ruby
1025
+ # Instead of multiple individual operations
1026
+ users = []
1027
+ 100.times do |i|
1028
+ user = User.new(name: "User #{i}", email: "user#{i}@example.com")
1029
+ users << user
1030
+ end
1031
+
1032
+ # Use Valkey/Redis pipelining for batch saves
1033
+ User.transaction do |redis|
1034
+ users.each do |user|
1035
+ # All operations batched in transaction
1036
+ user.object.set_all(user.to_hash)
1037
+ User.email_index.set(user.email, user.identifier)
1038
+ end
1039
+ end
1040
+ ```
1041
+
1042
+ ### Memory Optimization
1043
+ Efficient memory usage patterns.
1044
+
1045
+ ```ruby
1046
+ class CacheEntry < Familia::Horreum
1047
+ feature :expiration
1048
+
1049
+ field :key, :value, :created_at
1050
+ default_expiration 1.hour
1051
+
1052
+ # Use shorter field names to reduce memory
1053
+ field :k, :v, :c # Instead of key, value, created_at
1054
+
1055
+ # Compress large values
1056
+ def value=(val)
1057
+ @value = val.length > 1000 ? Zlib.deflate(val) : val
1058
+ end
1059
+
1060
+ def value
1061
+ val = @value || ""
1062
+ val.start_with?("\x78\x9c") ? Zlib.inflate(val) : val
1063
+ rescue Zlib::DataError
1064
+ @value # Return original if decompression fails
1065
+ end
1066
+ end
1067
+ ```
1068
+
1069
+ ### Connection Pool Sizing
1070
+ Configure connection pools based on application needs.
1071
+
1072
+ ```ruby
1073
+ # High-throughput application
1074
+ Familia.connection_provider = lambda do |uri|
1075
+ ConnectionPool.new(size: 25, timeout: 5) do
1076
+ Redis.new(uri, connect_timeout: 0.1, read_timeout: 1, write_timeout: 1)
1077
+ end.with { |conn| yield conn if block_given?; conn }
1078
+ end
1079
+
1080
+ # Memory-constrained environment
1081
+ Familia.connection_provider = lambda do |uri|
1082
+ ConnectionPool.new(size: 5, timeout: 10) do
1083
+ Redis.new(uri, connect_timeout: 2, read_timeout: 5, write_timeout: 5)
1084
+ end.with { |conn| yield conn if block_given?; conn }
1085
+ end
1086
+ ```
1087
+
1088
+ ---
1089
+
1090
+ ## Migration and Upgrading
1091
+
1092
+ ### From v1.x to v2.0.0-pre
1093
+ Key changes and migration steps.
1094
+
1095
+ ```ruby
1096
+ # OLD v1.x syntax
1097
+ class User < Familia
1098
+ identifier :email
1099
+ string :name
1100
+ list :sessions
1101
+ end
1102
+
1103
+ # NEW v2.0.0-pre syntax
1104
+ class User < Familia::Horreum
1105
+ identifier_field :email # Updated method name
1106
+ field :name # Generic field method
1107
+ list :sessions # Data types unchanged
1108
+ end
1109
+
1110
+ # Feature activation (NEW)
1111
+ class User < Familia::Horreum
1112
+ feature :expiration # Explicit feature activation
1113
+ feature :safe_dump
1114
+
1115
+ identifier_field :email
1116
+ field :name
1117
+ list :sessions
1118
+
1119
+ default_expiration 24.hours # Feature-specific methods
1120
+ safe_dump_field :name # Feature-specific methods
1121
+ end
1122
+ ```
1123
+
1124
+ ### Encryption Migration
1125
+ Migrating existing fields to encrypted storage.
1126
+
1127
+ ```ruby
1128
+ # Step 1: Add feature without changing existing fields
1129
+ class User < Familia::Horreum
1130
+ feature :encrypted_fields # Add feature
1131
+
1132
+ field :name, :email
1133
+ field :api_key # Still plaintext during migration
1134
+ end
1135
+
1136
+ # Step 2: Migrate data with dual read/write
1137
+ class User < Familia::Horreum
1138
+ feature :encrypted_fields
1139
+
1140
+ field :name, :email
1141
+ encrypted_field :api_key # Now encrypted
1142
+
1143
+ # Temporary migration method
1144
+ def migrate_api_key!
1145
+ if raw_api_key = object.get("api_key") # Read old plaintext
1146
+ self.api_key = raw_api_key # Write as encrypted
1147
+ object.delete("api_key") # Remove plaintext
1148
+ save
1149
+ end
1150
+ end
1151
+ end
1152
+
1153
+ # Step 3: Run migration for all users
1154
+ User.all.each(&:migrate_api_key!)
1155
+ ```
1156
+
1157
+ ---
1158
+
1159
+ ## Testing Patterns
1160
+
1161
+ ### Test Helpers and Utilities
1162
+ Common patterns for testing Familia applications.
1163
+
1164
+ ```ruby
1165
+ # test_helper.rb
1166
+ require 'familia'
1167
+
1168
+ # Use separate Valkey/Redis database for tests
1169
+ Familia.configure do |config|
1170
+ config.redis_uri = ENV.fetch('REDIS_TEST_URI', 'redis://localhost:2525/3')
1171
+ end
1172
+
1173
+ module TestHelpers
1174
+ def setup_redis
1175
+ # Clear test database
1176
+ Familia.connection.flushdb
1177
+ end
1178
+
1179
+ def teardown_redis
1180
+ Familia.connection.flushdb
1181
+ end
1182
+
1183
+ def create_test_user(**attrs)
1184
+ User.new({
1185
+ email: "test@example.com",
1186
+ name: "Test User",
1187
+ created_at: Familia.now.to_i
1188
+ }.merge(attrs))
1189
+ end
1190
+ end
1191
+
1192
+ # In test files
1193
+ class UserTest < Minitest::Test
1194
+ include TestHelpers
1195
+
1196
+ def setup
1197
+ setup_redis
1198
+ end
1199
+
1200
+ def teardown
1201
+ teardown_redis
1202
+ end
1203
+
1204
+ def test_user_creation_with_automatic_indexing
1205
+ user = create_test_user(name: "Alice")
1206
+ user.save # Automatically adds to class-level indexes
1207
+
1208
+ assert user.exists?
1209
+ assert_equal "Alice", user.name
1210
+
1211
+ # Test automatic indexing
1212
+ found_id = User.email_lookup.get(user.email)
1213
+ assert_equal user.identifier, found_id
1214
+ end
1215
+
1216
+ def test_relationships_with_clean_syntax
1217
+ user = create_test_user
1218
+ user.save # Automatic class-level tracking
1219
+
1220
+ domain = Domain.new(domain_id: "test_domain", name: "test.com")
1221
+ domain.save # Automatic class-level tracking
1222
+
1223
+ # Test clean relationship syntax
1224
+ user.domains << domain # Ruby-like collection syntax
1225
+ assert domain.in_user_domains?(user.identifier)
1226
+ assert user.domains.member?(domain.identifier)
1227
+ end
1228
+
1229
+ def test_encrypted_fields_concealment
1230
+ setup_encryption_keys
1231
+
1232
+ vault = SecureVault.new(
1233
+ name: "Test Vault",
1234
+ secret_key: "super-secret-123",
1235
+ api_token: "sk-1234567890"
1236
+ )
1237
+ vault.save
1238
+
1239
+ # Test ConcealedString behavior
1240
+ assert_instance_of Familia::Features::EncryptedFields::ConcealedString, vault.secret_key
1241
+ assert_equal "[CONCEALED]", vault.secret_key.to_s
1242
+ assert_equal "super-secret-123", vault.secret_key.reveal
1243
+
1244
+ # Test JSON safety
1245
+ json_data = vault.to_json
1246
+ refute_includes json_data, "super-secret-123"
1247
+ assert_includes json_data, "[CONCEALED]"
1248
+ end
1249
+
1250
+ def test_transient_fields_non_persistence
1251
+ form = LoginForm.new(
1252
+ username: "testuser",
1253
+ password: "secret123",
1254
+ csrf_token: "abc123"
1255
+ )
1256
+ form.save
1257
+
1258
+ # Reload and verify transient fields not persisted
1259
+ reloaded = LoginForm.load(form.identifier)
1260
+ assert_equal "testuser", reloaded.username
1261
+ assert_nil reloaded.password
1262
+ assert_nil reloaded.csrf_token
1263
+ end
1264
+
1265
+ def test_object_identifier_generation
1266
+ # Test UUID generation
1267
+ doc = UuidDocument.create(title: "Test Doc")
1268
+ assert_match(/\A[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i, doc.objid)
1269
+
1270
+ # Test hex generation
1271
+ session = HexSession.create(user_id: "123")
1272
+ assert_match(/\A[0-9a-f]+\z/i, session.objid)
1273
+ assert_equal 16, session.objid.length
1274
+ end
1275
+
1276
+ def test_quantization_time_bucketing
1277
+ timestamp = Time.parse("2024-12-15 14:37:23")
1278
+ bucket = MetricsBucket.quantize_timestamp(timestamp)
1279
+ assert_equal "20241215_1430", bucket # 10-minute bucket
1280
+
1281
+ # Test multiple timestamps in same bucket
1282
+ timestamp2 = Time.parse("2024-12-15 14:39:45")
1283
+ bucket2 = MetricsBucket.quantize_timestamp(timestamp2)
1284
+ assert_equal bucket, bucket2
1285
+ end
1286
+
1287
+ def test_external_identifier_mapping
1288
+ user = ExternalUser.new(
1289
+ internal_id: SecureRandom.uuid,
1290
+ external_id: "ext_123456",
1291
+ name: "External User"
1292
+ )
1293
+ user.save
1294
+
1295
+ # Test bidirectional mapping
1296
+ found_by_external = ExternalUser.find_by_external_id("ext_123456")
1297
+ assert_equal user.internal_id, found_by_external.internal_id
1298
+
1299
+ # Test sync status tracking
1300
+ user.mark_sync_pending
1301
+ assert_equal "pending", user.sync_status
1302
+
1303
+ user.mark_sync_completed
1304
+ assert_equal "completed", user.sync_status
1305
+ end
1306
+
1307
+ private
1308
+
1309
+ def setup_encryption_keys
1310
+ test_keys = {
1311
+ v1: Base64.strict_encode64('a' * 32),
1312
+ v2: Base64.strict_encode64('b' * 32)
1313
+ }
1314
+ Familia.configure do |config|
1315
+ config.encryption_keys = test_keys
1316
+ config.current_key_version = :v1
1317
+ config.encryption_personalization = 'TestApp-Test'
1318
+ end
1319
+ end
1320
+ end
1321
+ ```
1322
+
1323
+ ---
1324
+
1325
+ ## Resources and References
1326
+
1327
+ ### Key Configuration
1328
+ Essential configuration options for Familia v2.0.0-pre.
1329
+
1330
+ ```ruby
1331
+ Familia.configure do |config|
1332
+ # Basic Valkey/Redis connection
1333
+ config.redis_uri = ENV['REDIS_URL'] || 'redis://localhost:6379/0'
1334
+
1335
+ # Connection provider for pooling (optional)
1336
+ config.connection_provider = MyConnectionProvider
1337
+
1338
+ # Encryption configuration (for encrypted_fields feature)
1339
+ config.encryption_keys = {
1340
+ v1: ENV['FAMILIA_ENCRYPTION_KEY_V1'],
1341
+ v2: ENV['FAMILIA_ENCRYPTION_KEY_V2']
1342
+ }
1343
+ config.current_key_version = :v2
1344
+
1345
+ # Debugging and logging
1346
+ config.debug = ENV['FAMILIA_DEBUG'] == 'true'
1347
+ config.enable_database_logging = ENV['FAMILIA_LOG_REDIS'] == 'true'
1348
+ end
1349
+ ```
1350
+
1351
+ ### Documentation Links
1352
+ - [Familia Repository](https://github.com/delano/familia)
1353
+ - [Wiki Home](../guides/Home.md)
1354
+ - [Feature System Guide](../guides/Feature-System-Guide.md)
1355
+ - [Relationships Guide](../guides/Relationships-Guide.md)
1356
+ - [Encrypted Fields Overview](../guides/Encrypted-Fields-Overview.md)
1357
+ - [Connection Pooling Guide](../guides/Connection-Pooling-Guide.md)
1358
+
1359
+ ### Version Information
1360
+ - **Current Version**: v2.0.0.pre6 (as of version.rb)
1361
+ - **Target Version**: v2.0.0.pre7 (relationships release)
1362
+ - **Ruby Compatibility**: 3.0+ (3.4+ recommended for optimal threading)
1363
+ - **Redis Compatibility**: 6.0+ (Valkey compatible)
1364
+
1365
+ This technical reference covers the major components and usage patterns available in Familia v2.0.0-pre series. For complete API documentation, see the generated YARD docs and wiki guides.