familia 2.0.0.pre18 → 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 (370) 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 +205 -88
  8. data/CLAUDE.md +62 -10
  9. data/Gemfile +3 -3
  10. data/Gemfile.lock +27 -62
  11. data/README.md +39 -0
  12. data/bin/try +16 -0
  13. data/bin/tryouts +16 -0
  14. data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
  15. data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
  16. data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
  17. data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
  18. data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
  19. data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -0
  20. data/docs/1106-participates_in-bidirectional-solution.md +129 -0
  21. data/docs/guides/encryption.md +486 -0
  22. data/docs/guides/feature-encrypted-fields.md +123 -7
  23. data/docs/guides/feature-expiration.md +177 -133
  24. data/docs/guides/feature-external-identifiers.md +415 -443
  25. data/docs/guides/feature-object-identifiers.md +400 -269
  26. data/docs/guides/feature-quantization.md +120 -6
  27. data/docs/guides/feature-relationships-indexing.md +318 -0
  28. data/docs/guides/feature-relationships-methods.md +146 -604
  29. data/docs/guides/feature-relationships-participation.md +263 -0
  30. data/docs/guides/feature-relationships.md +118 -136
  31. data/docs/guides/feature-system-devs.md +176 -693
  32. data/docs/guides/feature-system.md +119 -6
  33. data/docs/guides/feature-transient-fields.md +81 -0
  34. data/docs/guides/field-system.md +778 -0
  35. data/docs/guides/index.md +32 -15
  36. data/docs/guides/logging.md +187 -0
  37. data/docs/guides/optimized-loading.md +674 -0
  38. data/docs/guides/thread-safety-monitoring.md +61 -0
  39. data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
  40. data/docs/migrating/v2.0.0-pre19.md +197 -0
  41. data/docs/migrating/v2.0.0-pre22.md +241 -0
  42. data/docs/overview.md +7 -9
  43. data/docs/reference/api-technical.md +267 -320
  44. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
  45. data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
  46. data/examples/autoloader/mega_customer.rb +2 -0
  47. data/examples/datatype_standalone.rb +282 -0
  48. data/examples/encrypted_fields.rb +2 -1
  49. data/examples/json_usage_patterns.rb +2 -0
  50. data/examples/relationships.rb +3 -0
  51. data/examples/safe_dump.rb +2 -1
  52. data/examples/sampling_demo.rb +53 -0
  53. data/examples/single_connection_transaction_confusions.rb +2 -1
  54. data/familia.gemspec +2 -1
  55. data/lib/familia/base.rb +2 -0
  56. data/lib/familia/connection/behavior.rb +254 -0
  57. data/lib/familia/connection/handlers.rb +97 -0
  58. data/lib/familia/connection/individual_command_proxy.rb +2 -0
  59. data/lib/familia/connection/middleware.rb +34 -24
  60. data/lib/familia/connection/operation_core.rb +3 -1
  61. data/lib/familia/connection/operations.rb +2 -0
  62. data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +4 -2
  63. data/lib/familia/connection/transaction_core.rb +75 -9
  64. data/lib/familia/connection.rb +21 -5
  65. data/lib/familia/data_type/class_methods.rb +3 -1
  66. data/lib/familia/data_type/connection.rb +153 -7
  67. data/lib/familia/data_type/database_commands.rb +9 -4
  68. data/lib/familia/data_type/serialization.rb +10 -4
  69. data/lib/familia/data_type/settings.rb +2 -0
  70. data/lib/familia/data_type/types/counter.rb +2 -0
  71. data/lib/familia/data_type/types/hashkey.rb +8 -6
  72. data/lib/familia/data_type/types/listkey.rb +2 -0
  73. data/lib/familia/data_type/types/lock.rb +2 -0
  74. data/lib/familia/data_type/types/sorted_set.rb +2 -0
  75. data/lib/familia/data_type/types/stringkey.rb +2 -0
  76. data/lib/familia/data_type/types/unsorted_set.rb +2 -0
  77. data/lib/familia/data_type.rb +2 -0
  78. data/lib/familia/encryption/encrypted_data.rb +4 -2
  79. data/lib/familia/encryption/manager.rb +2 -0
  80. data/lib/familia/encryption/provider.rb +2 -0
  81. data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
  82. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
  83. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
  84. data/lib/familia/encryption/registry.rb +2 -0
  85. data/lib/familia/encryption/request_cache.rb +2 -0
  86. data/lib/familia/encryption.rb +9 -2
  87. data/lib/familia/errors.rb +53 -14
  88. data/lib/familia/features/autoloader.rb +2 -0
  89. data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
  90. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
  91. data/lib/familia/features/encrypted_fields.rb +2 -2
  92. data/lib/familia/features/expiration/extensions.rb +11 -11
  93. data/lib/familia/features/expiration.rb +29 -21
  94. data/lib/familia/features/external_identifier.rb +33 -7
  95. data/lib/familia/features/object_identifier.rb +2 -0
  96. data/lib/familia/features/quantization.rb +3 -1
  97. data/lib/familia/features/relationships/README.md +3 -1
  98. data/lib/familia/features/relationships/collection_operations.rb +2 -0
  99. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +177 -47
  100. data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
  101. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +203 -63
  102. data/lib/familia/features/relationships/indexing.rb +40 -42
  103. data/lib/familia/features/relationships/indexing_relationship.rb +17 -5
  104. data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
  105. data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
  106. data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
  107. data/lib/familia/features/relationships/participation.rb +155 -69
  108. data/lib/familia/features/relationships/participation_membership.rb +69 -0
  109. data/lib/familia/features/relationships/participation_relationship.rb +34 -6
  110. data/lib/familia/features/relationships/score_encoding.rb +2 -0
  111. data/lib/familia/features/relationships.rb +5 -3
  112. data/lib/familia/features/safe_dump.rb +2 -0
  113. data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
  114. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
  115. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
  116. data/lib/familia/features/transient_fields.rb +2 -0
  117. data/lib/familia/features.rb +2 -0
  118. data/lib/familia/field_type.rb +5 -2
  119. data/lib/familia/horreum/connection.rb +28 -36
  120. data/lib/familia/horreum/database_commands.rb +131 -10
  121. data/lib/familia/horreum/definition.rb +18 -7
  122. data/lib/familia/horreum/management.rb +233 -57
  123. data/lib/familia/horreum/persistence.rb +314 -122
  124. data/lib/familia/horreum/related_fields.rb +2 -0
  125. data/lib/familia/horreum/serialization.rb +26 -4
  126. data/lib/familia/horreum/settings.rb +2 -0
  127. data/lib/familia/horreum/utils.rb +2 -8
  128. data/lib/familia/horreum.rb +46 -13
  129. data/lib/familia/identifier_extractor.rb +2 -0
  130. data/lib/familia/instrumentation.rb +156 -0
  131. data/lib/familia/json_serializer.rb +2 -0
  132. data/lib/familia/logging.rb +94 -37
  133. data/lib/familia/refinements/dear_json.rb +2 -0
  134. data/lib/familia/refinements/stylize_words.rb +2 -14
  135. data/lib/familia/refinements/time_literals.rb +2 -0
  136. data/lib/familia/refinements.rb +2 -0
  137. data/lib/familia/secure_identifier.rb +10 -2
  138. data/lib/familia/settings.rb +9 -7
  139. data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
  140. data/lib/familia/thread_safety/monitor.rb +328 -0
  141. data/lib/familia/utils.rb +13 -0
  142. data/lib/familia/verifiable_identifier.rb +3 -1
  143. data/lib/familia/version.rb +3 -1
  144. data/lib/familia.rb +31 -4
  145. data/lib/middleware/database_command_counter.rb +152 -0
  146. data/lib/middleware/database_logger.rb +325 -129
  147. data/lib/multi_result.rb +2 -0
  148. data/try/edge_cases/empty_identifiers_try.rb +2 -0
  149. data/try/edge_cases/hash_symbolization_try.rb +2 -0
  150. data/try/edge_cases/json_serialization_try.rb +2 -0
  151. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
  152. data/try/edge_cases/race_conditions_try.rb +4 -0
  153. data/try/edge_cases/reserved_keywords_try.rb +4 -0
  154. data/try/edge_cases/string_coercion_try.rb +6 -4
  155. data/try/edge_cases/ttl_side_effects_try.rb +4 -0
  156. data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
  157. data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
  158. data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
  159. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
  160. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
  161. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
  162. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
  163. data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
  164. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
  165. data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
  166. data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
  167. data/try/features/encrypted_fields/memory_security_try.rb +4 -0
  168. data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
  169. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
  170. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
  171. data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
  172. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
  173. data/try/features/encryption/config_persistence_try.rb +4 -0
  174. data/try/features/encryption/core_try.rb +4 -0
  175. data/try/features/encryption/instance_variable_scope_try.rb +4 -0
  176. data/try/features/encryption/module_loading_try.rb +4 -0
  177. data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
  178. data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
  179. data/try/features/encryption/roundtrip_validation_try.rb +4 -0
  180. data/try/features/encryption/secure_memory_handling_try.rb +4 -0
  181. data/try/features/expiration/expiration_try.rb +5 -1
  182. data/try/features/external_identifier/external_identifier_try.rb +171 -8
  183. data/try/features/feature_dependencies_try.rb +2 -0
  184. data/try/features/feature_improvements_try.rb +2 -0
  185. data/try/features/field_groups_try.rb +2 -0
  186. data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
  187. data/try/features/object_identifier/object_identifier_try.rb +2 -0
  188. data/try/features/quantization/quantization_try.rb +4 -0
  189. data/try/features/real_feature_integration_try.rb +2 -0
  190. data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
  191. data/try/features/relationships/indexing_rebuild_try.rb +600 -0
  192. data/try/features/relationships/indexing_try.rb +30 -4
  193. data/try/features/relationships/participation_bidirectional_try.rb +242 -0
  194. data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
  195. data/try/features/relationships/participation_commands_verification_try.rb +2 -0
  196. data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
  197. data/try/features/relationships/participation_reverse_index_try.rb +15 -13
  198. data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
  199. data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
  200. data/try/features/relationships/relationships_api_changes_try.rb +6 -4
  201. data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
  202. data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
  203. data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
  204. data/try/features/relationships/relationships_performance_try.rb +4 -0
  205. data/try/features/relationships/relationships_performance_working_try.rb +4 -0
  206. data/try/features/relationships/relationships_try.rb +6 -4
  207. data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
  208. data/try/features/safe_dump/safe_dump_try.rb +4 -0
  209. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  210. data/try/features/transient_fields/refresh_reset_try.rb +3 -0
  211. data/try/features/transient_fields/simple_refresh_test.rb +3 -0
  212. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  213. data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
  214. data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
  215. data/try/integration/connection/fiber_context_preservation_try.rb +7 -3
  216. data/try/integration/connection/handler_constraints_try.rb +4 -0
  217. data/try/integration/connection/isolated_dbclient_try.rb +4 -0
  218. data/try/integration/connection/middleware_reconnect_try.rb +2 -0
  219. data/try/integration/connection/operation_mode_guards_try.rb +5 -1
  220. data/try/integration/connection/pipeline_fallback_integration_try.rb +15 -12
  221. data/try/integration/connection/pools_try.rb +4 -0
  222. data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
  223. data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
  224. data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
  225. data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
  226. data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
  227. data/try/integration/connection/transaction_modes_try.rb +4 -0
  228. data/try/integration/conventional_inheritance_try.rb +4 -0
  229. data/try/integration/create_method_try.rb +26 -22
  230. data/try/integration/cross_component_try.rb +4 -0
  231. data/try/integration/data_types/datatype_pipelines_try.rb +108 -0
  232. data/try/integration/data_types/datatype_transactions_try.rb +251 -0
  233. data/try/integration/database_consistency_try.rb +4 -0
  234. data/try/integration/familia_extended_try.rb +4 -0
  235. data/try/integration/familia_members_methods_try.rb +4 -0
  236. data/try/integration/models/customer_safe_dump_try.rb +9 -1
  237. data/try/integration/models/customer_try.rb +4 -0
  238. data/try/integration/models/datatype_base_try.rb +4 -0
  239. data/try/integration/models/familia_object_try.rb +5 -1
  240. data/try/integration/persistence_operations_try.rb +166 -10
  241. data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
  242. data/try/integration/save_methods_consistency_try.rb +241 -0
  243. data/try/integration/scenarios_try.rb +4 -0
  244. data/try/integration/secure_identifier_try.rb +4 -0
  245. data/try/integration/transaction_safety_core_try.rb +176 -0
  246. data/try/integration/transaction_safety_workflow_try.rb +291 -0
  247. data/try/integration/verifiable_identifier_try.rb +4 -0
  248. data/try/investigation/pipeline_routing/README.md +228 -0
  249. data/try/performance/benchmarks_try.rb +4 -0
  250. data/try/performance/transaction_safety_benchmark_try.rb +238 -0
  251. data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
  252. data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
  253. data/try/support/debugging/cache_behavior_tracer.rb +4 -0
  254. data/try/support/debugging/debug_aad_process.rb +3 -0
  255. data/try/support/debugging/debug_concealed_internal.rb +3 -0
  256. data/try/support/debugging/debug_concealed_reveal.rb +3 -0
  257. data/try/support/debugging/debug_context_aad.rb +3 -0
  258. data/try/support/debugging/debug_context_simple.rb +3 -0
  259. data/try/support/debugging/debug_cross_context.rb +3 -0
  260. data/try/support/debugging/debug_database_load.rb +3 -0
  261. data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
  262. data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
  263. data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
  264. data/try/support/debugging/debug_field_decrypt.rb +3 -0
  265. data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
  266. data/try/support/debugging/debug_load_path.rb +3 -0
  267. data/try/support/debugging/debug_method_definition.rb +3 -0
  268. data/try/support/debugging/debug_method_resolution.rb +3 -0
  269. data/try/support/debugging/debug_minimal.rb +3 -0
  270. data/try/support/debugging/debug_provider.rb +3 -0
  271. data/try/support/debugging/debug_secure_behavior.rb +3 -0
  272. data/try/support/debugging/debug_string_class.rb +3 -0
  273. data/try/support/debugging/debug_test.rb +3 -0
  274. data/try/support/debugging/debug_test_design.rb +3 -0
  275. data/try/support/debugging/encryption_method_tracer.rb +4 -0
  276. data/try/support/debugging/provider_diagnostics.rb +4 -0
  277. data/try/support/helpers/test_cleanup.rb +4 -0
  278. data/try/support/helpers/test_helpers.rb +5 -0
  279. data/try/support/memory/memory_basic_test.rb +4 -0
  280. data/try/support/memory/memory_detailed_test.rb +4 -0
  281. data/try/support/memory/memory_search_for_string.rb +4 -0
  282. data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
  283. data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
  284. data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
  285. data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
  286. data/try/support/prototypes/atomic_saves_v4.rb +4 -0
  287. data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
  288. data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  289. data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
  290. data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  291. data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
  292. data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
  293. data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
  294. data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
  295. data/try/support/prototypes/pooling/pool_siege.rb +4 -2
  296. data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
  297. data/try/thread_safety/README.md +496 -0
  298. data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
  299. data/try/thread_safety/connection_chain_race_try.rb +148 -0
  300. data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
  301. data/try/thread_safety/feature_registry_race_try.rb +226 -0
  302. data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
  303. data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
  304. data/try/thread_safety/field_registration_race_try.rb +222 -0
  305. data/try/thread_safety/logger_initialization_race_try.rb +170 -0
  306. data/try/thread_safety/middleware_registration_race_try.rb +154 -0
  307. data/try/thread_safety/module_config_race_try.rb +175 -0
  308. data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
  309. data/try/unit/core/autoloader_try.rb +4 -0
  310. data/try/unit/core/base_enhancements_try.rb +4 -0
  311. data/try/unit/core/connection_try.rb +4 -0
  312. data/try/unit/core/errors_try.rb +4 -0
  313. data/try/unit/core/extensions_try.rb +4 -0
  314. data/try/unit/core/familia_logger_try.rb +2 -0
  315. data/try/unit/core/familia_try.rb +4 -0
  316. data/try/unit/core/middleware_sampling_try.rb +335 -0
  317. data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
  318. data/try/unit/core/middleware_thread_safety_try.rb +245 -0
  319. data/try/unit/core/middleware_try.rb +4 -0
  320. data/try/unit/core/settings_try.rb +4 -0
  321. data/try/unit/core/time_utils_try.rb +4 -0
  322. data/try/unit/core/tools_try.rb +4 -0
  323. data/try/unit/core/utils_try.rb +37 -0
  324. data/try/unit/data_types/boolean_try.rb +5 -1
  325. data/try/unit/data_types/counter_try.rb +4 -0
  326. data/try/unit/data_types/datatype_base_try.rb +4 -0
  327. data/try/unit/data_types/hash_try.rb +4 -0
  328. data/try/unit/data_types/list_try.rb +4 -0
  329. data/try/unit/data_types/lock_try.rb +4 -0
  330. data/try/unit/data_types/sorted_set_try.rb +4 -0
  331. data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
  332. data/try/unit/data_types/string_try.rb +5 -1
  333. data/try/unit/data_types/unsortedset_try.rb +4 -0
  334. data/try/unit/familia_resolve_class_try.rb +116 -0
  335. data/try/unit/horreum/auto_indexing_on_save_try.rb +36 -16
  336. data/try/unit/horreum/automatic_index_validation_try.rb +255 -0
  337. data/try/unit/horreum/base_try.rb +5 -1
  338. data/try/unit/horreum/class_methods_try.rb +6 -2
  339. data/try/unit/horreum/commands_try.rb +4 -0
  340. data/try/unit/horreum/defensive_initialization_try.rb +4 -0
  341. data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +4 -0
  342. data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
  343. data/try/unit/horreum/field_categories_try.rb +4 -0
  344. data/try/unit/horreum/field_definition_try.rb +4 -0
  345. data/try/unit/horreum/initialization_try.rb +5 -1
  346. data/try/unit/horreum/json_type_preservation_try.rb +2 -0
  347. data/try/unit/horreum/optimized_loading_try.rb +156 -0
  348. data/try/unit/horreum/relations_try.rb +8 -4
  349. data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
  350. data/try/unit/horreum/serialization_try.rb +6 -2
  351. data/try/unit/horreum/settings_try.rb +4 -0
  352. data/try/unit/horreum/unique_index_edge_cases_try.rb +380 -0
  353. data/try/unit/horreum/unique_index_guard_validation_try.rb +283 -0
  354. data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
  355. data/try/unit/middleware/database_logger_methods_try.rb +251 -0
  356. data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
  357. data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
  358. data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
  359. data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
  360. data/try/unit/thread_safety_monitor_try.rb +149 -0
  361. metadata +81 -14
  362. data/.github/workflows/code-quality.yml +0 -138
  363. data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
  364. data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
  365. data/docs/archive/FAMILIA_UPDATE.md +0 -226
  366. data/docs/archive/README.md +0 -64
  367. data/docs/archive/api-reference.md +0 -333
  368. data/docs/guides/core-field-system.md +0 -806
  369. data/docs/guides/implementation.md +0 -276
  370. data/docs/guides/security-model.md +0 -183
