familia 2.0.0.pre18 → 2.0.0.pre21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +4 -9
  3. data/.github/workflows/code-smells.yml +64 -3
  4. data/.pre-commit-config.yaml +8 -6
  5. data/.reek.yml +10 -9
  6. data/.rubocop.yml +4 -0
  7. data/CHANGELOG.rst +205 -88
  8. data/CLAUDE.md +62 -10
  9. data/Gemfile +3 -3
  10. data/Gemfile.lock +27 -62
  11. data/README.md +39 -0
  12. data/bin/try +16 -0
  13. data/bin/tryouts +16 -0
  14. data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
  15. data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
  16. data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
  17. data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
  18. data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
  19. data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -0
  20. data/docs/1106-participates_in-bidirectional-solution.md +129 -0
  21. data/docs/guides/encryption.md +486 -0
  22. data/docs/guides/feature-encrypted-fields.md +123 -7
  23. data/docs/guides/feature-expiration.md +177 -133
  24. data/docs/guides/feature-external-identifiers.md +415 -443
  25. data/docs/guides/feature-object-identifiers.md +400 -269
  26. data/docs/guides/feature-quantization.md +120 -6
  27. data/docs/guides/feature-relationships-indexing.md +318 -0
  28. data/docs/guides/feature-relationships-methods.md +146 -604
  29. data/docs/guides/feature-relationships-participation.md +263 -0
  30. data/docs/guides/feature-relationships.md +118 -136
  31. data/docs/guides/feature-system-devs.md +176 -693
  32. data/docs/guides/feature-system.md +119 -6
  33. data/docs/guides/feature-transient-fields.md +81 -0
  34. data/docs/guides/field-system.md +778 -0
  35. data/docs/guides/index.md +32 -15
  36. data/docs/guides/logging.md +187 -0
  37. data/docs/guides/optimized-loading.md +674 -0
  38. data/docs/guides/thread-safety-monitoring.md +61 -0
  39. data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
  40. data/docs/migrating/v2.0.0-pre19.md +197 -0
  41. data/docs/migrating/v2.0.0-pre22.md +241 -0
  42. data/docs/overview.md +7 -9
  43. data/docs/reference/api-technical.md +267 -320
  44. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
  45. data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
  46. data/examples/autoloader/mega_customer.rb +2 -0
  47. data/examples/datatype_standalone.rb +282 -0
  48. data/examples/encrypted_fields.rb +2 -1
  49. data/examples/json_usage_patterns.rb +2 -0
  50. data/examples/relationships.rb +3 -0
  51. data/examples/safe_dump.rb +2 -1
  52. data/examples/sampling_demo.rb +53 -0
  53. data/examples/single_connection_transaction_confusions.rb +2 -1
  54. data/familia.gemspec +2 -1
  55. data/lib/familia/base.rb +2 -0
  56. data/lib/familia/connection/behavior.rb +254 -0
  57. data/lib/familia/connection/handlers.rb +97 -0
  58. data/lib/familia/connection/individual_command_proxy.rb +2 -0
  59. data/lib/familia/connection/middleware.rb +34 -24
  60. data/lib/familia/connection/operation_core.rb +3 -1
  61. data/lib/familia/connection/operations.rb +2 -0
  62. data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +4 -2
  63. data/lib/familia/connection/transaction_core.rb +75 -9
  64. data/lib/familia/connection.rb +21 -5
  65. data/lib/familia/data_type/class_methods.rb +3 -1
  66. data/lib/familia/data_type/connection.rb +153 -7
  67. data/lib/familia/data_type/database_commands.rb +9 -4
  68. data/lib/familia/data_type/serialization.rb +10 -4
  69. data/lib/familia/data_type/settings.rb +2 -0
  70. data/lib/familia/data_type/types/counter.rb +2 -0
  71. data/lib/familia/data_type/types/hashkey.rb +8 -6
  72. data/lib/familia/data_type/types/listkey.rb +2 -0
  73. data/lib/familia/data_type/types/lock.rb +2 -0
  74. data/lib/familia/data_type/types/sorted_set.rb +2 -0
  75. data/lib/familia/data_type/types/stringkey.rb +2 -0
  76. data/lib/familia/data_type/types/unsorted_set.rb +2 -0
  77. data/lib/familia/data_type.rb +2 -0
  78. data/lib/familia/encryption/encrypted_data.rb +4 -2
  79. data/lib/familia/encryption/manager.rb +2 -0
  80. data/lib/familia/encryption/provider.rb +2 -0
  81. data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
  82. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
  83. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
  84. data/lib/familia/encryption/registry.rb +2 -0
  85. data/lib/familia/encryption/request_cache.rb +2 -0
  86. data/lib/familia/encryption.rb +9 -2
  87. data/lib/familia/errors.rb +53 -14
  88. data/lib/familia/features/autoloader.rb +2 -0
  89. data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
  90. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
  91. data/lib/familia/features/encrypted_fields.rb +2 -2
  92. data/lib/familia/features/expiration/extensions.rb +11 -11
  93. data/lib/familia/features/expiration.rb +29 -21
  94. data/lib/familia/features/external_identifier.rb +33 -7
  95. data/lib/familia/features/object_identifier.rb +2 -0
  96. data/lib/familia/features/quantization.rb +3 -1
  97. data/lib/familia/features/relationships/README.md +3 -1
  98. data/lib/familia/features/relationships/collection_operations.rb +2 -0
  99. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +177 -47
  100. data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
  101. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +203 -63
  102. data/lib/familia/features/relationships/indexing.rb +40 -42
  103. data/lib/familia/features/relationships/indexing_relationship.rb +17 -5
  104. data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
  105. data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
  106. data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
  107. data/lib/familia/features/relationships/participation.rb +155 -69
  108. data/lib/familia/features/relationships/participation_membership.rb +69 -0
  109. data/lib/familia/features/relationships/participation_relationship.rb +34 -6
  110. data/lib/familia/features/relationships/score_encoding.rb +2 -0
  111. data/lib/familia/features/relationships.rb +5 -3
  112. data/lib/familia/features/safe_dump.rb +2 -0
  113. data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
  114. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
  115. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
  116. data/lib/familia/features/transient_fields.rb +2 -0
  117. data/lib/familia/features.rb +2 -0
  118. data/lib/familia/field_type.rb +5 -2
  119. data/lib/familia/horreum/connection.rb +28 -36
  120. data/lib/familia/horreum/database_commands.rb +131 -10
  121. data/lib/familia/horreum/definition.rb +18 -7
  122. data/lib/familia/horreum/management.rb +233 -57
  123. data/lib/familia/horreum/persistence.rb +314 -122
  124. data/lib/familia/horreum/related_fields.rb +2 -0
  125. data/lib/familia/horreum/serialization.rb +26 -4
  126. data/lib/familia/horreum/settings.rb +2 -0
  127. data/lib/familia/horreum/utils.rb +2 -8
  128. data/lib/familia/horreum.rb +46 -13
  129. data/lib/familia/identifier_extractor.rb +2 -0
  130. data/lib/familia/instrumentation.rb +156 -0
  131. data/lib/familia/json_serializer.rb +2 -0
  132. data/lib/familia/logging.rb +94 -37
  133. data/lib/familia/refinements/dear_json.rb +2 -0
  134. data/lib/familia/refinements/stylize_words.rb +2 -14
  135. data/lib/familia/refinements/time_literals.rb +2 -0
  136. data/lib/familia/refinements.rb +2 -0
  137. data/lib/familia/secure_identifier.rb +10 -2
  138. data/lib/familia/settings.rb +9 -7
  139. data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
  140. data/lib/familia/thread_safety/monitor.rb +328 -0
  141. data/lib/familia/utils.rb +13 -0
  142. data/lib/familia/verifiable_identifier.rb +3 -1
  143. data/lib/familia/version.rb +3 -1
  144. data/lib/familia.rb +31 -4
  145. data/lib/middleware/database_command_counter.rb +152 -0
  146. data/lib/middleware/database_logger.rb +325 -129
  147. data/lib/multi_result.rb +2 -0
  148. data/try/edge_cases/empty_identifiers_try.rb +2 -0
  149. data/try/edge_cases/hash_symbolization_try.rb +2 -0
  150. data/try/edge_cases/json_serialization_try.rb +2 -0
  151. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
  152. data/try/edge_cases/race_conditions_try.rb +4 -0
  153. data/try/edge_cases/reserved_keywords_try.rb +4 -0
  154. data/try/edge_cases/string_coercion_try.rb +6 -4
  155. data/try/edge_cases/ttl_side_effects_try.rb +4 -0
  156. data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
  157. data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
  158. data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
  159. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
  160. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
  161. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
  162. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
  163. data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
  164. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
  165. data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
  166. data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
  167. data/try/features/encrypted_fields/memory_security_try.rb +4 -0
  168. data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
  169. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
  170. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
  171. data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
  172. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
  173. data/try/features/encryption/config_persistence_try.rb +4 -0
  174. data/try/features/encryption/core_try.rb +4 -0
  175. data/try/features/encryption/instance_variable_scope_try.rb +4 -0
  176. data/try/features/encryption/module_loading_try.rb +4 -0
  177. data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
  178. data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
  179. data/try/features/encryption/roundtrip_validation_try.rb +4 -0
  180. data/try/features/encryption/secure_memory_handling_try.rb +4 -0
  181. data/try/features/expiration/expiration_try.rb +5 -1
  182. data/try/features/external_identifier/external_identifier_try.rb +171 -8
  183. data/try/features/feature_dependencies_try.rb +2 -0
  184. data/try/features/feature_improvements_try.rb +2 -0
  185. data/try/features/field_groups_try.rb +2 -0
  186. data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
  187. data/try/features/object_identifier/object_identifier_try.rb +2 -0
  188. data/try/features/quantization/quantization_try.rb +4 -0
  189. data/try/features/real_feature_integration_try.rb +2 -0
  190. data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
  191. data/try/features/relationships/indexing_rebuild_try.rb +600 -0
  192. data/try/features/relationships/indexing_try.rb +30 -4
  193. data/try/features/relationships/participation_bidirectional_try.rb +242 -0
  194. data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
  195. data/try/features/relationships/participation_commands_verification_try.rb +2 -0
  196. data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
  197. data/try/features/relationships/participation_reverse_index_try.rb +15 -13
  198. data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
  199. data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
  200. data/try/features/relationships/relationships_api_changes_try.rb +6 -4
  201. data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
  202. data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
  203. data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
  204. data/try/features/relationships/relationships_performance_try.rb +4 -0
  205. data/try/features/relationships/relationships_performance_working_try.rb +4 -0
  206. data/try/features/relationships/relationships_try.rb +6 -4
  207. data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
  208. data/try/features/safe_dump/safe_dump_try.rb +4 -0
  209. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  210. data/try/features/transient_fields/refresh_reset_try.rb +3 -0
  211. data/try/features/transient_fields/simple_refresh_test.rb +3 -0
  212. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  213. data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
  214. data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
  215. data/try/integration/connection/fiber_context_preservation_try.rb +7 -3
  216. data/try/integration/connection/handler_constraints_try.rb +4 -0
  217. data/try/integration/connection/isolated_dbclient_try.rb +4 -0
  218. data/try/integration/connection/middleware_reconnect_try.rb +2 -0
  219. data/try/integration/connection/operation_mode_guards_try.rb +5 -1
  220. data/try/integration/connection/pipeline_fallback_integration_try.rb +15 -12
  221. data/try/integration/connection/pools_try.rb +4 -0
  222. data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
  223. data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
  224. data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
  225. data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
  226. data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
  227. data/try/integration/connection/transaction_modes_try.rb +4 -0
  228. data/try/integration/conventional_inheritance_try.rb +4 -0
  229. data/try/integration/create_method_try.rb +26 -22
  230. data/try/integration/cross_component_try.rb +4 -0
  231. data/try/integration/data_types/datatype_pipelines_try.rb +108 -0
  232. data/try/integration/data_types/datatype_transactions_try.rb +251 -0
  233. data/try/integration/database_consistency_try.rb +4 -0
  234. data/try/integration/familia_extended_try.rb +4 -0
  235. data/try/integration/familia_members_methods_try.rb +4 -0
  236. data/try/integration/models/customer_safe_dump_try.rb +9 -1
  237. data/try/integration/models/customer_try.rb +4 -0
  238. data/try/integration/models/datatype_base_try.rb +4 -0
  239. data/try/integration/models/familia_object_try.rb +5 -1
  240. data/try/integration/persistence_operations_try.rb +166 -10
  241. data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
  242. data/try/integration/save_methods_consistency_try.rb +241 -0
  243. data/try/integration/scenarios_try.rb +4 -0
  244. data/try/integration/secure_identifier_try.rb +4 -0
  245. data/try/integration/transaction_safety_core_try.rb +176 -0
  246. data/try/integration/transaction_safety_workflow_try.rb +291 -0
  247. data/try/integration/verifiable_identifier_try.rb +4 -0
  248. data/try/investigation/pipeline_routing/README.md +228 -0
  249. data/try/performance/benchmarks_try.rb +4 -0
  250. data/try/performance/transaction_safety_benchmark_try.rb +238 -0
  251. data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
  252. data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
  253. data/try/support/debugging/cache_behavior_tracer.rb +4 -0
  254. data/try/support/debugging/debug_aad_process.rb +3 -0
  255. data/try/support/debugging/debug_concealed_internal.rb +3 -0
  256. data/try/support/debugging/debug_concealed_reveal.rb +3 -0
  257. data/try/support/debugging/debug_context_aad.rb +3 -0
  258. data/try/support/debugging/debug_context_simple.rb +3 -0
  259. data/try/support/debugging/debug_cross_context.rb +3 -0
  260. data/try/support/debugging/debug_database_load.rb +3 -0
  261. data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
  262. data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
  263. data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
  264. data/try/support/debugging/debug_field_decrypt.rb +3 -0
  265. data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
  266. data/try/support/debugging/debug_load_path.rb +3 -0
  267. data/try/support/debugging/debug_method_definition.rb +3 -0
  268. data/try/support/debugging/debug_method_resolution.rb +3 -0
  269. data/try/support/debugging/debug_minimal.rb +3 -0
  270. data/try/support/debugging/debug_provider.rb +3 -0
  271. data/try/support/debugging/debug_secure_behavior.rb +3 -0
  272. data/try/support/debugging/debug_string_class.rb +3 -0
  273. data/try/support/debugging/debug_test.rb +3 -0
  274. data/try/support/debugging/debug_test_design.rb +3 -0
  275. data/try/support/debugging/encryption_method_tracer.rb +4 -0
  276. data/try/support/debugging/provider_diagnostics.rb +4 -0
  277. data/try/support/helpers/test_cleanup.rb +4 -0
  278. data/try/support/helpers/test_helpers.rb +5 -0
  279. data/try/support/memory/memory_basic_test.rb +4 -0
  280. data/try/support/memory/memory_detailed_test.rb +4 -0
  281. data/try/support/memory/memory_search_for_string.rb +4 -0
  282. data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
  283. data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
  284. data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
  285. data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
  286. data/try/support/prototypes/atomic_saves_v4.rb +4 -0
  287. data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
  288. data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  289. data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
  290. data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  291. data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
  292. data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
  293. data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
  294. data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
  295. data/try/support/prototypes/pooling/pool_siege.rb +4 -2
  296. data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
  297. data/try/thread_safety/README.md +496 -0
  298. data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
  299. data/try/thread_safety/connection_chain_race_try.rb +148 -0
  300. data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
  301. data/try/thread_safety/feature_registry_race_try.rb +226 -0
  302. data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
  303. data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
  304. data/try/thread_safety/field_registration_race_try.rb +222 -0
  305. data/try/thread_safety/logger_initialization_race_try.rb +170 -0
  306. data/try/thread_safety/middleware_registration_race_try.rb +154 -0
  307. data/try/thread_safety/module_config_race_try.rb +175 -0
  308. data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
  309. data/try/unit/core/autoloader_try.rb +4 -0
  310. data/try/unit/core/base_enhancements_try.rb +4 -0
  311. data/try/unit/core/connection_try.rb +4 -0
  312. data/try/unit/core/errors_try.rb +4 -0
  313. data/try/unit/core/extensions_try.rb +4 -0
  314. data/try/unit/core/familia_logger_try.rb +2 -0
  315. data/try/unit/core/familia_try.rb +4 -0
  316. data/try/unit/core/middleware_sampling_try.rb +335 -0
  317. data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
  318. data/try/unit/core/middleware_thread_safety_try.rb +245 -0
  319. data/try/unit/core/middleware_try.rb +4 -0
  320. data/try/unit/core/settings_try.rb +4 -0
  321. data/try/unit/core/time_utils_try.rb +4 -0
  322. data/try/unit/core/tools_try.rb +4 -0
  323. data/try/unit/core/utils_try.rb +37 -0
  324. data/try/unit/data_types/boolean_try.rb +5 -1
  325. data/try/unit/data_types/counter_try.rb +4 -0
  326. data/try/unit/data_types/datatype_base_try.rb +4 -0
  327. data/try/unit/data_types/hash_try.rb +4 -0
  328. data/try/unit/data_types/list_try.rb +4 -0
  329. data/try/unit/data_types/lock_try.rb +4 -0
  330. data/try/unit/data_types/sorted_set_try.rb +4 -0
  331. data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
  332. data/try/unit/data_types/string_try.rb +5 -1
  333. data/try/unit/data_types/unsortedset_try.rb +4 -0
  334. data/try/unit/familia_resolve_class_try.rb +116 -0
  335. data/try/unit/horreum/auto_indexing_on_save_try.rb +36 -16
  336. data/try/unit/horreum/automatic_index_validation_try.rb +255 -0
  337. data/try/unit/horreum/base_try.rb +5 -1
  338. data/try/unit/horreum/class_methods_try.rb +6 -2
  339. data/try/unit/horreum/commands_try.rb +4 -0
  340. data/try/unit/horreum/defensive_initialization_try.rb +4 -0
  341. data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +4 -0
  342. data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
  343. data/try/unit/horreum/field_categories_try.rb +4 -0
  344. data/try/unit/horreum/field_definition_try.rb +4 -0
  345. data/try/unit/horreum/initialization_try.rb +5 -1
  346. data/try/unit/horreum/json_type_preservation_try.rb +2 -0
  347. data/try/unit/horreum/optimized_loading_try.rb +156 -0
  348. data/try/unit/horreum/relations_try.rb +8 -4
  349. data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
  350. data/try/unit/horreum/serialization_try.rb +6 -2
  351. data/try/unit/horreum/settings_try.rb +4 -0
  352. data/try/unit/horreum/unique_index_edge_cases_try.rb +380 -0
  353. data/try/unit/horreum/unique_index_guard_validation_try.rb +283 -0
  354. data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
  355. data/try/unit/middleware/database_logger_methods_try.rb +251 -0
  356. data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
  357. data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
  358. data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
  359. data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
  360. data/try/unit/thread_safety_monitor_try.rb +149 -0
  361. metadata +81 -14
  362. data/.github/workflows/code-quality.yml +0 -138
  363. data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
  364. data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
  365. data/docs/archive/FAMILIA_UPDATE.md +0 -226
  366. data/docs/archive/README.md +0 -64
  367. data/docs/archive/api-reference.md +0 -333
  368. data/docs/guides/core-field-system.md +0 -806
  369. data/docs/guides/implementation.md +0 -276
  370. data/docs/guides/security-model.md +0 -183
