familia 2.0.0.pre19 → 2.0.0.pre22

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/.talismanrc +5 -1
  8. data/CHANGELOG.rst +220 -112
  9. data/CLAUDE.md +28 -1
  10. data/Gemfile +1 -1
  11. data/Gemfile.lock +20 -17
  12. data/bin/try +16 -0
  13. data/bin/tryouts +16 -0
  14. data/docs/1106-participates_in-bidirectional-solution.md +129 -0
  15. data/docs/guides/encryption.md +486 -0
  16. data/docs/guides/feature-encrypted-fields.md +123 -7
  17. data/docs/guides/feature-expiration.md +161 -117
  18. data/docs/guides/feature-external-identifiers.md +415 -443
  19. data/docs/guides/feature-object-identifiers.md +400 -269
  20. data/docs/guides/feature-quantization.md +120 -6
  21. data/docs/guides/feature-relationships-indexing.md +318 -0
  22. data/docs/guides/feature-relationships-methods.md +146 -604
  23. data/docs/guides/feature-relationships-participation.md +263 -0
  24. data/docs/guides/feature-relationships.md +118 -136
  25. data/docs/guides/feature-system-devs.md +176 -693
  26. data/docs/guides/feature-system.md +119 -6
  27. data/docs/guides/feature-transient-fields.md +81 -0
  28. data/docs/guides/field-system.md +778 -0
  29. data/docs/guides/index.md +32 -15
  30. data/docs/guides/logging.md +187 -0
  31. data/docs/guides/optimized-loading.md +674 -0
  32. data/docs/guides/thread-safety-monitoring.md +61 -0
  33. data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
  34. data/docs/migrating/v2.0.0-pre22.md +241 -0
  35. data/docs/overview.md +7 -9
  36. data/docs/reference/api-technical.md +267 -320
  37. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
  38. data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
  39. data/examples/autoloader/mega_customer.rb +2 -0
  40. data/examples/datatype_standalone.rb +4 -3
  41. data/examples/encrypted_fields.rb +2 -1
  42. data/examples/json_usage_patterns.rb +2 -0
  43. data/examples/relationships.rb +3 -0
  44. data/examples/safe_dump.rb +2 -1
  45. data/examples/sampling_demo.rb +53 -0
  46. data/examples/single_connection_transaction_confusions.rb +2 -1
  47. data/familia.gemspec +2 -1
  48. data/lib/familia/base.rb +2 -0
  49. data/lib/familia/connection/behavior.rb +2 -0
  50. data/lib/familia/connection/handlers.rb +2 -0
  51. data/lib/familia/connection/individual_command_proxy.rb +2 -0
  52. data/lib/familia/connection/middleware.rb +34 -24
  53. data/lib/familia/connection/operation_core.rb +3 -2
  54. data/lib/familia/connection/operations.rb +2 -0
  55. data/lib/familia/connection/pipelined_core.rb +3 -3
  56. data/lib/familia/connection/transaction_core.rb +69 -2
  57. data/lib/familia/connection.rb +18 -3
  58. data/lib/familia/data_type/class_methods.rb +3 -1
  59. data/lib/familia/data_type/connection.rb +2 -0
  60. data/lib/familia/data_type/database_commands.rb +2 -0
  61. data/lib/familia/data_type/serialization.rb +79 -52
  62. data/lib/familia/data_type/settings.rb +2 -0
  63. data/lib/familia/data_type/types/counter.rb +2 -0
  64. data/lib/familia/data_type/types/hashkey.rb +7 -5
  65. data/lib/familia/data_type/types/listkey.rb +2 -0
  66. data/lib/familia/data_type/types/lock.rb +2 -0
  67. data/lib/familia/data_type/types/sorted_set.rb +7 -10
  68. data/lib/familia/data_type/types/stringkey.rb +24 -0
  69. data/lib/familia/data_type/types/unsorted_set.rb +2 -0
  70. data/lib/familia/data_type.rb +2 -0
  71. data/lib/familia/encryption/encrypted_data.rb +4 -2
  72. data/lib/familia/encryption/manager.rb +2 -0
  73. data/lib/familia/encryption/provider.rb +2 -0
  74. data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
  75. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
  76. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
  77. data/lib/familia/encryption/registry.rb +2 -0
  78. data/lib/familia/encryption/request_cache.rb +2 -0
  79. data/lib/familia/encryption.rb +9 -2
  80. data/lib/familia/errors.rb +2 -0
  81. data/lib/familia/features/autoloader.rb +2 -0
  82. data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
  83. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
  84. data/lib/familia/features/encrypted_fields.rb +2 -2
  85. data/lib/familia/features/expiration/extensions.rb +3 -1
  86. data/lib/familia/features/expiration.rb +12 -4
  87. data/lib/familia/features/external_identifier.rb +62 -7
  88. data/lib/familia/features/object_identifier.rb +49 -0
  89. data/lib/familia/features/quantization.rb +3 -1
  90. data/lib/familia/features/relationships/README.md +3 -1
  91. data/lib/familia/features/relationships/collection_operations.rb +2 -0
  92. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +138 -9
  93. data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
  94. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +97 -21
  95. data/lib/familia/features/relationships/indexing.rb +3 -0
  96. data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
  97. data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
  98. data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
  99. data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
  100. data/lib/familia/features/relationships/participation.rb +155 -69
  101. data/lib/familia/features/relationships/participation_membership.rb +69 -0
  102. data/lib/familia/features/relationships/participation_relationship.rb +34 -6
  103. data/lib/familia/features/relationships/score_encoding.rb +2 -0
  104. data/lib/familia/features/relationships.rb +5 -3
  105. data/lib/familia/features/safe_dump.rb +2 -0
  106. data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
  107. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
  108. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
  109. data/lib/familia/features/transient_fields.rb +2 -0
  110. data/lib/familia/features.rb +2 -0
  111. data/lib/familia/field_type.rb +3 -1
  112. data/lib/familia/horreum/connection.rb +17 -1
  113. data/lib/familia/horreum/database_commands.rb +8 -1
  114. data/lib/familia/horreum/definition.rb +16 -6
  115. data/lib/familia/horreum/management.rb +353 -52
  116. data/lib/familia/horreum/persistence.rb +179 -108
  117. data/lib/familia/horreum/related_fields.rb +2 -0
  118. data/lib/familia/horreum/serialization.rb +23 -4
  119. data/lib/familia/horreum/settings.rb +2 -0
  120. data/lib/familia/horreum/utils.rb +2 -0
  121. data/lib/familia/horreum.rb +15 -1
  122. data/lib/familia/identifier_extractor.rb +3 -1
  123. data/lib/familia/instrumentation.rb +156 -0
  124. data/lib/familia/json_serializer.rb +2 -0
  125. data/lib/familia/logging.rb +92 -32
  126. data/lib/familia/refinements/dear_json.rb +2 -0
  127. data/lib/familia/refinements/stylize_words.rb +2 -14
  128. data/lib/familia/refinements/time_literals.rb +2 -0
  129. data/lib/familia/refinements.rb +2 -0
  130. data/lib/familia/secure_identifier.rb +10 -2
  131. data/lib/familia/settings.rb +2 -0
  132. data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
  133. data/lib/familia/thread_safety/monitor.rb +328 -0
  134. data/lib/familia/utils.rb +13 -0
  135. data/lib/familia/verifiable_identifier.rb +3 -1
  136. data/lib/familia/version.rb +3 -1
  137. data/lib/familia.rb +31 -4
  138. data/lib/middleware/database_command_counter.rb +152 -0
  139. data/lib/middleware/database_logger.rb +295 -170
  140. data/lib/multi_result.rb +61 -31
  141. data/try/edge_cases/empty_identifiers_try.rb +2 -0
  142. data/try/edge_cases/hash_symbolization_try.rb +2 -0
  143. data/try/edge_cases/json_serialization_try.rb +2 -0
  144. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
  145. data/try/edge_cases/race_conditions_try.rb +4 -0
  146. data/try/edge_cases/reserved_keywords_try.rb +4 -0
  147. data/try/edge_cases/string_coercion_try.rb +2 -0
  148. data/try/edge_cases/ttl_side_effects_try.rb +4 -0
  149. data/try/features/count_any_edge_cases_try.rb +486 -0
  150. data/try/features/count_any_methods_try.rb +197 -0
  151. data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
  152. data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
  153. data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
  154. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
  155. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
  156. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
  157. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
  158. data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
  159. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
  160. data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
  161. data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
  162. data/try/features/encrypted_fields/memory_security_try.rb +4 -0
  163. data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
  164. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
  165. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
  166. data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
  167. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
  168. data/try/features/encryption/config_persistence_try.rb +4 -0
  169. data/try/features/encryption/core_try.rb +4 -0
  170. data/try/features/encryption/instance_variable_scope_try.rb +4 -0
  171. data/try/features/encryption/module_loading_try.rb +4 -0
  172. data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
  173. data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
  174. data/try/features/encryption/roundtrip_validation_try.rb +4 -0
  175. data/try/features/encryption/secure_memory_handling_try.rb +4 -0
  176. data/try/features/expiration/expiration_try.rb +4 -0
  177. data/try/features/external_identifier/external_identifier_try.rb +305 -8
  178. data/try/features/feature_dependencies_try.rb +2 -0
  179. data/try/features/feature_improvements_try.rb +2 -0
  180. data/try/features/field_groups_try.rb +2 -0
  181. data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
  182. data/try/features/object_identifier/object_identifier_try.rb +140 -0
  183. data/try/features/quantization/quantization_try.rb +4 -0
  184. data/try/features/real_feature_integration_try.rb +2 -0
  185. data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
  186. data/try/features/relationships/indexing_rebuild_try.rb +606 -0
  187. data/try/features/relationships/indexing_try.rb +2 -0
  188. data/try/features/relationships/participation_bidirectional_try.rb +242 -0
  189. data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
  190. data/try/features/relationships/participation_commands_verification_try.rb +2 -0
  191. data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
  192. data/try/features/relationships/participation_reverse_index_try.rb +15 -13
  193. data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
  194. data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
  195. data/try/features/relationships/relationships_api_changes_try.rb +2 -0
  196. data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
  197. data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
  198. data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
  199. data/try/features/relationships/relationships_performance_try.rb +4 -0
  200. data/try/features/relationships/relationships_performance_working_try.rb +4 -0
  201. data/try/features/relationships/relationships_try.rb +6 -4
  202. data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
  203. data/try/features/safe_dump/safe_dump_try.rb +4 -0
  204. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  205. data/try/features/transient_fields/refresh_reset_try.rb +3 -0
  206. data/try/features/transient_fields/simple_refresh_test.rb +3 -0
  207. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  208. data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
  209. data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
  210. data/try/integration/connection/fiber_context_preservation_try.rb +4 -0
  211. data/try/integration/connection/handler_constraints_try.rb +4 -0
  212. data/try/integration/connection/isolated_dbclient_try.rb +4 -0
  213. data/try/integration/connection/middleware_reconnect_try.rb +2 -0
  214. data/try/integration/connection/operation_mode_guards_try.rb +4 -0
  215. data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
  216. data/try/integration/connection/pools_try.rb +4 -0
  217. data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
  218. data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
  219. data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
  220. data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
  221. data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
  222. data/try/integration/connection/transaction_modes_try.rb +4 -0
  223. data/try/integration/conventional_inheritance_try.rb +4 -0
  224. data/try/integration/create_method_try.rb +4 -0
  225. data/try/integration/cross_component_try.rb +4 -0
  226. data/try/integration/data_types/datatype_pipelines_try.rb +9 -3
  227. data/try/integration/data_types/datatype_transactions_try.rb +17 -7
  228. data/try/integration/database_consistency_try.rb +4 -0
  229. data/try/integration/familia_extended_try.rb +4 -0
  230. data/try/integration/familia_members_methods_try.rb +4 -0
  231. data/try/integration/models/customer_safe_dump_try.rb +4 -0
  232. data/try/integration/models/customer_try.rb +7 -3
  233. data/try/integration/models/datatype_base_try.rb +4 -0
  234. data/try/integration/models/familia_object_try.rb +4 -0
  235. data/try/integration/persistence_operations_try.rb +4 -0
  236. data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
  237. data/try/integration/save_methods_consistency_try.rb +241 -0
  238. data/try/integration/scenarios_try.rb +4 -0
  239. data/try/integration/secure_identifier_try.rb +4 -0
  240. data/try/integration/transaction_safety_core_try.rb +176 -0
  241. data/try/integration/transaction_safety_workflow_try.rb +291 -0
  242. data/try/integration/verifiable_identifier_try.rb +4 -0
  243. data/try/investigation/pipeline_routing/README.md +228 -0
  244. data/try/performance/benchmarks_try.rb +4 -0
  245. data/try/performance/transaction_safety_benchmark_try.rb +238 -0
  246. data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
  247. data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
  248. data/try/support/debugging/cache_behavior_tracer.rb +4 -0
  249. data/try/support/debugging/debug_aad_process.rb +3 -0
  250. data/try/support/debugging/debug_concealed_internal.rb +3 -0
  251. data/try/support/debugging/debug_concealed_reveal.rb +3 -0
  252. data/try/support/debugging/debug_context_aad.rb +3 -0
  253. data/try/support/debugging/debug_context_simple.rb +3 -0
  254. data/try/support/debugging/debug_cross_context.rb +3 -0
  255. data/try/support/debugging/debug_database_load.rb +3 -0
  256. data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
  257. data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
  258. data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
  259. data/try/support/debugging/debug_field_decrypt.rb +3 -0
  260. data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
  261. data/try/support/debugging/debug_load_path.rb +3 -0
  262. data/try/support/debugging/debug_method_definition.rb +3 -0
  263. data/try/support/debugging/debug_method_resolution.rb +3 -0
  264. data/try/support/debugging/debug_minimal.rb +3 -0
  265. data/try/support/debugging/debug_provider.rb +3 -0
  266. data/try/support/debugging/debug_secure_behavior.rb +3 -0
  267. data/try/support/debugging/debug_string_class.rb +3 -0
  268. data/try/support/debugging/debug_test.rb +3 -0
  269. data/try/support/debugging/debug_test_design.rb +3 -0
  270. data/try/support/debugging/encryption_method_tracer.rb +4 -0
  271. data/try/support/debugging/provider_diagnostics.rb +4 -0
  272. data/try/support/helpers/test_cleanup.rb +4 -0
  273. data/try/support/helpers/test_helpers.rb +5 -0
  274. data/try/support/memory/memory_basic_test.rb +4 -0
  275. data/try/support/memory/memory_detailed_test.rb +4 -0
  276. data/try/support/memory/memory_search_for_string.rb +4 -0
  277. data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
  278. data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
  279. data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
  280. data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
  281. data/try/support/prototypes/atomic_saves_v4.rb +4 -0
  282. data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
  283. data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  284. data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
  285. data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  286. data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
  287. data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
  288. data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
  289. data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
  290. data/try/support/prototypes/pooling/pool_siege.rb +4 -2
  291. data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
  292. data/try/thread_safety/README.md +496 -0
  293. data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
  294. data/try/thread_safety/connection_chain_race_try.rb +148 -0
  295. data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
  296. data/try/thread_safety/feature_registry_race_try.rb +226 -0
  297. data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
  298. data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
  299. data/try/thread_safety/field_registration_race_try.rb +222 -0
  300. data/try/thread_safety/logger_initialization_race_try.rb +170 -0
  301. data/try/thread_safety/middleware_registration_race_try.rb +154 -0
  302. data/try/thread_safety/module_config_race_try.rb +175 -0
  303. data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
  304. data/try/unit/core/autoloader_try.rb +4 -0
  305. data/try/unit/core/base_enhancements_try.rb +4 -0
  306. data/try/unit/core/connection_try.rb +4 -0
  307. data/try/unit/core/errors_try.rb +4 -0
  308. data/try/unit/core/extensions_try.rb +4 -0
  309. data/try/unit/core/familia_logger_try.rb +2 -0
  310. data/try/unit/core/familia_try.rb +4 -0
  311. data/try/unit/core/middleware_sampling_try.rb +335 -0
  312. data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
  313. data/try/unit/core/middleware_thread_safety_try.rb +245 -0
  314. data/try/unit/core/middleware_try.rb +4 -0
  315. data/try/unit/core/settings_try.rb +4 -0
  316. data/try/unit/core/time_utils_try.rb +4 -0
  317. data/try/unit/core/tools_try.rb +4 -0
  318. data/try/unit/core/utils_try.rb +37 -0
  319. data/try/unit/data_types/boolean_try.rb +39 -22
  320. data/try/unit/data_types/counter_try.rb +4 -0
  321. data/try/unit/data_types/datatype_base_try.rb +4 -0
  322. data/try/unit/data_types/hash_try.rb +6 -2
  323. data/try/unit/data_types/list_try.rb +4 -0
  324. data/try/unit/data_types/lock_try.rb +4 -0
  325. data/try/unit/data_types/serialization_try.rb +386 -0
  326. data/try/unit/data_types/sorted_set_try.rb +4 -0
  327. data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
  328. data/try/unit/data_types/string_try.rb +4 -0
  329. data/try/unit/data_types/unsortedset_try.rb +4 -0
  330. data/try/unit/familia_resolve_class_try.rb +116 -0
  331. data/try/unit/horreum/auto_indexing_on_save_try.rb +5 -1
  332. data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
  333. data/try/unit/horreum/base_try.rb +4 -0
  334. data/try/unit/horreum/class_methods_try.rb +4 -0
  335. data/try/unit/horreum/commands_try.rb +4 -0
  336. data/try/unit/horreum/defensive_initialization_try.rb +4 -0
  337. data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +6 -1
  338. data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
  339. data/try/unit/horreum/field_categories_try.rb +4 -0
  340. data/try/unit/horreum/field_definition_try.rb +4 -0
  341. data/try/unit/horreum/initialization_try.rb +4 -0
  342. data/try/unit/horreum/json_type_preservation_try.rb +2 -0
  343. data/try/unit/horreum/optimized_loading_try.rb +156 -0
  344. data/try/unit/horreum/relations_try.rb +4 -0
  345. data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
  346. data/try/unit/horreum/serialization_try.rb +4 -0
  347. data/try/unit/horreum/settings_try.rb +4 -0
  348. data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
  349. data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
  350. data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
  351. data/try/unit/middleware/database_logger_methods_try.rb +251 -0
  352. data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
  353. data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
  354. data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
  355. data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
  356. data/try/unit/thread_safety_monitor_try.rb +149 -0
  357. metadata +69 -17
  358. data/.github/workflows/code-quality.yml +0 -138
  359. data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
  360. data/changelog.d/20251011_203905_delano_next.rst +0 -30
  361. data/changelog.d/20251011_212633_delano_next.rst +0 -13
  362. data/changelog.d/20251011_221253_delano_next.rst +0 -26
  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/lib/multi_result.rb CHANGED