@@ -1,30 +1,30 @@
1
- # Features System Developer Guide
1
+ # Feature System Developer Guide
2
2
 
3
3
  ## Overview
4
4
 
5
- This developer guide covers the internal architecture of Familia's feature system, including feature registration, dependency resolution, loading mechanisms, and best practices for creating robust, maintainable features.
5
+ The Familia Feature system provides a simple, modular way to add optional functionality to Familia classes. Features are Ruby modules that get included into classes, extending them with additional methods and capabilities.
6
6
 
7
- ## Architecture Deep Dive
7
+ ## Architecture
8
8
 
9
9
  ### Core Components
10
10
 
11
11
  #### 1. Feature Registration (`Familia::Base`)
12
12
 
13
+ Features are registered using the `add_feature` method:
14
+
13
15
  ```ruby
14
- # lib/familia/base.rb
15
16
  module Familia::Base
16
- @features_available = {} # Registry of available features
17
- @feature_definitions = {} # Feature metadata and dependencies
18
-
19
- def self.add_feature(klass, feature_name, depends_on: [])
17
+ def self.add_feature(klass, feature_name, depends_on: [], field_group: nil)
20
18
  @features_available ||= {}
21
19
 
22
- # Create feature definition with metadata
20
+ # Create simple feature definition
23
21
  feature_def = FeatureDefinition.new(
24
22
  name: feature_name,
25
23
  depends_on: depends_on,
24
+ field_group: field_group
26
25
  )
27
26
 
27
+ # Track feature definitions and availability
28
28
  @feature_definitions ||= {}
29
29
  @feature_definitions[feature_name] = feature_def
30
30
  features_available[feature_name] = klass
@@ -34,859 +34,342 @@ end
34
34
 
35
35
  #### 2. Feature Activation (Horreum Classes)
36
36
 
37
- ```ruby
38
- # When a class declares `feature :name`
39
- module Familia::Horreum::ClassMethods
40
- def feature(name)
41
- # 1. Validate feature exists
42
- feature_klass = Familia::Base.features_available[name]
43
- raise Familia::Problem, "Unknown feature: #{name}" unless feature_klass
44
-
45
- # 2. Check dependencies
46
- validate_feature_dependencies(name)
47
-
48
- # 3. Include the feature module
49
- include feature_klass
50
-
51
- # 4. Track enabled features
52
- @features_enabled ||= Set.new
53
- @features_enabled.add(name)
54
- end
55
-
56
- private
37
+ Features are activated using the `feature` method:
57
38
 
