familia 2.0.0.pre19 → 2.0.0.pre21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (372) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +4 -9
  3. data/.github/workflows/code-smells.yml +64 -3
  4. data/.pre-commit-config.yaml +8 -6
  5. data/.reek.yml +10 -9
  6. data/.rubocop.yml +4 -0
  7. data/CHANGELOG.rst +177 -112
  8. data/CLAUDE.md +28 -1
  9. data/Gemfile +1 -1
  10. data/Gemfile.lock +20 -17
  11. data/bin/try +16 -0
  12. data/bin/tryouts +16 -0
  13. data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
  14. data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
  15. data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
  16. data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
  17. data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
  18. data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -0
  19. data/docs/1106-participates_in-bidirectional-solution.md +129 -0
  20. data/docs/guides/encryption.md +486 -0
  21. data/docs/guides/feature-encrypted-fields.md +123 -7
  22. data/docs/guides/feature-expiration.md +161 -117
  23. data/docs/guides/feature-external-identifiers.md +415 -443
  24. data/docs/guides/feature-object-identifiers.md +400 -269
  25. data/docs/guides/feature-quantization.md +120 -6
  26. data/docs/guides/feature-relationships-indexing.md +318 -0
  27. data/docs/guides/feature-relationships-methods.md +146 -604
  28. data/docs/guides/feature-relationships-participation.md +263 -0
  29. data/docs/guides/feature-relationships.md +118 -136
  30. data/docs/guides/feature-system-devs.md +176 -693
  31. data/docs/guides/feature-system.md +119 -6
  32. data/docs/guides/feature-transient-fields.md +81 -0
  33. data/docs/guides/field-system.md +778 -0
  34. data/docs/guides/index.md +32 -15
  35. data/docs/guides/logging.md +187 -0
  36. data/docs/guides/optimized-loading.md +674 -0
  37. data/docs/guides/thread-safety-monitoring.md +61 -0
  38. data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
  39. data/docs/migrating/v2.0.0-pre22.md +241 -0
  40. data/docs/overview.md +7 -9
  41. data/docs/reference/api-technical.md +267 -320
  42. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
  43. data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
  44. data/examples/autoloader/mega_customer.rb +2 -0
  45. data/examples/datatype_standalone.rb +4 -3
  46. data/examples/encrypted_fields.rb +2 -1
  47. data/examples/json_usage_patterns.rb +2 -0
  48. data/examples/relationships.rb +3 -0
  49. data/examples/safe_dump.rb +2 -1
  50. data/examples/sampling_demo.rb +53 -0
  51. data/examples/single_connection_transaction_confusions.rb +2 -1
  52. data/familia.gemspec +2 -1
  53. data/lib/familia/base.rb +2 -0
  54. data/lib/familia/connection/behavior.rb +2 -0
  55. data/lib/familia/connection/handlers.rb +2 -0
  56. data/lib/familia/connection/individual_command_proxy.rb +2 -0
  57. data/lib/familia/connection/middleware.rb +34 -24
  58. data/lib/familia/connection/operation_core.rb +2 -0
  59. data/lib/familia/connection/operations.rb +2 -0
  60. data/lib/familia/connection/pipelined_core.rb +2 -0
  61. data/lib/familia/connection/transaction_core.rb +68 -0
  62. data/lib/familia/connection.rb +18 -3
  63. data/lib/familia/data_type/class_methods.rb +3 -1
  64. data/lib/familia/data_type/connection.rb +2 -0
  65. data/lib/familia/data_type/database_commands.rb +2 -0
  66. data/lib/familia/data_type/serialization.rb +6 -4
  67. data/lib/familia/data_type/settings.rb +2 -0
  68. data/lib/familia/data_type/types/counter.rb +2 -0
  69. data/lib/familia/data_type/types/hashkey.rb +7 -5
  70. data/lib/familia/data_type/types/listkey.rb +2 -0
  71. data/lib/familia/data_type/types/lock.rb +2 -0
  72. data/lib/familia/data_type/types/sorted_set.rb +2 -0
  73. data/lib/familia/data_type/types/stringkey.rb +2 -0
  74. data/lib/familia/data_type/types/unsorted_set.rb +2 -0
  75. data/lib/familia/data_type.rb +2 -0
  76. data/lib/familia/encryption/encrypted_data.rb +4 -2
  77. data/lib/familia/encryption/manager.rb +2 -0
  78. data/lib/familia/encryption/provider.rb +2 -0
  79. data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
  80. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
  81. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
  82. data/lib/familia/encryption/registry.rb +2 -0
  83. data/lib/familia/encryption/request_cache.rb +2 -0
  84. data/lib/familia/encryption.rb +9 -2
  85. data/lib/familia/errors.rb +2 -0
  86. data/lib/familia/features/autoloader.rb +2 -0
  87. data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
  88. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
  89. data/lib/familia/features/encrypted_fields.rb +2 -2
  90. data/lib/familia/features/expiration/extensions.rb +3 -1
  91. data/lib/familia/features/expiration.rb +12 -4
  92. data/lib/familia/features/external_identifier.rb +33 -7
  93. data/lib/familia/features/object_identifier.rb +2 -0
  94. data/lib/familia/features/quantization.rb +3 -1
  95. data/lib/familia/features/relationships/README.md +3 -1
  96. data/lib/familia/features/relationships/collection_operations.rb +2 -0
  97. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +138 -9
  98. data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
  99. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +89 -21
  100. data/lib/familia/features/relationships/indexing.rb +3 -0
  101. data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
  102. data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
  103. data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
  104. data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
  105. data/lib/familia/features/relationships/participation.rb +155 -69
  106. data/lib/familia/features/relationships/participation_membership.rb +69 -0
  107. data/lib/familia/features/relationships/participation_relationship.rb +34 -6
  108. data/lib/familia/features/relationships/score_encoding.rb +2 -0
  109. data/lib/familia/features/relationships.rb +5 -3
  110. data/lib/familia/features/safe_dump.rb +2 -0
  111. data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
  112. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
  113. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
  114. data/lib/familia/features/transient_fields.rb +2 -0
  115. data/lib/familia/features.rb +2 -0
  116. data/lib/familia/field_type.rb +3 -1
  117. data/lib/familia/horreum/connection.rb +17 -1
  118. data/lib/familia/horreum/database_commands.rb +2 -0
  119. data/lib/familia/horreum/definition.rb +16 -6
  120. data/lib/familia/horreum/management.rb +212 -42
  121. data/lib/familia/horreum/persistence.rb +176 -108
  122. data/lib/familia/horreum/related_fields.rb +2 -0
  123. data/lib/familia/horreum/serialization.rb +23 -4
  124. data/lib/familia/horreum/settings.rb +2 -0
  125. data/lib/familia/horreum/utils.rb +2 -0
  126. data/lib/familia/horreum.rb +15 -1
  127. data/lib/familia/identifier_extractor.rb +2 -0
  128. data/lib/familia/instrumentation.rb +156 -0
  129. data/lib/familia/json_serializer.rb +2 -0
  130. data/lib/familia/logging.rb +92 -32
  131. data/lib/familia/refinements/dear_json.rb +2 -0
  132. data/lib/familia/refinements/stylize_words.rb +2 -14
  133. data/lib/familia/refinements/time_literals.rb +2 -0
  134. data/lib/familia/refinements.rb +2 -0
  135. data/lib/familia/secure_identifier.rb +10 -2
  136. data/lib/familia/settings.rb +2 -0
  137. data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
  138. data/lib/familia/thread_safety/monitor.rb +328 -0
  139. data/lib/familia/utils.rb +13 -0
  140. data/lib/familia/verifiable_identifier.rb +3 -1
  141. data/lib/familia/version.rb +3 -1
  142. data/lib/familia.rb +31 -4
  143. data/lib/middleware/database_command_counter.rb +152 -0
  144. data/lib/middleware/database_logger.rb +295 -170
  145. data/lib/multi_result.rb +2 -0
  146. data/try/edge_cases/empty_identifiers_try.rb +2 -0
  147. data/try/edge_cases/hash_symbolization_try.rb +2 -0
  148. data/try/edge_cases/json_serialization_try.rb +2 -0
  149. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
  150. data/try/edge_cases/race_conditions_try.rb +4 -0
  151. data/try/edge_cases/reserved_keywords_try.rb +4 -0
  152. data/try/edge_cases/string_coercion_try.rb +2 -0
  153. data/try/edge_cases/ttl_side_effects_try.rb +4 -0
  154. data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
  155. data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
  156. data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
  157. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
  158. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
  159. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
  160. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
  161. data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
  162. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
  163. data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
  164. data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
  165. data/try/features/encrypted_fields/memory_security_try.rb +4 -0
  166. data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
  167. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
  168. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
  169. data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
  170. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
  171. data/try/features/encryption/config_persistence_try.rb +4 -0
  172. data/try/features/encryption/core_try.rb +4 -0
  173. data/try/features/encryption/instance_variable_scope_try.rb +4 -0
  174. data/try/features/encryption/module_loading_try.rb +4 -0
  175. data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
  176. data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
  177. data/try/features/encryption/roundtrip_validation_try.rb +4 -0
  178. data/try/features/encryption/secure_memory_handling_try.rb +4 -0
  179. data/try/features/expiration/expiration_try.rb +4 -0
  180. data/try/features/external_identifier/external_identifier_try.rb +171 -8
  181. data/try/features/feature_dependencies_try.rb +2 -0
  182. data/try/features/feature_improvements_try.rb +2 -0
  183. data/try/features/field_groups_try.rb +2 -0
  184. data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
  185. data/try/features/object_identifier/object_identifier_try.rb +2 -0
  186. data/try/features/quantization/quantization_try.rb +4 -0
  187. data/try/features/real_feature_integration_try.rb +2 -0
  188. data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
  189. data/try/features/relationships/indexing_rebuild_try.rb +600 -0
  190. data/try/features/relationships/indexing_try.rb +2 -0
  191. data/try/features/relationships/participation_bidirectional_try.rb +242 -0
  192. data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
  193. data/try/features/relationships/participation_commands_verification_try.rb +2 -0
  194. data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
  195. data/try/features/relationships/participation_reverse_index_try.rb +15 -13
  196. data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
  197. data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
  198. data/try/features/relationships/relationships_api_changes_try.rb +2 -0
  199. data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
  200. data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
  201. data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
  202. data/try/features/relationships/relationships_performance_try.rb +4 -0
  203. data/try/features/relationships/relationships_performance_working_try.rb +4 -0
  204. data/try/features/relationships/relationships_try.rb +6 -4
  205. data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
  206. data/try/features/safe_dump/safe_dump_try.rb +4 -0
  207. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  208. data/try/features/transient_fields/refresh_reset_try.rb +3 -0
  209. data/try/features/transient_fields/simple_refresh_test.rb +3 -0
  210. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  211. data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
  212. data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
  213. data/try/integration/connection/fiber_context_preservation_try.rb +4 -0
  214. data/try/integration/connection/handler_constraints_try.rb +4 -0
  215. data/try/integration/connection/isolated_dbclient_try.rb +4 -0
  216. data/try/integration/connection/middleware_reconnect_try.rb +2 -0
  217. data/try/integration/connection/operation_mode_guards_try.rb +4 -0
  218. data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
  219. data/try/integration/connection/pools_try.rb +4 -0
  220. data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
  221. data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
  222. data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
  223. data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
  224. data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
  225. data/try/integration/connection/transaction_modes_try.rb +4 -0
  226. data/try/integration/conventional_inheritance_try.rb +4 -0
  227. data/try/integration/create_method_try.rb +4 -0
  228. data/try/integration/cross_component_try.rb +4 -0
  229. data/try/integration/data_types/datatype_pipelines_try.rb +4 -0
  230. data/try/integration/data_types/datatype_transactions_try.rb +4 -0
  231. data/try/integration/database_consistency_try.rb +4 -0
  232. data/try/integration/familia_extended_try.rb +4 -0
  233. data/try/integration/familia_members_methods_try.rb +4 -0
  234. data/try/integration/models/customer_safe_dump_try.rb +4 -0
  235. data/try/integration/models/customer_try.rb +4 -0
  236. data/try/integration/models/datatype_base_try.rb +4 -0
  237. data/try/integration/models/familia_object_try.rb +4 -0
  238. data/try/integration/persistence_operations_try.rb +4 -0
  239. data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
  240. data/try/integration/save_methods_consistency_try.rb +241 -0
  241. data/try/integration/scenarios_try.rb +4 -0
  242. data/try/integration/secure_identifier_try.rb +4 -0
  243. data/try/integration/transaction_safety_core_try.rb +176 -0
  244. data/try/integration/transaction_safety_workflow_try.rb +291 -0
  245. data/try/integration/verifiable_identifier_try.rb +4 -0
  246. data/try/investigation/pipeline_routing/README.md +228 -0
  247. data/try/performance/benchmarks_try.rb +4 -0
  248. data/try/performance/transaction_safety_benchmark_try.rb +238 -0
  249. data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
  250. data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
  251. data/try/support/debugging/cache_behavior_tracer.rb +4 -0
  252. data/try/support/debugging/debug_aad_process.rb +3 -0
  253. data/try/support/debugging/debug_concealed_internal.rb +3 -0
  254. data/try/support/debugging/debug_concealed_reveal.rb +3 -0
  255. data/try/support/debugging/debug_context_aad.rb +3 -0
  256. data/try/support/debugging/debug_context_simple.rb +3 -0
  257. data/try/support/debugging/debug_cross_context.rb +3 -0
  258. data/try/support/debugging/debug_database_load.rb +3 -0
  259. data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
  260. data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
  261. data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
  262. data/try/support/debugging/debug_field_decrypt.rb +3 -0
  263. data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
  264. data/try/support/debugging/debug_load_path.rb +3 -0
  265. data/try/support/debugging/debug_method_definition.rb +3 -0
  266. data/try/support/debugging/debug_method_resolution.rb +3 -0
  267. data/try/support/debugging/debug_minimal.rb +3 -0
  268. data/try/support/debugging/debug_provider.rb +3 -0
  269. data/try/support/debugging/debug_secure_behavior.rb +3 -0
  270. data/try/support/debugging/debug_string_class.rb +3 -0
  271. data/try/support/debugging/debug_test.rb +3 -0
  272. data/try/support/debugging/debug_test_design.rb +3 -0
  273. data/try/support/debugging/encryption_method_tracer.rb +4 -0
  274. data/try/support/debugging/provider_diagnostics.rb +4 -0
  275. data/try/support/helpers/test_cleanup.rb +4 -0
  276. data/try/support/helpers/test_helpers.rb +5 -0
  277. data/try/support/memory/memory_basic_test.rb +4 -0
  278. data/try/support/memory/memory_detailed_test.rb +4 -0
  279. data/try/support/memory/memory_search_for_string.rb +4 -0
  280. data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
  281. data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
  282. data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
  283. data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
  284. data/try/support/prototypes/atomic_saves_v4.rb +4 -0
  285. data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
  286. data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  287. data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
  288. data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  289. data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
  290. data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
  291. data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
  292. data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
  293. data/try/support/prototypes/pooling/pool_siege.rb +4 -2
  294. data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
  295. data/try/thread_safety/README.md +496 -0
  296. data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
  297. data/try/thread_safety/connection_chain_race_try.rb +148 -0
  298. data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
  299. data/try/thread_safety/feature_registry_race_try.rb +226 -0
  300. data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
  301. data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
  302. data/try/thread_safety/field_registration_race_try.rb +222 -0
  303. data/try/thread_safety/logger_initialization_race_try.rb +170 -0
  304. data/try/thread_safety/middleware_registration_race_try.rb +154 -0
  305. data/try/thread_safety/module_config_race_try.rb +175 -0
  306. data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
  307. data/try/unit/core/autoloader_try.rb +4 -0
  308. data/try/unit/core/base_enhancements_try.rb +4 -0
  309. data/try/unit/core/connection_try.rb +4 -0
  310. data/try/unit/core/errors_try.rb +4 -0
  311. data/try/unit/core/extensions_try.rb +4 -0
  312. data/try/unit/core/familia_logger_try.rb +2 -0
  313. data/try/unit/core/familia_try.rb +4 -0
  314. data/try/unit/core/middleware_sampling_try.rb +335 -0
  315. data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
  316. data/try/unit/core/middleware_thread_safety_try.rb +245 -0
  317. data/try/unit/core/middleware_try.rb +4 -0
  318. data/try/unit/core/settings_try.rb +4 -0
  319. data/try/unit/core/time_utils_try.rb +4 -0
  320. data/try/unit/core/tools_try.rb +4 -0
  321. data/try/unit/core/utils_try.rb +37 -0
  322. data/try/unit/data_types/boolean_try.rb +4 -0
  323. data/try/unit/data_types/counter_try.rb +4 -0
  324. data/try/unit/data_types/datatype_base_try.rb +4 -0
  325. data/try/unit/data_types/hash_try.rb +4 -0
  326. data/try/unit/data_types/list_try.rb +4 -0
  327. data/try/unit/data_types/lock_try.rb +4 -0
  328. data/try/unit/data_types/sorted_set_try.rb +4 -0
  329. data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
  330. data/try/unit/data_types/string_try.rb +4 -0
  331. data/try/unit/data_types/unsortedset_try.rb +4 -0
  332. data/try/unit/familia_resolve_class_try.rb +116 -0
  333. data/try/unit/horreum/auto_indexing_on_save_try.rb +5 -1
  334. data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
  335. data/try/unit/horreum/base_try.rb +4 -0
  336. data/try/unit/horreum/class_methods_try.rb +4 -0
  337. data/try/unit/horreum/commands_try.rb +4 -0
  338. data/try/unit/horreum/defensive_initialization_try.rb +4 -0
  339. data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +4 -0
  340. data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
  341. data/try/unit/horreum/field_categories_try.rb +4 -0
  342. data/try/unit/horreum/field_definition_try.rb +4 -0
  343. data/try/unit/horreum/initialization_try.rb +4 -0
  344. data/try/unit/horreum/json_type_preservation_try.rb +2 -0
  345. data/try/unit/horreum/optimized_loading_try.rb +156 -0
  346. data/try/unit/horreum/relations_try.rb +4 -0
  347. data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
  348. data/try/unit/horreum/serialization_try.rb +4 -0
  349. data/try/unit/horreum/settings_try.rb +4 -0
  350. data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
  351. data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
  352. data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
  353. data/try/unit/middleware/database_logger_methods_try.rb +251 -0
  354. data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
  355. data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
  356. data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
  357. data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
  358. data/try/unit/thread_safety_monitor_try.rb +149 -0
  359. metadata +72 -17
  360. data/.github/workflows/code-quality.yml +0 -138
  361. data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
  362. data/changelog.d/20251011_203905_delano_next.rst +0 -30
  363. data/changelog.d/20251011_212633_delano_next.rst +0 -13
  364. data/changelog.d/20251011_221253_delano_next.rst +0 -26
  365. data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
  366. data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
  367. data/docs/archive/FAMILIA_UPDATE.md +0 -226
  368. data/docs/archive/README.md +0 -64
  369. data/docs/archive/api-reference.md +0 -333
  370. data/docs/guides/core-field-system.md +0 -806
  371. data/docs/guides/implementation.md +0 -276
  372. data/docs/guides/security-model.md +0 -183