@@ -1,24 +1,29 @@
1
1
  # lib/multi_result.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
- # Represents the result of a Valkey/Redis transaction operation.
5
+ # Represents the result of a Valkey/Redis transaction or pipeline operation.
6
+ #
7
+ # This class encapsulates the outcome of a Database multi-command operation,
8
+ # providing access to both the command results and derived success status
9
+ # based on the presence of errors in the results.
4
10
  #
5
- # This class encapsulates the outcome of a Database transaction,
6
- # providing access to both the success status and the individual
7
- # command results returned by the transaction.
11
+ # Success is determined by checking for Exception objects in the results array.
12
+ # When Redis commands fail within a transaction or pipeline, they return
13
+ # exception objects rather than raising them, allowing other commands to
14
+ # continue executing.
8
15
  #
9
- # @attr_reader success [Boolean] Indicates whether all commands
10
- # in the transaction completed successfully.
11
- # @attr_reader results [Array<String>] Array of return values
12
- # from the Database commands executed in the transaction.
16
+ # @attr_reader results [Array] Array of return values from the Database commands.
17
+ # Values can be strings, integers, booleans, or Exception objects for failed commands.
13
18
  #
14
19
  # @example Creating a MultiResult instance
15
- # result = MultiResult.new(true, ["OK", "OK"])
20
+ # result = MultiResult.new(["OK", "OK", 1])
16
21
  #
