familia 2.0.0.pre19 → 2.0.0.pre21

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 (372) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +4 -9
  3. data/.github/workflows/code-smells.yml +64 -3
  4. data/.pre-commit-config.yaml +8 -6
  5. data/.reek.yml +10 -9
  6. data/.rubocop.yml +4 -0
  7. data/CHANGELOG.rst +177 -112
  8. data/CLAUDE.md +28 -1
  9. data/Gemfile +1 -1
  10. data/Gemfile.lock +20 -17
  11. data/bin/try +16 -0
  12. data/bin/tryouts +16 -0
  13. data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
  14. data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
  15. data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
  16. data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
  17. data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
  18. data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -0
  19. data/docs/1106-participates_in-bidirectional-solution.md +129 -0
  20. data/docs/guides/encryption.md +486 -0
  21. data/docs/guides/feature-encrypted-fields.md +123 -7
  22. data/docs/guides/feature-expiration.md +161 -117
  23. data/docs/guides/feature-external-identifiers.md +415 -443
  24. data/docs/guides/feature-object-identifiers.md +400 -269
  25. data/docs/guides/feature-quantization.md +120 -6
  26. data/docs/guides/feature-relationships-indexing.md +318 -0
  27. data/docs/guides/feature-relationships-methods.md +146 -604
  28. data/docs/guides/feature-relationships-participation.md +263 -0
  29. data/docs/guides/feature-relationships.md +118 -136
  30. data/docs/guides/feature-system-devs.md +176 -693
  31. data/docs/guides/feature-system.md +119 -6
  32. data/docs/guides/feature-transient-fields.md +81 -0
  33. data/docs/guides/field-system.md +778 -0
  34. data/docs/guides/index.md +32 -15
  35. data/docs/guides/logging.md +187 -0
  36. data/docs/guides/optimized-loading.md +674 -0
  37. data/docs/guides/thread-safety-monitoring.md +61 -0
  38. data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
  39. data/docs/migrating/v2.0.0-pre22.md +241 -0
  40. data/docs/overview.md +7 -9
  41. data/docs/reference/api-technical.md +267 -320
  42. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
  43. data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
  44. data/examples/autoloader/mega_customer.rb +2 -0
  45. data/examples/datatype_standalone.rb +4 -3
  46. data/examples/encrypted_fields.rb +2 -1
  47. data/examples/json_usage_patterns.rb +2 -0
  48. data/examples/relationships.rb +3 -0
  49. data/examples/safe_dump.rb +2 -1
  50. data/examples/sampling_demo.rb +53 -0
  51. data/examples/single_connection_transaction_confusions.rb +2 -1
  52. data/familia.gemspec +2 -1
  53. data/lib/familia/base.rb +2 -0
  54. data/lib/familia/connection/behavior.rb +2 -0
  55. data/lib/familia/connection/handlers.rb +2 -0
  56. data/lib/familia/connection/individual_command_proxy.rb +2 -0
  57. data/lib/familia/connection/middleware.rb +34 -24
  58. data/lib/familia/connection/operation_core.rb +2 -0
  59. data/lib/familia/connection/operations.rb +2 -0
  60. data/lib/familia/connection/pipelined_core.rb +2 -0
  61. data/lib/familia/connection/transaction_core.rb +68 -0
  62. data/lib/familia/connection.rb +18 -3
  63. data/lib/familia/data_type/class_methods.rb +3 -1
  64. data/lib/familia/data_type/connection.rb +2 -0
  65. data/lib/familia/data_type/database_commands.rb +2 -0
  66. data/lib/familia/data_type/serialization.rb +6 -4
  67. data/lib/familia/data_type/settings.rb +2 -0
  68. data/lib/familia/data_type/types/counter.rb +2 -0
  69. data/lib/familia/data_type/types/hashkey.rb +7 -5
  70. data/lib/familia/data_type/types/listkey.rb +2 -0
  71. data/lib/familia/data_type/types/lock.rb +2 -0
  72. data/lib/familia/data_type/types/sorted_set.rb +2 -0
  73. data/lib/familia/data_type/types/stringkey.rb +2 -0
  74. data/lib/familia/data_type/types/unsorted_set.rb +2 -0
  75. data/lib/familia/data_type.rb +2 -0
  76. data/lib/familia/encryption/encrypted_data.rb +4 -2
  77. data/lib/familia/encryption/manager.rb +2 -0
  78. data/lib/familia/encryption/provider.rb +2 -0
  79. data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
  80. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
  81. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
  82. data/lib/familia/encryption/registry.rb +2 -0
  83. data/lib/familia/encryption/request_cache.rb +2 -0
  84. data/lib/familia/encryption.rb +9 -2
  85. data/lib/familia/errors.rb +2 -0
  86. data/lib/familia/features/autoloader.rb +2 -0
  87. data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
  88. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
  89. data/lib/familia/features/encrypted_fields.rb +2 -2
  90. data/lib/familia/features/expiration/extensions.rb +3 -1
  91. data/lib/familia/features/expiration.rb +12 -4
  92. data/lib/familia/features/external_identifier.rb +33 -7
  93. data/lib/familia/features/object_identifier.rb +2 -0
  94. data/lib/familia/features/quantization.rb +3 -1
  95. data/lib/familia/features/relationships/README.md +3 -1
  96. data/lib/familia/features/relationships/collection_operations.rb +2 -0
  97. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +138 -9
  98. data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
  99. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +89 -21
  100. data/lib/familia/features/relationships/indexing.rb +3 -0
  101. data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
  102. data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
  103. data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
  104. data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
  105. data/lib/familia/features/relationships/participation.rb +155 -69
  106. data/lib/familia/features/relationships/participation_membership.rb +69 -0
  107. data/lib/familia/features/relationships/participation_relationship.rb +34 -6
  108. data/lib/familia/features/relationships/score_encoding.rb +2 -0
  109. data/lib/familia/features/relationships.rb +5 -3
  110. data/lib/familia/features/safe_dump.rb +2 -0
  111. data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
  112. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
  113. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
  114. data/lib/familia/features/transient_fields.rb +2 -0
  115. data/lib/familia/features.rb +2 -0
  116. data/lib/familia/field_type.rb +3 -1
  117. data/lib/familia/horreum/connection.rb +17 -1
  118. data/lib/familia/horreum/database_commands.rb +2 -0
  119. data/lib/familia/horreum/definition.rb +16 -6
  120. data/lib/familia/horreum/management.rb +212 -42
  121. data/lib/familia/horreum/persistence.rb +176 -108
  122. data/lib/familia/horreum/related_fields.rb +2 -0
  123. data/lib/familia/horreum/serialization.rb +23 -4
  124. data/lib/familia/horreum/settings.rb +2 -0
  125. data/lib/familia/horreum/utils.rb +2 -0
  126. data/lib/familia/horreum.rb +15 -1
  127. data/lib/familia/identifier_extractor.rb +2 -0
  128. data/lib/familia/instrumentation.rb +156 -0
  129. data/lib/familia/json_serializer.rb +2 -0
  130. data/lib/familia/logging.rb +92 -32
  131. data/lib/familia/refinements/dear_json.rb +2 -0
  132. data/lib/familia/refinements/stylize_words.rb +2 -14
  133. data/lib/familia/refinements/time_literals.rb +2 -0
  134. data/lib/familia/refinements.rb +2 -0
  135. data/lib/familia/secure_identifier.rb +10 -2
  136. data/lib/familia/settings.rb +2 -0
  137. data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
  138. data/lib/familia/thread_safety/monitor.rb +328 -0
  139. data/lib/familia/utils.rb +13 -0
  140. data/lib/familia/verifiable_identifier.rb +3 -1
  141. data/lib/familia/version.rb +3 -1
  142. data/lib/familia.rb +31 -4
  143. data/lib/middleware/database_command_counter.rb +152 -0
  144. data/lib/middleware/database_logger.rb +295 -170
  145. data/lib/multi_result.rb +2 -0
  146. data/try/edge_cases/empty_identifiers_try.rb +2 -0
  147. data/try/edge_cases/hash_symbolization_try.rb +2 -0
  148. data/try/edge_cases/json_serialization_try.rb +2 -0
  149. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
  150. data/try/edge_cases/race_conditions_try.rb +4 -0
  151. data/try/edge_cases/reserved_keywords_try.rb +4 -0
  152. data/try/edge_cases/string_coercion_try.rb +2 -0
  153. data/try/edge_cases/ttl_side_effects_try.rb +4 -0
  154. data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
  155. data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
  156. data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
  157. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
  158. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
  159. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
  160. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
  161. data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
  162. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
  163. data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
  164. data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
  165. data/try/features/encrypted_fields/memory_security_try.rb +4 -0
  166. data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
  167. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
  168. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
  169. data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
  170. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
  171. data/try/features/encryption/config_persistence_try.rb +4 -0
  172. data/try/features/encryption/core_try.rb +4 -0
  173. data/try/features/encryption/instance_variable_scope_try.rb +4 -0
  174. data/try/features/encryption/module_loading_try.rb +4 -0
  175. data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
  176. data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
  177. data/try/features/encryption/roundtrip_validation_try.rb +4 -0
  178. data/try/features/encryption/secure_memory_handling_try.rb +4 -0
  179. data/try/features/expiration/expiration_try.rb +4 -0
  180. data/try/features/external_identifier/external_identifier_try.rb +171 -8
  181. data/try/features/feature_dependencies_try.rb +2 -0
  182. data/try/features/feature_improvements_try.rb +2 -0
  183. data/try/features/field_groups_try.rb +2 -0
  184. data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
  185. data/try/features/object_identifier/object_identifier_try.rb +2 -0
  186. data/try/features/quantization/quantization_try.rb +4 -0
  187. data/try/features/real_feature_integration_try.rb +2 -0
  188. data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
  189. data/try/features/relationships/indexing_rebuild_try.rb +600 -0
  190. data/try/features/relationships/indexing_try.rb +2 -0
  191. data/try/features/relationships/participation_bidirectional_try.rb +242 -0
  192. data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
  193. data/try/features/relationships/participation_commands_verification_try.rb +2 -0
  194. data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
  195. data/try/features/relationships/participation_reverse_index_try.rb +15 -13
  196. data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
  197. data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
  198. data/try/features/relationships/relationships_api_changes_try.rb +2 -0
  199. data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
  200. data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
  201. data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
  202. data/try/features/relationships/relationships_performance_try.rb +4 -0
  203. data/try/features/relationships/relationships_performance_working_try.rb +4 -0
  204. data/try/features/relationships/relationships_try.rb +6 -4
  205. data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
  206. data/try/features/safe_dump/safe_dump_try.rb +4 -0
  207. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  208. data/try/features/transient_fields/refresh_reset_try.rb +3 -0
  209. data/try/features/transient_fields/simple_refresh_test.rb +3 -0
  210. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  211. data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
  212. data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
  213. data/try/integration/connection/fiber_context_preservation_try.rb +4 -0
  214. data/try/integration/connection/handler_constraints_try.rb +4 -0
  215. data/try/integration/connection/isolated_dbclient_try.rb +4 -0
  216. data/try/integration/connection/middleware_reconnect_try.rb +2 -0
  217. data/try/integration/connection/operation_mode_guards_try.rb +4 -0
  218. data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
  219. data/try/integration/connection/pools_try.rb +4 -0
  220. data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
  221. data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
  222. data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
  223. data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
  224. data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
  225. data/try/integration/connection/transaction_modes_try.rb +4 -0
  226. data/try/integration/conventional_inheritance_try.rb +4 -0
  227. data/try/integration/create_method_try.rb +4 -0
  228. data/try/integration/cross_component_try.rb +4 -0
  229. data/try/integration/data_types/datatype_pipelines_try.rb +4 -0
  230. data/try/integration/data_types/datatype_transactions_try.rb +4 -0
  231. data/try/integration/database_consistency_try.rb +4 -0
  232. data/try/integration/familia_extended_try.rb +4 -0
  233. data/try/integration/familia_members_methods_try.rb +4 -0
  234. data/try/integration/models/customer_safe_dump_try.rb +4 -0
  235. data/try/integration/models/customer_try.rb +4 -0
  236. data/try/integration/models/datatype_base_try.rb +4 -0
  237. data/try/integration/models/familia_object_try.rb +4 -0
  238. data/try/integration/persistence_operations_try.rb +4 -0
  239. data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
  240. data/try/integration/save_methods_consistency_try.rb +241 -0
  241. data/try/integration/scenarios_try.rb +4 -0
  242. data/try/integration/secure_identifier_try.rb +4 -0
  243. data/try/integration/transaction_safety_core_try.rb +176 -0
  244. data/try/integration/transaction_safety_workflow_try.rb +291 -0
  245. data/try/integration/verifiable_identifier_try.rb +4 -0
  246. data/try/investigation/pipeline_routing/README.md +228 -0
  247. data/try/performance/benchmarks_try.rb +4 -0
  248. data/try/performance/transaction_safety_benchmark_try.rb +238 -0
  249. data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
  250. data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
  251. data/try/support/debugging/cache_behavior_tracer.rb +4 -0
  252. data/try/support/debugging/debug_aad_process.rb +3 -0
  253. data/try/support/debugging/debug_concealed_internal.rb +3 -0
  254. data/try/support/debugging/debug_concealed_reveal.rb +3 -0
  255. data/try/support/debugging/debug_context_aad.rb +3 -0
  256. data/try/support/debugging/debug_context_simple.rb +3 -0
  257. data/try/support/debugging/debug_cross_context.rb +3 -0
  258. data/try/support/debugging/debug_database_load.rb +3 -0
  259. data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
  260. data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
  261. data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
  262. data/try/support/debugging/debug_field_decrypt.rb +3 -0
  263. data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
  264. data/try/support/debugging/debug_load_path.rb +3 -0
  265. data/try/support/debugging/debug_method_definition.rb +3 -0
  266. data/try/support/debugging/debug_method_resolution.rb +3 -0
  267. data/try/support/debugging/debug_minimal.rb +3 -0
  268. data/try/support/debugging/debug_provider.rb +3 -0
  269. data/try/support/debugging/debug_secure_behavior.rb +3 -0
  270. data/try/support/debugging/debug_string_class.rb +3 -0
  271. data/try/support/debugging/debug_test.rb +3 -0
  272. data/try/support/debugging/debug_test_design.rb +3 -0
  273. data/try/support/debugging/encryption_method_tracer.rb +4 -0
  274. data/try/support/debugging/provider_diagnostics.rb +4 -0
  275. data/try/support/helpers/test_cleanup.rb +4 -0
  276. data/try/support/helpers/test_helpers.rb +5 -0
  277. data/try/support/memory/memory_basic_test.rb +4 -0
  278. data/try/support/memory/memory_detailed_test.rb +4 -0
  279. data/try/support/memory/memory_search_for_string.rb +4 -0
  280. data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
  281. data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
  282. data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
  283. data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
  284. data/try/support/prototypes/atomic_saves_v4.rb +4 -0
  285. data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
  286. data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  287. data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
  288. data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  289. data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
  290. data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
  291. data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
  292. data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
  293. data/try/support/prototypes/pooling/pool_siege.rb +4 -2
  294. data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
  295. data/try/thread_safety/README.md +496 -0
  296. data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
  297. data/try/thread_safety/connection_chain_race_try.rb +148 -0
  298. data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
  299. data/try/thread_safety/feature_registry_race_try.rb +226 -0
  300. data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
  301. data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
  302. data/try/thread_safety/field_registration_race_try.rb +222 -0
  303. data/try/thread_safety/logger_initialization_race_try.rb +170 -0
  304. data/try/thread_safety/middleware_registration_race_try.rb +154 -0
  305. data/try/thread_safety/module_config_race_try.rb +175 -0
  306. data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
  307. data/try/unit/core/autoloader_try.rb +4 -0
  308. data/try/unit/core/base_enhancements_try.rb +4 -0
  309. data/try/unit/core/connection_try.rb +4 -0
  310. data/try/unit/core/errors_try.rb +4 -0
  311. data/try/unit/core/extensions_try.rb +4 -0
  312. data/try/unit/core/familia_logger_try.rb +2 -0
  313. data/try/unit/core/familia_try.rb +4 -0
  314. data/try/unit/core/middleware_sampling_try.rb +335 -0
  315. data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
  316. data/try/unit/core/middleware_thread_safety_try.rb +245 -0
  317. data/try/unit/core/middleware_try.rb +4 -0
  318. data/try/unit/core/settings_try.rb +4 -0
  319. data/try/unit/core/time_utils_try.rb +4 -0
  320. data/try/unit/core/tools_try.rb +4 -0
  321. data/try/unit/core/utils_try.rb +37 -0
  322. data/try/unit/data_types/boolean_try.rb +4 -0
  323. data/try/unit/data_types/counter_try.rb +4 -0
  324. data/try/unit/data_types/datatype_base_try.rb +4 -0
  325. data/try/unit/data_types/hash_try.rb +4 -0
  326. data/try/unit/data_types/list_try.rb +4 -0
  327. data/try/unit/data_types/lock_try.rb +4 -0
  328. data/try/unit/data_types/sorted_set_try.rb +4 -0
  329. data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
  330. data/try/unit/data_types/string_try.rb +4 -0
  331. data/try/unit/data_types/unsortedset_try.rb +4 -0
  332. data/try/unit/familia_resolve_class_try.rb +116 -0
  333. data/try/unit/horreum/auto_indexing_on_save_try.rb +5 -1
  334. data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
  335. data/try/unit/horreum/base_try.rb +4 -0
  336. data/try/unit/horreum/class_methods_try.rb +4 -0
  337. data/try/unit/horreum/commands_try.rb +4 -0
  338. data/try/unit/horreum/defensive_initialization_try.rb +4 -0
  339. data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +4 -0
  340. data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
  341. data/try/unit/horreum/field_categories_try.rb +4 -0
  342. data/try/unit/horreum/field_definition_try.rb +4 -0
  343. data/try/unit/horreum/initialization_try.rb +4 -0
  344. data/try/unit/horreum/json_type_preservation_try.rb +2 -0
  345. data/try/unit/horreum/optimized_loading_try.rb +156 -0
  346. data/try/unit/horreum/relations_try.rb +4 -0
  347. data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
  348. data/try/unit/horreum/serialization_try.rb +4 -0
  349. data/try/unit/horreum/settings_try.rb +4 -0
  350. data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
  351. data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
  352. data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
  353. data/try/unit/middleware/database_logger_methods_try.rb +251 -0
  354. data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
  355. data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
  356. data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
  357. data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
  358. data/try/unit/thread_safety_monitor_try.rb +149 -0
  359. metadata +72 -17
  360. data/.github/workflows/code-quality.yml +0 -138
  361. data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
  362. data/changelog.d/20251011_203905_delano_next.rst +0 -30
  363. data/changelog.d/20251011_212633_delano_next.rst +0 -13
  364. data/changelog.d/20251011_221253_delano_next.rst +0 -26
  365. data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
  366. data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
  367. data/docs/archive/FAMILIA_UPDATE.md +0 -226
  368. data/docs/archive/README.md +0 -64
  369. data/docs/archive/api-reference.md +0 -333
  370. data/docs/guides/core-field-system.md +0 -806
  371. data/docs/guides/implementation.md +0 -276
  372. data/docs/guides/security-model.md +0 -183
