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
@@ -0,0 +1,226 @@
1
+ # try/thread_safety/feature_registry_race_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require_relative '../support/helpers/test_helpers'
6
+
7
+ # Thread safety tests for feature registry initialization
8
+ #
9
+ # Tests concurrent feature registration to ensure that lazy initialization
10
+ # of @features_available and @feature_definitions doesn't result in
11
+ # corruption or missing feature registrations.
12
+ #
13
+ # These tests verify:
14
+ # 1. Concurrent feature registration on same class
15
+ # 2. Feature dependency resolution during concurrent registration
16
+ # 3. Feature metadata consistency
17
+ # 4. Feature activation during concurrent access
18
+
19
+ ## Concurrent feature registration on same class
20
+ module TestFeature1; end
21
+ module TestFeature2; end
22
+ module TestFeature3; end
23
+
24
+ class FeatureTestModel1 < Familia::Horreum
25
+ identifier_field :test_id
26
+ field :test_id
27
+
28
+ def init
29
+ @test_id ||= SecureRandom.hex(4)
30
+ end
31
+ end
32
+
33
+ FeatureTestModel1.instance_variable_set(:@features_available, nil)
34
+ FeatureTestModel1.instance_variable_set(:@feature_definitions, nil)
35
+
36
+ barrier = Concurrent::CyclicBarrier.new(20)
37
+ features = Concurrent::Array.new
38
+
39
+ threads = 20.times.map do |i|
40
+ Thread.new do
41
+ barrier.wait
42
+ feature_module = Module.new
43
+ feature_name = "test_feature_#{i}".to_sym
44
+ FeatureTestModel1.add_feature(feature_module, feature_name)
45
+ features << feature_name
46
+ end
47
+ end
48
+
49
+ threads.each(&:join)
50
+
51
+ [features.size, FeatureTestModel1.features_available.is_a?(Hash)]
52
+ #=> [20, true]
53
+
54
+ ## Feature activation during concurrent declaration
55
+ class FeatureTestModel2 < Familia::Horreum
56
+ identifier_field :test_id
57
+ field :test_id
58
+
59
+ def init
60
+ @test_id ||= SecureRandom.hex(4)
61
+ end
62
+ end
63
+
64
+ barrier = Concurrent::CyclicBarrier.new(15)
65
+ activated = Concurrent::Array.new
66
+
67
+ threads = 15.times.map do |i|
68
+ Thread.new do
69
+ barrier.wait
70
+ feature_name = "activated_feature_#{i}".to_sym
71
+ feature_module = Module.new
72
+ FeatureTestModel2.add_feature(feature_module, feature_name)
73
+ FeatureTestModel2.feature(feature_name)
74
+ activated << feature_name
75
+ end
76
+ end
77
+
78
+ threads.each(&:join)
79
+ activated.size
80
+ #=> 15
81
+
82
+ ## Concurrent feature registration with field groups
83
+ class FeatureFieldGroupModel < Familia::Horreum
84
+ identifier_field :test_id
85
+ field :test_id
86
+
87
+ def init
88
+ @test_id ||= SecureRandom.hex(4)
89
+ end
90
+ end
91
+
92
+ barrier = Concurrent::CyclicBarrier.new(20)
93
+ feature_groups = Concurrent::Array.new
94
+
95
+ threads = 20.times.map do |i|
96
+ Thread.new do
97
+ barrier.wait
98
+ feature_module = Module.new
99
+ feature_name = "grouped_feature_#{i}".to_sym
100
+ field_group = "group_#{i}".to_sym
101
+ FeatureFieldGroupModel.add_feature(feature_module, feature_name, field_group: field_group)
102
+ feature_groups << field_group
103
+ end
104
+ end
105
+
106
+ threads.each(&:join)
107
+ feature_groups.size
108
+ #=> 20
109
+
110
+ ## Feature dependency registration during concurrent access
111
+ class DependencyTestModel < Familia::Horreum
112
+ identifier_field :test_id
113
+ field :test_id
114
+
115
+ def init
116
+ @test_id ||= SecureRandom.hex(4)
117
+ end
118
+ end
119
+
120
+ barrier = Concurrent::CyclicBarrier.new(25)
121
+ dependencies = Concurrent::Array.new
122
+
123
+ threads = 25.times.map do |i|
124
+ Thread.new do
125
+ barrier.wait
126
+ feature_module = Module.new
127
+ feature_name = "dependent_feature_#{i}".to_sym
128
+ depends_on = i > 0 ? ["dependent_feature_#{i - 1}".to_sym] : []
129
+ DependencyTestModel.add_feature(feature_module, feature_name, depends_on: depends_on)
130
+ dependencies << feature_name
131
+ end
132
+ end
133
+
134
+ threads.each(&:join)
135
+ dependencies.size
136
+ #=> 25
137
+
138
+ ## Concurrent feature queries
139
+ class QueryTestModel < Familia::Horreum
140
+ identifier_field :test_id
141
+ field :test_id
142
+
143
+ def init
144
+ @test_id ||= SecureRandom.hex(4)
145
+ end
146
+ end
147
+
148
+ # Pre-register some features
149
+ 5.times do |i|
150
+ QueryTestModel.add_feature(Module.new, "query_feature_#{i}".to_sym)
151
+ end
152
+
153
+ barrier = Concurrent::CyclicBarrier.new(30)
154
+ query_results = Concurrent::Array.new
155
+
156
+ threads = 30.times.map do
157
+ Thread.new do
158
+ barrier.wait
159
+ available = QueryTestModel.features_available
160
+ query_results << available.keys.size
161
+ end
162
+ end
163
+
164
+ threads.each(&:join)
165
+ query_results.size
166
+ #=> 30
167
+
168
+
169
+ ## Feature metadata consistency during concurrent registration
170
+ class MetadataTestModel < Familia::Horreum
171
+ identifier_field :test_id
172
+ field :test_id
173
+
174
+ def init
175
+ @test_id ||= SecureRandom.hex(4)
176
+ end
177
+ end
178
+
179
+ barrier = Concurrent::CyclicBarrier.new(20)
180
+ metadata = Concurrent::Array.new
181
+
182
+ threads = 20.times.map do |i|
183
+ Thread.new do
184
+ barrier.wait
185
+ feature_module = Module.new do
186
+ def self.feature_name
187
+ "metadata_feature"
188
+ end
189
+ end
190
+ feature_name = "meta_feature_#{i}".to_sym
191
+ MetadataTestModel.add_feature(feature_module, feature_name)
192
+ metadata << [feature_name, MetadataTestModel.features_available[feature_name]]
193
+ end
194
+ end
195
+
196
+ threads.each(&:join)
197
+ metadata.size
198
+ #=> 20
199
+
200
+
201
+ ## Feature definitions hash consistency
202
+ class DefinitionsTestModel < Familia::Horreum
203
+ identifier_field :test_id
204
+ field :test_id
205
+
206
+ def init
207
+ @test_id ||= SecureRandom.hex(4)
208
+ end
209
+ end
210
+
211
+ barrier = Concurrent::CyclicBarrier.new(30)
212
+ definitions = Concurrent::Array.new
213
+
214
+ threads = 30.times.map do |i|
215
+ Thread.new do
216
+ barrier.wait
217
+ feature_module = Module.new
218
+ feature_name = "def_feature_#{i}".to_sym
219
+ DefinitionsTestModel.add_feature(feature_module, feature_name)
220
+ definitions << DefinitionsTestModel.feature_definitions.keys.size
221
+ end
222
+ end
223
+
224
+ threads.each(&:join)
225
+ definitions.size
226
+ #=> 30
@@ -0,0 +1,235 @@
1
+ # try/thread_safety/fiber_pipeline_isolation_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require_relative '../support/helpers/test_helpers'
6
+
7
+ # Thread safety tests for fiber-local pipeline storage
8
+ #
9
+ # Tests that fiber-local pipeline storage properly isolates pipelines
10
+ # between different fibers and threads, ensuring no cross-contamination.
11
+ #
12
+ # These tests verify:
13
+ # 1. Concurrent pipelines in different fibers are isolated
14
+ # 2. Pipeline context doesn't leak between fibers
15
+ # 3. Nested pipelines work correctly across fibers
16
+ # 4. Pipeline cleanup happens properly per fiber
17
+
18
+ ## Concurrent threads with isolated pipelines
19
+ results = Concurrent::Array.new
20
+ barrier = Concurrent::CyclicBarrier.new(10)
21
+
22
+ # Each thread has its own root fiber, so pipelines are isolated per-thread
23
+ threads = 10.times.map do |i|
24
+ Thread.new do
25
+ barrier.wait # Synchronize start for maximum contention
26
+ Familia.pipeline do
27
+ # Each thread's fiber should have isolated pipeline context
28
+ conn = Fiber[:familia_pipeline]
29
+ # Use direct Redis commands instead of save
30
+ conn.set("test:pipe:#{i}", "value_#{i}")
31
+ results << [i, conn.object_id, "pipe_#{i}"]
32
+ end
33
+ end
34
+ end
35
+
36
+ threads.each(&:join)
37
+
38
+ results
39
+ #==> _.size == 10
40
+ #==> _.map { |(i, _, _)| i }.sort == (0..9).to_a
41
+ #==> _.map { |(_, _, pipe_id)| pipe_id }.uniq.size == 10
42
+
43
+ ## Pipeline isolation across multiple threads
44
+ barrier = Concurrent::CyclicBarrier.new(15)
45
+ fiber_results = Concurrent::Array.new
46
+
47
+ # Each thread automatically has its own root fiber
48
+ threads = 15.times.map do |i|
49
+ Thread.new do
50
+ barrier.wait
51
+ Familia.pipeline do
52
+ conn = Fiber[:familia_pipeline]
53
+ fiber_results << [Thread.current.object_id, conn.object_id]
54
+ end
55
+ end
56
+ end
57
+
58
+ threads.each(&:join)
59
+
60
+ fiber_results
61
+ #==> _.size == 15
62
+ #==> _.map { |(thread_id, _)| thread_id }.uniq.size >= 10
63
+
64
+ ## Nested pipelines in same fiber maintain context
65
+ nested_results = Concurrent::Array.new
66
+
67
+ fiber = Fiber.new do
68
+ Familia.pipeline do
69
+ outer_conn = Fiber[:familia_pipeline]
70
+ nested_results << [:outer, outer_conn.object_id]
71
+
72
+ Familia.pipeline do
73
+ inner_conn = Fiber[:familia_pipeline]
74
+ nested_results << [:inner, inner_conn.object_id]
75
+ end
76
+
77
+ after_inner = Fiber[:familia_pipeline]
78
+ nested_results << [:after_inner, after_inner.object_id]
79
+ end
80
+
81
+ after_outer = Fiber[:familia_pipeline]
82
+ nested_results << [:after_outer, after_outer.nil?]
83
+ end
84
+
85
+ fiber.resume
86
+
87
+ nested_results
88
+ #==> _.size == 4
89
+ #==> _[0][1] == _[1][1] # Same connection for nested
90
+ #==> _[0][1] == _[2][1] # Same connection after inner
91
+ #==> _[3][1] == true # Cleaned up after outer
92
+
93
+ ## Pipeline context doesn't leak between sequential fibers
94
+ sequential_results = Concurrent::Array.new
95
+
96
+ 5.times do |i|
97
+ fiber = Fiber.new do
98
+ Familia.pipeline do
99
+ conn = Fiber[:familia_pipeline]
100
+ sequential_results << [i, conn.object_id]
101
+ end
102
+ end
103
+ fiber.resume
104
+ end
105
+
106
+ sequential_results
107
+ #==> _.size == 5
108
+ #==> _.map { |(i, _)| i } == [0, 1, 2, 3, 4]
109
+
110
+ ## Concurrent fiber creation and pipeline execution
111
+ creation_barrier = Concurrent::CyclicBarrier.new(20)
112
+ execution_results = Concurrent::Array.new
113
+
114
+ threads = 20.times.map do |i|
115
+ Thread.new do
116
+ creation_barrier.wait
117
+ fiber = Fiber.new do
118
+ begin
119
+ Familia.pipeline do
120
+ conn = Fiber[:familia_pipeline]
121
+ # Use direct Redis commands instead of save
122
+ conn.set("test:concurrent_pipe:#{i}", "value_#{i}")
123
+ execution_results << "concurrent_#{i}"
124
+ end
125
+ rescue => e
126
+ execution_results << [:error, e.class.name]
127
+ end
128
+ end
129
+ fiber.resume
130
+ end
131
+ end
132
+
133
+ threads.each(&:join)
134
+
135
+ [execution_results.size, execution_results.count { |r| r.is_a?(String) && r.start_with?('concurrent_') }]
136
+ #=> [20, 20]
137
+
138
+ ## Pipeline cleanup after exception
139
+ exception_results = Concurrent::Array.new
140
+
141
+ fiber = Fiber.new do
142
+ begin
143
+ Familia.pipeline do
144
+ exception_results << Fiber[:familia_pipeline].object_id
145
+ raise "Test exception"
146
+ end
147
+ rescue => e
148
+ exception_results << [:exception, e.message]
149
+ end
150
+ exception_results << [:after_exception, Fiber[:familia_pipeline].nil?]
151
+ end
152
+
153
+ fiber.resume
154
+
155
+ exception_results
156
+ #==> _.size == 3
157
+ #==> _[0].is_a?(Integer)
158
+ #==> _[1] == [:exception, "Test exception"]
159
+ #==> _[2] == [:after_exception, true]
160
+
161
+ ## Fiber switching during pipeline maintains context
162
+ switch_results = Concurrent::Array.new
163
+
164
+ fiber1 = Fiber.new do
165
+ Familia.pipeline do
166
+ conn1 = Fiber[:familia_pipeline]
167
+ switch_results << [:fiber1_before_yield, conn1.object_id]
168
+ Fiber.yield
169
+ conn1_after = Fiber[:familia_pipeline]
170
+ switch_results << [:fiber1_after_yield, conn1_after.object_id]
171
+ end
172
+ end
173
+
174
+ fiber2 = Fiber.new do
175
+ Familia.pipeline do
176
+ conn2 = Fiber[:familia_pipeline]
177
+ switch_results << [:fiber2, conn2.object_id]
178
+ end
179
+ end
180
+
181
+ fiber1.resume
182
+ fiber2.resume
183
+ fiber1.resume
184
+
185
+ switch_results
186
+ #==> _.size == 3
187
+ #==> _[0][1] == _[2][1] # Same connection before/after yield
188
+ #==> _[0][1] != _[1][1] # Different connections per fiber
189
+
190
+ ## Multiple pipelines per fiber (sequential)
191
+ multi_pipe_results = Concurrent::Array.new
192
+
193
+ fiber = Fiber.new do
194
+ 3.times do |i|
195
+ Familia.pipeline do
196
+ conn = Fiber[:familia_pipeline]
197
+ multi_pipe_results << [i, conn.object_id]
198
+ end
199
+ multi_pipe_results << [:between, Fiber[:familia_pipeline].nil?]
200
+ end
201
+ end
202
+
203
+ fiber.resume
204
+
205
+ multi_pipe_results
206
+ #==> _.size == 6
207
+ #==> _.select { |(label, _)| label == :between }.all? { |(_, is_nil)| is_nil }
208
+
209
+ ## Pipeline and transaction isolation in same fiber
210
+ mixed_results = Concurrent::Array.new
211
+
212
+ fiber = Fiber.new do
213
+ Familia.pipeline do
214
+ pipe_conn = Fiber[:familia_pipeline]
215
+ mixed_results << [:pipeline, pipe_conn.object_id]
216
+
217
+ Familia.transaction do
218
+ txn_conn = Fiber[:familia_transaction]
219
+ pipe_during_txn = Fiber[:familia_pipeline]
220
+ mixed_results << [:transaction, txn_conn.object_id]
221
+ mixed_results << [:pipeline_during_txn, pipe_during_txn.object_id]
222
+ end
223
+
224
+ pipe_after_txn = Fiber[:familia_pipeline]
225
+ mixed_results << [:pipeline_after_txn, pipe_after_txn.object_id]
226
+ end
227
+ end
228
+
229
+ fiber.resume
230
+
231
+ mixed_results
232
+ #==> _.size == 4
233
+ #==> _[0][1] == _[2][1] # Pipeline preserved during transaction
234
+ #==> _[0][1] == _[3][1] # Pipeline preserved after transaction
235
+ #==> _[0][1] != _[1][1] # Different connections for pipeline and transaction
@@ -0,0 +1,208 @@
1
+ # try/thread_safety/fiber_transaction_isolation_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require_relative '../support/helpers/test_helpers'
6
+
7
+ # Thread safety tests for fiber-local transaction storage
8
+ #
9
+ # Tests that fiber-local transaction storage properly isolates transactions
10
+ # between different fibers and threads, ensuring no cross-contamination.
11
+ #
12
+ # These tests verify:
13
+ # 1. Concurrent transactions in different fibers are isolated
14
+ # 2. Transaction context doesn't leak between fibers
15
+ # 3. Nested transactions work correctly across fibers
16
+ # 4. Transaction cleanup happens properly per fiber
17
+
18
+ ## Concurrent threads with isolated transactions
19
+ results = Concurrent::Array.new
20
+ barrier = Concurrent::CyclicBarrier.new(10)
21
+
22
+ # Each thread has its own root fiber, so transactions are isolated per-thread
23
+ threads = 10.times.map do |i|
24
+ Thread.new do
25
+ barrier.wait # Synchronize start for maximum contention
26
+ Familia.transaction do
27
+ # Each thread's fiber should have isolated transaction context
28
+ conn = Fiber[:familia_transaction]
29
+ # Use direct Redis commands instead of save (which is not allowed in transactions)
30
+ conn.set("test:txn:#{i}", "value_#{i}")
31
+ results << [i, conn.object_id, "txn_#{i}"]
32
+ end
33
+ end
34
+ end
35
+
36
+ threads.each(&:join)
37
+
38
+ results
39
+ #==> _.size == 10
40
+ #==> _.map { |(i, _, _)| i }.sort == (0..9).to_a
41
+ #==> _.map { |(_, _, txn_id)| txn_id }.uniq.size == 10
42
+
43
+ ## Transaction isolation across multiple threads
44
+ barrier = Concurrent::CyclicBarrier.new(15)
45
+ fiber_results = Concurrent::Array.new
46
+
47
+ # Each thread automatically has its own root fiber
48
+ threads = 15.times.map do |i|
49
+ Thread.new do
50
+ barrier.wait
51
+ Familia.transaction do
52
+ conn = Fiber[:familia_transaction]
53
+ fiber_results << [Thread.current.object_id, conn.object_id]
54
+ end
55
+ end
56
+ end
57
+
58
+ threads.each(&:join)
59
+
60
+ fiber_results
61
+ #==> _.size == 15
62
+ #==> _.map { |(thread_id, _)| thread_id }.uniq.size >= 10
63
+
64
+ ## Nested transactions in same fiber maintain context
65
+ nested_results = Concurrent::Array.new
66
+
67
+ fiber = Fiber.new do
68
+ Familia.transaction do
69
+ outer_conn = Fiber[:familia_transaction]
70
+ nested_results << [:outer, outer_conn.object_id]
71
+
72
+ Familia.transaction do
73
+ inner_conn = Fiber[:familia_transaction]
74
+ nested_results << [:inner, inner_conn.object_id]
75
+ end
76
+
77
+ after_inner = Fiber[:familia_transaction]
78
+ nested_results << [:after_inner, after_inner.object_id]
79
+ end
80
+
81
+ after_outer = Fiber[:familia_transaction]
82
+ nested_results << [:after_outer, after_outer.nil?]
83
+ end
84
+
85
+ fiber.resume
86
+
87
+ nested_results
88
+ #==> _.size == 4
89
+ #==> _[0][1] == _[1][1] # Same connection for nested
90
+ #==> _[0][1] == _[2][1] # Same connection after inner
91
+ #==> _[3][1] == true # Cleaned up after outer
92
+
93
+ ## Transaction context doesn't leak between sequential fibers
94
+ sequential_results = Concurrent::Array.new
95
+
96
+ 5.times do |i|
97
+ fiber = Fiber.new do
98
+ Familia.transaction do
99
+ conn = Fiber[:familia_transaction]
100
+ sequential_results << [i, conn.object_id]
101
+ end
102
+ end
103
+ fiber.resume
104
+ end
105
+
106
+ sequential_results
107
+ #==> _.size == 5
108
+ #==> _.map { |(i, _)| i } == [0, 1, 2, 3, 4]
109
+
110
+ ## Concurrent fiber creation and transaction execution
111
+ creation_barrier = Concurrent::CyclicBarrier.new(20)
112
+ execution_results = Concurrent::Array.new
113
+
114
+ threads = 20.times.map do |i|
115
+ Thread.new do
116
+ creation_barrier.wait
117
+ fiber = Fiber.new do
118
+ begin
119
+ Familia.transaction do
120
+ # Use direct Redis commands instead of save
121
+ conn = Fiber[:familia_transaction]
122
+ conn.set("test:concurrent:#{i}", "value_#{i}")
123
+ execution_results << "concurrent_#{i}"
124
+ end
125
+ rescue => e
126
+ execution_results << [:error, e.class.name]
127
+ end
128
+ end
129
+ fiber.resume
130
+ end
131
+ end
132
+
133
+ threads.each(&:join)
134
+
135
+ execution_results
136
+ #==> _.size == 20
137
+ #==> _.all? { |r| r.is_a?(String) && r.start_with?('concurrent_') }
138
+ #==> true
139
+
140
+ ## Transaction cleanup after exception
141
+ exception_results = Concurrent::Array.new
142
+
143
+ fiber = Fiber.new do
144
+ begin
145
+ Familia.transaction do
146
+ exception_results << Fiber[:familia_transaction].object_id
147
+ raise "Test exception"
148
+ end
149
+ rescue => e
150
+ exception_results << [:exception, e.message]
151
+ end
152
+ exception_results << [:after_exception, Fiber[:familia_transaction].nil?]
153
+ end
154
+
155
+ fiber.resume
156
+ exception_results
157
+ #==> _.size == 3
158
+ #==> _[0].is_a?(Integer)
159
+ #==> _[1] == [:exception, "Test exception"]
160
+ #==> _[2] == [:after_exception, true]
161
+
162
+ ## Fiber switching during transaction maintains context
163
+ switch_results = Concurrent::Array.new
164
+
165
+ fiber1 = Fiber.new do
166
+ Familia.transaction do
167
+ conn1 = Fiber[:familia_transaction]
168
+ switch_results << [:fiber1_before_yield, conn1.object_id]
169
+ Fiber.yield
170
+ conn1_after = Fiber[:familia_transaction]
171
+ switch_results << [:fiber1_after_yield, conn1_after.object_id]
172
+ end
173
+ end
174
+
175
+ fiber2 = Fiber.new do
176
+ Familia.transaction do
177
+ conn2 = Fiber[:familia_transaction]
178
+ switch_results << [:fiber2, conn2.object_id]
179
+ end
180
+ end
181
+
182
+ fiber1.resume
183
+ fiber2.resume
184
+ fiber1.resume
185
+
186
+ switch_results
187
+ #==> _.size == 3
188
+ #==> _[0][1] == _[2][1] # Same connection before/after yield
189
+ #==> _[0][1] != _[1][1] # Different connections per fiber
190
+
191
+ ## Multiple transactions per fiber (sequential)
192
+ multi_txn_results = Concurrent::Array.new
193
+
194
+ fiber = Fiber.new do
195
+ 3.times do |i|
196
+ Familia.transaction do
197
+ conn = Fiber[:familia_transaction]
198
+ multi_txn_results << [i, conn.object_id]
199
+ end
200
+ multi_txn_results << [:between, Fiber[:familia_transaction].nil?]
201
+ end
202
+ end
203
+
204
+ fiber.resume
205
+
206
+ multi_txn_results
207
+ #==> _.size == 6
208
+ #==> _.select { |(label, _)| label == :between }.all? { |(_, is_nil)| is_nil }