17
22
  # @example Checking transaction success
18
23
  # if result.successful?
19
- # puts "Transaction completed successfully"
24
+ # puts "All commands completed without errors"
20
25
  # else
21
- # puts "Transaction failed"
26
+ # puts "#{result.errors.size} command(s) failed"
22
27
  # end
23
28
  #
24
29
  # @example Accessing individual command results
@@ -26,24 +31,55 @@
26
31
  # puts "Command #{index + 1} returned: #{value}"
27
32
  # end
28
33
  #
34
+ # @example Inspecting errors
35
+ # if result.errors?
36
+ # result.errors.each do |error|
37
+ # puts "Error: #{error.message}"
38
+ # end
39
+ # end
40
+ #
29
41
  class MultiResult
30
- # @return [Boolean] true if all commands in the transaction succeeded,
31
- # false otherwise
32
- attr_reader :success
33
-
34
- # @return [Array<String>] The raw return values from the Database commands
42
+ # @return [Array] The raw return values from the Database commands
35
43
  attr_reader :results
36
44
 
37
45
  # Creates a new MultiResult instance.
38
46
  #
39
- # @param success [Boolean] Whether all commands succeeded
40
- # @param results [Array<String>] The raw results from Database commands
41
- def initialize(success, results)
42
- @success = success
47
+ # @param results [Array] The raw results from Database commands.
48
+ # Exception objects in the array indicate command failures.
49
+ def initialize(results)
43
50
  @results = results