@@ -12,424 +12,555 @@
12
12
 
13
13
  ## Overview
14
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.
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 high-entropy hexadecimal strings.
16
16
 
17
17
  ## Why Use Object Identifiers?
18
18
 
19
- **Consistency**: Ensures all objects have properly formatted, unique identifiers without manual management.
19
+ **Automatic Generation**: No manual ID management - identifiers are generated lazily when first accessed.
20
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.
21
+ **Configurable Strategies**: Choose from UUID v7 (timestamped), UUID v4 (random), hex (high-entropy), or custom generators.
22
22
 
23
- **Collision Avoidance**: Built-in collision detection and retry logic ensures identifier uniqueness even under high concurrency.
23
+ **Provenance Tracking**: System tracks which generator created each ID for security and debugging.
24
24
 
25
- **Integration Ready**: Generated IDs work seamlessly with external APIs, logging systems, and database relationships.
25
+ **Data Integrity**: Preserves existing IDs during initialization - never overwrites loaded data.
26
26
 
27
- ## Quick Start
27
+ **Lookup Support**: Automatic bidirectional mapping enables finding objects by their objid.
28
28
 
29
- ### Basic UUID Generation
29
+ ## Generation Strategies
30
+
31
+ ### UUID v7 (Default)
32
+
33
+ UUID version 7 with embedded timestamp for natural sorting:
30
34
 