58
- def validate_feature_dependencies(feature_name)
59
- feature_def = Familia::Base.feature_definitions[feature_name]
60
- return unless feature_def&.depends_on&.any?
61
-
62
- missing_deps = feature_def.depends_on - features_enabled.to_a
63
- if missing_deps.any?
64
- raise Familia::Problem,
65
- "Feature #{feature_name} requires: #{missing_deps.join(', ')}"
66
- end
67
- end
39
+ ```ruby
40
+ class MyModel < Familia::Horreum
41
+ feature :expiration
42
+ feature :encrypted_fields
43
+ feature :safe_dump
68
44
  end
69
45
  ```
70
46
 
71
47
  #### 3. Feature Definition Structure
72
48
 
73
- ```ruby
74
- class FeatureDefinition
75
- attr_reader :name, :depends_on, :conflicts_with, :provides
76
-
77
- def initialize(name:, depends_on: [], conflicts_with: [], provides: [])
78
- @name = name.to_sym
79
- @depends_on = Array(depends_on).map(&:to_sym)
80
- @conflicts_with = Array(conflicts_with).map(&:to_sym)
81
- @provides = Array(provides).map(&:to_sym)
82
- end
83
-
84
- def compatible_with?(other_feature)
85
- !conflicts_with.include?(other_feature.name)
86
- end
49
+ Features are defined using a simple Data class:
87
50
 
88
- def dependencies_satisfied?(enabled_features)
89
- depends_on.all? { |dep| enabled_features.include?(dep) }
90
- end
91
- end
51
+ ```ruby
52
+ FeatureDefinition = Data.define(:name, :depends_on, :field_group)
92
53
  ```
93
54
 
94
55
  ### Feature Loading Lifecycle
95
56
 
96
- #### 1. Automatic Discovery
57
+ #### 1. Feature Self-Registration
97
58
 
98
- ```ruby
99
- # lib/familia/features.rb - loads all features automatically
100
- features_dir = File.join(__dir__, 'features')
101
- Dir.glob(File.join(features_dir, '*.rb')).sort.each do |feature_file|
102
- begin
103
- require_relative feature_file
104
- rescue LoadError => e
105
- Familia.logger.warn "Failed to load feature #{feature_file}: #{e.message}"
106
- end
107
- end
108
- ```
109
-
110
- #### 2. Feature Self-Registration
59
+ Each feature module registers itself:
111
60
 
112
61
  ```ruby
