familia 2.0.0.pre18 → 2.0.0.pre21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +4 -9
  3. data/.github/workflows/code-smells.yml +64 -3
  4. data/.pre-commit-config.yaml +8 -6
  5. data/.reek.yml +10 -9
  6. data/.rubocop.yml +4 -0
  7. data/CHANGELOG.rst +205 -88
  8. data/CLAUDE.md +62 -10
  9. data/Gemfile +3 -3
  10. data/Gemfile.lock +27 -62
  11. data/README.md +39 -0
  12. data/bin/try +16 -0
  13. data/bin/tryouts +16 -0
  14. data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
  15. data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
  16. data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
  17. data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
  18. data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
  19. data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -0
  20. data/docs/1106-participates_in-bidirectional-solution.md +129 -0
  21. data/docs/guides/encryption.md +486 -0
  22. data/docs/guides/feature-encrypted-fields.md +123 -7
  23. data/docs/guides/feature-expiration.md +177 -133
  24. data/docs/guides/feature-external-identifiers.md +415 -443
  25. data/docs/guides/feature-object-identifiers.md +400 -269
  26. data/docs/guides/feature-quantization.md +120 -6
  27. data/docs/guides/feature-relationships-indexing.md +318 -0
  28. data/docs/guides/feature-relationships-methods.md +146 -604
  29. data/docs/guides/feature-relationships-participation.md +263 -0
  30. data/docs/guides/feature-relationships.md +118 -136
  31. data/docs/guides/feature-system-devs.md +176 -693
  32. data/docs/guides/feature-system.md +119 -6
  33. data/docs/guides/feature-transient-fields.md +81 -0
  34. data/docs/guides/field-system.md +778 -0
  35. data/docs/guides/index.md +32 -15
  36. data/docs/guides/logging.md +187 -0
  37. data/docs/guides/optimized-loading.md +674 -0
  38. data/docs/guides/thread-safety-monitoring.md +61 -0
  39. data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
  40. data/docs/migrating/v2.0.0-pre19.md +197 -0
  41. data/docs/migrating/v2.0.0-pre22.md +241 -0
  42. data/docs/overview.md +7 -9
  43. data/docs/reference/api-technical.md +267 -320
  44. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
  45. data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
  46. data/examples/autoloader/mega_customer.rb +2 -0
  47. data/examples/datatype_standalone.rb +282 -0
  48. data/examples/encrypted_fields.rb +2 -1
  49. data/examples/json_usage_patterns.rb +2 -0
  50. data/examples/relationships.rb +3 -0
  51. data/examples/safe_dump.rb +2 -1
  52. data/examples/sampling_demo.rb +53 -0
  53. data/examples/single_connection_transaction_confusions.rb +2 -1
  54. data/familia.gemspec +2 -1
  55. data/lib/familia/base.rb +2 -0
  56. data/lib/familia/connection/behavior.rb +254 -0
  57. data/lib/familia/connection/handlers.rb +97 -0
  58. data/lib/familia/connection/individual_command_proxy.rb +2 -0
  59. data/lib/familia/connection/middleware.rb +34 -24
  60. data/lib/familia/connection/operation_core.rb +3 -1
  61. data/lib/familia/connection/operations.rb +2 -0
  62. data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +4 -2
  63. data/lib/familia/connection/transaction_core.rb +75 -9
  64. data/lib/familia/connection.rb +21 -5
  65. data/lib/familia/data_type/class_methods.rb +3 -1
  66. data/lib/familia/data_type/connection.rb +153 -7
  67. data/lib/familia/data_type/database_commands.rb +9 -4
  68. data/lib/familia/data_type/serialization.rb +10 -4
  69. data/lib/familia/data_type/settings.rb +2 -0
  70. data/lib/familia/data_type/types/counter.rb +2 -0
  71. data/lib/familia/data_type/types/hashkey.rb +8 -6
  72. data/lib/familia/data_type/types/listkey.rb +2 -0
  73. data/lib/familia/data_type/types/lock.rb +2 -0
  74. data/lib/familia/data_type/types/sorted_set.rb +2 -0
  75. data/lib/familia/data_type/types/stringkey.rb +2 -0
  76. data/lib/familia/data_type/types/unsorted_set.rb +2 -0
  77. data/lib/familia/data_type.rb +2 -0
  78. data/lib/familia/encryption/encrypted_data.rb +4 -2
  79. data/lib/familia/encryption/manager.rb +2 -0
  80. data/lib/familia/encryption/provider.rb +2 -0
  81. data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
  82. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
  83. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
  84. data/lib/familia/encryption/registry.rb +2 -0
  85. data/lib/familia/encryption/request_cache.rb +2 -0
  86. data/lib/familia/encryption.rb +9 -2
  87. data/lib/familia/errors.rb +53 -14
  88. data/lib/familia/features/autoloader.rb +2 -0
  89. data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
  90. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
  91. data/lib/familia/features/encrypted_fields.rb +2 -2
  92. data/lib/familia/features/expiration/extensions.rb +11 -11
  93. data/lib/familia/features/expiration.rb +29 -21
  94. data/lib/familia/features/external_identifier.rb +33 -7
  95. data/lib/familia/features/object_identifier.rb +2 -0
  96. data/lib/familia/features/quantization.rb +3 -1
  97. data/lib/familia/features/relationships/README.md +3 -1
  98. data/lib/familia/features/relationships/collection_operations.rb +2 -0
  99. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +177 -47
  100. data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
  101. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +203 -63
  102. data/lib/familia/features/relationships/indexing.rb +40 -42
  103. data/lib/familia/features/relationships/indexing_relationship.rb +17 -5
  104. data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
  105. data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
  106. data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
  107. data/lib/familia/features/relationships/participation.rb +155 -69
  108. data/lib/familia/features/relationships/participation_membership.rb +69 -0
  109. data/lib/familia/features/relationships/participation_relationship.rb +34 -6
  110. data/lib/familia/features/relationships/score_encoding.rb +2 -0
  111. data/lib/familia/features/relationships.rb +5 -3
  112. data/lib/familia/features/safe_dump.rb +2 -0
  113. data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
  114. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
  115. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
  116. data/lib/familia/features/transient_fields.rb +2 -0
  117. data/lib/familia/features.rb +2 -0
  118. data/lib/familia/field_type.rb +5 -2
  119. data/lib/familia/horreum/connection.rb +28 -36
  120. data/lib/familia/horreum/database_commands.rb +131 -10
  121. data/lib/familia/horreum/definition.rb +18 -7
  122. data/lib/familia/horreum/management.rb +233 -57
  123. data/lib/familia/horreum/persistence.rb +314 -122
  124. data/lib/familia/horreum/related_fields.rb +2 -0
  125. data/lib/familia/horreum/serialization.rb +26 -4
  126. data/lib/familia/horreum/settings.rb +2 -0
  127. data/lib/familia/horreum/utils.rb +2 -8
  128. data/lib/familia/horreum.rb +46 -13
  129. data/lib/familia/identifier_extractor.rb +2 -0
  130. data/lib/familia/instrumentation.rb +156 -0
  131. data/lib/familia/json_serializer.rb +2 -0
  132. data/lib/familia/logging.rb +94 -37
  133. data/lib/familia/refinements/dear_json.rb +2 -0
  134. data/lib/familia/refinements/stylize_words.rb +2 -14
  135. data/lib/familia/refinements/time_literals.rb +2 -0
  136. data/lib/familia/refinements.rb +2 -0
  137. data/lib/familia/secure_identifier.rb +10 -2
  138. data/lib/familia/settings.rb +9 -7
  139. data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
  140. data/lib/familia/thread_safety/monitor.rb +328 -0
  141. data/lib/familia/utils.rb +13 -0
  142. data/lib/familia/verifiable_identifier.rb +3 -1
  143. data/lib/familia/version.rb +3 -1
  144. data/lib/familia.rb +31 -4
  145. data/lib/middleware/database_command_counter.rb +152 -0
  146. data/lib/middleware/database_logger.rb +325 -129
  147. data/lib/multi_result.rb +2 -0
  148. data/try/edge_cases/empty_identifiers_try.rb +2 -0
  149. data/try/edge_cases/hash_symbolization_try.rb +2 -0
  150. data/try/edge_cases/json_serialization_try.rb +2 -0
  151. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
  152. data/try/edge_cases/race_conditions_try.rb +4 -0
  153. data/try/edge_cases/reserved_keywords_try.rb +4 -0
  154. data/try/edge_cases/string_coercion_try.rb +6 -4
  155. data/try/edge_cases/ttl_side_effects_try.rb +4 -0
  156. data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
  157. data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
  158. data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
  159. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
  160. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
  161. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
  162. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
  163. data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
  164. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
  165. data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
  166. data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
  167. data/try/features/encrypted_fields/memory_security_try.rb +4 -0
  168. data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
  169. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
  170. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
  171. data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
  172. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
  173. data/try/features/encryption/config_persistence_try.rb +4 -0
  174. data/try/features/encryption/core_try.rb +4 -0
  175. data/try/features/encryption/instance_variable_scope_try.rb +4 -0
  176. data/try/features/encryption/module_loading_try.rb +4 -0
  177. data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
  178. data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
  179. data/try/features/encryption/roundtrip_validation_try.rb +4 -0
  180. data/try/features/encryption/secure_memory_handling_try.rb +4 -0
  181. data/try/features/expiration/expiration_try.rb +5 -1
  182. data/try/features/external_identifier/external_identifier_try.rb +171 -8
  183. data/try/features/feature_dependencies_try.rb +2 -0
  184. data/try/features/feature_improvements_try.rb +2 -0
  185. data/try/features/field_groups_try.rb +2 -0
  186. data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
  187. data/try/features/object_identifier/object_identifier_try.rb +2 -0
  188. data/try/features/quantization/quantization_try.rb +4 -0
  189. data/try/features/real_feature_integration_try.rb +2 -0
  190. data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
  191. data/try/features/relationships/indexing_rebuild_try.rb +600 -0
  192. data/try/features/relationships/indexing_try.rb +30 -4
  193. data/try/features/relationships/participation_bidirectional_try.rb +242 -0
  194. data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
  195. data/try/features/relationships/participation_commands_verification_try.rb +2 -0
  196. data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
  197. data/try/features/relationships/participation_reverse_index_try.rb +15 -13
  198. data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
  199. data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
  200. data/try/features/relationships/relationships_api_changes_try.rb +6 -4
  201. data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
  202. data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
  203. data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
  204. data/try/features/relationships/relationships_performance_try.rb +4 -0
  205. data/try/features/relationships/relationships_performance_working_try.rb +4 -0
  206. data/try/features/relationships/relationships_try.rb +6 -4
  207. data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
  208. data/try/features/safe_dump/safe_dump_try.rb +4 -0
  209. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  210. data/try/features/transient_fields/refresh_reset_try.rb +3 -0
  211. data/try/features/transient_fields/simple_refresh_test.rb +3 -0
  212. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  213. data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
  214. data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
  215. data/try/integration/connection/fiber_context_preservation_try.rb +7 -3
  216. data/try/integration/connection/handler_constraints_try.rb +4 -0
  217. data/try/integration/connection/isolated_dbclient_try.rb +4 -0
  218. data/try/integration/connection/middleware_reconnect_try.rb +2 -0
  219. data/try/integration/connection/operation_mode_guards_try.rb +5 -1
  220. data/try/integration/connection/pipeline_fallback_integration_try.rb +15 -12
  221. data/try/integration/connection/pools_try.rb +4 -0
  222. data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
  223. data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
  224. data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
  225. data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
  226. data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
  227. data/try/integration/connection/transaction_modes_try.rb +4 -0
  228. data/try/integration/conventional_inheritance_try.rb +4 -0
  229. data/try/integration/create_method_try.rb +26 -22
  230. data/try/integration/cross_component_try.rb +4 -0
  231. data/try/integration/data_types/datatype_pipelines_try.rb +108 -0
  232. data/try/integration/data_types/datatype_transactions_try.rb +251 -0
  233. data/try/integration/database_consistency_try.rb +4 -0
  234. data/try/integration/familia_extended_try.rb +4 -0
  235. data/try/integration/familia_members_methods_try.rb +4 -0
  236. data/try/integration/models/customer_safe_dump_try.rb +9 -1
  237. data/try/integration/models/customer_try.rb +4 -0
  238. data/try/integration/models/datatype_base_try.rb +4 -0
  239. data/try/integration/models/familia_object_try.rb +5 -1
  240. data/try/integration/persistence_operations_try.rb +166 -10
  241. data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
  242. data/try/integration/save_methods_consistency_try.rb +241 -0
  243. data/try/integration/scenarios_try.rb +4 -0
  244. data/try/integration/secure_identifier_try.rb +4 -0
  245. data/try/integration/transaction_safety_core_try.rb +176 -0
  246. data/try/integration/transaction_safety_workflow_try.rb +291 -0
  247. data/try/integration/verifiable_identifier_try.rb +4 -0
  248. data/try/investigation/pipeline_routing/README.md +228 -0
  249. data/try/performance/benchmarks_try.rb +4 -0
  250. data/try/performance/transaction_safety_benchmark_try.rb +238 -0
  251. data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
  252. data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
  253. data/try/support/debugging/cache_behavior_tracer.rb +4 -0
  254. data/try/support/debugging/debug_aad_process.rb +3 -0
  255. data/try/support/debugging/debug_concealed_internal.rb +3 -0
  256. data/try/support/debugging/debug_concealed_reveal.rb +3 -0
  257. data/try/support/debugging/debug_context_aad.rb +3 -0
  258. data/try/support/debugging/debug_context_simple.rb +3 -0
  259. data/try/support/debugging/debug_cross_context.rb +3 -0
  260. data/try/support/debugging/debug_database_load.rb +3 -0
  261. data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
  262. data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
  263. data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
  264. data/try/support/debugging/debug_field_decrypt.rb +3 -0
  265. data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
  266. data/try/support/debugging/debug_load_path.rb +3 -0
  267. data/try/support/debugging/debug_method_definition.rb +3 -0
  268. data/try/support/debugging/debug_method_resolution.rb +3 -0
  269. data/try/support/debugging/debug_minimal.rb +3 -0
  270. data/try/support/debugging/debug_provider.rb +3 -0
  271. data/try/support/debugging/debug_secure_behavior.rb +3 -0
  272. data/try/support/debugging/debug_string_class.rb +3 -0
  273. data/try/support/debugging/debug_test.rb +3 -0
  274. data/try/support/debugging/debug_test_design.rb +3 -0
  275. data/try/support/debugging/encryption_method_tracer.rb +4 -0
  276. data/try/support/debugging/provider_diagnostics.rb +4 -0
  277. data/try/support/helpers/test_cleanup.rb +4 -0
  278. data/try/support/helpers/test_helpers.rb +5 -0
  279. data/try/support/memory/memory_basic_test.rb +4 -0
  280. data/try/support/memory/memory_detailed_test.rb +4 -0
  281. data/try/support/memory/memory_search_for_string.rb +4 -0
  282. data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
  283. data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
  284. data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
  285. data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
  286. data/try/support/prototypes/atomic_saves_v4.rb +4 -0
  287. data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
  288. data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  289. data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
  290. data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  291. data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
  292. data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
  293. data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
  294. data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
  295. data/try/support/prototypes/pooling/pool_siege.rb +4 -2
  296. data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
  297. data/try/thread_safety/README.md +496 -0
  298. data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
  299. data/try/thread_safety/connection_chain_race_try.rb +148 -0
  300. data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
  301. data/try/thread_safety/feature_registry_race_try.rb +226 -0
  302. data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
  303. data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
  304. data/try/thread_safety/field_registration_race_try.rb +222 -0
  305. data/try/thread_safety/logger_initialization_race_try.rb +170 -0
  306. data/try/thread_safety/middleware_registration_race_try.rb +154 -0
  307. data/try/thread_safety/module_config_race_try.rb +175 -0
  308. data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
  309. data/try/unit/core/autoloader_try.rb +4 -0
  310. data/try/unit/core/base_enhancements_try.rb +4 -0
  311. data/try/unit/core/connection_try.rb +4 -0
  312. data/try/unit/core/errors_try.rb +4 -0
  313. data/try/unit/core/extensions_try.rb +4 -0
  314. data/try/unit/core/familia_logger_try.rb +2 -0
  315. data/try/unit/core/familia_try.rb +4 -0
  316. data/try/unit/core/middleware_sampling_try.rb +335 -0
  317. data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
  318. data/try/unit/core/middleware_thread_safety_try.rb +245 -0
  319. data/try/unit/core/middleware_try.rb +4 -0
  320. data/try/unit/core/settings_try.rb +4 -0
  321. data/try/unit/core/time_utils_try.rb +4 -0
  322. data/try/unit/core/tools_try.rb +4 -0
  323. data/try/unit/core/utils_try.rb +37 -0
  324. data/try/unit/data_types/boolean_try.rb +5 -1
  325. data/try/unit/data_types/counter_try.rb +4 -0
  326. data/try/unit/data_types/datatype_base_try.rb +4 -0
  327. data/try/unit/data_types/hash_try.rb +4 -0
  328. data/try/unit/data_types/list_try.rb +4 -0
  329. data/try/unit/data_types/lock_try.rb +4 -0
  330. data/try/unit/data_types/sorted_set_try.rb +4 -0
  331. data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
  332. data/try/unit/data_types/string_try.rb +5 -1
  333. data/try/unit/data_types/unsortedset_try.rb +4 -0
  334. data/try/unit/familia_resolve_class_try.rb +116 -0
  335. data/try/unit/horreum/auto_indexing_on_save_try.rb +36 -16
  336. data/try/unit/horreum/automatic_index_validation_try.rb +255 -0
  337. data/try/unit/horreum/base_try.rb +5 -1
  338. data/try/unit/horreum/class_methods_try.rb +6 -2
  339. data/try/unit/horreum/commands_try.rb +4 -0
  340. data/try/unit/horreum/defensive_initialization_try.rb +4 -0
  341. data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +4 -0
  342. data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
  343. data/try/unit/horreum/field_categories_try.rb +4 -0
  344. data/try/unit/horreum/field_definition_try.rb +4 -0
  345. data/try/unit/horreum/initialization_try.rb +5 -1
  346. data/try/unit/horreum/json_type_preservation_try.rb +2 -0
  347. data/try/unit/horreum/optimized_loading_try.rb +156 -0
  348. data/try/unit/horreum/relations_try.rb +8 -4
  349. data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
  350. data/try/unit/horreum/serialization_try.rb +6 -2
  351. data/try/unit/horreum/settings_try.rb +4 -0
  352. data/try/unit/horreum/unique_index_edge_cases_try.rb +380 -0
  353. data/try/unit/horreum/unique_index_guard_validation_try.rb +283 -0
  354. data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
  355. data/try/unit/middleware/database_logger_methods_try.rb +251 -0
  356. data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
  357. data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
  358. data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
  359. data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
  360. data/try/unit/thread_safety_monitor_try.rb +149 -0
  361. metadata +81 -14
  362. data/.github/workflows/code-quality.yml +0 -138
  363. data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
  364. data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
  365. data/docs/archive/FAMILIA_UPDATE.md +0 -226
  366. data/docs/archive/README.md +0 -64
  367. data/docs/archive/api-reference.md +0 -333
  368. data/docs/guides/core-field-system.md +0 -806
  369. data/docs/guides/implementation.md +0 -276
  370. data/docs/guides/security-model.md +0 -183