31
35
  ```ruby
32
36
  class User < Familia::Horreum
33
- feature :object_identifier, generator: :uuid_v4
37
+ feature :object_identifier # Uses :uuid_v7 by default
34
38
 
35
- field :name, :email, :created_at
39
+ field :email, :name
36
40
  end
37
41
 
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"
42
+ user = User.new(email: 'alice@example.com')
43
+ user.objid # => "01234567-89ab-7def-8000-123456789abc"
41
44
  ```
42
45
 
43
- ### Compact Hex Identifiers
46
+ **Characteristics:**
47
+ - 128-bit identifier with embedded timestamp
48
+ - Naturally sortable by creation time
49
+ - Globally unique across distributed systems
50
+ - May leak timing information (consider for security-sensitive apps)
51
+
52
+ ### UUID v4 (Random)
53
+
54
+ UUID version 4 for legacy compatibility and maximum randomness:
44
55
 
45
56
  ```ruby
46
- class Session < Familia::Horreum
47
- feature :object_identifier, generator: :hex, length: 16
57
+ class LegacyUser < Familia::Horreum
58
+ feature :object_identifier, generator: :uuid_v4
48
59
 
49
- field :user_id, :data, :expires_at
60
+ field :email, :username
50
61
  end
51
62
 
52
- session = Session.create(user_id: "user123")
53
- puts session.objid # => "a1b2c3d4e5f67890"
63
+ user = LegacyUser.new(email: 'bob@example.com')
64
+ user.objid # => "f47ac10b-58cc-4372-a567-0e02b2c3d479"
54
65
  ```