44
51
  end
45
52
 
46
- # Returns a tuple representing the result of the transaction.
53
+ # Returns all Exception objects from the results array.
54
+ #
55
+ # This method is memoized for performance when called multiple times
56
+ # on the same MultiResult instance.
57
+ #
58
+ # @return [Array<Exception>] Array of exceptions that occurred during execution
59
+ def errors
60
+ @errors ||= results.select { |ret| ret.is_a?(Exception) }
61
+ end
62
+
63
+ # Checks if any errors occurred during execution.
64
+ #
65
+ # @return [Boolean] true if at least one command failed, false otherwise
66
+ def errors?
67
+ !errors.empty?
68
+ end
69
+
70
+ # Checks if all commands completed successfully (no exceptions).
71
+ #
72
+ # This is the primary method for determining if a multi-command
73
+ # operation completed without errors.
74
+ #
75
+ # @return [Boolean] true if no exceptions in results, false otherwise
76
+ def successful?
77
+ errors.empty?
78
+ end
79
+ alias success? successful?
80
+ alias areyouhappynow? successful?
81
+
82
+ # Returns a tuple representing the result of the operation.
47
83
  #
48
84
  # @return [Array] A tuple containing the success status and the raw results.
49
85
  # The success status is a boolean indicating if all commands succeeded.
@@ -59,21 +95,15 @@ class MultiResult
59
95
 
60
96
  # Returns the number of results in the multi-operation.
61
97
  #