@@ -1,684 +1,226 @@
1
1
  # Relationship Methods Reference
2
2
 
3
- This guide provides detailed documentation for all methods automatically generated by Familia's relationships feature. Each relationship declaration creates a comprehensive set of methods for managing object associations, indexing, and tracking.
3
+ Complete reference for methods generated by Familia's relationships feature. Each relationship declaration creates predictable, consistently-named methods on both participating classes.
4
4
 
5
- ## Method Generation Overview
5
+ ## Quick Reference
6
6
 
7
- The relationships feature uses consistent naming patterns to generate methods based on your declarations. Understanding these patterns helps you predict and use the available methods effectively.
7
+ ### Basic Participation
8
8
 
9
- ## Participation Methods (`participates_in`)
10
-
11
- ### Basic Participation Declaration
9
+ When `Domain` declares `participates_in Customer, :domains`:
12
10
 
13
11
  ```ruby
14
- class Domain < Familia::Horreum
15
- feature :relationships
16
- participates_in Customer, :domains
17
- end
18
-
19
- class Customer < Familia::Horreum
20
- feature :relationships
21
- set :domains # Must define the collection
22
- end
23
- ```
12
+ # Target class (Customer) gets:
13
+ customer.domains # Collection accessor
14
+ customer.add_domains_instance(domain) # Add single
15
+ customer.add_domains([d1, d2]) # Add multiple
16
+ customer.remove_domains_instance(domain) # Remove
24
17
 