55
66
 
56
- ## Generator Types
67
+ **Characteristics:**
68
+ - 122-bit random identifier (6 bits for version/variant)
69
+ - No timing correlation for enhanced security
70
+ - Widely supported format
71
+ - Compatible with existing UUID systems
57
72
 
58
- ### UUID v4 Generator
73
+ ### High-Entropy Hex
59
74
 
60
- Standard UUID format providing global uniqueness across distributed systems.
75
+ 256-bit hexadecimal for security-critical applications:
61
76
 
62
77
  ```ruby
63
- class Document < Familia::Horreum
64
- feature :object_identifier, generator: :uuid_v4
65
- field :title, :content
78
+ class SecureDocument < Familia::Horreum
79
+ feature :object_identifier, generator: :hex
80
+
81
+ field :title, :classification
66
82
  end
67
83
 
68
- doc = Document.create(title: "My Document")
69
- doc.objid # => "550e8400-e29b-41d4-a716-446655440000"
84
+ doc = SecureDocument.new(title: 'Classified Report')
85
+ doc.objid # => "a1b2c3d4e5f6789012345678901234567890abcdef..." (64 chars)
70
86
  ```
71
87
 
72
88
  **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.
89
+ - Maximum entropy (256 bits of randomness)
90
+ - No structure or timing information
91
+ - Compact representation without hyphens
92
+ - Ideal for security-sensitive applications
81
93
 