113
- # Each feature registers itself when loaded
114
62
  module Familia::Features::MyFeature
115
63
  def self.included(base)
116
64
  base.extend ClassMethods
117
- base.prepend InstanceMethods
65
+ base.include InstanceMethods
118
66
  end
119
67
 
120
68
  module ClassMethods
121
- # Class-level functionality
122
- end
123
-
124
- module InstanceMethods
125
- # Instance-level functionality
126
- end
127
-
128
- # Self-registration at module definition time
129
- Familia::Base.add_feature self, :my_feature, depends_on: [:other_feature]
130
- end
131
- ```
132
-
133
- #### 3. Runtime Inclusion
134
-
135
- ```ruby
136
- # When a class declares a feature
137
- class MyModel < Familia::Horreum
138
- feature :expiration # 1. Validation and dependency check
139
- feature :encrypted_fields # 2. Module inclusion
140
- feature :safe_dump # 3. Method definition and setup
141
- end
142
- ```
143
-
144
- ## Advanced Feature Patterns
145
-
146
- ### Conditional Feature Loading
147
-
148
- ```ruby
149
- module Familia::Features::ConditionalFeature
150
- def self.included(base)
151
- # Only add functionality if conditions are met
152
- if defined?(Rails) && Rails.env.production?
153
- base.extend ProductionMethods
154
- else
155
- base.extend DevelopmentMethods
156
- end
157
-
158
- # Conditional method definitions based on available libraries
159
- if defined?(Sidekiq)
160
- base.include BackgroundJobIntegration
161
- end
162
-
163
- if defined?(ActiveRecord)
164
- base.include ActiveRecordCompatibility
165
- end
166
- end
167
-
168
- module ProductionMethods
169
- def production_only_method
170
- # Implementation only available in production
171
- end
172
- end
173
-
174
- module DevelopmentMethods
175
- def debug_helper_method
176
- # Development and test helper methods
177
- end
178
- end
179
-
180
- # Register with environment-specific dependencies
181
- dependencies = []
182
- dependencies << :logging if defined?(Rails)
183
- dependencies << :metrics if ENV['ENABLE_METRICS']
184
-
185
- Familia::Base.add_feature self, :conditional_feature, depends_on: dependencies
186
- end
187
- ```
188
-
189
- ### Feature Conflicts and Compatibility
190
-
191
- ```ruby
192
- # Feature that conflicts with others
193
- module Familia::Features::AlternativeImplementation
194
- def self.included(base)
195
- # Check for conflicting features
196
- conflicting_features = [:original_implementation, :legacy_mode]
197
- enabled_conflicts = conflicting_features & base.features_enabled.to_a
198
-
199
- if enabled_conflicts.any?
200
- raise Familia::Problem,
201
- "#{self} conflicts with: #{enabled_conflicts.join(', ')}"
69
+ def my_feature_config
70
+ # Class-level functionality
202
71
  end
203
-
204
- base.extend ClassMethods
205
72
  end
206
73
 
207
- module ClassMethods
208
- def alternative_method
209
- # Different implementation approach
74
+ module InstanceMethods
75
+ def my_feature_method
76
+ # Instance-level functionality
210
77
  end
