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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: familia
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre18
4
+ version: 2.0.0.pre21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0.4'
26
+ - !ruby/object:Gem::Dependency
27
+ name: concurrent-ruby
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.3'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.3'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: connection_pool
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -137,7 +151,6 @@ files:
137
151
  - ".github/workflows/ci.yml"
138
152
  - ".github/workflows/claude-code-review.yml"
139
153
  - ".github/workflows/claude.yml"
140
- - ".github/workflows/code-quality.yml"
141
154
  - ".github/workflows/code-smells.yml"
142
155
  - ".github/workflows/docs.yml"
143
156
  - ".gitignore"
@@ -153,31 +166,39 @@ files:
153
166
  - LICENSE.txt
154
167
  - README.md
155
168
  - bin/irb
169
+ - bin/try
170
+ - bin/tryouts
171
+ - changelog.d/20251105_flexible_external_identifier_format.rst
172
+ - changelog.d/20251107_112554_delano_179_participation_asymmetry.rst
173
+ - changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst
174
+ - changelog.d/20251107_fix_participates_in_symbol_resolution.rst
175
+ - changelog.d/20251107_optimized_redis_exists_checks.rst
176
+ - changelog.d/20251108_frozen_string_literal_pragma.rst
156
177
  - changelog.d/README.md
157
178
  - changelog.d/scriv.ini
179
+ - docs/1106-participates_in-bidirectional-solution.md
158
180
  - docs/archive/.gitignore
159
- - docs/archive/FAMILIA_RELATIONSHIPS.md
160
- - docs/archive/FAMILIA_TECHNICAL.md
161
- - docs/archive/FAMILIA_UPDATE.md
162
- - docs/archive/README.md
163
- - docs/archive/api-reference.md
164
181
  - docs/conf.py
165
182
  - docs/guides/.gitignore
166
- - docs/guides/core-field-system.md
183
+ - docs/guides/encryption.md
167
184
  - docs/guides/feature-encrypted-fields.md
168
185
  - docs/guides/feature-expiration.md
169
186
  - docs/guides/feature-external-identifiers.md
170
187
  - docs/guides/feature-object-identifiers.md
171
188
  - docs/guides/feature-quantization.md
189
+ - docs/guides/feature-relationships-indexing.md
172
190
  - docs/guides/feature-relationships-methods.md
191
+ - docs/guides/feature-relationships-participation.md
173
192
  - docs/guides/feature-relationships.md
174
193
  - docs/guides/feature-system-devs.md
175
194
  - docs/guides/feature-system.md
176
195
  - docs/guides/feature-transient-fields.md
177
- - docs/guides/implementation.md
196
+ - docs/guides/field-system.md
178
197
  - docs/guides/index.md
179
- - docs/guides/security-model.md
180
- - docs/guides/time-utilities.md
198
+ - docs/guides/logging.md
199
+ - docs/guides/optimized-loading.md
200
+ - docs/guides/thread-safety-monitoring.md
201
+ - docs/guides/time-literals.md
181
202
  - docs/migrating/.gitignore
182
203
  - docs/migrating/v2.0.0-pre.md
183
204
  - docs/migrating/v2.0.0-pre11.md
@@ -185,6 +206,8 @@ files:
185
206
  - docs/migrating/v2.0.0-pre13.md
186
207
  - docs/migrating/v2.0.0-pre14.md
187
208
  - docs/migrating/v2.0.0-pre18.md
209
+ - docs/migrating/v2.0.0-pre19.md
210
+ - docs/migrating/v2.0.0-pre22.md
188
211
  - docs/migrating/v2.0.0-pre5.md
189
212
  - docs/migrating/v2.0.0-pre6.md
190
213
  - docs/migrating/v2.0.0-pre7.md
@@ -194,21 +217,24 @@ files:
194
217
  - examples/autoloader/mega_customer.rb
195
218
  - examples/autoloader/mega_customer/features/deprecated_fields.rb