82
- ### Hex Generator
94
+ ### Custom Generator
83
95
 
84
- Compact hexadecimal strings ideal for internal use and high-volume scenarios.
96
+ Provide your own generation logic:
85
97
 
86
98
  ```ruby
87
- class ApiKey < Familia::Horreum
88
- feature :object_identifier, generator: :hex, length: 24
89
- field :name, :permissions, :created_at
99
+ class TimestampedItem < Familia::Horreum
100
+ feature :object_identifier,
101
+ generator: -> { "item_#{Familia.now.to_i}_#{SecureRandom.hex(4)}" }
102
+
103
+ field :data, :category
90
104
  end
91
105
 
92
- key = ApiKey.create(name: "Production API")
93
- key.objid # => "1a2b3c4d5e6f7890abcdef12"
106
+ item = TimestampedItem.new(data: 'test')
107
+ item.objid # => "item_1693857600_a1b2c3d4"
94
108
  ```
95
109
 
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)
110
+ **Custom Generator Requirements:**
111
+ - Must be callable (Proc, lambda, or respond to `call`)
112
+ - Should return unique strings
113
+ - Avoid collision-prone patterns
114
+
115
+ ## Basic Usage
116
+
117
+ ### Lazy Generation
118
+
119
+ Object identifiers are only generated when first accessed:
100
120
 
101
121
  ```ruby
102
- class InternalToken < Familia::Horreum
103
- feature :object_identifier,
104
- generator: :hex,
105
- length: 16,
106
- prefix: "tk_"
122
+ user = User.new(email: 'test@example.com')
107
123
 
108
- field :scope, :issued_at
109
- end
124
+ # No objid generated yet
125
+ user.instance_variable_get(:@objid) # => nil
126
+
127
+ # First access triggers generation
128
+ user.objid # => "01234567-89ab-7def-8000-123456789abc"
110
129
 
111
- token = InternalToken.create(scope: "read:users")
112
- token.objid # => "tk_a1b2c3d4e5f67890"
130
+ # Subsequent access returns cached value
131
+ user.objid # => "01234567-89ab-7def-8000-123456789abc" (same)
113
132
  ```
114
133
 
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
134
+ ### Preserved During Initialization
120
135
 
121
- > **⚠️ Important**
122
- >
123
- > Hex generators provide good uniqueness but aren't globally unique like UUIDs. Use appropriate length for your collision tolerance.
136
+ Existing IDs are never overwritten:
124
137
 
125
- ### Custom Generator
138
+ ```ruby
139
+ # Loading existing object from database
140
+ existing_user = User.new(
141
+ objid: '01234567-89ab-7def-8000-existing123',
142
+ email: 'existing@example.com'
143
+ )
144
+
145
+ # Existing ID is preserved, not regenerated
146
+ existing_user.objid # => "01234567-89ab-7def-8000-existing123"
147
+ ```
126
148
 
127
- Define your own identifier generation logic for business-specific requirements.
149
+ ### Finding by Object ID
128
150
 
129
151
  ```ruby
130
- class OrderNumber < Familia::Horreum
131
- feature :object_identifier, generator: :custom
152
+ # Save user first
153
+ user = User.new(email: 'findme@example.com')
154
+ user.save
155
+ puts user.objid # => "01234567-89ab-7def-8000-123456789abc"
132
156
 
133
- field :customer_id, :amount, :created_at
157
+ # Find by object identifier
158
+ found = User.find_by_objid('01234567-89ab-7def-8000-123456789abc')
159
+ found.email # => "findme@example.com"
134
160
 
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
161
+ # Returns nil if not found
162
+ missing = User.find_by_objid('nonexistent') # => nil
163
+ ```
164
+
165
+ ### Long-form Methods
142
166
 