211
78
  end
212
79
 
213
- Familia::Base.add_feature self, :alternative_implementation,
214
- conflicts_with: [:original_implementation, :legacy_mode]
80
+ # Self-register with the feature system
81
+ Familia::Base.add_feature self, :my_feature, depends_on: []
215
82
  end
216
83
  ```
217
84
 
218
- ### Feature Capability Flags
219
-
220
- ```ruby
221
- module Familia::Features::CapabilityProvider
222
- def self.included(base)
223
- base.extend ClassMethods
224
-
225
- # Add capability flags to the class
226
- base.instance_variable_set(:@capabilities, Set.new)
227
- base.capabilities.merge([:search, :indexing, :full_text])
228
- end
229
-
230
- module ClassMethods
231
- attr_reader :capabilities
232
-
233
- def has_capability?(capability)
234
- capabilities.include?(capability.to_sym)
235
- end
236
-
237
- def requires_capability(capability)
238
- unless has_capability?(capability)
239
- raise Familia::Problem,
240
- "#{self} requires #{capability} capability"
241
- end
242
- end
243
- end
85
+ #### 2. Runtime Inclusion
244
86
 
245
- # Feature provides capabilities that other features can depend on
246
- Familia::Base.add_feature self, :capability_provider, provides: [:search, :indexing]
247
- end
87
+ When `feature` is called, the system:
248
88
 
249
- # Feature that requires specific capabilities
250
- module Familia::Features::SearchDependent
251
- def self.included(base)
252
- # Check that required capabilities are available
253
- base.requires_capability(:search)
254
- base.requires_capability(:indexing)
255
-
256
- base.extend ClassMethods
257
- end
258
-
259
- module ClassMethods
260
- def search_by_field(field, query)
261
- # Implementation that uses search capabilities
262
- end
263
- end
264
-
265
- Familia::Base.add_feature self, :search_dependent,
266
- depends_on: [:capability_provider]
267
- end
268
- ```
269
-
270
- ### Dynamic Feature Configuration
89
+ 1. Validates the feature exists
90
+ 2. Checks dependencies are satisfied
91
+ 3. Includes the feature module into the class
92
+ 4. Stores feature options if provided
271
93
 
272
94
  ```ruby
273
- module Familia::Features::ConfigurableFeature
274
- def self.included(base)
275
- base.extend ClassMethods
276
-
277
- # Initialize configuration
278
- config = base.feature_config(:configurable_feature)
279
-
280
- if config[:enable_caching]
281
- base.include CachingMethods
282
- end
283
-
284
- if config[:enable_logging]
285
- base.include LoggingMethods
286
- end
95
+ def feature(feature_name = nil, **options)
96
+ @features_enabled ||= []
287
97
 
288
- # Configure behavior based on settings
289
- base.instance_variable_set(:@batch_size, config[:batch_size] || 100)
290
- end
98
+ return features_enabled if feature_name.nil?
291
99
 
292
- module ClassMethods
293
- def feature_config(feature_name)
294
- @feature_configs ||= {}
295
- @feature_configs[feature_name] ||= load_feature_config(feature_name)
296
- end
297
-
298
- private
100
+ feature_name = feature_name.to_sym
101
+ feature_module = Familia::Base.find_feature(feature_name, self)
102
+ raise Familia::Problem, "Unsupported feature: #{feature_name}" unless feature_module
299
103
 
300
- def load_feature_config(feature_name)
301
- # Load from various sources
302
- config = {}
303
-
304
- # 1. Default configuration
305
- config.merge!(default_config_for(feature_name))
306
-
307
- # 2. Environment variables
308
- env_config = ENV.select { |k, v| k.start_with?("FAMILIA_#{feature_name.upcase}_") }
309
- env_config.each { |k, v| config[k.split('_').last.downcase.to_sym] = v }
310
-
311
- # 3. Configuration files
312
- if defined?(Rails)
313
- rails_config = Rails.application.config.familia&.features&.dig(feature_name)
314
- config.merge!(rails_config) if rails_config
315
- end
316
-
317
- config
318
- end
319
-
320
- def default_config_for(feature_name)
321
- case feature_name
322
- when :configurable_feature
323
- {
324
- enable_caching: true,
325
- enable_logging: Rails.env.development?,
326
- batch_size: 100,
327
- timeout: 30
328
- }
329
- else
330
- {}
331
- end
332
- end
333
- end
334
-
335
- module CachingMethods
336
- def cached_operation(&block)
337
- # Caching implementation
338
- end
339
- end
340
-
341
- module LoggingMethods
342
- def log_operation(operation, &block)
343
- # Logging implementation
104
+ # Check dependencies
105
+ feature_def = Familia::Base.feature_definitions[feature_name]
106
+ if feature_def&.depends_on&.any?
107
+ missing = feature_def.depends_on - features_enabled
108
+ if missing.any?
109
+ raise Familia::Problem,
110
+ "Feature #{feature_name} requires missing dependencies: #{missing.join(', ')}"
344
111
  end
345
112
  end
346
113
 
347
- Familia::Base.add_feature self, :configurable_feature
114
+ features_enabled << feature_name
115
+ include feature_module
348
116
  end
349
117
  ```
350
118
 
351
- ## Feature Development Best Practices
119
+ ## Basic Feature Development
352
120
 
353
121
  ### 1. Feature Structure Template
354
122
 
355
123
  ```ruby
356
- # lib/familia/features/my_feature.rb
357
124
  module Familia
358
125
  module Features
359
126
  module MyFeature
360
- # Feature metadata
361
- FEATURE_VERSION = '1.0.0'
362
- REQUIRED_FAMILIA_VERSION = '>= 2.0.0'
363
-
364
127
  def self.included(base)
365
- # Validation and setup
366
- validate_environment!(base)
367
-
368
- Familia.ld "[#{base}] Loading #{self} v#{FEATURE_VERSION}"
369
-
370
- # Module inclusion
371
128
  base.extend ClassMethods
372
- base.prepend InstanceMethods # Use prepend for method interception
373
- base.include HelperMethods # Use include for utility methods
374
-
375
- # Post-inclusion setup
376
- configure_feature(base)
377
- end
378
-
379
- def self.validate_environment!(base)
380
- # Check Familia version compatibility
381
- familia_version = Gem::Version.new(Familia::VERSION)
382
- required_version = Gem::Requirement.new(REQUIRED_FAMILIA_VERSION)
383
-
384
- unless required_version.satisfied_by?(familia_version)
385
- raise Familia::Problem,
386
- "#{self} requires Familia #{REQUIRED_FAMILIA_VERSION}, " \
387
- "got #{familia_version}"
388
- end
389
-
390
- # Check for required methods/capabilities on the base class
391
- required_methods = [:identifier_field, :field]
392
- missing_methods = required_methods.reject { |m| base.respond_to?(m) }
393
-
394
- if missing_methods.any?
395
- raise Familia::Problem,
396
- "#{base} missing required methods: #{missing_methods.join(', ')}"
397
- end
398
- end
399
-
400
- def self.configure_feature(base)
401
- # Feature-specific initialization
402
- base.instance_variable_set(:@my_feature_config, {
403
- enabled: true,
404
- options: {}
405
- })
406
-
407
- # Set up feature-specific data structures
408
- base.class_eval do
409
- @my_feature_data ||= {}
410
- end
129
+ base.include InstanceMethods
411
130
  end