25
- ### Generated Instance Methods
18
+ # Participant class (Domain) gets:
19
+ domain.add_to_customer_domains(customer) # Add self
20
+ domain.remove_from_customer_domains(customer) # Remove self
21
+ domain.in_customer_domains?(customer) # Check membership
22
+ domain.score_in_customer_domains(customer) # Get score (sorted_set)
26
23
 
27
- **On Domain instances:**
28
- - **`add_to_customer_domains(customer_obj_or_id)`** - Add this domain to customer's domains collection
29
- - **`remove_from_customer_domains(customer_obj_or_id)`** - Remove this domain from customer's domains collection
30
- - **`in_customer_domains?(customer_obj_or_id)`** - Check if this domain is in customer's domains collection
31
-
32
- ```ruby
33
- domain = Domain.new(name: "example.com")
34
- customer = Customer.new(name: "Acme Corp")
35
-
36
- # Explicit method calls
37
- domain.add_to_customer_domains(customer)
38
- domain.in_customer_domains?(customer) # => true
39
- domain.remove_from_customer_domains(customer)
40
- domain.in_customer_domains?(customer) # => false
24
+ # Reverse collection methods (Domain):
25
+ domain.customer_instances # Load Customer objects
26
+ domain.customer_ids # Just IDs
27
+ domain.customer? # Has any?
28
+ domain.customer_count # Count
41
29
  ```