143
- order = OrderNumber.create(customer_id: "cust123", amount: 99.99)
144
- order.objid # => "ORD-20241215-000001"
167
+ ```ruby
168
+ user.object_identifier # Same as user.objid
169
+ user.object_identifier = 'new' # Same as user.objid = 'new'
145
170
  ```
146
171
 
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
172
+ ## Provenance Tracking
152
173
 
153
- > **🔧 Advanced Pattern**
154
- >
155
- > Custom generators can integrate with external services, database sequences, or business rules for sophisticated ID schemes.
174
+ The system tracks which generator created each objid:
175
+
176
+ ```ruby
177
+ # Generated by the system
178
+ user = User.new
179
+ user.objid # Triggers generation
180
+ user.objid_generator_used # => :uuid_v7
181
+
182
+ # Loaded from database (provenance inferred from format)
183
+ loaded = User.new(objid: 'f47ac10b-58cc-4372-a567-0e02b2c3d479')
184
+ loaded.objid_generator_used # => :uuid_v4 (inferred from format)
185
+
186
+ # Unknown format
187
+ custom = User.new(objid: 'custom-format-id')
188
+ custom.objid_generator_used # => nil (unknown provenance)
189
+ ```
190
+
191
+ **Why Provenance Matters:**
192
+ - Security features like ExternalIdentifier require known provenance
193
+ - Debugging and auditing benefit from generator tracking
194
+ - Format validation can be performed based on expected generator
156
195
 
157
- ## Advanced Configuration
196
+ ## Lookup Management
158
197
 
159
- ### Collision Detection
198
+ ### Automatic Mapping
160
199
 
161
- Enable automatic collision detection and retry logic:
200
+ The feature maintains lookup tables for objid-to-primary-key mapping:
162
201
 
163
202
  ```ruby
164
203
  class Product < Familia::Horreum
165
- feature :object_identifier,
166
- generator: :hex,
167
- collision_check: true,
168
- max_retries: 5
204
+ feature :object_identifier
205
+ identifier_field :product_code # Different from objid
169
206
 
170
- field :name, :price, :sku
207
+ field :product_code, :name, :price
171
208
  end
209
+
210
+ product = Product.new(product_code: 'PROD123', name: 'Widget')
211
+ product.save
212
+
213
+ # Lookup table maps objid to primary key
214
+ Product.objid_lookup.class # => Familia::DataType::HashKey
215
+ Product.objid_lookup[product.objid] # => "PROD123"
172
216
  ```
173
217
 
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)
218
+ ### Cleanup on Destroy
178
219
 
179
- ### Identifier Validation
220
+ ```ruby
221
+ product.destroy!
222
+
223
+ # Lookup entry is automatically cleaned up
224
+ Product.objid_lookup[product.objid] # => nil
225
+ ```
226
+
227
+ ## Integration Patterns
180
228
 
181
- Add custom validation logic for generated identifiers:
229
+ ### Using objid as Primary Key
182
230
 
183
231
  ```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
232
+ class SimpleModel < Familia::Horreum
233
+ feature :object_identifier
234
+ identifier_field :objid # Use objid as the primary key
195
235
 
196
- def self.valid_identifier?(id)
197
- id.match?(/^st_[a-z0-9]{32}$/) && !id.match?(/[0O1lI]/)
198
- end
236
+ field :data, :status
199
237
  end
200
- ```
201
238
 
202
- ## Performance Considerations
239
+ model = SimpleModel.new(data: 'test')
240
+ model.save
203
241
 
204
- ### Generation Speed Benchmarks
242
+ # No separate lookup needed - objid is the primary key
243
+ SimpleModel.find_by_objid(model.objid) == SimpleModel.find(model.objid) # => true
244
+ ```
205
245
 
206
- Different generators have varying performance characteristics:
246
+ ### Combining with External Identifiers
207
247
 
208
248
  ```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 } }
249
+ class User < Familia::Horreum
250
+ feature :object_identifier # Provides internal objid
251
+ feature :external_identifier # Derives public extid from objid
252
+
253
+ field :email, :name
217
254
  end
255
+
256
+ user = User.new(email: 'test@example.com')
257
+ user.save
258
+
259
+ user.objid # => "01234567-89ab-7def-8000-123456789abc" (internal)
260
+ user.extid # => "ext_abc123def456ghi789jkl" (public-facing)
218
261
  ```
219
262
 
220
- ### Memory Usage
263
+ ### API Design Patterns
221
264
 
222
- - **UUID v4**: 36 bytes per identifier
223
- - **Hex**: Variable based on length (2 bytes per hex character)
224
- - **Custom**: Depends on implementation
265
+ ```ruby
266
+ # Internal operations use objid
267
+ def sync_user_data(objid)
268
+ user = User.find_by_objid(objid)
269
+ # ... sync logic
270
+ end
225
271
 
226
- ### Collision Probability
272
+ # Public APIs use external identifiers
273
+ class UsersController
274
+ def show
275
+ @user = User.find_by_extid(params[:id]) # Public ID
276
+ render json: {
277
+ id: @user.extid, # Hide internal objid
278
+ email: @user.email,
279
+ name: @user.name
280
+ }
281
+ end
282
+ end
283
+ ```
284
+
285
+ ## Error Handling
227
286
 
228
- For hex generators, collision probability depends on length and volume:
287
+ ### Invalid Generator Configuration
229
288
 
230
289
  ```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))
290
+ # Invalid generator type
291
+ class BadModel < Familia::Horreum
292
+ feature :object_identifier, generator: :invalid_type
235
293
  end