412
131
 
413
- # Class-level methods added to including class
414
132
  module ClassMethods
415
133
  def my_feature_config
416
- @my_feature_config ||= { enabled: true, options: {} }
134
+ @my_feature_config ||= {}
417
135
  end
418
136
 
419
137
  def configure_my_feature(**options)
420
- my_feature_config[:options].merge!(options)
138
+ my_feature_config.merge!(options)
421
139
  end
422
140
 
423
141
  def my_feature_enabled?
424
- my_feature_config[:enabled]
142
+ features_enabled.include?(:my_feature)
425
143
  end
426
144
  end
427
145
 
428
- # Instance methods that intercept/override existing methods
429
146
  module InstanceMethods
430
147
  def save
431
- # Pre-processing
432
148
  before_my_feature_save if respond_to?(:before_my_feature_save, true)
433
-
434
- # Call original save
435
149
  result = super
436
-
437
- # Post-processing
438
150
  after_my_feature_save if respond_to?(:after_my_feature_save, true)
439
-
440
151
  result
441
152
  end
442
153
 
443
154
  private
444
155
 
445
156
  def before_my_feature_save
446
- # Feature-specific pre-save logic
157
+ # Pre-save logic
447
158
  end
448
159
 
449
160
  def after_my_feature_save
450
- # Feature-specific post-save logic
451
- end
452
- end
453
-
454
- # Utility methods that don't override existing functionality
455
- module HelperMethods
456
- def my_feature_helper
457
- return unless self.class.my_feature_enabled?
458
- # Helper implementation
161
+ # Post-save logic
459
162
  end
460
163
  end
461
164
 
462
165
  # Register the feature
463
- Familia::Base.add_feature self, :my_feature, depends_on: [:required_feature]
166
+ Familia::Base.add_feature self, :my_feature
464
167
  end
465
168
  end
466
169
  end
467
170
  ```
468
171
 
469
- ### 2. Robust Error Handling
172
+ ### 2. Feature with Dependencies
470
173
 
471
174
  ```ruby
472
- module Familia::Features::RobustFeature
473
- class FeatureError < Familia::Problem; end
474
- class ConfigurationError < FeatureError; end
475
- class DependencyError < FeatureError; end
476
-
175
+ module Familia::Features::AdvancedFeature
477
176
  def self.included(base)
478
- begin
479
- validate_dependencies!(base)
480
- configure_feature_safely(base)
481
- rescue => e
482
- handle_inclusion_error(base, e)
483
- end
484
- end
485
-
486
- def self.validate_dependencies!(base)
487
- # Check external dependencies
488
- unless defined?(SomeGem)
489
- raise DependencyError, "#{self} requires 'some_gem' gem"
490
- end
491
-
492
- # Check feature dependencies
493
- required_features = [:base_feature]
494
- missing_features = required_features - base.features_enabled.to_a
495
-
496
- if missing_features.any?
497
- raise DependencyError,
498
- "#{self} requires features: #{missing_features.join(', ')}"
499
- end
500
- end
501
-
502
- def self.configure_feature_safely(base)
503
- # Safely configure with fallbacks
504
- config = load_configuration
505
- apply_configuration(base, config)
506
- rescue => e
507
- Familia.logger.warn "Feature configuration failed: #{e.message}"
508
- apply_default_configuration(base)
509
- end
510
-
511
- def self.handle_inclusion_error(base, error)
512
- case error
513
- when DependencyError
514
- # Log dependency issues and disable feature
515
- Familia.logger.error "Feature #{self} disabled: #{error.message}"
516
- base.instance_variable_set(:@robust_feature_disabled, true)
517
- when ConfigurationError
518
- # Try default configuration
519
- Familia.logger.warn "Using default configuration: #{error.message}"
520
- apply_default_configuration(base)
521
- else
522
- # Re-raise unexpected errors
523
- raise
524
- end
177
+ base.extend ClassMethods
525
178
  end
526
179
 
527
180
  module ClassMethods
528
- def robust_feature_enabled?
529
- !@robust_feature_disabled
530
- end
531
-
532
- def with_robust_feature(&block)
533
- return unless robust_feature_enabled?
534
- block.call
535
- rescue => e
536
- Familia.logger.error "Robust feature operation failed: #{e.message}"
537
- nil
181
+ def advanced_method
182
+ # This feature requires :basic_feature to be enabled
183
+ raise "Basic feature required" unless features_enabled.include?(:basic_feature)
184
+ # Advanced functionality here
538
185
  end
539
186
  end
540
187
 
541
- Familia::Base.add_feature self, :robust_feature
188
+ # Register with dependency
189
+ Familia::Base.add_feature self, :advanced_feature, depends_on: [:basic_feature]
542
190
  end
543
191
  ```
544
192
 
545
- ### 3. Feature Testing Infrastructure
193
+ ### 3. Feature with Field Groups
546
194
 
547
195
  ```ruby
548
- # Test helpers for feature development
549
- module FeatureTestHelpers
550
- def with_feature(feature_name, config = {})
551
- # Create temporary test class with feature
552
- test_class = Class.new(Familia::Horreum) do
553
- def self.name
554
- 'FeatureTestClass'
555
- end
556
-
557
- identifier_field :test_id
558
- field :test_id
559
- end
560
-
561
- # Configure feature if needed
562
- if config.any?
563
- test_class.define_singleton_method(:feature_config) do |name|
564
- config
565
- end
566
- end
567
-
568
- # Enable the feature
569
- test_class.feature feature_name
570
-
571
- yield test_class
572
- end
573
-
574
- def feature_enabled?(klass, feature_name)
575
- klass.features_enabled.include?(feature_name)
196
+ module Familia::Features::FieldGroupFeature
197
+ def self.included(base)
198
+ base.extend ClassMethods
576
199
  end
577
200
 
578
- def assert_feature_methods(klass, expected_methods)
579
- expected_methods.each do |method|
580
- assert klass.method_defined?(method),
581
- "Expected #{klass} to have method #{method}"
201
+ module ClassMethods
202
+ def special_field(name, **options)
203
+ # Define fields that belong to this feature
204
+ field(name, **options)
582
205
  end
583
206
  end
584
- end
585
207
 
586
- # RSpec helper
587
- RSpec.configure do |config|
588
- config.include FeatureTestHelpers
208
+ # Register with field group
209
+ Familia::Base.add_feature self, :field_group_feature, field_group: :special_fields
589
210
  end