196
219
  - examples/autoloader/mega_customer/safe_dump_fields.rb
220
+ - examples/datatype_standalone.rb
197
221
  - examples/encrypted_fields.rb
198
222
  - examples/json_usage_patterns.rb
199
223
  - examples/relationships.rb
200
224
  - examples/safe_dump.rb
225
+ - examples/sampling_demo.rb
201
226
  - examples/single_connection_transaction_confusions.rb
202
227
  - familia.gemspec
203
228
  - lib/familia.rb
204
229
  - lib/familia/base.rb
205
230
  - lib/familia/connection.rb
231
+ - lib/familia/connection/behavior.rb
206
232
  - lib/familia/connection/handlers.rb
207
233
  - lib/familia/connection/individual_command_proxy.rb
208
234
  - lib/familia/connection/middleware.rb
209
235
  - lib/familia/connection/operation_core.rb
210
236
  - lib/familia/connection/operations.rb
211
- - lib/familia/connection/pipeline_core.rb
237
+ - lib/familia/connection/pipelined_core.rb
212
238
  - lib/familia/connection/transaction_core.rb
213
239
  - lib/familia/data_type.rb
214
240
  - lib/familia/data_type/class_methods.rb
@@ -248,11 +274,14 @@ files:
248
274
  - lib/familia/features/relationships/collection_operations.rb
249
275
  - lib/familia/features/relationships/indexing.rb
250
276
  - lib/familia/features/relationships/indexing/multi_index_generators.rb
277
+ - lib/familia/features/relationships/indexing/rebuild_strategies.rb
251
278
  - lib/familia/features/relationships/indexing/unique_index_generators.rb
252
279
  - lib/familia/features/relationships/indexing_relationship.rb
253
280
  - lib/familia/features/relationships/participation.rb
254
281
  - lib/familia/features/relationships/participation/participant_methods.rb
282
+ - lib/familia/features/relationships/participation/rebuild_strategies.md
255
283
  - lib/familia/features/relationships/participation/target_methods.rb
284
+ - lib/familia/features/relationships/participation_membership.rb
256
285
  - lib/familia/features/relationships/participation_relationship.rb
257
286
  - lib/familia/features/relationships/score_encoding.rb
258
287
  - lib/familia/features/safe_dump.rb
@@ -272,6 +301,7 @@ files:
272
301
  - lib/familia/horreum/settings.rb
273
302
  - lib/familia/horreum/utils.rb
274
303
  - lib/familia/identifier_extractor.rb
304
+ - lib/familia/instrumentation.rb
275
305
  - lib/familia/json_serializer.rb
276
306
  - lib/familia/logging.rb
277
307
  - lib/familia/refinements.rb
@@ -280,9 +310,12 @@ files:
280
310
  - lib/familia/refinements/time_literals.rb
281
311
  - lib/familia/secure_identifier.rb
282
312
  - lib/familia/settings.rb
313
+ - lib/familia/thread_safety/instrumented_mutex.rb
314
+ - lib/familia/thread_safety/monitor.rb
283
315
  - lib/familia/utils.rb
284
316
  - lib/familia/verifiable_identifier.rb
285
317
  - lib/familia/version.rb
318
+ - lib/middleware/database_command_counter.rb
286
319
  - lib/middleware/database_logger.rb
287
320
  - lib/multi_result.rb
288
321
  - pr_agent.toml
@@ -330,11 +363,15 @@ files:
330
363
  - try/features/quantization/quantization_try.rb
331
364
  - try/features/real_feature_integration_try.rb
332
365
  - try/features/relationships/indexing_commands_verification_try.rb
366
+ - try/features/relationships/indexing_rebuild_try.rb
333
367
  - try/features/relationships/indexing_try.rb
368
+ - try/features/relationships/participation_bidirectional_try.rb
334
369
  - try/features/relationships/participation_commands_verification_spec.rb
335
370
  - try/features/relationships/participation_commands_verification_try.rb
336
371
  - try/features/relationships/participation_performance_improvements_try.rb