@@ -1,3 +1,7 @@
1
+ # try/integration/create_method_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/core/create_method_try.rb
2
6
  #
3
7
  # Comprehensive test coverage for the create method
@@ -37,7 +41,7 @@ end
37
41
  # =============================================
38
42
 
39
43
  ## create method successfully creates new object
40
- @created_obj = CreateTestModel.create(id: @first_test_id, name: 'Created Object', value: 'test_value')
44
+ @created_obj = CreateTestModel.create!(id: @first_test_id, name: 'Created Object', value: 'test_value')
41
45
  [@created_obj.class, @created_obj.exists?, @created_obj.name]
42
46
  #=> [CreateTestModel, true, 'Created Object']
43
47
 
@@ -55,17 +59,17 @@ end
55
59
  # =============================================
56
60
 
57
61
  ## create method raises RecordExistsError for duplicate
58
- CreateTestModel.create(id: @first_test_id, name: 'Duplicate Attempt')
62
+ CreateTestModel.create!(id: @first_test_id, name: 'Duplicate Attempt')
59
63
  #=!> Familia::RecordExistsError
60
64
 
61
65
  ## RecordExistsError includes the dbkey in the message
62
- CreateTestModel.create(id: @first_test_id, name: 'Another Duplicate')
66
+ CreateTestModel.create!(id: @first_test_id, name: 'Another Duplicate')
63
67
  #=!> Familia::RecordExistsError