@@ -0,0 +1,255 @@
1
+ # try/unit/horreum/automatic_index_validation_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ #
6
+ # Automatic index validation tests
7
+ # Tests that unique index validation happens automatically when adding to indexes
8
+ #
9
+
10
+ require_relative '../../support/helpers/test_helpers'
11
+
12
+ # Test classes for automatic validation
13
+ class ::AutoValidCompany < Familia::Horreum
14
+ feature :relationships
15
+
16
+ identifier_field :company_id
17
+ field :company_id
18
+ field :name
19
+ end
20
+
21
+ class ::AutoValidEmployee < Familia::Horreum
22
+ feature :relationships
23
+
24
+ identifier_field :emp_id
25
+ field :emp_id
26
+ field :badge_number
27
+ field :email
28
+
29
+ # Instance-scoped unique index (should auto-validate in add_to_* methods)
30
+ unique_index :badge_number, :badge_index, within: AutoValidCompany
31
+
32
+ # Class-level unique index (auto-validates in save)
33
+ unique_index :email, :email_index
34
+ end
35
+
36
+ class ::AutoValidUser < Familia::Horreum
37
+ feature :relationships
38
+
39
+ identifier_field :user_id
40
+ field :user_id
41
+ field :email
42
+
43
+ unique_index :email, :email_index
44
+ end
45
+
46
+ # Setup
47
+ @company_id = "comp_#{rand(1000000)}"
48
+ @company = AutoValidCompany.new(company_id: @company_id, name: 'Test Corp')
49
+ @company.save
50
+
51
+ @emp1_id = "emp_#{rand(1000000)}"
52
+ @emp2_id = "emp_#{rand(1000000)}"
53
+
54
+ # =============================================
55
+ # 1. Automatic Validation in add_to_* Methods
56
+ # =============================================
57
+
58
+ ## First employee can add badge to company index
59
+ @emp1 = AutoValidEmployee.new(emp_id: @emp1_id, badge_number: 'BADGE123', email: 'emp1@example.com')
60
+ @emp1.save # Save first to establish class-level email index
61
+ @emp1.add_to_auto_valid_company_badge_index(@company)
62
+ @company.badge_index.has_key?('BADGE123')
63
+ #=> true
64
+
65
+ ## Duplicate badge is automatically rejected without manual guard call
66
+ @emp2 = AutoValidEmployee.new(emp_id: @emp2_id, badge_number: 'BADGE123', email: 'emp2@example.com')
67
+ begin
68
+ @emp2.add_to_auto_valid_company_badge_index(@company)
69
+ false
70
+ rescue Familia::RecordExistsError => e
71
+ e.message.include?('AutoValidEmployee exists in AutoValidCompany with badge_number=BADGE123')
72
+ end
73
+ #=> true
74
+
75
+ ## Badge was not added after validation failure
76
+ @company.badge_index.get('BADGE123')
77
+ #=> @emp1_id
78
+
79
+ ## Different badge number works fine
80
+ @emp2.badge_number = 'BADGE456'
81
+ @emp2.add_to_auto_valid_company_badge_index(@company)
82
+ @company.badge_index.has_key?('BADGE456')
83
+ #=> true
84
+
85
+ ## Same employee can re-add (idempotent)
86
+ @emp1.add_to_auto_valid_company_badge_index(@company)
87
+ @company.badge_index.get('BADGE123')
88
+ #=> @emp1_id
89
+
90
+ ## Different company allows same badge (scoped uniqueness)
91
+ @company2_id = "comp_#{rand(1000000)}"
92
+ @company2 = AutoValidCompany.new(company_id: @company2_id, name: 'Other Corp')
93
+ @company2.save
94
+ @emp3_id = "emp_#{rand(1000000)}"
95
+ @emp3 = AutoValidEmployee.new(emp_id: @emp3_id, badge_number: 'BADGE123', email: 'emp3@example.com')
96
+ @emp3.add_to_auto_valid_company_badge_index(@company2)
97
+ @company2.badge_index.has_key?('BADGE123')
98
+ #=> true
99
+
100
+ ## Nil badge_number handled gracefully (no validation or addition)
101
+ @emp_nil = AutoValidEmployee.new(emp_id: "emp_nil_#{rand(1000000)}", badge_number: nil, email: 'empnil@example.com')
102
+ @emp_nil.add_to_auto_valid_company_badge_index(@company)
103
+ #=> nil
104
+
105
+ ## Nil parent handled gracefully (no validation or addition)
106
+ @emp4 = AutoValidEmployee.new(emp_id: "emp4_#{rand(1000000)}", badge_number: 'BADGE789', email: 'emp4@example.com')
107
+ @emp4.add_to_auto_valid_company_badge_index(nil)
108
+ #=> nil
109
+
110
+ # =============================================
111
+ # 2. Transaction Detection in save()
112
+ # =============================================
113
+
114
+ ## Normal save works outside transaction
115
+ @user1_id = "user_#{rand(1000000)}"
116
+ @user1 = AutoValidUser.new(user_id: @user1_id, email: 'user1@example.com')
117
+ @user1.save
118
+ #=> true
119
+
120
+ ## save() raises error when called within transaction
121
+ @user2_id = "user_#{rand(1000000)}"
122
+ begin
123
+ AutoValidUser.transaction do
124
+ @user2 = AutoValidUser.new(user_id: @user2_id, email: 'user2@example.com')
125
+ @user2.save
126
+ end
127
+ false
128
+ rescue Familia::OperationModeError => e
129
+ e.message.include?('Cannot call save within a transaction')
130
+ end
131
+ #=> true
132
+
133
+ ## Object was not saved due to transaction error
134
+ AutoValidUser.find_by_email('user2@example.com')
135
+ #=> nil
136
+
137
+ ## Transaction with explicit field updates works (bypass save)
138
+ @user3_id = "user_#{rand(1000000)}"
139
+ @user3 = AutoValidUser.new(user_id: @user3_id, email: 'user3@example.com')
140
+ AutoValidUser.transaction do |_tx|
141
+ @user3.hmset(@user3.to_h_for_storage)
142
+ end
143
+ @user3.exists?
144
+ #=> true
145
+
146
+ ## save() works after transaction completes
147
+ @user4_id = "user_#{rand(1000000)}"
148
+ AutoValidUser.transaction do
149
+ # Do something else in transaction
150
+ end
151
+ @user4 = AutoValidUser.new(user_id: @user4_id, email: 'user4@example.com')
152
+ @user4.save
153
+ #=> true
154
+
155
+ # =============================================
156
+ # 3. Combined Automatic Validation Scenarios
157
+ # =============================================
158
+
159
+ ## Employee with duplicate class-level email caught in save
160
+ @emp5_id = "emp_#{rand(1000000)}"
161
+ @emp5 = AutoValidEmployee.new(emp_id: @emp5_id, badge_number: 'BADGE999', email: 'emp1@example.com')
162
+ begin
163
+ @emp5.save
164
+ false
165
+ rescue Familia::RecordExistsError => e
166
+ e.message.include?('AutoValidEmployee exists email=emp1@example.com')
167
+ end
168
+ #=> true
169
+
170
+ ## Employee can save with unique email
171
+ @emp1.save
172
+ AutoValidEmployee.find_by_email('emp1@example.com')&.emp_id
173
+ #=> @emp1_id
174
+
175
+ ## After save, duplicate instance-scoped index still caught automatically
176
+ @emp6_id = "emp_#{rand(1000000)}"
177
+ @emp6 = AutoValidEmployee.new(emp_id: @emp6_id, badge_number: 'BADGE123', email: 'emp6@example.com')
178
+ @emp6.save # Class-level index is fine
179
+ begin
180
+ @emp6.add_to_auto_valid_company_badge_index(@company) # Instance-scoped duplicate
181
+ false
182
+ rescue Familia::RecordExistsError => e
183
+ e.message.include?('badge_number=BADGE123')
184
+ end
185
+ #=> true
186
+
187
+ # =============================================
188
+ # 4. Error Message Quality
189
+ # =============================================
190
+
191
+ ## Instance-scoped validation error includes both class names
192
+ begin
193
+ @emp2.badge_number = 'BADGE123' # Reset to duplicate
194
+ @emp2.add_to_auto_valid_company_badge_index(@company)
195
+ rescue Familia::RecordExistsError => e
196
+ [e.message.include?('AutoValidEmployee'), e.message.include?('AutoValidCompany')]
197
+ end
198
+ #=> [true, true]
199
+
200
+ ## Instance-scoped validation error includes field name and value
201
+ begin
202
+ @emp2.add_to_auto_valid_company_badge_index(@company)
203
+ rescue Familia::RecordExistsError => e
204
+ [e.message.include?('badge_number'), e.message.include?('BADGE123')]
205
+ end
206
+ #=> [true, true]
207
+
208
+ ## Error type is RecordExistsError
209
+ begin
210
+ @emp2.add_to_auto_valid_company_badge_index(@company)
211
+ rescue => e
212
+ e.class
213
+ end
214
+ #=> Familia::RecordExistsError
215
+
216
+ # =============================================
217
+ # 5. Performance - No Double Validation
218
+ # =============================================
219
+
220
+ ## Manual guard call before add_to_* is redundant but harmless
221
+ @emp7_id = "emp_#{rand(1000000)}"
222
+ @emp7 = AutoValidEmployee.new(emp_id: @emp7_id, badge_number: 'BADGE777', email: 'emp7@example.com')
223
+ @emp7.guard_unique_auto_valid_company_badge_index!(@company)
224
+ @emp7.add_to_auto_valid_company_badge_index(@company)
225
+ @company.badge_index.has_key?('BADGE777')
226
+ #=> true
227
+
228
+ ## Manual guard call detects duplicate
229
+ @emp8_id = "emp_#{rand(1000000)}"
230
+ @emp8 = AutoValidEmployee.new(emp_id: @emp8_id, badge_number: 'BADGE777', email: 'emp8@example.com')
231
+ begin
232
+ @emp8.guard_unique_auto_valid_company_badge_index!(@company) # Should fail - duplicate badge
233
+ false
234
+ rescue Familia::RecordExistsError
235
+ true
236
+ end
237
+ #=> true
238
+
239
+ # Teardown - clean up test objects
240
+ [@emp1, @emp2, @emp3, @emp_nil, @emp4, @emp5, @emp6, @emp7, @emp8].compact.each do |obj|
241
+ obj.destroy! if obj.respond_to?(:destroy!) && obj.respond_to?(:exists?) && obj.exists?
242
+ end
243
+
244
+ [@user1, @user3, @user4].compact.each do |obj|
245
+ obj.destroy! if obj.respond_to?(:destroy!) && obj.respond_to?(:exists?) && obj.exists?
246
+ end
247
+
248
+ [@company, @company2].each do |obj|
249
+ obj.destroy! if obj.respond_to?(:destroy!) && obj.respond_to?(:exists?) && obj.exists?
250
+ end
251
+
252
+ # Clean up class-level indexes
253
+ [AutoValidEmployee.email_index, AutoValidUser.email_index].each do |index|
254
+ index.delete! if index.respond_to?(:delete!) && index.respond_to?(:exists?) && index.exists?
255
+ end
@@ -1,3 +1,7 @@
1
+ # try/unit/horreum/base_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/horreum/base_try.rb
2
6
 