337
372
  - try/features/relationships/participation_reverse_index_try.rb
373
+ - try/features/relationships/participation_target_class_resolution_try.rb
374
+ - try/features/relationships/participation_unresolved_target_try.rb
338
375
  - try/features/relationships/relationships_api_changes_try.rb
339
376
  - try/features/relationships/relationships_edge_cases_try.rb
340
377
  - try/features/relationships/relationships_performance_minimal_try.rb
@@ -366,6 +403,8 @@ files:
366
403
  - try/integration/conventional_inheritance_try.rb
367
404
  - try/integration/create_method_try.rb
368
405
  - try/integration/cross_component_try.rb
406
+ - try/integration/data_types/datatype_pipelines_try.rb
407
+ - try/integration/data_types/datatype_transactions_try.rb
369
408
  - try/integration/database_consistency_try.rb
370
409
  - try/integration/familia_extended_try.rb
371
410
  - try/integration/familia_members_methods_try.rb
@@ -375,10 +414,15 @@ files:
375
414
  - try/integration/models/familia_object_try.rb
376
415
  - try/integration/persistence_operations_try.rb
377
416
  - try/integration/relationships_persistence_round_trip_try.rb
417
+ - try/integration/save_methods_consistency_try.rb
378
418
  - try/integration/scenarios_try.rb
379
419
  - try/integration/secure_identifier_try.rb
420
+ - try/integration/transaction_safety_core_try.rb
421
+ - try/integration/transaction_safety_workflow_try.rb
380
422
  - try/integration/verifiable_identifier_try.rb
423
+ - try/investigation/pipeline_routing/README.md
381
424
  - try/performance/benchmarks_try.rb
425
+ - try/performance/transaction_safety_benchmark_try.rb
382
426
  - try/support/benchmarks/deserialization_benchmark.rb
383
427
  - try/support/benchmarks/deserialization_correctness_test.rb
384
428
  - try/support/debugging/README.md
@@ -428,6 +472,18 @@ files:
428
472
  - try/support/prototypes/pooling/lib/visualize_stress_results.rb
429
473
  - try/support/prototypes/pooling/pool_siege.rb
430
474
  - try/support/prototypes/pooling/run_stress_tests.rb
475
+ - try/thread_safety/README.md
476
+ - try/thread_safety/class_connection_chain_race_try.rb
477
+ - try/thread_safety/connection_chain_race_try.rb
478
+ - try/thread_safety/encryption_manager_cache_race_try.rb
479
+ - try/thread_safety/feature_registry_race_try.rb
480
+ - try/thread_safety/fiber_pipeline_isolation_try.rb
481
+ - try/thread_safety/fiber_transaction_isolation_try.rb
482
+ - try/thread_safety/field_registration_race_try.rb
483
+ - try/thread_safety/logger_initialization_race_try.rb
484
+ - try/thread_safety/middleware_registration_race_try.rb
485
+ - try/thread_safety/module_config_race_try.rb
486
+ - try/thread_safety/secure_identifier_cache_race_try.rb
431
487
  - try/unit/core/autoloader_try.rb
432
488
  - try/unit/core/base_enhancements_try.rb
433
489
  - try/unit/core/connection_try.rb
@@ -435,6 +491,9 @@ files:
435
491
  - try/unit/core/extensions_try.rb
436
492
  - try/unit/core/familia_logger_try.rb
437
493
  - try/unit/core/familia_try.rb
494
+ - try/unit/core/middleware_sampling_try.rb
495
+ - try/unit/core/middleware_test_helpers_bug_try.rb
496
+ - try/unit/core/middleware_thread_safety_try.rb
438
497
  - try/unit/core/middleware_try.rb
439
498
  - try/unit/core/settings_try.rb
440
499
  - try/unit/core/time_utils_try.rb
@@ -450,7 +509,9 @@ files:
450
509
  - try/unit/data_types/sorted_set_zadd_options_try.rb