42
30
 
43
- ### Collection Operator Support
31
+ ## Participation Methods
44
32
 
45
- **Ruby-like syntax with automatic bidirectional updates:**
46
- ```ruby
47
- customer.domains << domain # Equivalent to domain.add_to_customer_domains(customer)
48
- customer.domains.delete(domain.identifier) # Removes relationship bidirectionally
49
- ```
33
+ ### Target Class Methods
50
34
 
51
- ### Method Naming Pattern
52
- `{action}_{to|from}_{lowercase_class_name}_{collection_name}` or `in_{lowercase_class_name}_{collection_name}?`
35
+ The target class (specified in `participates_in`) receives collection management methods.
53
36
 
54
- ## Class-Level Tracking Methods (`class_participates_in`)
55
-
56
- ### Declaration
57
37
  ```ruby
58
- class Customer < Familia::Horreum
59
- feature :relationships
60
- class_participates_in :all_customers, score: :created_at
61
- class_participates_in :active_customers, score: ->(customer) {
62
- customer.status == 'active' ? customer.last_activity : 0
63
- }
38
+ class Domain < Familia::Horreum
39
+ participates_in Customer, :domains, score: :priority
64
40
  end
65
41
  ```
66
42
 
67
- ### Generated Class Methods
43
+ | Method | Description | Example |
44
+ |--------|-------------|---------|
45
+ | `domains` | Collection accessor | `customer.domains.size` |
46
+ | `add_domains_instance(item, score)` | Add single item | `customer.add_domains_instance(domain, 100)` |
47
+ | `add_domains([items])` | Bulk add | `customer.add_domains([d1, d2, d3])` |
48
+ | `remove_domains_instance(item)` | Remove item | `customer.remove_domains_instance(domain)` |
68
49
 
69
- **Collection access:**
70
- - **`Customer.all_customers`** - Returns the sorted set collection directly
71
- - **`Customer.active_customers`** - Returns the active customers sorted set
50
+ ### Participant Class Methods
72
51
 
73
- **Manual management (rarely needed):**
74
- - **`Customer.add_to_all_customers(customer)`** - Manually add customer to tracking
75
- - **`Customer.remove_from_all_customers(customer)`** - Manually remove customer from tracking
52
+ The participant class (calling `participates_in`) receives membership management methods.
76
53
 
77
- ### Collection Operations
54
+ | Method | Description | Example |
55
+ |--------|-------------|---------|
56
+ | `add_to_customer_domains(target, score)` | Add to target's collection | `domain.add_to_customer_domains(customer)` |
57
+ | `remove_from_customer_domains(target)` | Remove from target's collection | `domain.remove_from_customer_domains(customer)` |
58
+ | `in_customer_domains?(target)` | Check membership | `domain.in_customer_domains?(customer)` |
59
+ | `score_in_customer_domains(target)` | Get score (sorted_set only) | `domain.score_in_customer_domains(customer)` |
60
+ | `position_in_customer_domains(target)` | Get position (list only) | `domain.position_in_customer_domains(customer)` |
78
61
 
79
- ```ruby
80
- # Query operations (delegated to underlying sorted set)
81
- Customer.all_customers.size # Count of tracked customers
82
- Customer.all_customers.range(0, 9) # First 10 customers (by score)
83
- Customer.all_customers.range_by_score(min_score, max_score) # Customers in score range
84
-
85
- # Score-based queries
86
- recent_customers = Customer.all_customers.range_by_score(
87
- (Time.now - 1.week).to_i, '+inf'
88
- )
89
-
90
- # Get customer with scores
91
- customers_with_scores = Customer.all_customers.range(0, -1, with_scores: true)
92
- # => [["customer_id_1", 1634567890], ["customer_id_2", 1634567920], ...]
93
- ```
94
-
95
- ### Automatic Behavior
96
- - Objects are **automatically added** to class-level tracking collections when saved
97
- - Objects are **automatically removed** when destroyed
98
- - Scores are **automatically calculated** using the provided field or lambda
99
- - No manual method calls required for basic lifecycle tracking
100
-
101
- ### Scored Participation in Parent Collections
62
+ ### Reverse Collection Methods
102
63
 