211
+ ```
590
212
 
591
- # Feature test example
592
- RSpec.describe Familia::Features::MyFeature do
593
- it "adds expected methods to class" do
594
- with_feature(:my_feature) do |test_class|
595
- expect(test_class).to respond_to(:my_feature_config)
596
- expect(test_class.new).to respond_to(:my_feature_helper)
597
- end
598
- end
599
-
600
- it "respects feature configuration" do
601
- config = { enabled: false }
602
-
603
- with_feature(:my_feature, config) do |test_class|
604
- expect(test_class.my_feature_enabled?).to be false
605
- end
606
- end
213
+ ## Feature Development Best Practices
607
214
 
608
- it "validates dependencies" do
609
- expect {
610
- Class.new(Familia::Horreum) do
611
- feature :my_feature # Missing :required_feature dependency
612
- end
613
- }.to raise_error(Familia::Problem, /requires.*required_feature/)
614
- end
615
- end
616
- ```
215
+ ### 1. Naming Conventions
617
216
 
618
- ## Performance Optimization
217
+ - Feature names should be symbols (`:my_feature`)
218
+ - Module names should match: `Familia::Features::MyFeature`
219
+ - Method names should be prefixed with feature name to avoid conflicts
619
220
 
620
- ### 1. Lazy Feature Loading
221
+ ### 2. Error Handling
621
222
 
622
223
  ```ruby
623
- module Familia::Features::LazyFeature
224
+ module Familia::Features::RobustFeature
225
+ class FeatureError < StandardError; end
226
+
624
227
  def self.included(base)
625
- # Minimal setup at include time
228
+ validate_environment!
626
229
  base.extend ClassMethods
230
+ end
627
231
 
628
- # Defer expensive setup until first use
629
- @setup_complete = false
232
+ def self.validate_environment!
233
+ raise FeatureError, "Ruby 3.0+ required" if RUBY_VERSION < "3.0"
630
234
  end
631
235
 
632
236
  module ClassMethods
633
- def ensure_lazy_feature_setup!
634
- return if @setup_complete
635
-
636
- # Expensive setup operations
637
- perform_expensive_setup
638
- @setup_complete = true
639
- end
640
-
641
- def lazy_feature_method
642
- ensure_lazy_feature_setup!
643
- # Method implementation
237
+ def robust_feature_method
238
+ raise FeatureError, "Feature not properly configured" unless configured?
239
+ # Feature logic here
644
240
  end
645
241
 
646
242
  private
647
243
 
648
- def perform_expensive_setup
649
- # Heavy initialization work
650
- @expensive_data = load_expensive_data
651
- @compiled_templates = compile_templates
244
+ def configured?
245
+ # Check if feature is properly set up
246
+ true
652
247
  end
653
248
  end
654
249
 
655
- Familia::Base.add_feature self, :lazy_feature
250
+ Familia::Base.add_feature self, :robust_feature
656
251
  end
657
252
  ```
658
253
 
659
- ### 2. Feature Method Caching
254
+ ### 3. Feature Options
255
+
256
+ Features can accept configuration options:
660
257
 
661
258
  ```ruby
662
- module Familia::Features::CachedFeature
663
- def self.included(base)
664
- base.extend ClassMethods
665
- end
259
+ class MyModel < Familia::Horreum
260
+ feature :my_feature, timeout: 30, retries: 3
261
+ end
666
262
 
263
+ # Access options in the feature
264
+ module Familia::Features::MyFeature
667
265
  module ClassMethods
668
- def cached_feature_method(key)
669
- @method_cache ||= {}
670
- @method_cache[key] ||= expensive_computation(key)
671
- end
672
-
673
- def clear_feature_cache!
674
- @method_cache = {}
675
- end
676
-
677
- private
678
-
679
- def expensive_computation(key)
680
- # Expensive operation
681
- sleep 0.1 # Simulate work
682
- "computed_#{key}"
266
+ def my_feature_timeout
267
+ feature_options(:my_feature)[:timeout] || 60
683
268
  end
684
269
  end
685
-
686
- Familia::Base.add_feature self, :cached_feature
687
270
  end
688
271
  ```
689
272
 
690
- ## Debugging Features
273
+ ## Testing Features
691
274
 
692
- ### 1. Feature Introspection
275
+ ### Feature Testing Helpers
693
276
 
694
277
  ```ruby
695
- module Familia::Features::Introspection
696
- def self.included(base)
697
- base.extend ClassMethods
698
- end
699
-
700
- module ClassMethods
701
- def feature_info
702
- {
703
- enabled_features: features_enabled.to_a,
704
- feature_dependencies: feature_dependency_graph,
705
- feature_conflicts: feature_conflict_map,
706
- feature_load_order: feature_load_order
707
- }
708
- end
709
-
710
- def feature_dependency_graph
711
- graph = {}
712
- features_enabled.each do |feature|
713
- definition = Familia::Base.feature_definitions[feature]
714
- graph[feature] = definition&.depends_on || []
715
- end
716
- graph
717
- end
718
-
719
- def feature_conflict_map
720
- conflicts = {}
721
- features_enabled.each do |feature|
722
- definition = Familia::Base.feature_definitions[feature]
723
- conflicts[feature] = definition&.conflicts_with || []
724
- end
725
- conflicts
726
- end
727
-
728
- def feature_load_order
729
- # Return the order features were loaded
730
- @feature_load_order ||= []
731
- end
732
-
733
- def debug_feature_issues
734
- issues = []
735
-
736
- # Check for circular dependencies
737
- issues.concat(detect_circular_dependencies)
738
-
739
- # Check for method conflicts
740
- issues.concat(detect_method_conflicts)
741
-
742
- # Check for missing dependencies
743
- issues.concat(detect_missing_dependencies)
744
-
745
- issues
746
- end
747
-
748
- private
749
-
750
- def detect_circular_dependencies
751
- # Implementation for circular dependency detection
752
- end
753
-
754
- def detect_method_conflicts
755
- # Implementation for method conflict detection
756
- end
278
+ module FeatureTestHelpers
279
+ def with_feature(klass, feature_name, **options)
280
+ original_features = klass.features_enabled.dup
757
281
 
758
- def detect_missing_dependencies
759
- # Implementation for missing dependency detection
282
+ begin
283
+ klass.feature(feature_name, **options)
284
+ yield
285
+ ensure
286
+ # Reset features (note: this is simplified - actual reset is more complex)
287
+ klass.instance_variable_set(:@features_enabled, original_features)
760
288
  end
761
289
  end
762
290
 
763
- Familia::Base.add_feature self, :introspection
291
+ def feature_enabled?(klass, feature_name)
292
+ klass.features_enabled.include?(feature_name)
293
+ end
764
294
  end
765
- ```
766
-
767
- ### 2. Feature Debug Logging
768
-
769
- ```ruby
770
- module Familia::Features::DebugLogging
771
- def self.included(base)
772
- return unless Familia.debug?
773
-
774
- base.extend ClassMethods
775
- original_feature_method = base.method(:feature)
776
-
777
- base.define_singleton_method(:feature) do |name|
778
- Familia.ld "[DEBUG] Loading feature #{name} on #{self}"
779
- start_time = Familia.now
780
295
 
781
- result = original_feature_method.call(name)
296
+ # Test example
297
+ describe Familia::Features::MyFeature do
298
+ include FeatureTestHelpers
782
299
 
783
- load_time = (Familia.now - start_time) * 1000
784
- Familia.ld "[DEBUG] Feature #{name} loaded in #{load_time.round(2)}ms"
785
-
786
- result
300
+ it "adds expected methods to class" do
301
+ with_feature(MyModel, :my_feature) do
302
+ expect(MyModel).to respond_to(:my_feature_config)
303
+ expect(MyModel.new).to respond_to(:my_feature_method)
787
304
  end
788
305
  end
789
306
 
790
- module ClassMethods
791
- def log_feature_method_call(method_name, &block)
792
- return block.call unless Familia.debug?
793
-
794
- Familia.ld "[DEBUG] Calling #{method_name} on #{self}"
795
- start_time = Familia.now
796
-
797
- result = block.call
798
-
799
- duration = (Familia.now - start_time) * 1000
800
- Familia.ld "[DEBUG] #{method_name} completed in #{duration.round(2)}ms"
801
-
802
- result
803
- end
307
+ it "validates dependencies" do
308
+ expect {
309
+ MyModel.feature(:advanced_feature) # requires :basic_feature
310
+ }.to raise_error(Familia::Problem, /requires missing dependencies/)
804
311
  end
805
-
806
- Familia::Base.add_feature self, :debug_logging
807
312
  end
808
313
  ```