64
68
  #==> !!error.message.match(/create_test_model:#{@first_test_id}:object/)
65
69
 
66
70
  ## RecordExistsError message follows consistent format
67
71
  begin
68
- CreateTestModel.create(id: @first_test_id, name: 'Yet Another Duplicate')
72
+ CreateTestModel.create!(id: @first_test_id, name: 'Yet Another Duplicate')
69
73
  false # Should not reach here
70
74
  rescue Familia::RecordExistsError => e
71
75
  e.message.start_with?('Key already exists:')
@@ -74,10 +78,10 @@ end
74
78
 
75
79
  ## RecordExistsError exposes key property for programmatic access
76
80
  @final_test_id = next_test_id
77
- CreateTestModel.create(id: @final_test_id, name: 'Setup for Key Test')
81
+ CreateTestModel.create!(id: @final_test_id, name: 'Setup for Key Test')
78
82
 
79
83
  begin
80
- CreateTestModel.create(id: @final_test_id, name: 'Key Test Duplicate')
84
+ CreateTestModel.create!(id: @final_test_id, name: 'Key Test Duplicate')
81
85
  false # Should not reach here
82
86
  rescue Familia::RecordExistsError => e
83
87
  # Key should be accessible and contain the identifier
@@ -90,22 +94,22 @@ end
90
94
  # =============================================