103
- ```ruby
104
- class User < Familia::Horreum
105
- feature :relationships
106
- participates_in Team, :active_users, score: :last_seen
107
- participates_in Team, :top_performers, score: ->(user) { user.performance_score }
108
- end
64
+ Participants also get methods to query all relationships of a given type.
109
65
 
110
- class Team < Familia::Horreum
111
- feature :relationships
112
- sorted_set :active_users, :top_performers
113
- end
114
- ```
115
-
116
- **Generated methods on Team instances:**
117
- - **`team.active_users`** - Access the scored collection
118
- - **`team.top_performers`** - Access the performance-scored collection
119
-
120
- **Usage:**
121
- ```ruby
122
- team = Team.new(name: "Development")
123
- user = User.new(name: "Alice", last_seen: Time.now.to_i)
124
-
125
- # User automatically added with score when relationship established
126
- team.active_users << user
127
-
128
- # Query by score ranges
129
- recently_active = team.active_users.range_by_score(
130
- (Time.now - 1.hour).to_i, '+inf'
131
- )
132
- ```
66
+ | Method | Description | Returns |
67
+ |--------|-------------|---------|
68
+ | `customer_instances` | Load all related objects | `Array<Customer>` |
69
+ | `customer_ids` | Get all related IDs | `Array<String>` |
70
+ | `customer?` | Check if has any relationships | `Boolean` |
71
+ | `customer_count` | Count relationships | `Integer` |
133
72
 
134
- ## Indexing Methods (`indexed_by`)
73
+ ## Class-Level Participation
135
74
 
136
- The `indexed_by` declaration creates Valkey/Redis hash-based indexes for O(1) field lookups with automatic management.
137
-
138
- ### Class-Level Indexing (`class_indexed_by`)
75
+ ### Declaration
139
76
 
140
77
  ```ruby
141
- class Customer < Familia::Horreum
142
- feature :relationships
143
- field :email, :username, :api_key
144
-
145
- class_indexed_by :email, :email_lookup
146
- class_indexed_by :username, :username_lookup
147
- class_indexed_by :api_key, :api_key_lookup
78
+ class User < Familia::Horreum
79
+ class_participates_in :all_users, score: :created_at
80
+ class_participates_in :active_users,
81
+ score: ->(u) { u.active? ? u.last_activity : 0 }
148
82
  end
149
83
  ```
150
84
 
151
- ### Generated Class Methods
85
+ ### Generated Methods
152
86
 
153
- **Index access:**
154
- - **`Customer.email_lookup`** - Returns the hash index directly
155
- - **`Customer.username_lookup`** - Returns the username hash index
156
- - **`Customer.api_key_lookup`** - Returns the API key hash index
87
+ | Level | Method | Description |
88
+ |-------|--------|-------------|
89
+ | **Class** | `User.all_users` | Collection accessor |
90
+ | | `User.add_to_all_users(user, score)` | Manual add |
91
+ | | `User.remove_from_all_users(user)` | Manual remove |
92
+ | **Instance** | `user.add_to_class_all_users(score)` | Add self |
93
+ | | `user.remove_from_class_all_users` | Remove self |
94
+ | | `user.in_class_all_users?` | Check membership |
95
+ | | `user.score_in_class_all_users` | Get score |
157
96
 
158
- **Convenience lookup methods:**
159
- - **`Customer.find_by_email(email)`** - Find customer ID by email (O(1) lookup)
160
- - **`Customer.find_by_username(username)`** - Find customer ID by username
161
- - **`Customer.find_by_api_key(api_key)`** - Find customer ID by API key
97
+ ## Indexing Methods
162
98
 
163
- ### Generated Instance Methods (rarely used manually)
164
-
165
- **Index management:**
166
- - **`customer.add_to_class_email_lookup`** - Manually add to email index
167
- - **`customer.remove_from_class_email_lookup`** - Manually remove from email index
168
- - **`customer.update_class_email_lookup(old_email)`** - Update index when email changes
169
-
170
- ### Usage Examples
99
+ ### Class-Level Unique Index
171
100
 
172
101
  ```ruby
173
- # Automatic indexing on save
174
- customer = Customer.new(
175
- email: "alice@example.com",
176
- username: "alice123",
177
- api_key: "ak_abcd1234"
178
- )
179
- customer.save # Automatically added to all indexes
180
-
181
- # O(1) lookups
182
- customer_id = Customer.find_by_email("alice@example.com")
183
- customer = Customer.load(customer_id) if customer_id
184
-
185
- # Direct index access
186
- all_emails = Customer.email_lookup.to_h
187
- # => {"alice@example.com" => "customer_1", "bob@example.com" => "customer_2"}
188
-
189
- # Index operations
190
- Customer.email_lookup.get("alice@example.com") # => "customer_1"
191
- Customer.email_lookup.set("new@example.com", "customer_3")
192
- ```
193
-
194
- ### Automatic Behavior
195
- - Objects are **automatically indexed** when saved
196
- - Indexes are **automatically updated** when indexed fields change
197
- - Objects are **automatically removed** from indexes when destroyed
198
- - No manual index management required for standard lifecycle operations
199
-
200
- **Redis key pattern:** `{class_name.downcase}:{index_name}`
201
-
202
- ### Relationship-Scoped Indexing (`indexed_by` with `target:`)
203
-
204
- ```ruby
205
- class Customer < Familia::Horreum
206
- feature :relationships
207
- field :name
208
- set :domains
209
- end
210
-
211
- class Domain < Familia::Horreum
212
- feature :relationships
213
- field :name, :subdomain, :port
214
- participates_in Customer, :domains
215
-
216
- # Index domains by name within each customer (domains can have same name across customers)
217
- indexed_by :name, :domain_index, target: Customer
218
- indexed_by :subdomain, :subdomain_index, target: Customer
219
- indexed_by :port, :port_index, target: Customer
102
+ class User < Familia::Horreum
103
+ unique_index :email, :email_lookup
104
+ unique_index :api_key, :api_key_lookup, query: false
220
105
  end
221
106
  ```
222
107
 
