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,435 @@
1
+ # Object Identifiers Guide
2
+
3
+ > **💡 Quick Reference**
4
+ >
5
+ > Enable automatic object ID generation with configurable strategies:
6
+ > ```ruby
7
+ > class Document < Familia::Horreum
8
+ > feature :object_identifier, generator: :uuid_v4
9
+ > field :title, :content
10
+ > end
11
+ > ```
12
+
13
+ ## Overview
14
+
15
+ The Object Identifier feature provides automatic generation of unique identifiers for Familia objects. Instead of manually creating identifiers, you can configure different generation strategies that suit your application's needs - from globally unique UUIDs to compact hexadecimal strings.
16
+
17
+ ## Why Use Object Identifiers?
18
+
19
+ **Consistency**: Ensures all objects have properly formatted, unique identifiers without manual management.
20
+
21
+ **Flexibility**: Different applications need different ID formats - UUIDs for distributed systems, short hex strings for internal tools, or custom formats for specific business requirements.
22
+
23
+ **Collision Avoidance**: Built-in collision detection and retry logic ensures identifier uniqueness even under high concurrency.
24
+
25
+ **Integration Ready**: Generated IDs work seamlessly with external APIs, logging systems, and database relationships.
26
+
27
+ ## Quick Start
28
+
29
+ ### Basic UUID Generation
30
+
31
+ ```ruby
32
+ class User < Familia::Horreum
33
+ feature :object_identifier, generator: :uuid_v4
34
+
35
+ field :name, :email, :created_at
36
+ end
37
+
38
+ # Automatic ID generation on creation
39
+ user = User.create(name: "Alice", email: "alice@example.com")
40
+ puts user.objid # => "f47ac10b-58cc-4372-a567-0e02b2c3d479"
41
+ ```
42
+
43
+ ### Compact Hex Identifiers
44
+
45
+ ```ruby
46
+ class Session < Familia::Horreum
47
+ feature :object_identifier, generator: :hex, length: 16
48
+
49
+ field :user_id, :data, :expires_at
50
+ end
51
+
52
+ session = Session.create(user_id: "user123")
53
+ puts session.objid # => "a1b2c3d4e5f67890"
54
+ ```
55
+
56
+ ## Generator Types
57
+
58
+ ### UUID v4 Generator
59
+
60
+ Standard UUID format providing global uniqueness across distributed systems.
61
+
62
+ ```ruby
63
+ class Document < Familia::Horreum
64
+ feature :object_identifier, generator: :uuid_v4
65
+ field :title, :content
66
+ end
67
+
68
+ doc = Document.create(title: "My Document")
69
+ doc.objid # => "550e8400-e29b-41d4-a716-446655440000"
70
+ ```
71
+
72
+ **Characteristics:**
73
+ - **Format**: 36 characters (8-4-4-4-12 hex pattern)
74
+ - **Uniqueness**: Globally unique across time and space
75
+ - **Performance**: Good for distributed systems
76
+ - **Use Cases**: Public APIs, microservices, external integrations
77
+
78
+ > **💡 Best Practice**
79
+ >
80
+ > Use UUID v4 for objects that will be exposed externally or across service boundaries.
81
+
82
+ ### Hex Generator
83
+
84
+ Compact hexadecimal strings ideal for internal use and high-volume scenarios.
85
+
86
+ ```ruby
87
+ class ApiKey < Familia::Horreum
88
+ feature :object_identifier, generator: :hex, length: 24
89
+ field :name, :permissions, :created_at
90
+ end
91
+
92
+ key = ApiKey.create(name: "Production API")
93
+ key.objid # => "1a2b3c4d5e6f7890abcdef12"
94
+ ```
95
+
96
+ **Configuration Options:**
97
+ - `length`: Number of hex characters (default: 12)
98
+ - `prefix`: Optional prefix for the identifier
99
+ - `charset`: Custom character set (default: hex digits)
100
+
101
+ ```ruby
102
+ class InternalToken < Familia::Horreum
103
+ feature :object_identifier,
104
+ generator: :hex,
105
+ length: 16,
106
+ prefix: "tk_"
107
+
108
+ field :scope, :issued_at
109
+ end
110
+
111
+ token = InternalToken.create(scope: "read:users")
112
+ token.objid # => "tk_a1b2c3d4e5f67890"
113
+ ```
114
+
115
+ **Characteristics:**
116
+ - **Format**: Configurable length hexadecimal string
117
+ - **Performance**: Very fast generation
118
+ - **Storage**: Compact representation
119
+ - **Use Cases**: Internal IDs, tokens, session identifiers
120
+
121
+ > **⚠️ Important**
122
+ >
123
+ > Hex generators provide good uniqueness but aren't globally unique like UUIDs. Use appropriate length for your collision tolerance.
124
+
125
+ ### Custom Generator
126
+
127
+ Define your own identifier generation logic for business-specific requirements.
128
+
129
+ ```ruby
130
+ class OrderNumber < Familia::Horreum
131
+ feature :object_identifier, generator: :custom
132
+
133
+ field :customer_id, :amount, :created_at
134
+
135
+ # Custom generator implementation
136
+ def self.generate_identifier
137
+ timestamp = Time.now.strftime('%Y%m%d')
138
+ sequence = Redis.current.incr("order_sequence:#{timestamp}")
139
+ "ORD-#{timestamp}-#{sequence.to_s.rjust(6, '0')}"
140
+ end
141
+ end
142
+
143
+ order = OrderNumber.create(customer_id: "cust123", amount: 99.99)
144
+ order.objid # => "ORD-20241215-000001"
145
+ ```
146
+
147
+ **Implementation Requirements:**
148
+ - Must define `self.generate_identifier` class method
149
+ - Should return a string identifier
150
+ - Must handle uniqueness and collision scenarios
151
+ - Consider thread safety for concurrent access
152
+
153
+ > **🔧 Advanced Pattern**
154
+ >
155
+ > Custom generators can integrate with external services, database sequences, or business rules for sophisticated ID schemes.
156
+
157
+ ## Advanced Configuration
158
+
159
+ ### Collision Detection
160
+
161
+ Enable automatic collision detection and retry logic:
162
+
163
+ ```ruby
164
+ class Product < Familia::Horreum
165
+ feature :object_identifier,
166
+ generator: :hex,
167
+ collision_check: true,
168
+ max_retries: 5
169
+
170
+ field :name, :price, :sku
171
+ end
172
+ ```
173
+
174
+ **Configuration Options:**
175
+ - `collision_check`: Enable/disable collision detection (default: true)
176
+ - `max_retries`: Maximum retry attempts on collision (default: 3)
177
+ - `retry_delay`: Delay between retries in seconds (default: 0.001)
178
+
179
+ ### Identifier Validation
180
+
181
+ Add custom validation logic for generated identifiers:
182
+
183
+ ```ruby
184
+ class SecureToken < Familia::Horreum
185
+ feature :object_identifier, generator: :custom
186
+
187
+ def self.generate_identifier
188
+ loop do
189
+ candidate = SecureRandom.alphanumeric(32)
190
+ # Ensure no ambiguous characters
191
+ next if candidate.match?(/[0O1lI]/)
192
+ return "st_#{candidate.downcase}"
193
+ end
194
+ end
195
+
196
+ def self.valid_identifier?(id)
197
+ id.match?(/^st_[a-z0-9]{32}$/) && !id.match?(/[0O1lI]/)
198
+ end
199
+ end
200
+ ```
201
+
202
+ ## Performance Considerations
203
+
204
+ ### Generation Speed Benchmarks
205
+
206
+ Different generators have varying performance characteristics:
207
+
208
+ ```ruby
209
+ # Benchmark different generators
210
+ require 'benchmark'
211
+
212
+ Benchmark.bm(10) do |x|
213
+ x.report("UUID v4:") { 10_000.times { SecureRandom.uuid } }
214
+ x.report("Hex 12:") { 10_000.times { SecureRandom.hex(6) } }
215
+ x.report("Hex 24:") { 10_000.times { SecureRandom.hex(12) } }
216
+ x.report("Custom:") { 10_000.times { MyClass.generate_identifier } }
217
+ end
218
+ ```
219
+
220
+ ### Memory Usage
221
+
222
+ - **UUID v4**: 36 bytes per identifier
223
+ - **Hex**: Variable based on length (2 bytes per hex character)
224
+ - **Custom**: Depends on implementation
225
+
226
+ ### Collision Probability
227
+
228
+ For hex generators, collision probability depends on length and volume:
229
+
230
+ ```ruby
231
+ # Approximate collision probability for hex identifiers
232
+ def collision_probability(length, count)
233
+ total_space = 16 ** length
234
+ 1 - Math.exp(-(count * (count - 1)) / (2.0 * total_space))
235
+ end
236
+
237
+ # Examples:
238
+ collision_probability(12, 1_000_000) # Very low
239
+ collision_probability(8, 100_000) # Consider longer length
240
+ ```
241
+
242
+ > **📊 Sizing Guidance**
243
+ >
244
+ > - **8 hex chars**: Good for < 10K objects
245
+ > - **12 hex chars**: Good for < 1M objects
246
+ > - **16 hex chars**: Good for < 100M objects
247
+ > - **UUID v4**: Suitable for any scale
248
+
249
+ ## Integration Patterns
250
+
251
+ ### External API Integration
252
+
253
+ ```ruby
254
+ class ExternalReference < Familia::Horreum
255
+ feature :object_identifier, generator: :uuid_v4
256
+ field :external_id, :sync_status, :last_sync
257
+
258
+ def sync_to_external_api
259
+ response = ExternalAPI.create_record(
260
+ id: self.objid, # Use generated ID
261
+ data: self.to_h
262
+ )
263
+
264
+ self.external_id = response['id']
265
+ self.sync_status = 'synced'
266
+ self.last_sync = Familia.now.to_i
267
+ save
268
+ end
269
+ end
270
+ ```
271
+
272
+ ### Database Relationships
273
+
274
+ ```ruby
275
+ class Order < Familia::Horreum
276
+ feature :object_identifier, generator: :custom
277
+ field :customer_id, :total_amount
278
+
279
+ def self.generate_identifier
280
+ "ORD-#{SecureRandom.hex(8).upcase}"
281
+ end
282
+ end
283
+
284
+ class OrderItem < Familia::Horreum
285
+ feature :object_identifier, generator: :hex
286
+ field :order_id, :product_id, :quantity
287
+
288
+ def order
289
+ Order.load(order_id)
290
+ end
291
+ end
292
+
293
+ # Usage
294
+ order = Order.create(customer_id: "cust123", total_amount: 299.99)
295
+ item = OrderItem.create(
296
+ order_id: order.objid, # Reference by generated ID
297
+ product_id: "prod456",
298
+ quantity: 2
299
+ )
300
+ ```
301
+
302
+ ### Logging and Debugging
303
+
304
+ Generated identifiers provide excellent debugging context:
305
+
306
+ ```ruby
307
+ class UserSession < Familia::Horreum
308
+ feature :object_identifier, generator: :hex, length: 16
309
+ field :user_id, :ip_address, :user_agent
310
+
311
+ def log_activity(action)
312
+ logger.info(
313
+ "Session #{objid}: User #{user_id} performed #{action}",
314
+ session_id: objid,
315
+ user_id: user_id,
316
+ action: action,
317
+ timestamp: Familia.now.to_i
318
+ )
319
+ end
320
+ end
321
+ ```
322
+
323
+ ## Testing Strategies
324
+
325
+ ### Test Identifier Generation
326
+
327
+ ```ruby
328
+ # test/models/user_test.rb
329
+ require 'test_helper'
330
+
331
+ class UserTest < Minitest::Test
332
+ def test_uuid_generation
333
+ user = User.create(name: "Test User")
334
+
335
+ # Verify UUID format
336
+ assert_match(
337
+ /\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,
338
+ user.objid
339
+ )
340
+ end
341
+
342
+ def test_hex_generation
343
+ session = Session.create(user_id: "123")
344
+
345
+ # Verify hex format and length
346
+ assert_match(/\A[0-9a-f]{16}\z/i, session.objid)
347
+ assert_equal 16, session.objid.length
348
+ end
349
+
350
+ def test_custom_identifier_format
351
+ order = OrderNumber.create(customer_id: "cust123")
352
+
353
+ # Verify custom format
354
+ assert_match(/\AORD-\d{8}-\d{6}\z/, order.objid)
355
+ end
356
+ end
357
+ ```
358
+
359
+ ### Mock Generators for Testing
360
+
361
+ ```ruby
362
+ # test/test_helper.rb
363
+ class TestIdentifierGenerator
364
+ def self.generate_test_uuid
365
+ "test-#{Time.now.to_f}-#{rand(1000)}"
366
+ end
367
+ end
368
+
369
+ # In tests
370
+ class User < Familia::Horreum
371
+ feature :object_identifier, generator: :custom
372
+
373
+ def self.generate_identifier
374
+ if Rails.env.test?
375
+ TestIdentifierGenerator.generate_test_uuid
376
+ else
377
+ SecureRandom.uuid
378
+ end
379
+ end
380
+ end
381
+ ```
382
+
383
+ ## Troubleshooting
384
+
385
+ ### Common Issues
386
+
387
+ **Identifier Not Generated**
388
+ ```ruby
389
+ # Ensure feature is enabled
390
+ class MyModel < Familia::Horreum
391
+ feature :object_identifier # This line is required!
392
+ field :name
393
+ end
394
+ ```
395
+
396
+ **Custom Generator Not Called**
397
+ ```ruby
398
+ # Verify method signature
399
+ def self.generate_identifier # Must be class method
400
+ # Implementation here
401
+ end
402
+ ```
403
+
404
+ **Collision Detection Failing**
405
+ ```ruby
406
+ # Check Valkey/Redis connectivity and permissions
407
+ begin
408
+ MyModel.create(name: "test")
409
+ rescue Familia::Problem => e
410
+ puts "Identifier collision: #{e.message}"
411
+ end
412
+ ```
413
+
414
+ ### Debug Identifier Generation
415
+
416
+ ```ruby
417
+ # Enable debug logging
418
+ Familia.debug = true
419
+
420
+ # Check feature configuration
421
+ MyModel.feature_options(:object_identifier)
422
+ #=> {generator: :uuid_v4, collision_check: true, max_retries: 3}
423
+
424
+ # Verify generation manually
425
+ MyModel.generate_identifier # Should return new identifier
426
+ ```
427
+
428
+ ---
429
+
430
+ ## See Also
431
+
432
+ - **[Technical Reference](../reference/api-technical.md#object-identifier-feature-v200-pre7)** - Implementation details and advanced patterns
433
+ - **[External Identifiers Guide](feature-external-identifiers.md)** - Integration with external systems
434
+ - **[Feature System Guide](feature-system.md)** - Understanding the feature architecture
435
+ - **[Implementation Guide](implementation.md)** - Advanced configuration patterns
@@ -1,19 +1,64 @@
1
1
  # Quantization Feature Guide
2
2
 
3
- ## Overview
3
+ The Quantization feature revolutionizes how you handle time-based data by providing automatic bucketing, consistent timestamps, and analytics-ready data organization. Instead of dealing with precise timestamps that make aggregation difficult, quantization rounds time values to predictable intervals, enabling efficient caching, analytics dashboards, and time-series data management.
4
4
 
5
- The Quantization feature provides time-based data bucketing capabilities for Familia objects. It allows you to round timestamps to specific intervals (quantums) and format them for consistent time-based data organization, analytics, and caching strategies.
5
+ > [!NOTE]
6
+ > **Perfect For:** Analytics dashboards, time-series metrics, cache keys, data retention policies, and any scenario where you need consistent time-based data grouping.
7
+
8
+ ## Understanding Time Quantization
9
+
10
+ ### The Problem with Precise Timestamps
11
+
12
+ Without quantization, time-based data becomes fragmented and difficult to aggregate:
13
+
14
+ ```ruby
15
+ # Problem: Each request gets a unique timestamp
16
+ user_activity_14_30_23 = "login at 2023-06-15 14:30:23"
17
+ user_activity_14_30_45 = "login at 2023-06-15 14:30:45"
18
+ user_activity_14_31_12 = "login at 2023-06-15 14:31:12"
19
+
20
+ # Result: Three separate data points instead of aggregated hourly data
21
+ # Makes analytics queries complex and cache keys unpredictable
22
+ ```
23
+
24
+ ### The Quantization Solution
25
+
26
+ Quantization groups timestamps into consistent buckets:
27
+
28
+ ```ruby
29
+ # Solution: All timestamps within an hour become the same bucket
30
+ qstamp(1.hour, time: Time.parse("2023-06-15 14:30:23")) # => 1687276800 (14:00:00)
31
+ qstamp(1.hour, time: Time.parse("2023-06-15 14:30:45")) # => 1687276800 (14:00:00)
32
+ qstamp(1.hour, time: Time.parse("2023-06-15 14:31:12")) # => 1687276800 (14:00:00)
33
+
34
+ # Result: Single aggregated data point for the entire hour
35
+ # Perfect for analytics and predictable cache keys!
36
+ ```
37
+
38
+ > [!NOTE]
39
+ > **Key Benefits:**
40
+ > - **Consistent Buckets**: All timestamps in a period map to the same value
41
+ > - **Predictable Keys**: Cache keys and identifiers become deterministic
42
+ > - **Efficient Aggregation**: Reduce millions of data points to manageable buckets
43
+ > - **Analytics Ready**: Perfect for dashboards requiring time-series data
6
44
 
7
45
  ## Core Concepts
8
46
 
9
- ### Quantum Intervals
47
+ ### Quantum Intervals Explained
10
48
 
11
- A **quantum** is a time interval used to bucket timestamps. Common quantums include:
49
+ A **quantum** represents the time bucket size for grouping timestamps. Think of it as the "resolution" of your time-based data:
12
50
 
13
- - **Minutes**: `1.minute`, `5.minutes`, `15.minutes`
14
- - **Hours**: `1.hour`, `6.hours`, `12.hours`
15
- - **Days**: `1.day`, `7.days`
16
- - **Custom**: Any number of seconds (e.g., `90` for 1.5 minutes)
51
+ **Common Quantum Patterns:**
52
+ - **High-Resolution**: `1.minute`, `5.minutes`, `15.minutes` - For real-time monitoring
53
+ - **Medium-Resolution**: `1.hour`, `6.hours`, `12.hours` - For hourly analytics
54
+ - **Low-Resolution**: `1.day`, `1.week`, `1.month` - For long-term trends
55
+ - **Custom Intervals**: Any number of seconds (e.g., `90` for 1.5 minutes, `300` for 5 minutes)
56
+
57
+ > [!TIP]
58
+ > **Choosing the Right Quantum:**
59
+ > - **Smaller quantums** = More granular data, more storage
60
+ > - **Larger quantums** = Less granular data, less storage
61
+ > - Consider your analytics needs and storage constraints
17
62
 
18
63
  ### Quantized Timestamps (qstamp)
19
64
 
@@ -33,21 +78,28 @@ qstamp(1.hour, pattern: '%H:%M:%S', time: Time.parse('14:05:12')) # => "14:00:0
33
78
  qstamp(1.hour, pattern: '%H:%M:%S', time: Time.parse('14:55:33')) # => "14:00:00"
34
79
  ```
35
80
 
36
- ## Basic Usage
81
+ ## Getting Started with Quantization
82
+
83
+ ### Enabling the Feature
37
84
 
38
- ### Enabling Quantization
85
+ The quantization feature integrates seamlessly with your existing Familia models:
39
86
 
40
87
  ```ruby
41
88
  class AnalyticsEvent < Familia::Horreum
42
89
  feature :quantization
43
- default_expiration 300 # 5 minutes (used as default quantum)
90
+ default_expiration 300 # 5 minutes (also used as default quantum)
44
91
 
45
92
  identifier_field :event_id
46
93
  field :event_id, :event_type, :user_id, :data, :timestamp
47
94
  end
48
95
  ```
49
96
 
50
- ### Simple Quantized Timestamps
97
+ > [!TIP]
98
+ > If you set `default_expiration`, it automatically becomes your default quantum for `qstamp` calls without an explicit interval.
99
+
100
+ ### Your First Quantized Timestamps
101
+
102
+ The `qstamp` method is your main tool for creating consistent time buckets:
51
103
 
52
104
  ```ruby
53
105
  event = AnalyticsEvent.new
@@ -65,6 +117,9 @@ AnalyticsEvent.qstamp(1.hour)
65
117
  # => 1687276800
66
118
  ```
67
119
 
120
+ > [!IMPORTANT]
121
+ > The `qstamp` method always rounds DOWN to the quantum boundary. A timestamp of 14:30:45 with a 1-hour quantum becomes 14:00:00, not 15:00:00.
122
+
68
123
  ### Formatted Timestamps
69
124
 
70
125
  ```ruby
@@ -145,7 +200,7 @@ class UserActivity < Familia::Horreum
145
200
  bucket.user_count ||= 0
146
201
  bucket.event_count ||= 0
147
202
 
148
- # Use Redis sets/hashes for precise counting
203
+ # Use Valkey/Redis sets/hashes for precise counting
149
204
  bucket_users = bucket.related_set("users")
150
205
  bucket_users.add(user_id)
151
206
 
@@ -172,7 +227,7 @@ end
172
227
 
173
228
  # Usage
174
229
  UserActivity.record_activity('user_123', 'page_view')
175
- hourly_buckets = UserActivity.activity_for_hour(Time.now)
230
+ hourly_buckets = UserActivity.activity_for_hour(Familia.now)
176
231
  ```
177
232
 
178
233
  ### Time-Series Data Storage
@@ -217,14 +272,14 @@ end
217
272
 
218
273
  # Usage - Record CPU usage every minute
219
274
  TimeSeriesMetric.record_metric('cpu_usage', 75.5, 1.minute)
220
- TimeSeriesMetric.record_metric('cpu_usage', 82.1, 1.minute, Time.now + 1.minute)
275
+ TimeSeriesMetric.record_metric('cpu_usage', 82.1, 1.minute, Familia.now + 1.minute)
221
276
 
222
277
  # Retrieve data for last hour
223
278
  series_data = TimeSeriesMetric.get_series(
224
279
  'cpu_usage',
225
280
  1.minute,
226
- Time.now - 1.hour,
227
- Time.now
281
+ Familia.now - 1.hour,
282
+ Familia.now
228
283
  )
229
284
  ```
230
285
 
@@ -252,7 +307,7 @@ class LogAggregator < Familia::Horreum
252
307
  )