236
294
 
237
- # Examples:
238
- collision_probability(12, 1_000_000) # Very low
239
- collision_probability(8, 100_000) # Consider longer length
295
+ model = BadModel.new
296
+ model.objid
297
+ # => Familia::Problem: Invalid object identifier generator: :invalid_type
240
298
  ```
241
299
 
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
300
+ ### Custom Generator Errors
248
301
 
249
- ## Integration Patterns
302
+ ```ruby
303
+ # ❌ Non-callable generator
304
+ class BadCustomModel < Familia::Horreum
305
+ feature :object_identifier, generator: "not_callable"
306
+ end
250
307
 
251
- ### External API Integration
308
+ model = BadCustomModel.new
309
+ model.objid
310
+ # => Familia::Problem: Invalid object identifier generator: "not_callable"
311
+ ```
312
+
313
+ ## Testing Strategies
314
+
315
+ ### Basic Generation Testing
252
316
 
253
317
  ```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
318
+ class ObjectIdentifierTest < Minitest::Test
319
+ def test_lazy_generation
320
+ user = User.new(email: 'test@example.com')
321
+
322
+ # No objid until first access
323
+ assert_nil user.instance_variable_get(:@objid)
324
+
325
+ # First access generates ID
326
+ objid = user.objid
327
+ assert_match UUID_V7_PATTERN, objid
328
+
329
+ # Subsequent access returns same ID
330
+ assert_equal objid, user.objid
331
+ end
332
+
333
+ def test_preserves_existing_objid
334
+ existing_id = '01234567-89ab-7def-8000-existing123'
335
+ user = User.new(objid: existing_id, email: 'test@example.com')
336
+
337
+ assert_equal existing_id, user.objid
338
+ end
339
+
340
+ def test_find_by_objid
341
+ user = User.new(email: 'findme@example.com')
342
+ user.save
343
+
344
+ found = User.find_by_objid(user.objid)
345
+ assert_equal user.email, found.email
268
346
  end
269
347
  end
270
348
  ```
271
349
 
272
- ### Database Relationships
350
+ ### Generator Strategy Testing
273
351
 
274
352
  ```ruby
275
- class Order < Familia::Horreum
276
- feature :object_identifier, generator: :custom
277
- field :customer_id, :total_amount
353
+ class GeneratorTest < Minitest::Test
354
+ def test_uuid_v7_format
355
+ user = User.new # Uses default :uuid_v7
356
+ objid = user.objid
357
+
358
+ assert_match(/\A[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/, objid)
359
+ assert_equal :uuid_v7, user.objid_generator_used
360
+ end
361
+
362
+ def test_uuid_v4_format
363
+ user = LegacyUser.new # Uses :uuid_v4
364
+ objid = user.objid
278
365
 
279
- def self.generate_identifier
280
- "ORD-#{SecureRandom.hex(8).upcase}"
366
+ 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/, objid)
367
+ assert_equal :uuid_v4, user.objid_generator_used
368
+ end
369
+
370
+ def test_hex_format
371
+ doc = SecureDocument.new # Uses :hex
372
+ objid = doc.objid
373
+
374
+ assert_match(/\A[0-9a-f]{64}\z/, objid) # 256 bits = 64 hex chars
375
+ assert_equal :hex, doc.objid_generator_used
376
+ end
377
+
378
+ def test_custom_generator
379
+ item = TimestampedItem.new
380
+ objid = item.objid
381
+
382
+ assert_match(/\Aitem_\d+_[0-9a-f]{8}\z/, objid)
281
383
  end
282
384
  end
385
+ ```
283
386
 
284
- class OrderItem < Familia::Horreum
285
- feature :object_identifier, generator: :hex
286
- field :order_id, :product_id, :quantity
387
+ ### Provenance Testing
287
388
 
288
- def order
289
- Order.load(order_id)
389
+ ```ruby
390
+ class ProvenanceTest < Minitest::Test
391
+ def test_provenance_inference
392
+ # UUID v7 format
393
+ user = User.new(objid: '01234567-89ab-7def-8000-123456789abc')
394
+ assert_equal :uuid_v7, user.objid_generator_used
395
+
396
+ # UUID v4 format
397
+ user = User.new(objid: 'f47ac10b-58cc-4372-a567-0e02b2c3d479')
398
+ assert_equal :uuid_v4, user.objid_generator_used
399
+
400
+ # Hex format
401
+ user = User.new(objid: 'a1b2c3d4e5f6789012345678901234567890abcdef1234567890123456789012')
402
+ assert_equal :hex, user.objid_generator_used
403
+
404
+ # Unknown format
405
+ user = User.new(objid: 'custom-format-id')
406
+ assert_nil user.objid_generator_used
290
407
  end
291
408
  end
409
+ ```
292
410
 
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
- )
411
+ ## Performance Considerations
412
+
413
+ ### Lookup Table Growth
414
+
415
+ Each class maintains its own lookup table:
416
+
417
+ ```ruby
418
+ # Monitor table sizes
419
+ puts User.objid_lookup.length # Number of objid mappings
420
+ puts Product.objid_lookup.length # Separate table per class
421
+
422
+ # Consider cleanup for large datasets
423
+ User.objid_lookup.clear if rebuilding_data
300
424
  ```
301
425
 
302
- ### Logging and Debugging
426
+ ### Lazy Generation Benefits
427
+
428
+ ```ruby
429
+ # ✅ Efficient - objid only generated if needed
430
+ users = 1000.times.map { User.new(email: "user#{_1}@example.com") }
431
+ # No objids generated yet
303
432
 