3
7
  require_relative '../../support/helpers/test_helpers'
@@ -41,7 +45,7 @@ Familia.debug = false
41
45
 
42
46
  ## Remove the key
43
47
  @hashkey.delete!
44
- #=> true
48
+ #=> 1
45
49
 
46
50
  ## Horreum objects can update and save their fields (1 of 2)
47
51
  @customer.name = 'John Doe'
@@ -1,3 +1,7 @@
1
+ # try/unit/horreum/class_methods_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/horreum/class_methods_try.rb
2
6
 
3
7
  # Test Horreum class methods
@@ -16,9 +20,9 @@ module AnotherModuleName
16
20
  end
17
21
  end
18
22
 
19
- ## create factory method with existence checking
23
+ ## create! factory method with existence checking
20
24
  TestUser
21
- #==> _.respond_to?(:create)
25
+ #==> _.respond_to?(:create!)
22
26
  #==> _.respond_to?(:exists?)
23
27
 
24
28
  ## multiget method is available
@@ -1,3 +1,7 @@
1
+ # try/unit/horreum/commands_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/horreum/commands_try.rb
2
6
 
3
7
  # Test Horreum Valkey/Redis commands
@@ -1,3 +1,7 @@
1
+ # try/unit/horreum/defensive_initialization_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/horreum/defensive_initialization_try.rb
2
6
 