223
- ### Generated Methods on Target Class (Customer)
224
-
225
- **Single lookups:**
226
- - **`customer.find_by_name(domain_name)`** - Find domain ID by name within this customer
227
- - **`customer.find_by_subdomain(subdomain)`** - Find domain ID by subdomain within this customer
228
- - **`customer.find_by_port(port)`** - Find domain ID by port within this customer
229
-
230
- **Multiple lookups:**
231
- - **`customer.find_all_by_name(domain_names)`** - Find multiple domain IDs by names
232
- - **`customer.find_all_by_subdomain(subdomains)`** - Find multiple domain IDs by subdomains
233
- - **`customer.find_all_by_port(ports)`** - Find multiple domain IDs by ports
234
-
235
- **Direct index access:**
236
- - **`customer.domain_index`** - Access the name index hash directly
237
- - **`customer.subdomain_index`** - Access the subdomain index hash directly
238
- - **`customer.port_index`** - Access the port index hash directly
239
-
240
- ### Usage Examples
241
-
242
- ```ruby
243
- customer = Customer.new(name: "Acme Corp")
244
- domain1 = Domain.new(name: "example.com", subdomain: "www", port: 443)
245
- domain2 = Domain.new(name: "api.example.com", subdomain: "api", port: 443)
246
-
247
- # Establish relationships (automatic indexing)
248
- customer.domains << domain1
249
- customer.domains << domain2
250
-
251
- # O(1) lookups within customer scope
252
- domain_id = customer.find_by_name("example.com")
253
- api_domain_id = customer.find_by_subdomain("api")
254
-
255
- # Multiple lookups
256
- ssl_domains = customer.find_all_by_port([443, 8443])
257
-
258
- # Direct index access
259
- all_domain_names = customer.domain_index.to_h
260
- # => {"example.com" => "domain_1", "api.example.com" => "domain_2"}
261
-
262
- # Check if customer has domain with specific name
263
- has_domain = customer.domain_index.get("example.com").present?
264
- ```
265
-
266
- ### Generated Methods on Indexed Class (Domain)
108
+ | Method | Generated When | Description |
109
+ |--------|---------------|-------------|
110
+ | `User.find_by_email(email)` | `query: true` (default) | O(1) lookup |
111
+ | `User.index_email_for(user)` | Always | Manual index |
112
+ | `User.unindex_email_for(user)` | Always | Remove from index |
113
+ | `User.reindex_email_for(user)` | Always | Update index |
267
114
 
268
- **Index management (automatic):**
269
- - **`domain.add_to_customer_domain_index(customer)`** - Add to customer's domain name index
270
- - **`domain.remove_from_customer_domain_index(customer)`** - Remove from customer's domain name index
271
- - **`domain.update_customer_domain_index(customer, old_name)`** - Update index when name changes
115
+ ### Instance-Scoped Unique Index
272
116
 
273
- ### Automatic Behavior
274
- - Domain is **automatically indexed** when added to customer's domains collection
275
- - Index is **automatically updated** when domain's indexed fields change
276
- - Domain is **automatically removed** from index when relationship is removed
277
- - All index management happens transparently during relationship operations
278
-
279
- **Redis key pattern:** `{target_class.downcase}:{target_id}:{index_name}`
280
-
281
- ### When to Use Each Indexing Context
282
-
283
- **Class-level indexing (`class_indexed_by`):**
284
- - Use for **system-wide unique** field lookups
285
- - Examples: email addresses, usernames, API keys, social security numbers
286
- - Best when field values should be unique across all instances
287
-
288
- **Relationship-scoped indexing (`indexed_by` with `target:`):**
289
- - Use for **context-specific** field lookups
290
- - Examples: domain names per customer, project names per team, usernames per organization
291
- - Best when field values are unique within a specific parent context but may duplicate across different parents
292
-
293
- ## Complete API Reference
294
-
295
- ### Method Naming Conventions
296
-
297
- **Participation methods:**
298
- - `{add_to|remove_from}_{target_class_downcase}_{collection_name}(target)`
299
- - `in_{target_class_downcase}_{collection_name}?(target)`
300
-
301
- **Class-level tracking:**
302
- - `{add_to|remove_from}_{collection_name}(object)` (class methods)
303
- - `{ClassName}.{collection_name}` (collection accessor)
304
-
305
- **Class-level indexing:**
306
- - `{add_to|remove_from}_class_{index_name}` (instance methods)
307
- - `{ClassName}.{index_name}` (index accessor)
308
- - `{ClassName}.find_by_{field_name}(value)` (convenience lookup)
309
-
310
- **Relationship-scoped indexing:**
311
- - `{target_instance}.find_by_{field_name}(value)` (single lookup)
312
- - `{target_instance}.find_all_by_{field_name}(values)` (multiple lookup)
313
- - `{target_instance}.{index_name}` (direct index access)
314
-
315
- ## Key Benefits of the Relationships API
316
-
317
- - **Automatic Management**: Save operations update indexes and tracking automatically
318
- - **Ruby-Idiomatic**: Use `<<` operator for natural collection syntax
319
- - **Consistent Storage**: All indexes stored at class level for architectural simplicity
320
- - **Predictable Naming**: Method names follow consistent patterns for easy discovery
321
- - **O(1) Performance**: Hash-based indexes provide constant-time lookups
322
- - **Bidirectional Sync**: Relationship changes automatically update both sides
323
-
324
- ## Advanced Implementation Patterns
325
-
326
- ### Error Handling and Edge Cases
327
-
328
- **Handling Missing Objects:**
329
117
  ```ruby
330
- # Safe relationship operations
331
- begin
332
- customer.domains << domain
333
- rescue Familia::Problem => e
334
- Rails.logger.error "Failed to establish relationship: #{e.message}"
335
- end
336
-
337
- # Check if objects exist before relating
338
- if domain.persisted? && customer.persisted?
339
- customer.domains << domain
340
- end
341
-
342
- # Batch operations with error handling
343
- domain_ids.each do |id|
344
- next unless Domain.exists?(id) # Custom existence check
345
- customer.domains.add(id)
118
+ class Employee < Familia::Horreum
119
+ unique_index :badge_number, :badge_index, within: Company
346
120
  end
347
121
  ```
348
122
 
349
- **Index Consistency Validation:**
350
- ```ruby
351
- class Customer < Familia::Horreum
352
- feature :relationships
353
-
354
- def validate_email_index_consistency
355
- stored_id = self.class.email_lookup.get(email)
356
- return true if stored_id == identifier
123
+ **Methods on scope class (Company):**
124
+ | Method | Description |
125
+ |--------|-------------|
126
+ | `company.find_by_badge_number(badge)` | Find employee |
127
+ | `company.index_badge_number_for(employee)` | Add to index |
128
+ | `company.unindex_badge_number_for(employee)` | Remove from index |
357
129
 
358
- Rails.logger.warn "Email index inconsistency: #{email} -> #{stored_id} vs #{identifier}"
359
- # Repair the index
360
- self.class.email_lookup.set(email, identifier)
361
- false
362
- end
130
+ **Methods on indexed class (Employee):**
131
+ | Method | Description |
132
+ |--------|-------------|
133
+ | `employee.add_to_company_badge_index(company)` | Add to company's index |
134
+ | `employee.remove_from_company_badge_index(company)` | Remove from index |
135
+ | `employee.in_company_badge_index?(company)` | Check if indexed |
363
136
 
364
- after_save :validate_email_index_consistency
365
- end
366
- ```
137
+ ### Multi-Value Index
367
138
 
