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
data/CLAUDE.md CHANGED
@@ -53,7 +53,7 @@ Add changelog fragment with each user-facing or documented change (optional but
53
53
  ### Known Issues & Quirks
54
54
  - **Reserved Keywords**: Cannot use `ttl`, `db`, `valkey`, `redis` as field names - use prefixed alternatives
55
55
  - **Empty Identifiers**: Cause stack overflow in key generation - validate before operations
56
- - **Connection Pool Race Conditions**: Thread safety issues under high concurrency
56
+ - **Lazy Initialization Races**: Connection chains and field collections use lazy initialization without synchronization (generally safe due to Ruby GIL, but not guaranteed)
57
57
 
58
58
  ### Debugging
59
59
  - **Database command logging**: You can request real-time Database command monitoring from the user
@@ -105,30 +105,55 @@ class User < Familia::Horreum
105
105
  end
106
106
  ```
107
107
 
108
- **Good - use the `init` hook instead:**
108
+ **Good - use the `init` hook to apply defaults (use `||=` not `=`):**
109
109
  ```ruby
110
110
  class User < Familia::Horreum
111
- def init(email = nil)
112
- @email = email # Called after super, related fields work
111
+ field :objid
112
+ field :email
113
+
114
+ # Called after Horreum sets fields from kwargs
115
+ # IMPORTANT: Use ||= to apply defaults, not = to override
116
+ def init
117
+ @objid ||= SecureRandom.uuid # Apply default only if not already set
118
+ _run_post_init_hooks # Additional setup logic
113
119
  end
114
120
  end
121
+
122
+ # This works correctly:
123
+ user = User.new(email: 'test@example.com')
124
+ user.objid # → generated UUID (applied by init)
125
+ user.email # → 'test@example.com' (set by Horreum from kwargs)
115
126
  ```
116
127
 
117
- **Good - call super explicitly:**
128
+ **Okay - if absolutely necessary, override and call super explicitly:**
118
129
  ```ruby
119
130
  class User < Familia::Horreum
120
131
  def initialize(email = nil, **kwargs)
121
- super(**kwargs) # Related fields initialized
122
- @email = email
132
+ super # Initializes related fields here and also calls init
133
+ @email ||= generate_email if email.nil?
123
134
  end
124
135
  end
125
136
  ```
126
137
 
127
- **Why this matters**: Familia's `initialize` method calls `initialize_relatives` to set up DataType objects (lists, sets, etc.). Without calling `super`, these objects remain nil and you'll get helpful errors pointing to the missing super call.
138
+ **Why this matters**: Familia's `initialize` method processes kwargs FIRST (setting fields), then calls `initialize_relatives` (setting up DataType objects), then calls your `init` hook. By the time `init` runs, kwargs have already been consumed and fields are set.
139
+
140
+ **The ||= Pattern Explained**:
141
+ ```ruby
142
+ # WRONG - overwrites what Horreum already set
143
+ def init
144
+ @email = generate_email # Overwrites the correct value
145
+ end
146
+
147
+ # RIGHT - applies default only if not already set
148
+ def init
149
+ @email ||= email # Preserves value Horreum set from kwargs
150
+ @email ||= 'default@example.com' # Apply fallback default if still nil
151
+ end
152
+ ```
128
153
 
129
154
  **When to use each approach:**
130
- - **Use `init` hook** (preferred): For simple initialization logic that doesn't need to intercept constructor arguments. The `init` method is called automatically after `super` with the same arguments passed to `new`.
131
- - **Use explicit `super`**: When you need full control over initialization order or need to transform arguments before passing to parent. Remember to pass `**kwargs` to preserve keyword argument handling.
155
+ - **Use `init` hook with `||=`** (preferred): Apply defaults, run validations, setup callbacks - any logic that should run after field initialization. Follows standard ORM lifecycle hook patterns.
156
+ - **Use explicit `super`**: Only when you need to intercept or transform arguments before Horreum processes them (rare).
132
157
 
133
158
  **DataType Definition**: Use class methods to define keystore database-backed attributes:
134
159
  ```ruby
@@ -166,3 +191,30 @@ end
166
191
  **Memory Efficiency**: Only non-nil values are stored in keystore database to optimize memory usage.
167
192
 
168
193
  **Thread Safety**: Data types are frozen after instantiation to ensure immutability.
194
+
195
+ ## Thread Safety Considerations
196
+
197
+ ### Current Thread Safety Status (as of 2025-10-21)
198
+
199
+ Familia has **good thread safety** for standard multi-threaded environments:
200
+
201
+ ### Testing Thread Safety
202
+
203
+ Thread safety tests are available in `try/thread_safety/`:
204
+ - **100% passing** (56/56 tests)
205
+ - **CyclicBarrier pattern** for maximum contention testing
206
+ - **Test execution**: ~300ms for full suite with 1,000+ concurrent operations
207
+ - **Production monitoring**: 10/10 monitoring tests passing
208
+
209
+ Run thread safety tests:
210
+ ```bash
211
+ bundle exec try --agent try/thread_safety/
212
+ bundle exec try --agent try/unit/thread_safety_monitor_try.rb
213
+ ```
214
+
215
+ ### Best Practices for Thread-Safe Usage
216
+
217
+ 1. **Configure Once at Startup**: Module-level configuration should be set before threads spawn
218
+ 2. **Use Immutable DataTypes**: Leverage the fact that DataType instances are frozen
219
+ 3. **Test Under Concurrency**: Use the patterns in `try/thread_safety/` to verify thread safety
220
+ 4. **Enable Production Monitoring**: Use `Familia.start_monitoring!` to track contention in production
data/Gemfile CHANGED
@@ -9,7 +9,7 @@ group :test do
9
9
  gem 'ruby-prof'
10
10
  gem 'stackprof'
11
11
  gem 'timecop', require: false
12
- gem 'tryouts', '~> 3.6.0', require: false
12
+ gem 'tryouts', '~> 3.7.1', require: false
13
13
  end
14
14
 
15
15
  group :development, :test do
@@ -17,10 +17,10 @@ group :development, :test do
17
17
  gem 'irb', '~> 1.15.2', require: false
18
18
  gem 'redcarpet', require: false
19
19
  gem 'reek', require: false
20
- gem 'rubocop', require: false
20
+ gem 'rubocop', '~> 1.81.1', require: false
21
21
  gem 'rubocop-performance', require: false
22
22
  gem 'rubocop-thread_safety', require: false
23
- gem 'solargraph', require: false
23
+ gem 'ruby-lsp', require: false
24
24
  gem 'yard', '~> 0.9', require: false
25
25
  end
26
26
 
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- familia (2.0.0.pre18)
4
+ familia (2.0.0.pre21)
5
5
  benchmark (~> 0.4)
6
+ concurrent-ruby (~> 1.3)
6
7
  connection_pool (~> 2.5)
7
8
  csv (~> 3.3)
8
9
  logger (~> 1.7)
@@ -15,14 +16,13 @@ GEM
15
16
  remote: https://rubygems.org/
16
17
  specs:
17
18
  ast (2.4.3)
18
- backport (1.2.0)
19
19
  base64 (0.3.0)
20
20
  benchmark (0.4.1)
21
21
  bigdecimal (3.2.3)
22
22
  concurrent-ruby (1.3.5)
23
23
  connection_pool (2.5.3)
24
24
  csv (3.3.5)
25
- date (3.4.1)
25
+ date (3.5.0)
26
26
  debug (1.11.0)
27
27
  irb (~> 1.10)
28
28
  reline (>= 0.3.8)
@@ -56,31 +56,19 @@ GEM
56
56
  dry-inflector (~> 1.0)
57
57
  dry-logic (~> 1.4)
58
58
  zeitwerk (~> 2.6)
59
- erb (5.0.2)
59
+ erb (5.1.3)
60
60
  ffi (1.17.2)
61
61
  ffi (1.17.2-arm64-darwin)
62
62
  io-console (0.8.1)
63
- irb (1.15.2)
63
+ irb (1.15.3)
64
64
  pp (>= 0.6.0)
65
65
  rdoc (>= 4.0.0)
66
66
  reline (>= 0.4.2)
67
- jaro_winkler (1.6.1)
68
- json (2.15.0)
69
- kramdown (2.5.1)
70
- rexml (>= 3.3.9)
71
- kramdown-parser-gfm (1.1.0)
72
- kramdown (~> 2.0)
67
+ json (2.15.1)
73
68
  language_server-protocol (3.17.0.5)
74
69
  lint_roller (1.1.0)
75
70
  logger (1.7.0)
76
- mini_portile2 (2.8.9)
77
- minitest (5.25.5)
78
- nokogiri (1.18.10)
79
- mini_portile2 (~> 2.8.2)
80
- racc (~> 1.4)
81
- nokogiri (1.18.10-arm64-darwin)
82
- racc (~> 1.4)
83
- observer (0.1.2)
71
+ minitest (5.26.0)
84
72
  oj (3.16.11)
85
73
  bigdecimal (>= 3.0)
86
74
  ostruct (>= 0.2)
@@ -91,10 +79,10 @@ GEM
91
79
  racc
92
80
  pastel (0.8.0)
93
81
  tty-color (~> 0.5)
94
- pp (0.6.2)
82
+ pp (0.6.3)
95
83
  prettyprint
96
84
  prettyprint (0.2.0)
97
- prism (1.5.1)
85
+ prism (1.6.0)
98
86
  psych (5.2.6)
99
87
  date
100
88
  stringio
@@ -104,9 +92,10 @@ GEM
104
92
  ffi (~> 1)
105
93
  rbs (3.9.5)
106
94
  logger
107
- rdoc (6.14.2)
95
+ rdoc (6.15.1)
108
96
  erb
109
97
  psych (>= 4.0.0)
98
+ tsort
110
99
  redcarpet (3.6.1)
111
100
  redis (5.4.1)
112
101
  redis-client (>= 0.22.0)
@@ -121,22 +110,20 @@ GEM
121
110
  regexp_parser (2.11.3)
122
111
  reline (0.6.2)
123
112
  io-console (~> 0.5)
124
- reverse_markdown (3.0.0)
125
- nokogiri
126
113
  rexml (3.4.1)
127
- rspec (3.13.1)
114
+ rspec (3.13.2)
128
115
  rspec-core (~> 3.13.0)
129
116
  rspec-expectations (~> 3.13.0)
130
117
  rspec-mocks (~> 3.13.0)
131
- rspec-core (3.13.5)
118
+ rspec-core (3.13.6)
132
119
  rspec-support (~> 3.13.0)
133
120
  rspec-expectations (3.13.5)
134
121
  diff-lcs (>= 1.2.0, < 2.0)
135
122
  rspec-support (~> 3.13.0)
136
- rspec-mocks (3.13.5)
123
+ rspec-mocks (3.13.7)
137
124
  diff-lcs (>= 1.2.0, < 2.0)
138
125
  rspec-support (~> 3.13.0)
139
- rspec-support (3.13.4)
126
+ rspec-support (3.13.6)
140
127
  rubocop (1.81.1)
141
128
  json (~> 2.3)
142
129
  language_server-protocol (~> 3.17.0.2)
@@ -159,44 +146,26 @@ GEM
159
146
  lint_roller (~> 1.1)
160
147
  rubocop (~> 1.72, >= 1.72.1)
161
148
  rubocop-ast (>= 1.44.0, < 2.0)
149
+ ruby-lsp (0.26.1)
150
+ language_server-protocol (~> 3.17.0)
151
+ prism (>= 1.2, < 2.0)
152
+ rbs (>= 3, < 5)
162
153
  ruby-prof (1.7.2)
163
154
  base64
164
155
  ruby-progressbar (1.13.0)
165
- solargraph (0.57.0)
166
- backport (~> 1.2)
167
- benchmark (~> 0.4)
168
- bundler (~> 2.0)
169
- diff-lcs (~> 1.4)
170
- jaro_winkler (~> 1.6, >= 1.6.1)
171
- kramdown (~> 2.3)
172
- kramdown-parser-gfm (~> 1.1)
173
- logger (~> 1.6)
174
- observer (~> 0.1)
175
- ostruct (~> 0.6)
176
- parser (~> 3.0)
177
- prism (~> 1.4)
178
- rbs (>= 3.6.1, <= 4.0.0.dev.4)
179
- reverse_markdown (~> 3.0)
180
- rubocop (~> 1.76)
181
- thor (~> 1.0)
182
- tilt (~> 2.0)
183
- yard (~> 0.9, >= 0.9.24)
184
- yard-activesupport-concern (~> 0.0)
185
- yard-solargraph (~> 0.1)
186
156
  stackprof (0.2.27)
187
157
  stringio (3.1.7)
188
- thor (1.4.0)
189
- tilt (2.6.1)
190
158
  timecop (0.9.10)
191
- tryouts (3.6.0)
192
- concurrent-ruby (~> 1.0)
159
+ tryouts (3.7.1)
160
+ concurrent-ruby (~> 1.0, < 2)
193
161
  irb
194
162
  minitest (~> 5.0)
195
163
  pastel (~> 0.8)
196
164
  prism (~> 1.0)
197
- rspec (~> 3.0)
165
+ rspec (>= 3.0, < 5.0)
198
166
  tty-cursor (~> 0.7)
199
167
  tty-screen (~> 0.8)
168
+ tsort (0.2.0)
200
169
  tty-color (0.6.0)
201
170
  tty-cursor (0.7.1)
202
171
  tty-screen (0.8.2)
@@ -205,10 +174,6 @@ GEM
205
174
  unicode-emoji (4.1.0)
206
175
  uri-valkey (1.4.0)
207
176
  yard (0.9.37)
208
- yard-activesupport-concern (0.0.1)
209
- yard (>= 0.8)
210
- yard-solargraph (0.1.0)
211
- yard (~> 0.9)
212
177
  zeitwerk (2.7.3)
213
178
 
214
179
  PLATFORMS
@@ -223,15 +188,15 @@ DEPENDENCIES
223
188
  rbnacl (~> 7.1, >= 7.1.1)
224
189
  redcarpet
225
190
  reek
226
- rubocop
191
+ rubocop (~> 1.81.1)
227
192
  rubocop-performance
228
193
  rubocop-thread_safety
194
+ ruby-lsp
229
195
  ruby-prof
230
- solargraph
231
196
  stackprof
232
197
  timecop
233
- tryouts (~> 3.6.0)
198
+ tryouts (~> 3.7.1)
234
199
  yard (~> 0.9)
235
200
 
236
201
  BUNDLED WITH
237
- 2.6.2
202
+ 2.7.2
data/README.md CHANGED
@@ -280,6 +280,7 @@ Flower.multiget("prose", "tulip", "daisy")
280
280
 
281
281
  ### Transactional Operations
282
282
 
283
+ **Horreum Model Transactions:**
283
284
  ```ruby
284
285
  user.transaction do |conn|
285
286
  conn.set("user:#{user.id}:status", "active")
@@ -287,6 +288,44 @@ user.transaction do |conn|
287
288
  end
288
289
  ```
289
290
 
291
+ **DataType Transactions** (standalone or parent-owned):
292
+ ```ruby
293
+ # Recommended: Use DataType methods for clean, automatic key handling
294
+ user.scores.transaction do
295
+ user.scores.add('level1', 100)
296
+ user.scores.add('level2', 200)
297
+ end
298
+
299
+ # Standalone DataType transaction (e.g., session storage)
300
+ session_key = Familia::StringKey.new('session:abc123')
301
+ session_key.transaction do
302
+ session_key.set(session_data)
303
+ session_key.expire(3600) # Atomic: both succeed or both fail
304
+ end
305
+
306
+ # Advanced: Connection available for low-level Redis commands
307
+ user.scores.transaction do |conn|
308
+ conn.zadd(user.scores.dbkey, 100, 'level1')
309
+ conn.hset(user.profile.dbkey, 'status', 'active')
310
+ end
311
+ ```
312
+
313
+ **Pipeline Operations** (batch commands for performance):
314
+ ```ruby
315
+ # Recommended: Use DataType methods
316
+ leaderboard.pipelined do
317
+ leaderboard.add('player1', 500)
318
+ leaderboard.add('player2', 600)
319
+ leaderboard.size
320
+ end
321
+
322
+ # Advanced: Raw Redis commands for fine-grained control
323
+ leaderboard.pipelined do |conn|
324
+ conn.zadd(leaderboard.dbkey, 500, 'player1')
325
+ conn.zadd(leaderboard.dbkey, 600, 'player2')
326
+ end
327
+ ```
328
+
290
329
  ### Advanced Patterns
291
330
 
292
331
  **Time-based Expiration:**
data/bin/try ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'try' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("tryouts", "try")
data/bin/tryouts ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'tryouts' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("tryouts", "tryouts")
@@ -0,0 +1,66 @@
1
+ .. Added
2
+ .. -----
3
+
4
+ .. Changed
5
+ .. -------
6
+
7
+ - **ExternalIdentifier Format Flexibility**: The `external_identifier` feature now supports customizable format templates via the `format` option. This allows you to control the entire format of generated external IDs, including the prefix, separator, and overall structure.
8
+
9
+ **Default format** (unchanged behavior):
10
+
11
+ .. code-block:: ruby
12
+
13
+ class User < Familia::Horreum
14
+ feature :external_identifier
15
+ end
16
+ user.extid # => "ext_abc123def456ghi789"
17
+
18
+ **Custom format with different prefix**:
19
+
20
+ .. code-block:: ruby
21
+
22
+ class Customer < Familia::Horreum
23
+ feature :external_identifier, format: 'cust_%{id}'
24
+ end
25
+ customer.extid # => "cust_abc123def456ghi789"
26
+
27
+ **Custom format with different separator**:
28
+
29
+ .. code-block:: ruby
30
+
31
+ class APIKey < Familia::Horreum
32
+ feature :external_identifier, format: 'api-%{id}'
33
+ end
34
+ key.extid # => "api-abc123def456ghi789"
35
+
36
+ **Custom format without traditional prefix**:
37
+
38
+ .. code-block:: ruby
39
+
40
+ class Resource < Familia::Horreum
41
+ feature :external_identifier, format: 'v2/%{id}'
42
+ end
43
+ resource.extid # => "v2/abc123def456ghi789"
44
+
45
+ The `format` option accepts a Ruby format string with the `%{id}` placeholder for the generated identifier. The default format is `'ext_%{id}'`. This provides complete flexibility for various ID formatting needs including different prefixes, separators (underscore, hyphen, slash), URL paths, or no prefix at all.
46
+
47
+ .. Deprecated
48
+ .. ----------
49
+
50
+ .. Removed
51
+ .. -------
52
+
53
+ .. Fixed
54
+ .. -----
55
+
56
+ .. Security
57
+ .. --------
58
+
59
+ .. Documentation
60
+ .. -------------
61
+
62
+ .. AI Assistance
63
+ .. -------------
64
+
65
+ - **Design Review**: Claude Code provided analysis of the current implementation and recommended several idiomatic Ruby approaches for format flexibility, ultimately suggesting the format template pattern using Ruby's native string formatting.
66
+ - **Implementation**: Claude Code implemented the format template feature including code changes, test cases, and documentation updates.
@@ -0,0 +1,44 @@
1
+ .. A new scriv changelog fragment.
2
+ ..
3
+ .. Uncomment the section that is right (remove the leading dots).
4
+ .. For top level release notes, leave all the headers commented out.
5
+ ..
6
+ Added
7
+ -----
8
+
9
+ - Bidirectional reverse collection methods for ``participates_in`` with ``_instances`` suffix (e.g., ``user.project_team_instances``, ``user.project_team_ids``). Supports union behavior for multiple collections and custom naming via ``as:`` parameter. Closes #179.
10
+
11
+ .. Changed
12
+ .. -------
13
+ ..
14
+ .. - A bullet item for the Changed category.
15
+ ..
16
+ .. Deprecated
17
+ .. ----------
18
+ ..
19
+ .. - A bullet item for the Deprecated category.
20
+ ..
21
+ .. Removed
22
+ .. -------
23
+ ..
24
+ .. - A bullet item for the Removed category.
25
+ ..
26
+ .. Fixed
27
+ .. -----
28
+ ..
29
+ .. - A bullet item for the Fixed category.
30
+ ..
31
+ .. Security
32
+ .. --------
33
+ ..
34
+ .. - A bullet item for the Security category.
35
+ ..
36
+ .. Documentation
37
+ .. -------------
38
+ ..
39
+ .. - A bullet item for the Documentation category.
40
+ ..
41
+ AI Assistance
42
+ -------------
43
+
44
+ - Claude Opus 4 assisted with implementation of bidirectional participation relationships using ``_instances`` suffix pattern. Pivoted from initial dry-inflector pluralization approach based on feedback.
@@ -0,0 +1,20 @@
1
+ .. Fixed mutex race conditions in thread safety implementation
2
+ ..
3
+
4
+ Fixed
5
+ -----
6
+
7
+ - Fixed critical race condition in mutex initialization for connection chain lazy loading. The mutex itself was being lazily initialized with ``||=``, which is not atomic and could result in multiple threads creating different mutex instances, defeating synchronization. Changed to eager initialization via ``Connection.included`` hook. (`lib/familia/horreum/connection.rb`)
8
+
9
+ - Fixed critical race condition in mutex initialization for logger lazy loading. Similar to connection chain issue, the logger mutex was lazily initialized with ``||=``. Changed to eager initialization at module definition time. (`lib/familia/logging.rb`)
10
+
11
+ - Fixed logger assignment atomicity issue where ``Familia.logger=`` set ``DatabaseLogger.logger`` outside the mutex synchronization block, potentially causing ``Familia.logger`` and ``DatabaseLogger.logger`` to be temporarily out of sync during concurrent access. Moved ``DatabaseLogger.logger`` assignment inside the synchronization block. (`lib/familia/logging.rb`)
12
+
13
+ - Added explicit return statement to ``Familia.logger`` method for robustness against future refactoring. (`lib/familia/logging.rb`)
14
+
15
+ AI Assistance
16
+ -------------
17
+
18
+ - Code review analysis to identify critical race conditions in mutex initialization
19
+ - Implementation of proper eager mutex initialization patterns
20
+ - Test file updates to reflect new initialization approach
@@ -0,0 +1,91 @@
1
+ .. Added
2
+ .. -----
3
+
4
+ .. Changed
5
+ .. -------
6
+
7
+ .. Deprecated
8
+ .. ----------
9
+
10
+ .. Removed
11
+ .. -------
12
+
13
+ .. Fixed
14
+ .. -----
15
+
16
+ - **Participation Relationships with Symbol/String Target Classes**: Fixed four bugs that occurred when calling `participates_in` with a Symbol or String target class instead of a Class object.
17
+
18
+ **Bug 1 - NoMethodError during relationship definition**:
19
+
20
+ The error was: ``private method 'member_by_config_name' called for module Familia``.
21
+
22
+ **Background**: The `participates_in` method supports flexible target class specifications:
23
+
24
+ .. code-block:: ruby
25
+
26
+ class Domain < Familia::Horreum
27
+ # All three forms should work:
28
+ participates_in Customer, :domains # Class object (always worked)
29
+ participates_in :Customer, :domains # Symbol (was broken)
30
+ participates_in 'Customer', :domains # String (was broken)
31
+ end
32
+
33
+ **Root Cause**: The method had redundant class resolution code that directly called the private `Familia.member_by_config_name` method instead of using the public `Familia.resolve_class` API.
34
+
35
+ **Solution**: Removed the redundant resolution code and now uses the already-resolved class from the public API, simplifying the implementation and fixing the visibility issue.
36
+
37
+ **Bug 2 - NoMethodError in current_participations**:
38
+
39
+ When calling `current_participations` on objects that used Symbol/String target classes, it would fail with ``undefined method 'familia_name' for Symbol``.
40
+
41
+ **Root Cause**: The `current_participations` method was calling `.familia_name` on `config.target_class`, which stores the original Symbol/String value passed to `participates_in`.
42
+
43
+ **Solution**: Use the resolved `target_class` variable instead of the stored config value. The resolved class is already available from the `Familia.resolve_class` call earlier in the method.
44
+
45
+ **Bug 3 - NoMethodError in target_class_config_name**:
46
+
47
+ When calling `current_participations`, the internal `target_class_config_name` method would fail with ``undefined method 'config_name' for Symbol``.
48
+
49
+ **Root Cause**: The `ParticipationRelationship.target_class_config_name` method was calling `.config_name` directly on the stored `target_class` value, which could be a Symbol or String.
50
+
51
+ **Solution**: Resolve the target class before calling `config_name` by using `Familia.resolve_class(target_class)`, which handles all input types (Class, Symbol, String) correctly.
52
+
53
+ **Bug 4 - Confusing error when target class not loaded**:
54
+
55
+ When the target class hasn't been loaded yet (load order issue), the error was: ``undefined method 'method_defined?' for nil``.
56
+
57
+ **Root Cause**: When `Familia.resolve_class` returns `nil` (because the target class isn't registered in `Familia.members` yet), the code would pass `nil` to `TargetMethods::Builder.build`, which then failed with a confusing error message that didn't explain the actual problem.
58
+
59
+ **Solution**: Added explicit nil check after `resolve_class` with a detailed ArgumentError that:
60
+
61
+ - Clearly states which target class couldn't be resolved
62
+ - Lists the three most common causes (load order, typo, not inheriting from Horreum)
63
+ - Shows all currently registered Familia classes for debugging
64
+ - Provides a clear solution for fixing the load order
65
+
66
+ **Impact**: Projects using Symbol or String target classes in `participates_in` declarations will now work correctly throughout the entire lifecycle, including relationship definition, method generation, and participation queries. When there's a load order issue or typo, developers get a clear, actionable error message instead of a confusing nil error. This pattern is common when avoiding circular dependencies or when target classes are defined in different files.
67
+
68
+ .. Security
69
+ .. --------
70
+
71
+ .. Documentation
72
+ .. -------------
73
+
74
+ .. AI Assistance
75
+ .. -------------
76
+
77
+ - **Root Cause Analysis**: Claude Code analyzed the error stack trace from the implementing project and identified that a private method was being called as a public method from outside the Familia module.
78
+ - **Fix Implementation**: Claude Code identified redundant class resolution code and simplified it to use the already-resolved class from the public API.
79
+ - **Test Coverage**: Claude Code created comprehensive regression tests including:
80
+
81
+ - Feature-level tests for Symbol/String target class resolution in participation relationships
82
+ - Unit tests for the `Familia.resolve_class` public API
83
+ - Edge case coverage for case-insensitive resolution and modularized classes
84
+
85
+ - **Second Bug Discovery**: During test execution, Claude Code discovered a related bug in `current_participations` that was also failing with Symbol/String target classes. The test coverage revealed that `.familia_name` was being called on the unresolved config value instead of the resolved class instance.
86
+
87
+ - **Third Bug Discovery**: Further test execution revealed another Symbol/String bug in `target_class_config_name`, where `.config_name` was being called directly on Symbol/String values. This was fixed by resolving the class first using `Familia.resolve_class`.
88
+
89
+ - **Test Coverage Refinement**: Claude Code identified and removed unrealistic test cases (all-uppercase, all-lowercase class names) that don't occur in real Ruby code and don't work with the `snake_case` method's design. Updated tests to focus on realistic naming conventions: PascalCase and snake_case, with clear documentation explaining why certain formats aren't supported.
90
+
91
+ - **Fourth Bug Discovery**: After merging to main, the implementing project revealed a load order issue where `Familia.resolve_class` returned `nil`, causing a confusing "undefined method for nil" error. Claude Code added explicit error handling with a detailed, actionable error message that helps developers quickly identify and fix load order issues, typos, or inheritance problems.