3
7
  require_relative '../../support/helpers/test_helpers'
@@ -1,3 +1,7 @@
1
+ # try/unit/horreum/destroy_related_fields_cleanup_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/horreum/destroy_related_fields_cleanup_try.rb
2
6
 
3
7
  # Horreum destroy! Related Fields Cleanup Tryouts
@@ -1,3 +1,7 @@
1
+ # try/unit/horreum/enhanced_conflict_handling_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/horreum/enhanced_conflict_handling_try.rb
2
6
 
3
7
  require_relative '../../support/helpers/test_helpers'
@@ -1,3 +1,7 @@
1
+ # try/unit/horreum/field_categories_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/horreum/field_categories_try.rb
2
6
 
3
7
  require_relative '../../support/helpers/test_helpers'
@@ -1,3 +1,7 @@
1
+ # try/unit/horreum/field_definition_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/horreum/field_definition_try.rb
2
6
 
3
7
  require_relative '../../support/helpers/test_helpers'
@@ -1,3 +1,7 @@
1
+ # try/unit/horreum/initialization_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/horreum/initialization_try.rb
2
6
 
3
7
  require_relative '../../support/helpers/test_helpers'
@@ -101,7 +105,7 @@ Familia.debug = false
101
105
 
102
106
  ## Clean up saved test objects