91
95
 
92
96
  ## create with empty identifier raises NoIdentifier error
93
- CreateTestModel.create(id: '')
97
+ CreateTestModel.create!(id: '')
94
98
  #=!> Familia::NoIdentifier
95
99
 
96
100
  ## create with nil identifier raises NoIdentifier error
97
- CreateTestModel.create(id: nil)
101
+ CreateTestModel.create!(id: nil)
98
102
  #=!> Familia::NoIdentifier
99
103
 
100
104
  ## create with only some fields set
101
105
  @partial_id = next_test_id
102
- @partial_obj = CreateTestModel.create(id: @partial_id, name: 'Partial Object')
106
+ @partial_obj = CreateTestModel.create!(id: @partial_id, name: 'Partial Object')
103
107
  [@partial_obj.exists?, @partial_obj.name, @partial_obj.value]
104
108
  #=> [true, 'Partial Object', nil]
105
109
 
106
110
  ## create with no additional fields (only identifier)
107
111
  @minimal_id = next_test_id
108
- @minimal_obj = CreateTestModel.create(id: @minimal_id)
112
+ @minimal_obj = CreateTestModel.create!(id: @minimal_id)
109
113
  [@minimal_obj.exists?, @minimal_obj.id]
110
114
  #=> [true, @minimal_id]