62
- # @return [Integer] The number of individual command results returned by the transaction.
98
+ # @return [Integer] The number of individual command results returned
63
99
  def size
64
100
  results.size
65
101
  end
66
102
 
103
+ # Returns a hash representation of the result.
104
+ #
105
+ # @return [Hash] Hash with :success and :results keys
67
106
  def to_h
68
107
  { success: successful?, results: results }
69
108
  end
70
-
71
- # Convenient method to check if the commit was successful.
72
- #
73
- # @return [Boolean] true if all commands succeeded, false otherwise
74
- def successful?
75
- @success
76
- end
77
- alias success? successful?
78
- alias areyouhappynow? successful?
79
109
  end
@@ -1,4 +1,6 @@
1
1
  # try/edge_cases/empty_identifiers_try.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # Test empty identifier edge cases
4
6
 
@@ -1,4 +1,6 @@
1
1
  # try/edge_cases/hash_symbolization_try.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # NOTE: These testcases are disabled b/c there's a shared context
4
6
  # bug in Tryouts 3.1 that prevents the setup instance vars from
@@ -1,4 +1,6 @@
1
1
  # try/edge_cases/json_serialization_try.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require_relative '../support/helpers/test_helpers'
4
6
 
@@ -1,3 +1,7 @@
1
+ # try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # Edge case tests for deserialize_value with legacy data detection
2
6
  #
3
7
  # Tests the nuanced deserialization that distinguishes between:
@@ -1,3 +1,7 @@
1
+ # try/edge_cases/race_conditions_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # Test connection race conditions
2
6
 
3
7
  require_relative '../support/helpers/test_helpers'
@@ -1,3 +1,7 @@
1
+ # try/edge_cases/reserved_keywords_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # Test reserved keyword handling
2
6
 
3
7
  require_relative '../support/helpers/test_helpers'
@@ -1,4 +1,6 @@
1
1
  # try/edge_cases/string_coercion_try.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require_relative '../support/helpers/test_helpers'
4
6
 
@@ -1,3 +1,7 @@
1
+ # try/edge_cases/ttl_side_effects_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # Test TTL side effects
2
6
 
3
7
  require_relative '../support/helpers/test_helpers'