809
314
 
810
- ## Migration and Versioning
315
+ ## Existing Features Overview
811
316
 
812
- ### Feature Versioning
317
+ ### Core Features
813
318
 
814
- ```ruby
815
- module Familia::Features::VersionedFeature
816
- VERSION = '2.1.0'
817
- MIGRATION_PATH = [
818
- { from: '1.0.0', to: '1.1.0', migration: :migrate_1_0_to_1_1 },
819
- { from: '1.1.0', to: '2.0.0', migration: :migrate_1_1_to_2_0 },
820
- { from: '2.0.0', to: '2.1.0', migration: :migrate_2_0_to_2_1 }
821
- ].freeze
319
+ - **`:expiration`** - TTL management for objects and fields
320
+ - **`:encrypted_fields`** - Encrypt sensitive fields before storage
321
+ - **`:safe_dump`** - API-safe serialization excluding sensitive fields
322
+ - **`:relationships`** - Object associations and indexing
323
+ - **`:transient_fields`** - Runtime-only fields that aren't persisted
324
+ - **`:quantization`** - Score quantization for sorted sets
325
+ - **`:object_identifier`** - Flexible object identification strategies
326
+ - **`:external_identifier`** - External system ID management
822
327
 
823
- def self.included(base)
824
- check_and_migrate_version(base)
825
- base.extend ClassMethods
826
- end
827
-
828
- def self.check_and_migrate_version(base)
829
- current_version = get_current_version(base)
830
- return if current_version == VERSION
831
-
832
- if current_version.nil?
833
- # First installation
834
- set_version(base, VERSION)
835
- return
836
- end
328
+ ### Feature Dependencies
837
329
 
838
- # Perform migration
839
- migrate_from_version(base, current_version, VERSION)
840
- end
330
+ Most features are independent, but some have dependencies:
841
331
 
842
- def self.migrate_from_version(base, from_version, to_version)
843
- migration_steps = find_migration_path(from_version, to_version)
332
+ ```ruby
333
+ # relationships feature has no dependencies
334
+ Familia::Base.add_feature Relationships, :relationships
844
335
 
845
- migration_steps.each do |step|
846
- Familia.logger.info "Migrating #{base} from #{step[:from]} to #{step[:to]}"
847
- send(step[:migration], base)
848
- end
336
+ # No complex dependency chains in current implementation
337
+ ```
849
338
 
850
- set_version(base, to_version)
851
- end
339
+ ## Debugging Features
852
340
 
853
- def self.find_migration_path(from, to)
854
- # Find path through migration steps
855
- current = from
856
- path = []
341
+ ### Feature Introspection
857
342
 
858
- while current != to
859
- step = MIGRATION_PATH.find { |s| s[:from] == current }
860
- break unless step
343
+ ```ruby
344
+ # Check what features are available
345
+ Familia::Base.features_available.keys
346
+ # => [:expiration, :encrypted_fields, :safe_dump, :relationships, ...]
861
347
 
862
- path << step
863
- current = step[:to]
864
- end
348
+ # Check what features are enabled on a class
349
+ MyModel.features_enabled
350
+ # => [:expiration, :safe_dump]
865
351
 
866
- path
867
- end
352
+ # Check feature definitions
353
+ Familia::Base.feature_definitions[:expiration]
354
+ # => #<data FeatureDefinition name=:expiration, depends_on=[], field_group=nil>
868
355
 
869
- # Migration methods
870
- def self.migrate_1_0_to_1_1(base)
871
- # Migration logic for 1.0 -> 1.1
872
- end
356
+ # Check if a specific feature is enabled
357
+ MyModel.features_enabled.include?(:expiration)
358
+ # => true
359
+ ```
873
360
 
874
- def self.migrate_1_1_to_2_0(base)
875
- # Migration logic for 1.1 -> 2.0
876
- end
361
+ ### Common Issues
877
362
 
878
- def self.migrate_2_0_to_2_1(base)
879
- # Migration logic for 2.0 -> 2.1
880
- end
363
+ 1. **Feature not found**: Ensure the feature module is loaded and registered
364
+ 2. **Dependency errors**: Check that required features are enabled first
365
+ 3. **Method conflicts**: Features that define the same methods will override each other
881
366
 
882
- module ClassMethods
883
- def feature_version
884
- self.class.instance_variable_get(:@versioned_feature_version) || VERSION
885
- end
886
- end
367
+ ## Migration Notes
887
368
 
888
- Familia::Base.add_feature self, :versioned_feature
889
- end
890
- ```
369
+ The feature system is intentionally simple in the current implementation. More complex features like conflict resolution, versioning, and capability flags are not currently implemented but could be added in future versions if needed.
891
370
 
892
- This developer guide provides the foundation for creating robust, maintainable features that integrate seamlessly with Familia's architecture while following best practices for error handling, performance, and maintainability.
371
+ For now, feature developers should:
372
+ - Keep features focused and independent
373
+ - Use clear naming to avoid method conflicts
374
+ - Test features thoroughly in isolation
375
+ - Document any dependencies clearly