451
510
  - try/unit/data_types/string_try.rb
452
511
  - try/unit/data_types/unsortedset_try.rb
512
+ - try/unit/familia_resolve_class_try.rb
453
513
  - try/unit/horreum/auto_indexing_on_save_try.rb
514
+ - try/unit/horreum/automatic_index_validation_try.rb
454
515
  - try/unit/horreum/base_try.rb
455
516
  - try/unit/horreum/class_methods_try.rb
456
517
  - try/unit/horreum/commands_try.rb
@@ -461,14 +522,20 @@ files:
461
522
  - try/unit/horreum/field_definition_try.rb
462
523
  - try/unit/horreum/initialization_try.rb
463
524
  - try/unit/horreum/json_type_preservation_try.rb
525
+ - try/unit/horreum/optimized_loading_try.rb
464
526
  - try/unit/horreum/relations_try.rb
465
527
  - try/unit/horreum/serialization_persistent_fields_try.rb
466
528
  - try/unit/horreum/serialization_try.rb
467
529
  - try/unit/horreum/settings_try.rb
530
+ - try/unit/horreum/unique_index_edge_cases_try.rb
531
+ - try/unit/horreum/unique_index_guard_validation_try.rb
532
+ - try/unit/middleware/database_command_counter_methods_try.rb
533
+ - try/unit/middleware/database_logger_methods_try.rb
468
534
  - try/unit/refinements/dear_json_array_methods_try.rb
469
535
  - try/unit/refinements/dear_json_hash_methods_try.rb
470
536
  - try/unit/refinements/time_literals_numeric_methods_try.rb
471
537
  - try/unit/refinements/time_literals_string_methods_try.rb
538
+ - try/unit/thread_safety_monitor_try.rb
472
539
  - try/valkey.conf
473
540
  homepage: https://github.com/delano/familia
474
541
  licenses:
@@ -482,14 +549,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
482
549
  requirements:
483
550
  - - ">="
484
551
  - !ruby/object:Gem::Version
485
- version: '3.4'
552
+ version: 3.3.6
486
553
  required_rubygems_version: !ruby/object:Gem::Requirement
487
554
  requirements:
488
555
  - - ">="
489
556
  - !ruby/object:Gem::Version
490
557
  version: '0'
491
558
  requirements: []
492
- rubygems_version: 3.6.9
559
+ rubygems_version: 3.7.2
493
560
  specification_version: 4
494
561
  summary: An ORM for Valkey-compatible databases in Ruby.
495
562
  test_files: []