368
- ### Performance Optimization Techniques
369
-
370
- **Lazy Loading Patterns:**
371
139
  ```ruby
372
- class Customer < Familia::Horreum
373
- feature :relationships
374
- set :domains
375
-
376
- # Memoized relationship loading
377
- def domain_objects
378
- @domain_objects ||= begin
379
- domain_ids = domains.to_a
380
- Domain.multiget(*domain_ids).compact
381
- end
382
- end
383
-
384
- # Paginated relationship loading
385
- def recent_domains(limit = 10)
386
- recent_ids = domains.range(0, limit - 1)
387
- Domain.multiget(*recent_ids).compact
388
- end
389
-
390
- # Selective field loading
391
- def domain_names
392
- domains.to_a.map do |id|
393
- Domain.new(domain_id: id).name # Load only name field
394
- end
395
- end
140
+ class Employee < Familia::Horreum
141
+ multi_index :department, :dept_index, within: Company
396
142
  end
397
143
  ```
398
144
 
399
- **Bulk Operations with Transaction Safety:**
400
- ```ruby
401
- class Team < Familia::Horreum
402
- feature :relationships
403
- set :members
404
-
405
- def bulk_add_members(user_ids)
406
- # Validate all IDs exist first
407
- valid_ids = user_ids.select { |id| User.exists?(id) }
408
-
409
- # Use Valkey/Redis pipeline for bulk operations
410
- Familia.redis.pipelined do |pipeline|
411
- valid_ids.each do |user_id|
412
- members.add(user_id)
413
- # Update reverse indexes in same pipeline
414
- user = User.new(user_id: user_id)
415
- user.add_to_team_members(self)
416
- end
417
- end
418
-
419
- valid_ids.size
420
- end
421
-
422
- def bulk_remove_members(user_ids)
423
- # Atomic bulk removal
424
- removed_count = 0
425
- user_ids.each do |user_id|
426
- if members.delete(user_id)
427
- removed_count += 1
428
- # Clean up reverse relationship
429
- user = User.new(user_id: user_id)
430
- user.remove_from_team_members(self)
431
- end
432
- end
433
- removed_count
434
- end
435
- end
436
- ```
145
+ **Methods on scope class (Company):**
146
+ | Method | Description |
147
+ |--------|-------------|
148
+ | `company.find_all_by_department(dept)` | Find all in department |
149
+ | `company.sample_from_department(dept, count)` | Random sample |
437
150
 
438
- ### Custom Scoring and Complex Relationships
151
+ **Methods on indexed class (Employee):**
152
+ | Method | Description |
153
+ |--------|-------------|
154
+ | `employee.add_to_company_dept_index(company)` | Add to index |
155
+ | `employee.remove_from_company_dept_index(company)` | Remove from index |
439
156
 
440
- **Dynamic Scoring with Context:**
441
- ```ruby
442
- class Project < Familia::Horreum
443
- feature :relationships
444
- field :priority, :created_at, :deadline
157
+ ## Method Naming Patterns
445
158
 
446
- participates_in Team, :projects, score: :calculated_priority
159
+ ### Participation
447
160
 
448
- private
161
+ - **Target methods**: `{collection}`, `add_{collection}_instance`, `remove_{collection}_instance`
162
+ - **Participant methods**: `{action}_to_{target_config_name}_{collection}`
163
+ - **Reverse methods**: `{target_config_name}_instances`, `{target_config_name}_ids`
449
164
 
450
- def calculated_priority
451
- base_priority = priority.to_i
452
- urgency_multiplier = deadline_urgency_factor
453
- age_factor = project_age_factor
165
+ ### Indexing
454
166
 
455
- (base_priority * urgency_multiplier * age_factor).to_i
456
- end
167
+ - **Class unique**: `find_by_{field}`, `index_{field}_for`, `unindex_{field}_for`
168
+ - **Scoped unique**: `{scope}.find_by_{field}`, `{item}.add_to_{scope}_{index}`
169
+ - **Multi-value**: `{scope}.find_all_by_{field}`, `{scope}.sample_from_{field}`
457
170
 
458
- def deadline_urgency_factor
459
- return 1.0 unless deadline
171
+ ## Common Usage Examples
460
172
 
461
- days_until_deadline = (deadline.to_time - Time.now) / 1.day
462
- return 3.0 if days_until_deadline <= 1 # Critical
463
- return 2.0 if days_until_deadline <= 7 # High
464
- 1.0 # Normal
465
- end
173
+ ### Establishing Relationships
466
174
 
467
- def project_age_factor
468
- days_old = (Time.now - created_at.to_time) / 1.day
469
- [1.0 + (days_old / 30.0), 2.0].min # Cap at 2x multiplier
470
- end
471
- end
472
- ```
473
-
474
- **Multi-Level Relationships:**
475
- ```ruby
476
- class Organization < Familia::Horreum
477
- feature :relationships
478
- set :departments
479
-
480
- # Find all users across all departments
481
- def all_users
482
- department_ids = departments.to_a
483
- user_ids = []
484
-
485
- department_ids.each do |dept_id|
486
- dept = Department.load(dept_id)
487
- user_ids.concat(dept.users.to_a) if dept
488
- end
489
-
490
- User.multiget(*user_ids.uniq).compact
491
- end
492
-
493
- # Hierarchical relationship queries
494
- def users_in_department(department_name)
495
- dept_id = find_by_name(department_name)
496
- return [] unless dept_id
497
-
498
- dept = Department.load(dept_id)
499
- return [] unless dept
500
-
501
- user_ids = dept.users.to_a
502
- User.multiget(*user_ids).compact
503
- end
504
- end
505
- ```
506
-
507
- ### Advanced Indexing Patterns
508
-
509
- **Composite Index Keys:**
510
175
  ```ruby
511
- class ApiKey < Familia::Horreum
512
- feature :relationships
513
- field :customer_id, :environment, :key_type, :key_hash
176
+ # Single addition with auto-calculated score
177
+ customer.add_domains_instance(domain)
514
178
 
515
- # Composite index for environment + type lookups
516
- indexed_by :environment_and_type, :env_type_lookup, target: Customer
179
+ # Single addition with explicit score
180
+ customer.add_domains_instance(domain, 100.0)
517
181
 
518
- private
519
-
520
- def environment_and_type
521
- "#{environment}:#{key_type}" # e.g., "production:read_write"
522
- end
523
- end
182
+ # Bulk addition
183
+ customer.add_domains([domain1, domain2, domain3])
524
184
 
525
- # Usage
526
- customer.find_by_environment_and_type("production:read_only")
527
- customer.find_all_by_environment_and_type(["staging:read_write", "production:read_write"])
185
+ # From participant side
186
+ domain.add_to_customer_domains(customer)
528
187
  ```
529
188
 