111
115
 
@@ -115,14 +119,14 @@ CreateTestModel.create(id: nil)
115
119
 
116
120
  ## create is atomic - no partial state on failure
117
121
  @concurrent_id = next_test_id
118
- @first_obj = CreateTestModel.create(id: @concurrent_id, name: 'First')
122
+ @first_obj = CreateTestModel.create!(id: @concurrent_id, name: 'First')
119
123
 
120
124
  # Verify first object exists
121
125
  first_exists = @first_obj.exists?
122
126
 
123
127
  # Attempt to create duplicate should not affect existing object
124
128
  begin
125
- CreateTestModel.create(id: @concurrent_id, name: 'Concurrent Attempt')
129
+ CreateTestModel.create!(id: @concurrent_id, name: 'Concurrent Attempt')
126
130
  false # Should not reach here
127
131
  rescue Familia::RecordExistsError
128
132
  # Original object should be unchanged
@@ -134,7 +138,7 @@ end
134
138
  ## create failure doesn't leave partial data
135
139
  before_failed_create = Familia.dbclient.keys("create_test_model:#{@concurrent_id}:*").length
136
140
  begin
137
- CreateTestModel.create(id: @concurrent_id, name: 'Should Fail')
141
+ CreateTestModel.create!(id: @concurrent_id, name: 'Should Fail')
138
142
  rescue Familia::RecordExistsError
139
143
  # Should not create any additional keys
140
144
  after_failed_create = Familia.dbclient.keys("create_test_model:#{@concurrent_id}:*").length
@@ -148,20 +152,20 @@ end
148
152
 
149
153
  ## Both create and save_if_not_exists raise same error type for duplicates
150
154
  @consistency_id = next_test_id
151
- @consistency_obj = CreateTestModel.create(id: @consistency_id, name: 'Consistency Test')
155
+ @consistency_obj = CreateTestModel.create!(id: @consistency_id, name: 'Consistency Test')
152
156
 
153
157
  # Test create raises RecordExistsError
154
158
  create_error_class = begin
155
- CreateTestModel.create(id: @consistency_id, name: 'Create Duplicate')
159
+ CreateTestModel.create!(id: @consistency_id, name: 'Create Duplicate')
156
160
  nil
157
161
  rescue => e
158
162
  e.class
159
163
  end
160
164
 
161
- # Test save_if_not_exists raises RecordExistsError
165
+ # Test save_if_not_exists! raises RecordExistsError
162
166
  sine_error_class = begin
163
167
  duplicate_obj = CreateTestModel.new(id: @consistency_id, name: 'SINE Duplicate')
164
- duplicate_obj.save_if_not_exists
168
+ duplicate_obj.save_if_not_exists!
165
169
  nil
166
170
  rescue => e
167
171
  e.class
@@ -172,17 +176,17 @@ end
172
176
 
173
177
  ## Both methods have similar error message patterns
174
178
  @error_comparison_id = next_test_id
175
- CreateTestModel.create(id: @error_comparison_id, name: 'Error Comparison')
179
+ CreateTestModel.create!(id: @error_comparison_id, name: 'Error Comparison')
176
180
 
177
181
  create_error_msg = begin
178
- CreateTestModel.create(id: @error_comparison_id, name: 'Create Error')
182
+ CreateTestModel.create!(id: @error_comparison_id, name: 'Create Error')
179
183
  nil
180
184
  rescue => e
181
185
  e.message
182
186
  end
183
187
 
184
188
  sine_error_msg = begin
185
- CreateTestModel.new(id: @error_comparison_id, name: 'SINE Error').save_if_not_exists
189
+ CreateTestModel.new(id: @error_comparison_id, name: 'SINE Error').save_if_not_exists!
186
190
  nil
187
191
  rescue => e
188
192
  e.message
@@ -198,7 +202,7 @@ end
198
202
 
199
203
  ## create works with complex field values
200
204
  @complex_id = next_test_id