304
- Generated identifiers provide excellent debugging context:
433
+ # objids generated only on access
434
+ users.each { |user| puts user.objid } # Now generated
435
+ ```
436
+
437
+ ### Memory Usage
305
438
 
306
439
  ```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
440
+ # Each objid uses approximately:
441
+ # - UUID: 36 bytes (with hyphens)
442
+ # - Hex: 64 bytes (256-bit)
443
+ # - Custom: varies by format
444
+
445
+ # Plus lookup table overhead per class
321
446
  ```
322
447
 
323
- ## Testing Strategies
448
+ ## Best Practices
324
449
 
325
- ### Test Identifier Generation
450
+ ### Choose Appropriate Generator
326
451
 
327
452
  ```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
453
+ # ✅ UUID v7 for distributed systems needing sortability
454
+ class DistributedEvent < Familia::Horreum
455
+ feature :object_identifier # Default :uuid_v7
456
+ end
341
457
 
342
- def test_hex_generation
343
- session = Session.create(user_id: "123")
458
+ # ✅ UUID v4 for legacy system compatibility
459
+ class LegacyRecord < Familia::Horreum
460
+ feature :object_identifier, generator: :uuid_v4
461
+ end
344
462
 
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
463
+ # Hex for maximum security
464
+ class CryptographicKey < Familia::Horreum
465
+ feature :object_identifier, generator: :hex
466
+ end
467
+ ```
349
468
 
350
- def test_custom_identifier_format
351
- order = OrderNumber.create(customer_id: "cust123")
469
+ ### Consistent Strategy Per Application
352
470
 
353
- # Verify custom format
354
- assert_match(/\AORD-\d{8}-\d{6}\z/, order.objid)
355
- end
471
+ ```ruby
472
+ # Define a base class with consistent strategy
473
+ class ApplicationRecord < Familia::Horreum
474
+ feature :object_identifier, generator: :uuid_v7
475
+ end
476
+
477
+ class User < ApplicationRecord
478
+ field :email, :name
479
+ end
480
+
481
+ class Order < ApplicationRecord
482
+ field :total, :status
356
483
  end
357
484
  ```
358
485
 
359
- ### Mock Generators for Testing
486
+ ### Use objid for Internal Operations
360
487
 
361
488
  ```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
489
+ # ✅ Internal APIs use objid
490
+ def process_user(user_objid)
491
+ user = User.find_by_objid(user_objid)
492
+ # ... processing logic
367
493
  end
368
494
 
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
495
+ # Public APIs use external identifiers
496
+ def public_user_profile(user_extid)
497
+ user = User.find_by_extid(user_extid)
498
+ # ... public profile logic
380
499
  end
381
500
  ```
382
501
 
383
502
  ## Troubleshooting
384
503
 
385
- ### Common Issues
504
+ ### objid Returns Nil
505
+
506
+ Check that object has been properly initialized:
386
507
 
387
- **Identifier Not Generated**
388
508
  ```ruby
389
- # Ensure feature is enabled
390
- class MyModel < Familia::Horreum
391
- feature :object_identifier # This line is required!
392
- field :name
393
- end
509
+ user = User.new
510
+ # Missing feature configuration?
511
+ user.class.features_enabled.include?(:object_identifier) # => Should be true
512
+
513
+ # Access objid to trigger generation
514
+ user.objid # Should generate and return ID
394
515
  ```
395
516
 
396
- **Custom Generator Not Called**
517
+ ### Lookup Not Working
518
+
519
+ Ensure object was saved to populate lookup table:
520
+
397
521
  ```ruby
398
- # Verify method signature
399
- def self.generate_identifier # Must be class method
400
- # Implementation here
401
- end
522
+ user = User.new(email: 'test@example.com')
523
+ objid = user.objid # Generates objid but doesn't save lookup
524
+
525
+ User.find_by_objid(objid) # => nil (lookup not saved)
526
+
527
+ user.save # Saves lookup mapping
528
+ User.find_by_objid(objid) # => user (now works)
402
529
  ```
403
530
 
404
- **Collision Detection Failing**
531
+ ### Generator Not Applied
532
+
533
+ Check feature options syntax:
534
+
405
535
  ```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}"
536
+ # Wrong - hash instead of keyword arguments
537
+ class User < Familia::Horreum
538
+ feature :object_identifier, { generator: :uuid_v4 }
539
+ end
540
+
541
+ # ✅ Correct - keyword arguments
542
+ class User < Familia::Horreum
543
+ feature :object_identifier, generator: :uuid_v4
411
544
  end
412
545
  ```
413
546
 
414
- ### Debug Identifier Generation
547
+ ### Custom Generator Errors
415
548
 
416
549
  ```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}
550
+ # Valid custom generator
551
+ class Model < Familia::Horreum
552
+ feature :object_identifier,
553
+ generator: -> { "prefix_#{SecureRandom.hex(8)}" }
554
+ end
423
555
 
424
- # Verify generation manually
425
- MyModel.generate_identifier # Should return new identifier
556
+ # Invalid - not callable
557
+ class Model < Familia::Horreum
558
+ feature :object_identifier, generator: "not_a_proc"
559
+ end
426
560
  ```
427
561
 
428
- ---
429
-
430
562
  ## See Also
431
563
 
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
564
+ - [External Identifiers](feature-external-identifiers.md) - Public-facing IDs derived from objid
565
+ - [Feature System](feature-system.md) - Understanding Familia's feature architecture
566
+ - [Relationships](feature-relationships.md) - Using object identifiers in relationships