103
107
  [@customer6, @complex].map(&:delete!)
104
- #=> [true, true]
108
+ #=> [1, 1]
105
109
 
106
110
  ## "Cleaning up" test objects that were never saved returns true regardless
107
111
  ## b/c it takes place in a transaction and it's the transaction's success
@@ -1,3 +1,5 @@
1
+ # try/unit/horreum/json_type_preservation_try.rb
2
+ #
1
3
  # frozen_string_literal: true
2
4
 
3
5
  require_relative '../../support/helpers/test_helpers'
@@ -0,0 +1,156 @@
1
+ # try/unit/horreum/optimized_loading_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ # Test optimized loading methods with check_exists parameter and pipelined bulk loading
6
+
7
+ require_relative '../../support/helpers/test_helpers'
8
+
9
+ OptimizedUser = Class.new(Familia::Horreum) do
10
+ identifier_field :user_id
11
+ field :user_id
12
+ field :name
13
+ field :email
14
+ field :age
15
+ end
16
+
17
+ # Setup: Create test users
18
+ setup_user1 = OptimizedUser.new(user_id: 'opt_user_1', name: 'Alice', email: 'alice@example.com', age: 30)
19
+ setup_user1.save
20
+
21
+ setup_user2 = OptimizedUser.new(user_id: 'opt_user_2', name: 'Bob', email: 'bob@example.com', age: 25)
22
+ setup_user2.save
23
+
24
+ setup_user3 = OptimizedUser.new(user_id: 'opt_user_3', name: 'Charlie', email: 'charlie@example.com', age: 35)
25
+ setup_user3.save
26
+
27
+ ## find_by_dbkey with check_exists: true (default) returns object for existing key
28
+ found_user = OptimizedUser.find_by_dbkey(OptimizedUser.dbkey('opt_user_1'))
29
+ found_user.name
30
+ #=> 'Alice'
31
+
32
+ ## find_by_dbkey with check_exists: true (default) returns nil for non-existent key
33
+ OptimizedUser.find_by_dbkey(OptimizedUser.dbkey('nonexistent'))
34
+ #=> nil
35
+
36
+ ## find_by_dbkey with check_exists: false returns object for existing key
37
+ found_user_fast = OptimizedUser.find_by_dbkey(OptimizedUser.dbkey('opt_user_1'), check_exists: false)
38
+ found_user_fast.name
39
+ #=> 'Alice'
40
+
41
+ ## find_by_dbkey with check_exists: false returns nil for non-existent key
42
+ OptimizedUser.find_by_dbkey(OptimizedUser.dbkey('nonexistent'), check_exists: false)
43
+ #=> nil
44
+
45
+ ## find_by_dbkey with check_exists: false correctly deserializes all fields
46
+ fast_loaded = OptimizedUser.find_by_dbkey(OptimizedUser.dbkey('opt_user_2'), check_exists: false)
47
+ [fast_loaded.user_id, fast_loaded.name, fast_loaded.email, fast_loaded.age]
48
+ #=> ['opt_user_2', 'Bob', 'bob@example.com', 25]
49
+
50
+ ## find_by_identifier with check_exists: true (default) returns object
51
+ OptimizedUser.find_by_identifier('opt_user_1').name
52
+ #=> 'Alice'
53
+
54
+ ## find_by_identifier with check_exists: false returns object
55
+ OptimizedUser.find_by_identifier('opt_user_1', check_exists: false).name
56
+ #=> 'Alice'
57
+
58
+ ## find_by_identifier with check_exists: false returns nil for non-existent
59
+ OptimizedUser.find_by_identifier('nonexistent', check_exists: false)
60
+ #=> nil
61
+
62
+ ## find_by_id alias works with check_exists parameter
63
+ OptimizedUser.find_by_id('opt_user_2', check_exists: false).email
64
+ #=> 'bob@example.com'
65
+
66
+ ## find alias works with check_exists parameter
67
+ OptimizedUser.find('opt_user_3', check_exists: false).age
68
+ #=> 35
69
+
70
+ ## load alias works with check_exists parameter
71
+ OptimizedUser.load('opt_user_1', check_exists: false).user_id
72
+ #=> 'opt_user_1'
73
+
74
+ ## load_multi loads multiple existing objects
75
+ users = OptimizedUser.load_multi(['opt_user_1', 'opt_user_2', 'opt_user_3'])
76
+ users.map(&:name)
77
+ #=> ['Alice', 'Bob', 'Charlie']
78
+
79
+ ## load_multi returns nils for non-existent objects in correct positions
80
+ users_mixed = OptimizedUser.load_multi(['opt_user_1', 'nonexistent', 'opt_user_3'])
81
+ [users_mixed[0]&.name, users_mixed[1], users_mixed[2]&.name]
82
+ #=> ['Alice', nil, 'Charlie']
83
+
84
+ ## load_multi with compact filters out nils
85
+ users_compact = OptimizedUser.load_multi(['opt_user_1', 'nonexistent', 'opt_user_2']).compact
86
+ users_compact.map(&:name)
87
+ #=> ['Alice', 'Bob']
88
+
89
+ ## load_multi preserves order of identifiers
90
+ users_ordered = OptimizedUser.load_multi(['opt_user_3', 'opt_user_1', 'opt_user_2'])
91
+ users_ordered.map(&:user_id)
92
+ #=> ['opt_user_3', 'opt_user_1', 'opt_user_2']
93
+
94
+ ## load_multi handles empty array
95
+ OptimizedUser.load_multi([])
96
+ #=> []
97
+
98
+ ## load_multi handles all non-existent identifiers
99
+ all_missing = OptimizedUser.load_multi(['missing1', 'missing2'])
100
+ all_missing.compact
101
+ #=> []
102
+
103
+ ## load_multi correctly deserializes all field types
104
+ multi_loaded = OptimizedUser.load_multi(['opt_user_2']).first
105
+ [multi_loaded.user_id, multi_loaded.name, multi_loaded.email, multi_loaded.age]
106
+ #=> ['opt_user_2', 'Bob', 'bob@example.com', 25]
107
+
108
+ ## load_batch alias works
109
+ batch_users = OptimizedUser.load_batch(['opt_user_1', 'opt_user_2'])
110
+ batch_users.map(&:name)
111
+ #=> ['Alice', 'Bob']
112
+
113
+ ## load_multi_by_keys loads by full dbkeys
114
+ keys = [OptimizedUser.dbkey('opt_user_1'), OptimizedUser.dbkey('opt_user_2')]
115
+ keyed_users = OptimizedUser.load_multi_by_keys(keys)
116
+ keyed_users.map(&:name)
117
+ #=> ['Alice', 'Bob']
118
+
119
+ ## load_multi_by_keys returns nil for non-existent keys
120
+ keys_mixed = [OptimizedUser.dbkey('opt_user_1'), OptimizedUser.dbkey('nonexistent')]
121
+ keyed_mixed = OptimizedUser.load_multi_by_keys(keys_mixed)
122
+ [keyed_mixed[0]&.name, keyed_mixed[1]]
123
+ #=> ['Alice', nil]
124
+
125
+ ## load_multi_by_keys handles empty array
126
+ OptimizedUser.load_multi_by_keys([])
127
+ #=> []
128
+
129
+ ## load_multi_by_keys handles empty/nil keys and maintains position alignment
130
+ keys_with_empty = [OptimizedUser.dbkey('opt_user_1'), '', OptimizedUser.dbkey('opt_user_2'), nil]
131
+ mixed_keys = OptimizedUser.load_multi_by_keys(keys_with_empty)
132
+ [mixed_keys[0]&.name, mixed_keys[1], mixed_keys[2]&.name, mixed_keys[3]]
133
+ #=> ['Alice', nil, 'Bob', nil]
134
+
135
+ ## load_multi handles nil identifiers gracefully
136
+ users_with_nils = OptimizedUser.load_multi(['opt_user_1', nil, 'opt_user_2'])
137
+ [users_with_nils[0]&.name, users_with_nils[1], users_with_nils[2]&.name]
138
+ #=> ['Alice', nil, 'Bob']
139
+
140
+ ## load_multi handles empty string identifiers
141
+ users_with_empty = OptimizedUser.load_multi(['opt_user_1', '', 'opt_user_2'])
142
+ [users_with_empty[0]&.name, users_with_empty[1], users_with_empty[2]&.name]
143
+ #=> ['Alice', nil, 'Bob']
144
+
145
+ ## find_by_identifier works with suffix as keyword parameter
146
+ OptimizedUser.find_by_identifier('opt_user_1', suffix: :object)&.name
147
+ #=> 'Alice'
148
+
149
+ ## find_by_identifier works with both keyword parameters
150
+ OptimizedUser.find_by_identifier('opt_user_1', suffix: :object, check_exists: false)&.name
151
+ #=> 'Alice'
152
+
153
+ # Teardown: Clean up test data
154
+ OptimizedUser.destroy!('opt_user_1')
155
+ OptimizedUser.destroy!('opt_user_2')
156
+ OptimizedUser.destroy!('opt_user_3')
@@ -1,3 +1,7 @@
1
+ # try/unit/horreum/relations_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/horreum/relations_try.rb
2
6
  # Test Horreum Database type relations functionality