@@ -0,0 +1,486 @@
1
+ require_relative '../support/helpers/test_helpers'
2
+
3
+ # Test class for count/any edge case testing - synchronization issues
4
+ class EdgeCaseCustomer < Familia::Horreum
5
+ identifier_field :custid
6
+ field :custid
7
+ field :name
8
+ end
9
+
10
+ # Setup - clear any existing data
11
+ EdgeCaseCustomer.instances.clear
12
+ # Clear all keys matching the pattern
13
+ EdgeCaseCustomer.dbclient.scan_each(match: EdgeCaseCustomer.dbkey('*')) do |key|
14
+ EdgeCaseCustomer.dbclient.del(key)
15
+ end
16
+
17
+ # =============================================================================
18
+ # Edge Case 1: Phantom Instances (stale additions)
19
+ # Identifier exists in instances sorted set but object key doesn't exist
20
+ # =============================================================================
21
+
22
+ ## EDGE: Phantom instance - count shows stale entry
23
+ # Manually add identifier to instances without creating object
24
+ EdgeCaseCustomer.instances.add('phantom1', Time.now.to_i)
25
+ EdgeCaseCustomer.count
26
+ #=> 1
27
+
28
+ ## EDGE: Phantom instance - keys_count shows authoritative count (0)
29
+ EdgeCaseCustomer.keys_count
30
+ #=> 0
31
+
32
+ ## EDGE: Phantom instance - scan_count shows authoritative count (0)
33
+ EdgeCaseCustomer.scan_count
34
+ #=> 0
35
+
36
+ ## EDGE: Phantom instance - count! shows authoritative count (0)
37
+ EdgeCaseCustomer.count!
38
+ #=> 0
39
+
40
+ ## EDGE: Phantom instance - any? returns true (stale)
41
+ EdgeCaseCustomer.any?
42
+ #=> true
43
+
44
+ ## EDGE: Phantom instance - keys_any? returns false (authoritative)
45
+ EdgeCaseCustomer.keys_any?
46
+ #=> false
47
+
48
+ ## EDGE: Phantom instance - scan_any? returns false (authoritative)
49
+ EdgeCaseCustomer.scan_any?
50
+ #=> false
51
+
52
+ ## EDGE: Phantom instance - any! returns false (authoritative)
53
+ EdgeCaseCustomer.any!
54
+ #=> false
55
+
56
+ ## EDGE: Phantom instance cleanup - clear instances
57
+ EdgeCaseCustomer.instances.clear
58
+ #=*> Integer
59
+
60
+ # =============================================================================
61
+ # Edge Case 2: Orphaned Objects
62
+ # Object exists in Redis but identifier NOT in instances sorted set
63
+ # =============================================================================
64
+
65
+ ## EDGE: Orphaned object - create and remove from instances manually
66
+ @orphan = EdgeCaseCustomer.create!(custid: 'orphan1', name: 'Orphan')
67
+ EdgeCaseCustomer.instances.remove('orphan1')
68
+ # After removal, count should be 0 but object still exists
69
+ EdgeCaseCustomer.count
70
+ #=> 0
71
+
72
+ ## EDGE: Orphaned object - keys_count finds the orphan
73
+ EdgeCaseCustomer.keys_count
74
+ #=> 1
75
+
76
+ ## EDGE: Orphaned object - scan_count finds the orphan
77
+ EdgeCaseCustomer.scan_count
78
+ #=> 1
79
+
80
+ ## EDGE: Orphaned object - count! finds the orphan
81
+ EdgeCaseCustomer.count!
82
+ #=> 1
83
+
84
+ ## EDGE: Orphaned object - any? returns false (no instances entry)
85
+ # Since instances is empty, any? should return false
86
+ EdgeCaseCustomer.any?
87
+ #=> false
88
+
89
+ ## EDGE: Orphaned object - keys_any? returns true (object exists)
90
+ EdgeCaseCustomer.keys_any?
91
+ #=> true
92
+
93
+ ## EDGE: Orphaned object - scan_any? returns true (object exists)
94
+ EdgeCaseCustomer.scan_any?
95
+ #=> true
96
+
97
+ ## EDGE: Orphaned object - any! returns true (object exists)
98
+ EdgeCaseCustomer.any!
99
+ #=> true
100
+
101
+ ## EDGE: Orphaned object cleanup - destroy and clear
102
+ @orphan.destroy!
103
+ EdgeCaseCustomer.instances.clear
104
+ EdgeCaseCustomer.all.each(&:destroy!)
105
+ #=*> Array
106
+
107
+ # =============================================================================
108
+ # Edge Case 3: Duplicate Instance Entries
109
+ # Same identifier added multiple times to instances (shouldn't happen but test it)
110
+ # =============================================================================
111
+
112
+ ## EDGE: Duplicate entries - create fresh customer
113
+ @dup = EdgeCaseCustomer.create!(custid: 'dup1', name: 'Duplicate')
114
+ # Manually add the same identifier again with different score
115
+ # ZADD should just update the score, not create duplicate
116
+ EdgeCaseCustomer.instances.add('dup1', Time.now.to_i + 1000)
117
+ EdgeCaseCustomer.count
118
+ #=> 1
119
+
120
+ ## EDGE: Duplicate entries - keys_count also shows 1
121
+ EdgeCaseCustomer.keys_count
122
+ #=> 1
123
+
124
+ ## EDGE: Duplicate entries - scan_count also shows 1
125
+ EdgeCaseCustomer.scan_count
126
+ #=> 1
127
+
128
+ ## EDGE: Duplicate entries cleanup - destroy and clear
129
+ @dup.destroy!
130
+ EdgeCaseCustomer.instances.clear
131
+ EdgeCaseCustomer.all.each(&:destroy!)
132
+ #=*> Array
133
+
134
+ # =============================================================================
135
+ # Edge Case 4: Empty Identifier
136
+ # What happens with empty string identifier?
137
+ # =============================================================================
138
+
139
+ ## EDGE: Empty identifier - instances can store empty string
140
+ EdgeCaseCustomer.instances.add('', Time.now.to_i)
141
+ EdgeCaseCustomer.count
142
+ #=> 1
143
+
144
+ ## EDGE: Empty identifier - keys_count returns 0 (no matching keys)
145
+ # Empty identifier doesn't create valid keys
146
+ EdgeCaseCustomer.keys_count
147
+ #=> 0
148
+
149
+ ## EDGE: Empty identifier - scan_count returns 0 (no matching keys)
150
+ EdgeCaseCustomer.scan_count
151
+ #=> 0
152
+
153
+ ## EDGE: Empty identifier cleanup - clear all
154
+ EdgeCaseCustomer.instances.clear
155
+ EdgeCaseCustomer.all.each(&:destroy!)
156
+ #=*> Array
157
+
158
+ # =============================================================================
159
+ # Edge Case 5: Mixed State - Multiple Desync Scenarios
160
+ # Combination of phantoms, orphans, and valid objects
161
+ # =============================================================================
162
+
163
+ ## EDGE: Mixed state - setup complex scenario
164
+ @valid1 = EdgeCaseCustomer.create!(custid: 'valid1', name: 'Valid 1')
165
+ @valid2 = EdgeCaseCustomer.create!(custid: 'valid2', name: 'Valid 2')
166
+ # Create phantom (in instances but no object)
167
+ EdgeCaseCustomer.instances.add('phantom2', Time.now.to_i)
168
+ # Create orphan (object but not in instances)
169
+ @orphan2 = EdgeCaseCustomer.create!(custid: 'orphan2', name: 'Orphan 2')
170
+ EdgeCaseCustomer.instances.remove('orphan2')
171
+ # instances has: valid1, valid2, phantom2 = 3
172
+ EdgeCaseCustomer.count
173
+ #=> 3
174
+
175
+ ## EDGE: Mixed state - keys_count shows authoritative count
176
+ # Actual objects exist: valid1, valid2, orphan2 = 3
177
+ EdgeCaseCustomer.keys_count
178
+ #=> 3
179
+
180
+ ## EDGE: Mixed state - scan_count shows authoritative count
181
+ EdgeCaseCustomer.scan_count
182
+ #=> 3
183
+
184
+ ## EDGE: Mixed state - count! shows authoritative count
185
+ EdgeCaseCustomer.count!
186
+ #=> 3
187
+
188
+ ## EDGE: Mixed state - both counts match in this case
189
+ # By coincidence, both are 3 (different reasons though)
190
+ EdgeCaseCustomer.keys_count == EdgeCaseCustomer.count
191
+ #=> true
192
+
193
+ ## EDGE: Mixed state cleanup - clear all
194
+ EdgeCaseCustomer.instances.clear
195
+ EdgeCaseCustomer.all.each(&:destroy!)
196
+ #=*> Array
197
+
198
+ # =============================================================================
199
+ # Edge Case 6: Special Characters in Identifiers
200
+ # Unicode, special chars, patterns that might break filters
201
+ # =============================================================================
202
+
203
+ ## EDGE: Special chars - asterisk in identifier
204
+ @special1 = EdgeCaseCustomer.create!(custid: 'user*123', name: 'Special')
205
+ EdgeCaseCustomer.count
206
+ #=> 1
207
+
208
+ ## EDGE: Special chars - keys_count finds it
209
+ EdgeCaseCustomer.keys_count
210
+ #=> 1
211
+
212
+ ## EDGE: Special chars - scan_count finds it
213
+ EdgeCaseCustomer.scan_count
214
+ #=> 1
215
+
216
+ ## EDGE: Special chars - filter with asterisk pattern
217
+ # This tests if the literal asterisk in custid affects pattern matching
218
+ EdgeCaseCustomer.keys_count('user*')
219
+ #=> 1
220
+
221
+ ## EDGE: Special chars - scan filter with asterisk pattern
222
+ EdgeCaseCustomer.scan_count('user*')
223
+ #=> 1
224
+
225
+ ## EDGE: Special chars cleanup - destroy special1
226
+ @special1.destroy!
227
+ #=*> Integer
228
+
229
+ ## EDGE: Special chars - question mark in identifier
230
+ @special2 = EdgeCaseCustomer.create!(custid: 'user?456', name: 'Special2')
231
+ EdgeCaseCustomer.keys_count('user?*')
232
+ #=> 1
233
+
234
+ ## EDGE: Special chars - scan filter with question mark pattern
235
+ EdgeCaseCustomer.scan_count('user?*')
236
+ #=> 1
237
+
238
+ ## EDGE: Special chars cleanup - destroy special2
239
+ @special2.destroy!
240
+ #=*> Integer
241
+
242
+ ## EDGE: Special chars - brackets in identifier
243
+ @special3 = EdgeCaseCustomer.create!(custid: 'user[test]', name: 'Special3')
244
+ EdgeCaseCustomer.keys_count('user*')
245
+ #=> 1
246
+
247
+ ## EDGE: Special chars - scan finds bracketed identifier
248
+ EdgeCaseCustomer.scan_count('user*')
249
+ #=> 1
250
+
251
+ ## EDGE: Special chars cleanup - destroy special3 and clear
252
+ @special3.destroy!
253
+ EdgeCaseCustomer.instances.clear
254
+ #=*> Integer
255
+
256
+ # =============================================================================
257
+ # Edge Case 7: Score Manipulation
258
+ # Instances sorted set uses scores (timestamps), test score edge cases
259
+ # =============================================================================
260
+
261
+ ## EDGE: Score manipulation - zero score
262
+ EdgeCaseCustomer.instances.add('score_test1', 0)
263
+ EdgeCaseCustomer.count
264
+ #=> 1
265
+
266
+ ## EDGE: Score manipulation - negative score
267
+ EdgeCaseCustomer.instances.add('score_test2', -12345)
268
+ EdgeCaseCustomer.count
269
+ #=> 2
270
+
271
+ ## EDGE: Score manipulation - very large score
272
+ EdgeCaseCustomer.instances.add('score_test3', 9999999999999)
273
+ EdgeCaseCustomer.count
274
+ #=> 3
275
+
276
+ ## EDGE: Score manipulation - scores don't affect count
277
+ # Regardless of score values, count should be accurate
278
+ EdgeCaseCustomer.count
279
+ #=> 3
280
+
281
+ ## EDGE: Score manipulation - keys_count ignores scores (checks actual keys)
282
+ EdgeCaseCustomer.keys_count
283
+ #=> 0
284
+
285
+ ## EDGE: Score manipulation - scan_count ignores scores
286
+ EdgeCaseCustomer.scan_count
287
+ #=> 0
288
+
289
+ ## EDGE: Score manipulation cleanup - clear instances
290
+ EdgeCaseCustomer.instances.clear
291
+ #=*> Integer
292
+
293
+ # =============================================================================
294
+ # Edge Case 8: Large Dataset - Scan Cursor Behavior
295
+ # Test that scan_count properly iterates through large datasets
296
+ # =============================================================================
297
+
298
+ ## EDGE: Large dataset - create 100 instances
299
+ 100.times do |i|
300
+ EdgeCaseCustomer.create!(custid: "large#{i}", name: "Large #{i}")
301
+ end
302
+ EdgeCaseCustomer.count
303
+ #=> 100
304
+
305
+ ## EDGE: Large dataset - keys_count matches (blocks but works)
306
+ EdgeCaseCustomer.keys_count
307
+ #=> 100
308
+
309
+ ## EDGE: Large dataset - scan_count matches (non-blocking, cursor iteration)
310
+ EdgeCaseCustomer.scan_count
311
+ #=> 100
312
+
313
+ ## EDGE: Large dataset - count! matches
314
+ EdgeCaseCustomer.count!
315
+ #=> 100
316
+
317
+ ## EDGE: Large dataset - scan_any? returns true efficiently
318
+ EdgeCaseCustomer.scan_any?
319
+ #=> true
320
+
321
+ ## EDGE: Large dataset - keys_any? returns true
322
+ EdgeCaseCustomer.keys_any?
323
+ #=> true
324
+
325
+ ## EDGE: Large dataset - filter scan works with many results
326
+ EdgeCaseCustomer.scan_count('large1*')
327
+ #=> 11
328
+
329
+ ## EDGE: Large dataset - filter keys works with many results
330
+ EdgeCaseCustomer.keys_count('large1*')
331
+ #=> 11
332
+
333
+ ## EDGE: Large dataset cleanup - clear all
334
+ EdgeCaseCustomer.instances.clear
335
+ EdgeCaseCustomer.all.each(&:destroy!)
336
+ #=*> Array
337
+
338
+ # =============================================================================
339
+ # Edge Case 9: Manual Redis Operations Breaking Consistency
340
+ # Direct Redis commands that bypass Familia's tracking
341
+ # =============================================================================
342
+
343
+ ## EDGE: Manual ops - RENAME breaks dbkey pattern
344
+ @manual1 = EdgeCaseCustomer.create!(custid: 'manual1', name: 'Manual')
345
+ original_key = @manual1.dbkey
346
+ EdgeCaseCustomer.dbclient.rename(original_key, 'some:random:key')
347
+ EdgeCaseCustomer.count
348
+ #=> 1
349
+
350
+ ## EDGE: Manual ops - keys_count doesn't find renamed key
351
+ EdgeCaseCustomer.keys_count
352
+ #=> 0
353
+
354
+ ## EDGE: Manual ops - scan_count doesn't find renamed key
355
+ EdgeCaseCustomer.scan_count
356
+ #=> 0
357
+
358
+ ## EDGE: Manual ops - instances still has stale entry
359
+ EdgeCaseCustomer.any?
360
+ #=> true
361
+
362
+ ## EDGE: Manual ops - but keys_any? correctly returns false
363
+ EdgeCaseCustomer.keys_any?
364
+ #=> false
365
+
366
+ ## EDGE: Manual ops - and scan_any? correctly returns false
367
+ EdgeCaseCustomer.scan_any?
368
+ #=> false
369
+
370
+ ## EDGE: Manual ops cleanup - delete renamed key and clear instances
371
+ EdgeCaseCustomer.dbclient.del('some:random:key')
372
+ EdgeCaseCustomer.instances.clear
373
+ #=*> Integer
374
+
375
+ # =============================================================================
376
+ # Edge Case 10: Partial Transaction Failures
377
+ # Simulate scenarios where object exists but instances tracking failed
378
+ # =============================================================================
379
+
380
+ ## EDGE: Partial failure - manually create object without instances entry
381
+ @direct_key = EdgeCaseCustomer.new(custid: 'partial1', name: 'Partial').dbkey
382
+ EdgeCaseCustomer.dbclient.hset(@direct_key, 'custid', 'partial1')
383
+ EdgeCaseCustomer.dbclient.hset(@direct_key, 'name', 'Partial')
384
+ EdgeCaseCustomer.count
385
+ #=> 0
386
+
387
+ ## EDGE: Partial failure - keys_count finds the manually created object
388
+ EdgeCaseCustomer.keys_count
389
+ #=> 1
390
+
391
+ ## EDGE: Partial failure - scan_count finds it too
392
+ EdgeCaseCustomer.scan_count
393
+ #=> 1
394
+
395
+ ## EDGE: Partial failure - count! finds it
396
+ EdgeCaseCustomer.count!
397
+ #=> 1
398
+
399
+ ## EDGE: Partial failure - any? returns false (not in instances)
400
+ EdgeCaseCustomer.any?
401
+ #=> false
402
+
403
+ ## EDGE: Partial failure - keys_any? returns true (key exists)
404
+ EdgeCaseCustomer.keys_any?
405
+ #=> true
406
+
407
+ ## EDGE: Partial failure - scan_any? returns true (key exists)
408
+ EdgeCaseCustomer.scan_any?
409
+ #=> true
410
+
411
+ ## EDGE: Partial failure cleanup - delete direct key and clear
412
+ EdgeCaseCustomer.dbclient.del(@direct_key)
413
+ EdgeCaseCustomer.instances.clear
414
+ #=*> Integer
415
+
416
+ # =============================================================================
417
+ # Edge Case 11: Filter Pattern Edge Cases
418
+ # Test filter behavior with complex patterns and edge cases
419
+ # =============================================================================
420
+
421
+ ## EDGE: Filter patterns - create first filter test object
422
+ @filter1 = EdgeCaseCustomer.create!(custid: 'filter1', name: 'Filter1')
423
+ @filter1.custid
424
+ #=> 'filter1'
425
+
426
+ ## EDGE: Filter patterns - single character wildcard
427
+ EdgeCaseCustomer.keys_count('?')
428
+ #=> 0
429
+
430
+ ## EDGE: Filter patterns - create second filter test object
431
+ @filter2 = EdgeCaseCustomer.create!(custid: 'filter2', name: 'Filter2')
432
+ @filter2.custid
433
+ #=> 'filter2'
434
+
435
+ ## EDGE: Filter patterns - multiple wildcards count
436
+ EdgeCaseCustomer.keys_count('*')
437
+ #=*> Integer
438
+
439
+ ## EDGE: Filter patterns - scan with wildcard matches all
440
+ EdgeCaseCustomer.scan_count('*')
441
+ #=*> Integer
442
+
443
+ ## EDGE: Filter patterns - range pattern
444
+ EdgeCaseCustomer.keys_count('filter[12]')
445
+ #=> 2
446
+
447
+ ## EDGE: Filter patterns - scan with range pattern
448
+ EdgeCaseCustomer.scan_count('filter[12]')
449
+ #=> 2
450
+
451
+ ## EDGE: Empty identifier cleanup - clear all
452
+ EdgeCaseCustomer.instances.clear
453
+ EdgeCaseCustomer.all.each(&:destroy!)
454
+ #=*> Array
455
+
456
+ # =============================================================================
457
+ # Edge Case 12: Boundary Conditions
458
+ # Test edge cases around zero, one, and boundary values
459
+ # =============================================================================
460
+
461
+ ## EDGE: Boundary - scan_any? short-circuits on first match
462
+ # Create one object and verify scan_any? doesn't iterate unnecessarily
463
+ @boundary1 = EdgeCaseCustomer.create!(custid: 'boundary1', name: 'Boundary')
464
+ EdgeCaseCustomer.scan_any?
465
+ #=> true
466
+
467
+ ## EDGE: Boundary - scan_any? with filter short-circuits
468
+ EdgeCaseCustomer.scan_any?('bound*')
469
+ #=> true
470
+
471
+ ## EDGE: Boundary - scan_any? returns false when no matches
472
+ EdgeCaseCustomer.scan_any?('nonexistent*')
473
+ #=> false
474
+
475
+ ## EDGE: Boundary - keys_any? with non-matching filter
476
+ EdgeCaseCustomer.keys_any?('nonexistent*')
477
+ #=> false
478
+
479
+ ## EDGE: Boundary cleanup - destroy and clear
480
+ @boundary1.destroy!
481
+ EdgeCaseCustomer.instances.clear
482
+ #=*> Integer
483
+
484
+ ## Final cleanup - clear instances
485
+ EdgeCaseCustomer.instances.clear
486
+ #=*> Integer