@@ -1,138 +0,0 @@
1
- name: Code Quality
2
-
3
- on:
4
- pull_request:
5
- branches: [ main ]
6
- push:
7
- branches: [ main ]
8
- workflow_dispatch:
9
-
10
- permissions:
11
- contents: read
12
- pull-requests: write # Needed to post comments on PRs
13
-
14
- jobs:
15
- reek:
16
- name: Reek Code Analysis
17
- runs-on: ubuntu-24.04
18
- timeout-minutes: 5
19
-
20
- steps:
21
- - name: Checkout code
22
- uses: actions/checkout@v4
23
-
24
- - name: Set up Ruby
25
- uses: ruby/setup-ruby@v1
26
- with:
27
- ruby-version: 3.4
28
- bundler-cache: true
29
-
30
- - name: Configure Bundler for secure gem installation
31
- run: |
32
- bundle config set --local path 'vendor/bundle'
33
- bundle config set --local deployment 'false'
34
-
35
- - name: Install dependencies
36
- run: bundle install
37
-
38
- - name: Run Reek analysis
39
- run: |
40
- echo "=== Running Reek code analysis ==="
41
- echo "This analysis identifies code smells and potential improvements."
42
- echo "Results are informational and won't fail the build."
43
- echo ""
44
-
45
- # Run reek and capture output (don't fail on warnings)
46
- # Use success-exit-code to prevent failures from stopping the analysis
47
- bundle exec reek --format=text --success-exit-code 0 --failure-exit-code 0 || true
48
-
49
- echo ""
50
- echo "=== Reek analysis complete ==="
51
- continue-on-error: true # Don't fail the build on code smells
52
-
53
- - name: Generate Reek report (if analysis available)
54
- run: |
55
- echo "=== Generating detailed Reek report ==="
56
-
57
- # Generate JSON report for potential future processing
58
- bundle exec reek --format=json --success-exit-code 0 --failure-exit-code 0 > reek-report.json || true
59
-
60
- # Display summary
61
- if [ -s reek-report.json ]; then
62
- echo "Reek JSON report generated: $(wc -l < reek-report.json) lines"
63
- echo "Top code smell types found:"
64
- jq -r '.[].smells[].smell_type' reek-report.json 2>/dev/null | sort | uniq -c | sort -rn | head -10 || echo "Unable to parse JSON report"
65
- else
66
- echo "No code smells detected or analysis failed"
67
- fi
68
- continue-on-error: true
69
-
70
- - name: Upload Reek report as artifact
71
- uses: actions/upload-artifact@v4
72
- if: always()
73
- with:
74
- name: reek-report
75
- path: reek-report.json
76
- retention-days: 30
77
-
78
- # Add other code quality checks here
79
- additional-quality-checks:
80
- name: Additional Quality Checks
81
- runs-on: ubuntu-24.04
82
- timeout-minutes: 5
83
-
84
- steps:
85
- - name: Checkout code
86
- uses: actions/checkout@v4
87
-
88
- - name: Set up Ruby
89
- uses: ruby/setup-ruby@v1
90
- with:
91
- ruby-version: 3.4
92
- bundler-cache: true
93
-
94
- - name: Configure Bundler for secure gem installation
95
- run: |
96
- bundle config set --local path 'vendor/bundle'
97
- bundle config set --local deployment 'false'
98
-
99
- - name: Install dependencies
100
- run: bundle install
101
-
102
- - name: Check for TODO/FIXME comments
103
- run: |
104
- echo "=== Scanning for TODO/FIXME comments ==="
105
- echo "This helps track technical debt and action items."
106
- echo ""
107
-
108
- # Find TODO/FIXME comments (excluding this workflow file)
109
- find . -name "*.rb" -not -path "./vendor/*" -not -path "./tmp/*" | \
110
- xargs grep -Hn -i -E "(TODO|FIXME|HACK|XXX|NOTE):" 2>/dev/null | \
111
- head -20 || echo "No TODO/FIXME comments found"
112
- continue-on-error: true
113
-
114
- - name: Check Ruby file syntax
115
- run: |
116
- echo "=== Checking Ruby syntax ==="
117
- echo "Validates that all Ruby files have correct syntax."
118
- echo ""
119
-
120
- find . -name "*.rb" -not -path "./vendor/*" -not -path "./tmp/*" | \
121
- while read -r file; do
122
- if ! ruby -c "$file" > /dev/null 2>&1; then
123
- echo "Syntax error in: $file"
124
- ruby -c "$file"
125
- fi
126
- done
127
- continue-on-error: true
128
-
129
- - name: Check for long lines
130
- run: |
131
- echo "=== Checking for long lines (>120 characters) ==="
132
- echo "Identifies potentially hard-to-read code lines."
133
- echo ""
134
-
135
- find . -name "*.rb" -not -path "./vendor/*" -not -path "./tmp/*" | \
136
- xargs grep -Hn "^.\{121,\}$" | \
137
- head -10 || echo "No overly long lines found"
138
- continue-on-error: true
@@ -1,210 +0,0 @@
1
- # Relationship methods
2
-
3
- here are the methods automatically generated for each relationship type:
4
-
5
- member_of Relationships
6
-
7
- When you declare:
8
- class Domain < Familia::Horreum
9
- member_of Customer, :domains, type: :set
10
- end
11
-
12
- Generated methods on Domain instances:
13
- - add_to_customer_domains(customer_id) - Add this domain to customer's domains collection
14
- - remove_from_customer_domains(customer_id) - Remove this domain from customer's domains collection
15
- - in_customer_domains?(customer_id) - Check if this domain is in customer's domains collection
16
-
17
- The method names follow the pattern: {action}_to_{lowercase_class_name}_{collection_name}
18
-
19
- participates_in Relationships
20
-
21
- When you declare:
22
- class Customer < Familia::Horreum
23
- participates_in :all_customers, type: :sorted_set, score: :created_at
24
- end
25
-
26
- Generated class methods:
27
- - Customer.add_to_all_customers(customer) - Add customer to global tracking
28
- - Customer.remove_from_all_customers(customer) - Remove customer from global tracking
29
- - Customer.all_customers - Access the sorted set collection directly
30
-
31
- For scoped tracking (with class prefix):
32
- participates_in :global, :active_users, score: :last_seen
33
- Generates: Customer.add_to_active_users(customer) and Customer.active_users
34
-
35
- indexed_by Relationships
36
-
37
- The `indexed_by` method creates Valkey/Redis hash-based indexes for O(1) field lookups. The `context` parameter determines index ownership and scope.
38
-
39
- **Global Context (Shared Index)**
40
- When you declare:
41
- ```ruby
42
- class Customer < Familia::Horreum
43
- indexed_by :email, :email_lookup, target: :global
44
- end
45
- ```
46
-
47
- Generated class methods:
48
- - Customer.add_to_email_lookup(customer) - Add customer to global email index
49
- - Customer.remove_from_email_lookup(customer) - Remove customer from global email index
50
- - Customer.email_lookup - Access the global hash index directly (supports .get(email))
51
-
52
- Valkey/Redis key pattern: `global:email_lookup`
53
-
54
- **Class Context (Per-Instance Index)**
55
- When you declare:
56
- ```ruby
57
- class Domain < Familia::Horreum
58
- indexed_by :name, :domain_index, target: Customer
59
- end
60
- ```
61
-
62
- Generated class methods on Customer:
63
- - Customer.find_by_name(domain_name) - Find domain by name within this customer
64
- - Customer.find_all_by_name(domain_names) - Find multiple domains by names
65
-
66
- Valkey/Redis key pattern: `customer:123:domain_index` (per customer instance)
67
-
68
- **When to Use Each Context**
69
- - **Global context (`:global`)**: Use for system-wide lookups where the field value should be unique across all instances
70
- - Examples: email addresses, usernames, API keys
71
- - **Class context (e.g., `Customer`)**: Use for scoped lookups where the field value is unique within a specific parent object
72
- - Examples: domain names per customer, project names per team
73
-
74
- Example from the Codebase
75
-
76
- From the relationships example file, you can see this in action:
77
-
78
- # Domain declares membership in Customer collections
79
- class Domain < Familia::Horreum
80
- member_of Customer, :domains, type: :set
81
- end
82
-
83
- # This generates these methods on Domain instances:
84
- domain.add_to_customer_domains(customer.custid) # Add to relationship
85
- domain.remove_from_customer_domains(customer.custid) # Remove from relationship
86
- domain.in_customer_domains?(customer.custid) # Query membership
87
-
88
- # For participates_in relationships:
89
- Customer.add_to_all_customers(customer) # Class method
90
- Customer.all_customers.range(0, -1) # Direct collection access
91
-
92
- # For indexed_by relationships:
93
- Customer.add_to_email_lookup(customer) # Class method
94
- Customer.email_lookup.get("user@example.com") # O(1) lookup
95
-
96
- Method Naming Conventions
97
-
98
- The relationship system uses consistent naming patterns:
99
- - member_of: {add_to|remove_from|in}_#{parent_class.downcase}_#{collection_name}
100
- - participates_in: {add_to|remove_from}_#{collection_name} (class methods)
101
- - indexed_by: {add_to|remove_from}_#{index_name} (class methods)
102
-
103
- This automatic method generation creates a clean, predictable API that handles both the db operations and maintains referential consistency
104
- across related objects.
105
-
106
-
107
- ## Context Parameter Usage Patterns
108
-
109
- The `context` parameter in `indexed_by` is a fundamental architectural decision that determines index scope and ownership. Here are practical patterns for when to use each approach:
110
-
111
- ### Global Context Pattern
112
- Use `context: :global` when field values should be unique system-wide:
113
-
114
- ```ruby
115
- class User < Familia::Horreum
116
- feature :relationships
117
-
118
- identifier_field :user_id
119
- field :user_id, :email, :username
120
-
121
- # System-wide unique email lookup
122
- indexed_by :email, :email_lookup, target: :global
123
- indexed_by :username, :username_lookup, target: :global
124
- end
125
-
126
- # Usage:
127
- User.add_to_email_lookup(user)
128
- found_user_id = User.email_lookup.get("john@example.com")
129
- ```
130
-
131
- **Valkey/Redis keys generated**: `global:email_lookup`, `global:username_lookup`
132
-
133
- ### Class Context Pattern
134
- Use `context: SomeClass` when field values are unique within a specific parent context:
135
-
136
- ```ruby
137
- class Customer < Familia::Horreum
138
- feature :relationships
139
-
140
- identifier_field :custid
141
- field :custid, :name
142
- sorted_set :domains
143
- end
144
-
145
- class Domain < Familia::Horreum
146
- feature :relationships
147
-
148
- identifier_field :domain_id
149
- field :domain_id, :name, :subdomain
150
-
151
- # Domains are unique per customer (customer can't have duplicate domain names)
152
- indexed_by :name, :domain_index, target: Customer
153
- indexed_by :subdomain, :subdomain_index, target: Customer
154
- end
155
-
156
- # Usage:
157
- customer = Customer.new(custid: "cust_123")
158
- customer.find_by_name("example.com") # Find domain within this customer
159
- customer.find_all_by_subdomain(["www", "api"]) # Find multiple subdomains
160
- ```
161
-
162
- **Valkey/Redis keys generated**: `customer:cust_123:domain_index`, `customer:cust_123:subdomain_index`
163
-
164
- ### Mixed Pattern Example
165
- A real-world example showing both patterns:
166
-
167
- ```ruby
168
- class ApiKey < Familia::Horreum
169
- feature :relationships
170
-
171
- identifier_field :key_id
172
- field :key_id, :key_hash, :name, :scope
173
-
174
- # API key hashes must be globally unique
175
- indexed_by :key_hash, :global_key_lookup, target: :global
176
-
177
- # But key names can be reused across different customers
178
- indexed_by :name, :customer_key_lookup, target: Customer
179
- indexed_by :scope, :scope_lookup, target: Customer
180
- end
181
-
182
- # Usage examples:
183
- # Global lookup (system-wide unique)
184
- ApiKey.key_lookup.get("sha256:abc123...")
185
-
186
- # Scoped lookup (unique per customer)
187
- customer = Customer.new(custid: "cust_456")
188
- customer.find_by_name("production-api-key")
189
- customer.find_all_by_scope(["read", "write"])
190
- ```
191
-
192
- ### Migrating Guide
193
- If you have existing code with incorrect syntax, here's how to fix it:
194
-
195
- ```ruby
196
- # ❌ Old incorrect syntax
197
- indexed_by :email_lookup, field: :email
198
-
199
- # ✅ New correct syntax - Global scope
200
- indexed_by :email, :email_lookup, target: :global
201
-
202
- # ✅ New correct syntax - Class scope
203
- indexed_by :email, :customer_email_lookup, target: Customer
204
- ```
205
-
206
- **Key Differences**:
207
- 1. Parameter order: `indexed_by(field, index_name, context:)`
208
- 2. The `field:` named parameter is now positional
209
- 3. The `context:` parameter is required and determines scope
210
- 4. Index name comes second (allows same field to have multiple indexes)