3
7
 
@@ -92,15 +96,15 @@ prefs = @test_user.preferences
92
96
  @test_user.preferences.size
93
97
  #=> 2
94
98
 
95
- ## Clearing a counter returns false when not set yet
99
+ ## Clearing a counter returns 0 when not set yet
96
100
  @test_product.views.clear
97
101
  @test_product.views.clear
98
- #=> false
102
+ #=> 0
99
103
 
100
- ## Clearing a counter returns true when it is set
104
+ ## Clearing a counter returns 1 when it is set
101
105
  @test_product.views.increment
102
106
  @test_product.views.clear
103
- #=> true
107
+ #=> 1
104
108
 
105
109
  ## Counter Database type works
106
110
  @test_product.views.increment
@@ -1,3 +1,7 @@
1
+ # try/unit/horreum/serialization_persistent_fields_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/horreum/serialization_persistent_fields_try.rb
2
6
 
3
7
  require_relative '../../support/helpers/test_helpers'
@@ -1,3 +1,7 @@
1
+ # try/unit/horreum/serialization_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/horreum/serialization_try.rb
2
6
 
3
7
  require_relative '../../support/helpers/test_helpers'
@@ -20,10 +24,10 @@ Familia.dbclient.set('debug:starting_save_if_not_exists_tests', Familia.now.to_s
20
24
  @new_customer.save_if_not_exists
21
25
  #=> true
22
26
 
23
- ## save_if_not_exists raises error when customer already exists
27
+ ## save_if_not_exists! raises error when customer already exists
24
28
  @duplicate_customer = Customer.new "new-customer-#{@test_id}@test.com"
25
29
  @duplicate_customer.name = 'Duplicate Customer'
26
- @duplicate_customer.save_if_not_exists
30
+ @duplicate_customer.save_if_not_exists!
27
31
  #=!> Familia::RecordExistsError
28
32
  #==> error.message.include?("Key already exists")
29
33
 
@@ -1,3 +1,7 @@
1
+ # try/unit/horreum/settings_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # try/horreum/settings_try.rb
2
6
 
3
7
  # Test Horreum settings