530
- **Time-Based Index Partitioning:**
531
- ```ruby
532
- class Event < Familia::Horreum
533
- feature :relationships
534
- field :event_type, :timestamp, :user_id
535
-
536
- participates_in User, :events, score: :timestamp
537
- indexed_by :daily_partition, :daily_events, target: User
538
-
539
- private
189
+ ### Querying Relationships
540
190
 
541
- def daily_partition
542
- Time.at(timestamp).strftime('%Y%m%d') # e.g., "20241215"
543
- end
544
- end
545
-
546
- # Usage - find today's events for user
547
- today = Time.now.strftime('%Y%m%d')
548
- todays_event_ids = user.find_all_by_daily_partition([today])
549
- ```
191
+ ```ruby
192
+ # Check membership
193
+ domain.in_customer_domains?(customer) # => true/false
550
194
 
551
- ### Testing Relationship Methods
195
+ # Get all relationships
196
+ domain.customer_instances # => [customer1, customer2]
197
+ domain.customer_ids # => ["cust_123", "cust_456"]
198
+ domain.customer_count # => 2
552
199
 
553
- **Unit Testing Patterns:**
554
- ```ruby
555
- # test/models/relationship_test.rb
556
- class RelationshipTest < Minitest::Test
557
- def setup
558
- @customer = Customer.create(email: "test@example.com", name: "Test Corp")
559
- @domain = Domain.create(name: "test.com", status: "active")
560
- end
561
-
562
- def test_bidirectional_relationship_establishment
563
- @customer.domains << @domain
564
-
565
- # Test both sides of relationship
566
- assert @customer.domains.member?(@domain.identifier)
567
- assert @domain.in_customer_domains?(@customer.identifier)
568
- end
569
-
570
- def test_relationship_removal
571
- @customer.domains << @domain
572
- @customer.domains.delete(@domain.identifier)
573
-
574
- # Verify complete cleanup
575
- refute @customer.domains.member?(@domain.identifier)
576
- refute @domain.in_customer_domains?(@customer.identifier)
577
- end
578
-
579
- def test_index_automatic_maintenance
580
- @customer.save # Should trigger indexing
581
-
582
- found_id = Customer.find_by_email("test@example.com")
583
- assert_equal @customer.identifier, found_id
584
- end
585
-
586
- def test_scoped_index_lookup
587
- @customer.domains << @domain
588
-
589
- found_id = @customer.find_by_name("test.com")
590
- assert_equal @domain.identifier, found_id
591
- end
592
-
593
- def test_scored_relationship_ordering
594
- project = Project.create(name: "Test Project")
595
- task1 = Task.create(title: "Low priority", priority: 1)
596
- task2 = Task.create(title: "High priority", priority: 10)
597
-
598
- project.tasks << task1
599
- project.tasks << task2
600
-
601
- # Should be ordered by priority (high to low)
602
- task_ids = project.tasks.range(0, -1, order: 'DESC')
603
- assert_equal task2.identifier, task_ids.first
604
- assert_equal task1.identifier, task_ids.last
605
- end
606
- end
200
+ # Direct collection access
201
+ customer.domains.size # Count
202
+ customer.domains.to_a # All IDs
203
+ customer.domains.range(0, 9) # First 10
607
204
  ```
608
205
 
609
- ### Complete Production Example
206
+ ### Working with Indexes
610
207
 
611
208
  ```ruby
612
- # Real-world e-commerce system
613
- class Customer < Familia::Horreum
614
- feature :relationships
615
- field :email, :username, :tier, :created_at, :last_activity
616
-
617
- # Global unique lookups
618
- class_indexed_by :email, :email_lookup
619
- class_indexed_by :username, :username_lookup
620
-
621
- # Tiered customer tracking
622
- class_participates_in :all_customers, score: :created_at
623
- class_participates_in :active_customers, score: :last_activity
624
- class_participates_in :premium_customers,
625
- score: ->(c) { c.tier == 'premium' ? c.last_activity : 0 }
626
-
627
- # Relationships
628
- set :orders, :addresses, :payment_methods
629
-
630
- def recent_orders(limit = 10)
631
- order_ids = orders.range(0, limit - 1)
632
- Order.multiget(*order_ids).compact
633
- end
634
-
635
- def total_spent
636
- recent_orders(100).sum(&:total_amount)
637
- end
638
- end
639
-
640
- class Order < Familia::Horreum
641
- feature :relationships
642
- field :total_amount, :status, :created_at, :order_number
643
-
644
- participates_in Customer, :orders, score: :created_at
645
- indexed_by :order_number, :order_lookup, target: Customer
646
- indexed_by :status, :status_lookup, target: Customer
647
-
648
- set :line_items
649
- end
650
-
651
- class Address < Familia::Horreum
652
- feature :relationships
653
- field :street, :city, :state, :zip_code, :address_type
654
-
655
- participates_in Customer, :addresses
656
- indexed_by :address_type, :address_type_lookup, target: Customer
657
- end
209
+ # Automatic class-level indexing
210
+ user = User.create(email: 'alice@example.com')
211
+ User.find_by_email('alice@example.com') # => user
658
212
 
659
- # Usage in production
660
- customer = Customer.create(
661
- email: "alice@example.com",
662
- username: "alice_smith",
663
- tier: "premium"
664
- )
213
+ # Manual scoped indexing
214
+ employee.add_to_company_badge_index(company)
215
+ company.find_by_badge_number('12345') # => employee
665
216
 
666
- # Automatic indexing and tracking
667
- Customer.find_by_email("alice@example.com") # => customer.identifier
668
- Customer.premium_customers.range(0, 9) # Recent premium customers
669
-
670
- # Complex relationship queries
671
- order = Order.create(order_number: "ORD-12345", status: "shipped")
672
- customer.orders << order
673
-
674
- customer.find_by_order_number("ORD-12345") # => order.identifier
675
- shipped_orders = customer.find_all_by_status(["shipped", "delivered"])
217
+ # Multi-value indexing
218
+ employee.add_to_company_dept_index(company)
219
+ engineers = company.find_all_by_department('engineering')
676
220
  ```
677
221
 
678
- ---
679
-
680
222
  ## See Also
681
223
 
682
- - **[Relationships Feature Guide](feature-relationships.md)** - Conceptual introduction to relationships
683
- - **[Technical Reference](../reference/api-technical.md#relationships-feature-v200-pre7)** - Advanced implementation patterns
684
- - **[Feature System Guide](feature-system.md)** - Understanding Familia's feature architecture
224
+ - [**Relationships Overview**](feature-relationships.md) - Core concepts and patterns
225
+ - [**Participation Guide**](feature-relationships-participation.md) - Detailed participation docs
226
+ - [**Indexing Guide**](feature-relationships-indexing.md) - Detailed indexing docs