201
- @complex_obj = CreateTestModel.create(
205
+ @complex_obj = CreateTestModel.create!(
202
206
  id: @complex_id,
203
207
  name: 'Complex Object',
204
208
  value: { nested: 'data', array: [1, 2, 3] }
@@ -214,7 +218,7 @@ end
214
218
  @consistency_check_id = next_test_id
215
219
 
216
220
  # Create via class method
217
- @class_created = CreateTestModel.create(id: @consistency_check_id, name: 'Class Created')
221
+ @class_created = CreateTestModel.create!(id: @consistency_check_id, name: 'Class Created')
218
222
 
219
223
  # Both class and instance methods should see the object as existing
220
224
  class_sees_exists = CreateTestModel.exists?(@consistency_check_id)
@@ -1,3 +1,7 @@
1
+ # try/integration/cross_component_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # Test cross-component integration scenarios
2
6
 
3
7
  require_relative '../support/helpers/test_helpers'
@@ -0,0 +1,108 @@
1
+ # try/integration/data_types/datatype_pipelines_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ # DataType Pipeline Support Tryouts
6
+ #
7
+ # Tests pipeline support for DataType objects. Pipelines provide performance
8
+ # optimization by batching commands without the atomicity guarantee of transactions.
9
+
10
+ require_relative '../../support/helpers/test_helpers'
11
+
12
+ # Setup
13
+ class PipelineTestUser < Familia::Horreum
14
+ logical_database 4
15
+ identifier_field :userid
16
+ field :userid
17
+ field :name
18
+
19
+ sorted_set :scores
20
+ hashkey :profile
21
+ set :tags
22
+ counter :visits
23
+ end
24
+
25
+ @user = PipelineTestUser.new(userid: 'pipe_user_001')
26
+ @user.name = 'Pipeline Tester'
27
+ @user.save
28
+
29
+ ## Parent-owned SortedSet can execute pipeline
30
+ result = @user.scores.pipelined do |pipe|
31
+ pipe.zadd(@user.scores.dbkey, 100, 'p1')
32
+ pipe.zadd(@user.scores.dbkey, 200, 'p2')
33
+ pipe.zcard(@user.scores.dbkey)
34
+ end
35
+ [result.is_a?(MultiResult), @user.scores.members.size]
36
+ #=> [true, 2]
37
+
38
+ ## Parent-owned HashKey can execute pipeline
39
+ result = @user.profile.pipelined do |pipe|
40
+ pipe.hset(@user.profile.dbkey, 'city', 'NYC')
41
+ pipe.hset(@user.profile.dbkey, 'state', 'NY')
42
+ pipe.hgetall(@user.profile.dbkey)
43
+ end
44
+ [result.is_a?(MultiResult), @user.profile.keys.sort]
45
+ #=> [true, ["city", "state"]]
46
+
47
+ ## Standalone SortedSet can execute pipeline
48
+ leaderboard = Familia::SortedSet.new('pipeline:leaderboard')
49
+ leaderboard.delete!
50
+ result = leaderboard.pipelined do |pipe|
51
+ pipe.zadd(leaderboard.dbkey, 100, 'player1')
52
+ pipe.zadd(leaderboard.dbkey, 200, 'player2')
53
+ pipe.zcard(leaderboard.dbkey)
54
+ end
55
+ [result.is_a?(MultiResult), leaderboard.members.size]
56
+ #=> [true, 2]
57
+
58
+ ## Pipeline with direct_access works correctly
59
+ result = @user.profile.pipelined do |pipe_conn|
60
+ pipe_conn.hset(@user.profile.dbkey, 'pipeline_test', 'yes')
61
+
62
+ @user.profile.direct_access do |conn, key|
63
+ conn.object_id == pipe_conn.object_id &&
64
+ conn.hset(key, 'direct_test', 'yes')
65
+ end
66
+ end
67
+ [@user.profile['pipeline_test'], @user.profile['direct_test']]
68
+ #=> ["yes", "yes"]
69
+
70
+ ## Pipeline returns MultiResult with correct structure
71
+ result = @user.scores.pipelined do |pipe|
72
+ pipe.zadd(@user.scores.dbkey, 300, 'p3')
73
+ pipe.zadd(@user.scores.dbkey, 400, 'p4')
74
+ end
75
+ [result.is_a?(MultiResult), result.results.is_a?(Array)]
76
+ #=> [true, true]
77
+
78
+ ## Empty pipeline returns empty MultiResult
79
+ result = @user.scores.pipelined { |pipe| }
80
+ [result.is_a?(MultiResult), result.results.empty?]
81
+ #=> [true, true]
82
+
83
+ ## Multiple DataType operations in single pipeline
84
+ result = @user.scores.pipelined do |pipe|
85
+ pipe.zadd(@user.scores.dbkey, 500, 'multi')
86
+ pipe.hset(@user.profile.dbkey, 'multi', 'pipeline')
87
+ pipe.sadd(@user.tags.dbkey, 'multi_tag')
88
+ end
89
+ [
90
+ result.is_a?(MultiResult),
91
+ @user.scores.member?('multi'),
92
+ @user.profile['multi'],
93
+ @user.tags.member?('multi_tag')
94
+ ]
95
+ #=> [true, true, "pipeline", true]
96
+
97
+ ## Standalone HashKey with logical_database option
98
+ custom = Familia::HashKey.new('pipeline:custom', logical_database: 5)
99
+ custom.delete!
100
+ result = custom.pipelined do |pipe|
101
+ pipe.hset(custom.dbkey, 'key1', 'value1')
102
+ pipe.hget(custom.dbkey, 'key1')
103
+ end
104
+ result.is_a?(MultiResult)
105
+ #=> true
106
+
107
+ # Cleanup
108
+ @user.destroy!
@@ -0,0 +1,251 @@
1
+ # try/integration/data_types/datatype_transactions_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ # DataType Transaction Support Tryouts
6
+ #
7
+ # Tests transaction support for DataType objects, covering both parent-owned
8
+ # DataTypes (delegating to parent) and standalone DataTypes (managing their
9
+ # own connections). Validates atomic operations, connection context handling,
10
+ # and integration with the transaction mode system.
11
+
12
+ require_relative '../../support/helpers/test_helpers'
13
+
14
+ # Setup - Create test model with various DataType fields
15
+ class TransactionTestUser < Familia::Horreum
16
+ logical_database 2
17
+ identifier_field :userid
18
+ field :userid
19
+ field :name
20
+ field :email
21
+
22
+ # Instance-level DataTypes
23
+ sorted_set :scores
24
+ hashkey :profile
25
+ set :tags
26
+ list :activity
27
+ counter :visits
28
+ string :bio
29
+ end
30
+
31
+ @user = TransactionTestUser.new(userid: 'txn_user_001')
32
+ @user.name = 'Transaction Tester'
33
+ @user.save
34
+
35
+ ## Parent-owned SortedSet can execute transaction
36
+ result = @user.scores.transaction do |conn|
37
+ conn.zadd(@user.scores.dbkey, 100, 'level1')
38
+ conn.zadd(@user.scores.dbkey, 200, 'level2')
39
+ conn.zadd(@user.scores.dbkey, 300, 'level3')
40
+ end
41
+ [result.is_a?(MultiResult), @user.scores.members.sort]
42
+ #=> [true, ["level1", "level2", "level3"]]
43
+
44
+ ## Parent-owned HashKey can execute transaction
45
+ result = @user.profile.transaction do |conn|
46
+ conn.hset(@user.profile.dbkey, 'city', 'San Francisco')
47
+ conn.hset(@user.profile.dbkey, 'country', 'USA')
48
+ conn.hget(@user.profile.dbkey, 'city')
49
+ end
50
+ [result.is_a?(MultiResult), result.results.last, @user.profile['country']]
51
+ #=> [true, "San Francisco", "USA"]
52
+
53
+ ## Parent-owned UnsortedSet can execute transaction
54
+ result = @user.tags.transaction do |conn|
55
+ conn.sadd(@user.tags.dbkey, 'ruby')
56
+ conn.sadd(@user.tags.dbkey, 'redis')
57
+ conn.scard(@user.tags.dbkey)
58
+ end
59
+ [result.is_a?(MultiResult), result.results.last, @user.tags.members.sort]
60
+ #=> [true, 2, ["redis", "ruby"]]
61
+
62
+ ## Parent-owned List can execute transaction
63
+ result = @user.activity.transaction do |conn|
64
+ conn.rpush(@user.activity.dbkey, 'login')
65
+ conn.rpush(@user.activity.dbkey, 'view_profile')
66
+ conn.rpush(@user.activity.dbkey, 'logout')
67
+ conn.llen(@user.activity.dbkey)
68
+ end
69
+ [result.is_a?(MultiResult), result.results.last, @user.activity.members]
70
+ #=> [true, 3, ["login", "view_profile", "logout"]]
71
+
72
+ ## Parent-owned Counter can execute transaction
73
+ result = @user.visits.transaction do |conn|
74
+ conn.set(@user.visits.dbkey, 0)
75
+ conn.incr(@user.visits.dbkey)
76
+ conn.incr(@user.visits.dbkey)
77
+ conn.get(@user.visits.dbkey)
78
+ end
79
+ [result.is_a?(MultiResult), result.results.last.to_i, @user.visits.value]
80
+ #=> [true, 2, 2]
81
+
82
+ ## Parent-owned StringKey can execute transaction
83
+ result = @user.bio.transaction do |conn|
84
+ conn.set(@user.bio.dbkey, 'Ruby developer')
85
+ conn.append(@user.bio.dbkey, ' and Redis enthusiast')
86
+ conn.get(@user.bio.dbkey)
87
+ end
88
+ [result.is_a?(MultiResult), result.results.last, @user.bio.value]
89
+ #=> [true, "Ruby developer and Redis enthusiast", "Ruby developer and Redis enthusiast"]
90
+
91
+ ## Standalone SortedSet can execute transaction
92
+ leaderboard = Familia::SortedSet.new('game:leaderboard')
93
+ leaderboard.delete!
94
+ result = leaderboard.transaction do |conn|
95
+ conn.zadd(leaderboard.dbkey, 500, 'player1')
96
+ conn.zadd(leaderboard.dbkey, 600, 'player2')
97
+ conn.zadd(leaderboard.dbkey, 450, 'player3')
98
+ conn.zcard(leaderboard.dbkey)
99
+ end
100
+ [result.is_a?(MultiResult), result.results.last, leaderboard.members.size]
101
+ #=> [true, 3, 3]
102
+
103
+ ## Standalone HashKey can execute transaction
104
+ cache = Familia::HashKey.new('app:cache')
105
+ cache.delete!
106
+ result = cache.transaction do |conn|
107
+ conn.hset(cache.dbkey, 'key1', 'value1')
108
+ conn.hset(cache.dbkey, 'key2', 'value2')
109
+ conn.hkeys(cache.dbkey)
110
+ end
111
+ [result.is_a?(MultiResult), result.results.last.sort, cache.keys.sort]
112
+ #=> [true, ["key1", "key2"], ["key1", "key2"]]
113
+
114
+ ## Standalone UnsortedSet can execute transaction
115
+ global_tags = Familia::UnsortedSet.new('app:tags')
116
+ global_tags.delete!
117
+ result = global_tags.transaction do |conn|
118
+ conn.sadd(global_tags.dbkey, 'tag1')
119
+ conn.sadd(global_tags.dbkey, 'tag2')
120
+ conn.smembers(global_tags.dbkey)
121
+ end
122
+ [result.is_a?(MultiResult), result.results.last.sort, global_tags.members.sort]
123
+ #=> [true, ["tag1", "tag2"], ["tag1", "tag2"]]
124
+
125
+ ## Standalone StringKey can execute transaction
126
+ session_data = Familia::StringKey.new('session:abc123')
127
+ session_data.delete!
128
+ result = session_data.transaction do |conn|
129
+ conn.set(session_data.dbkey, '{"user_id": 123}')
130
+ conn.expire(session_data.dbkey, 3600)
131
+ conn.get(session_data.dbkey)
132
+ end
133
+ [result.is_a?(MultiResult), result.results.last, session_data.value]
134
+ #=> [true, "{\"user_id\": 123}", "{\"user_id\": 123}"]
135
+
136
+ ## Transaction with logical_database option works
137
+ custom_cache = Familia::HashKey.new('custom:cache', logical_database: 3)
138
+ custom_cache.delete!
139
+ result = custom_cache.transaction do |conn|
140
+ conn.hset(custom_cache.dbkey, 'setting', 'enabled')
141
+ conn.hget(custom_cache.dbkey, 'setting')
142
+ end
143
+ [result.is_a?(MultiResult), result.results.last]
144
+ #=> [true, "enabled"]
145
+
146
+ ## Transaction provides correct connection object type
147
+ conn_class = nil
148
+ @user.scores.transaction do |conn|
149
+ conn_class = conn.class.name
150
+ end
151
+ conn_class
152
+ #=> "Redis::MultiConnection"
153
+
154
+ ## Transaction with direct_access works correctly
155
+ result = @user.profile.transaction do |trans_conn|
156
+ trans_conn.hset(@user.profile.dbkey, 'status', 'active')
157
+
158
+ # direct_access should use the same transaction connection
159
+ @user.profile.direct_access do |conn, key|
160
+ conn.object_id == trans_conn.object_id &&
161
+ conn.hset(key, 'verified', 'true')
162
+ end
163
+ end
164
+ [@user.profile['status'], @user.profile['verified']]
165
+ #=> ["active", "true"]
166
+
167
+ ## Transaction atomicity - all commands succeed or none
168
+ test_zset = Familia::SortedSet.new('atomic:test')
169
+ test_zset.delete!
170
+ test_zset.add('initial', 1)
171
+
172
+ begin
173
+ test_zset.transaction do |conn|
174
+ conn.zadd(test_zset.dbkey, 100, 'member1')
175
+ conn.zadd(test_zset.dbkey, 200, 'member2')
176
+ raise 'Intentional error to test rollback'
177
+ end
178
+ rescue => e
179
+ # Transaction should have rolled back
180
+ test_zset.members
181
+ end
182
+ #=> ["initial"]
183
+
184
+ ## Nested transactions with parent-owned DataTypes work
185
+ outer_result = @user.scores.transaction do |outer_conn|
186
+ outer_conn.zadd(@user.scores.dbkey, 999, 'outer_member')
187
+
188
+ inner_result = @user.tags.transaction do |inner_conn|
189
+ inner_conn.sadd(@user.tags.dbkey, 'nested_tag')
190
+ end
191
+
192
+ inner_result.is_a?(MultiResult)
193
+ end
194
+ [outer_result.is_a?(MultiResult), @user.tags.member?('nested_tag')]
195
+ #=> [true, true]
196
+
197
+ ## Transaction respects transaction modes (permissive)
198
+ begin
199
+ original_mode = Familia.transaction_mode
200
+ Familia.configure { |config| config.transaction_mode = :permissive }
201
+
202
+ # Force a cached connection to trigger fallback
203
+ @user.class.instance_variable_set(:@dbclient, Familia.create_dbclient)
204
+
205
+ result = @user.scores.transaction do |conn|
206
+ # Should be IndividualCommandProxy in fallback mode
207
+ conn.class == Familia::Connection::IndividualCommandProxy &&
208
+ conn.zadd(@user.scores.dbkey, 888, 'fallback_test')
209
+ end
210
+
211
+ result.is_a?(MultiResult)
212
+ ensure
213
+ @user.class.remove_instance_variable(:@dbclient)
214
+ Familia.configure { |config| config.transaction_mode = original_mode }
215
+ end
216
+ #=> true
217
+
218
+ ## Transaction with empty block returns empty MultiResult
219
+ result = @user.scores.transaction { |conn| }
220
+ [result.is_a?(MultiResult), result.results.empty?]
221
+ #=> [true, true]
222
+
223
+ ## Transaction connection uses parent's logical_database
224
+ # TransactionTestUser has logical_database 2
225
+ # Parent-owned DataType delegates to parent, verify via class setting
226
+ @user.scores.delete!
227
+ @user.scores.transaction do |conn|
228
+ conn.zadd(@user.scores.dbkey, 1, 'test_member')
229
+ end
230
+ TransactionTestUser.logical_database
231
+ #=> 2
232
+
233
+ ## Multiple DataType types in single transaction
234
+ result = @user.scores.transaction do |conn|
235
+ # Can operate on different DataTypes using same connection
236
+ conn.zadd(@user.scores.dbkey, 777, 'multi_test')
237
+ conn.hset(@user.profile.dbkey, 'multi', 'yes')
238
+ conn.sadd(@user.tags.dbkey, 'multi_tag')
239
+ conn.rpush(@user.activity.dbkey, 'multi_action')
240
+ end
241
+ [
242
+ result.is_a?(MultiResult),
243
+ @user.scores.member?('multi_test'),
244
+ @user.profile['multi'],
245
+ @user.tags.member?('multi_tag'),
246
+ @user.activity.members.include?('multi_action')
247
+ ]
248
+ #=> [true, true, "yes", true, true]
249
+
250
+ # Cleanup
251
+ @user.destroy!
@@ -1,3 +1,7 @@
1
+ # try/integration/database_consistency_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/core/database_consistency_try.rb
2
6
  #
3
7
  # Database consistency verification and edge case testing
@@ -1,3 +1,7 @@
1
+ # try/integration/familia_extended_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/core/familia_extended_try.rb
2
6
 
3
7
  require 'time'
@@ -1,3 +1,7 @@
1
+ # try/integration/familia_members_methods_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/core/familia_members_methods_try.rb
2
6
 
3
7
  require_relative '../support/helpers/test_helpers'
@@ -1,3 +1,7 @@
1
+ # try/integration/models/customer_safe_dump_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/models/customer_safedump_try.rb
2
6
 
3
7
  require_relative '../../support/helpers/test_helpers'
@@ -39,7 +43,11 @@ require_relative '../../support/helpers/test_helpers'
39
43
 
40
44
  ## Safe dump includes correct updated timestamp
41
45
  @safe_dump[:updated]
42
- #=> @now
46
+ #=:> Float
47
+
48
+ ## Safe dump includes correct updated timestamp
49
+ @safe_dump[:updated].to_i
50
+ #=> @now.to_i
43
51
 
44
52
  ## Safe dump includes correct secrets_created count
45
53
  @customer.secrets_created.increment
@@ -1,3 +1,7 @@
1
+ # try/integration/models/customer_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/models/customer_try.rb
2
6
 
3
7
  # Customer Tryouts
@@ -1,3 +1,7 @@
1
+ # try/integration/models/datatype_base_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/data_types/data_type_base_try.rb
2
6
 
3
7
  # Test DataType base functionality
@@ -1,3 +1,7 @@
1
+ # try/integration/models/familia_object_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/models/familia_object_try.rb
2
6
 
3
7
  require_relative '../../support/helpers/test_helpers'
@@ -71,7 +75,7 @@ Customer.all_customers.size
71
75
 
72
76
  ## Familia class clear
73
77
  Customer.all_customers.delete!
74
- #=> true
78
+ #=> 1
75
79
 
76
80
  ## Familia class replace 1 of 4
77
81
  Customer.message.value = 'msg1'