253
308
 
254
309
  aggregator.count += 1
255
- aggregator.last_seen = Time.now.to_i
310
+ aggregator.last_seen = Familia.now.to_i
256
311
 
257
312
  # Keep sample messages (up to 10)
258
313
  sample_key = "sample_#{aggregator.count}"
@@ -265,8 +320,8 @@ class LogAggregator < Familia::Horreum
265
320
  end
266
321
 
267
322
  def self.error_summary(time_range = 1.hour)
268
- start_time = Time.now - time_range
269
- end_time = Time.now
323
+ start_time = Familia.now - time_range
324
+ end_time = Familia.now
270
325
 
271
326
  # Find all error buckets in time range
272
327
  buckets = []
@@ -300,7 +355,7 @@ class ApplicationMetrics
300
355
  include Familia::Horreum
301
356
  feature :quantization
302
357
 
303
- # Set up different quantum intervals for different metrics
358
+ # UnsortedSet up different quantum intervals for different metrics
304
359
  QUANTUM_CONFIGS = {
305
360
  real_time: 1.minute, # High frequency metrics
306
361
  standard: 5.minutes, # Regular analytics
@@ -325,7 +380,7 @@ class MetricsCollector
325
380
  # Increment counter
326
381
  Familia.dbclient.incr(key)
327
382
 
328
- # Set expiration based on quantum type
383
+ # UnsortedSet expiration based on quantum type
329
384
  ttl = case quantum_type
330
385
  when :real_time then 2.hours
331
386
  when :standard then 1.day
@@ -352,7 +407,7 @@ class QuantizedDataProcessor
352
407
  process_bucket(current_bucket)
353
408
 
354
409
  # Also process previous bucket in case of delayed data
355
- previous_bucket = AnalyticsEvent.qstamp(5.minutes, time: Time.now - 5.minutes)
410
+ previous_bucket = AnalyticsEvent.qstamp(5.minutes, time: Familia.now - 5.minutes)
356
411
  process_bucket(previous_bucket)
357
412
  end
358
413
 
@@ -463,13 +518,13 @@ class GlobalMetrics < Familia::Horreum
463
518
 
464
519
  def self.utc_hourly_key(time = nil)
465
520
  # Always quantize in UTC for global consistency
466
- utc_time = time&.utc || Time.now.utc
521
+ utc_time = time&.utc || Familia.now
467
522
  qstamp(1.hour, pattern: '%Y%m%d%H', time: utc_time)
468
523
  end
469
524
 
470
525
  def self.local_daily_key(timezone, time = nil)
471
526
  # Quantize in local timezone for regional reports
472
- local_time = time || Time.now
527
+ local_time = time || Familia.now
473
528
  local_time = local_time.in_time_zone(timezone) if local_time.respond_to?(:in_time_zone)
474
529
  qstamp(1.day, pattern: '%Y%m%d', time: local_time)
475
530
  end
@@ -526,7 +581,7 @@ class CompactTimeSeriesStorage < Familia::Horreum
526
581
  identifier_field :series_id
527
582
  field :series_id, :metric_name, :quantum
528
583
 
529
- # Store quantized data in Redis sorted sets for efficiency
584
+ # Store quantized data in Valkey/Redis sorted sets for efficiency
530
585
  def record_value(value, time = nil)
531
586
  bucket_timestamp = self.class.qstamp(quantum, time: time)
532
587
 
@@ -534,7 +589,7 @@ class CompactTimeSeriesStorage < Familia::Horreum
534
589
  data_key = "#{series_id}:data"
535
590
  Familia.dbclient.zadd(data_key, bucket_timestamp, value)
536
591
 
537
- # Set TTL based on quantum (longer quantum = longer retention)
592
+ # UnsortedSet TTL based on quantum (longer quantum = longer retention)
538
593
  ttl = calculate_retention_period
539
594
  Familia.dbclient.expire(data_key, ttl)
540
595
  end
@@ -696,8 +751,8 @@ end
696
751
  class QuantizationMonitor
697
752
  def self.analyze_bucket_distribution(metric_name, quantum, time_range = 24.hours)
698
753
  buckets = {}
699
- current_time = Time.now - time_range
700
- end_time = Time.now
754
+ current_time = Familia.now - time_range
755
+ end_time = Familia.now
701
756
 
702
757
  while current_time <= end_time
703
758
  bucket = AnalyticsEvent.qstamp(quantum, time: current_time)
@@ -718,4 +773,14 @@ class QuantizationMonitor
718
773
  end
719
774
  ```
720
775
 
776
+ ---
777
+
778
+ ## See Also
779
+
780
+ - **[Technical Reference](../reference/api-technical.md#quantization-feature-v200-pre7)** - Implementation details and advanced patterns
781
+ - **[Overview](../overview.md#time-based-quantization)** - Conceptual introduction to quantization
782
+ - **[Time Utilities Guide](time-utilities.md)** - Time manipulation and formatting utilities
783
+ - **[Feature System Guide](feature-system.md)** - Understanding Familia's feature architecture
784
+ - **[Implementation Guide](implementation.md)** - Production deployment and configuration patterns
785
+
721
786
  The Quantization feature provides powerful time-based data organization capabilities, enabling efficient analytics, caching, and time-series data management in Familia applications.