familia 2.0.0.pre17 → 2.0.0.pre19

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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.rst +118 -6
  3. data/CLAUDE.md +43 -11
  4. data/Gemfile +2 -2
  5. data/Gemfile.lock +9 -47
  6. data/README.md +52 -0
  7. data/bin/irb +1 -1
  8. data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +91 -0
  9. data/changelog.d/20251011_203905_delano_next.rst +30 -0
  10. data/changelog.d/20251011_212633_delano_next.rst +13 -0
  11. data/changelog.d/20251011_221253_delano_next.rst +26 -0
  12. data/docs/guides/core-field-system.md +48 -26
  13. data/docs/guides/feature-expiration.md +18 -18
  14. data/docs/migrating/v2.0.0-pre18.md +58 -0
  15. data/docs/migrating/v2.0.0-pre19.md +197 -0
  16. data/docs/qodo-merge-compliance.md +96 -0
  17. data/examples/datatype_standalone.rb +281 -0
  18. data/lib/familia/base.rb +0 -2
  19. data/lib/familia/connection/behavior.rb +252 -0
  20. data/lib/familia/connection/handlers.rb +95 -0
  21. data/lib/familia/connection/middleware.rb +58 -4
  22. data/lib/familia/connection/operation_core.rb +1 -1
  23. data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +2 -2
  24. data/lib/familia/connection/transaction_core.rb +7 -9
  25. data/lib/familia/connection.rb +2 -1
  26. data/lib/familia/data_type/connection.rb +151 -7
  27. data/lib/familia/data_type/{commands.rb → database_commands.rb} +9 -6
  28. data/lib/familia/data_type/serialization.rb +9 -5
  29. data/lib/familia/data_type/types/hashkey.rb +1 -1
  30. data/lib/familia/data_type.rb +2 -2
  31. data/lib/familia/encryption/encrypted_data.rb +12 -2
  32. data/lib/familia/encryption/manager.rb +11 -4
  33. data/lib/familia/errors.rb +51 -14
  34. data/lib/familia/features/autoloader.rb +3 -1
  35. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +11 -3
  36. data/lib/familia/features/expiration/extensions.rb +8 -10
  37. data/lib/familia/features/expiration.rb +19 -19
  38. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +45 -44
  39. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +151 -65
  40. data/lib/familia/features/relationships/indexing.rb +37 -42
  41. data/lib/familia/features/relationships/indexing_relationship.rb +14 -4
  42. data/lib/familia/features/safe_dump.rb +2 -3
  43. data/lib/familia/field_type.rb +2 -1
  44. data/lib/familia/horreum/connection.rb +11 -35
  45. data/lib/familia/horreum/database_commands.rb +130 -11
  46. data/lib/familia/horreum/definition.rb +8 -38
  47. data/lib/familia/horreum/management.rb +38 -27
  48. data/lib/familia/horreum/persistence.rb +191 -67
  49. data/lib/familia/horreum/serialization.rb +94 -73
  50. data/lib/familia/horreum/utils.rb +0 -8
  51. data/lib/familia/horreum.rb +41 -18
  52. data/lib/familia/identifier_extractor.rb +60 -0
  53. data/lib/familia/logging.rb +268 -112
  54. data/lib/familia/refinements.rb +0 -1
  55. data/lib/familia/settings.rb +7 -7
  56. data/lib/familia/version.rb +1 -1
  57. data/lib/familia.rb +2 -2
  58. data/lib/middleware/{database_middleware.rb → database_logger.rb} +118 -14
  59. data/pr_agent.toml +31 -0
  60. data/pr_compliance_checklist.yaml +45 -0
  61. data/try/edge_cases/empty_identifiers_try.rb +1 -1
  62. data/try/edge_cases/hash_symbolization_try.rb +31 -31
  63. data/try/edge_cases/json_serialization_try.rb +2 -2
  64. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +170 -0
  65. data/try/edge_cases/race_conditions_try.rb +1 -1
  66. data/try/edge_cases/reserved_keywords_try.rb +1 -1
  67. data/try/edge_cases/string_coercion_try.rb +5 -5
  68. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  69. data/try/features/encrypted_fields/aad_protection_try.rb +1 -1
  70. data/try/features/encrypted_fields/concealed_string_core_try.rb +1 -1
  71. data/try/features/encrypted_fields/context_isolation_try.rb +1 -1
  72. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  73. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +1 -1
  74. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +1 -1
  75. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +1 -1
  76. data/try/features/encrypted_fields/error_conditions_try.rb +1 -1
  77. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +1 -1
  78. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  79. data/try/features/encrypted_fields/key_rotation_try.rb +1 -1
  80. data/try/features/encrypted_fields/memory_security_try.rb +1 -1
  81. data/try/features/encrypted_fields/missing_current_key_version_try.rb +1 -1
  82. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  83. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +1 -1
  84. data/try/features/encrypted_fields/thread_safety_try.rb +1 -1
  85. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +1 -1
  86. data/try/{encryption → features/encryption}/config_persistence_try.rb +1 -1
  87. data/try/{encryption/encryption_core_try.rb → features/encryption/core_try.rb} +2 -2
  88. data/try/{encryption → features/encryption}/instance_variable_scope_try.rb +1 -1
  89. data/try/{encryption → features/encryption}/module_loading_try.rb +1 -1
  90. data/try/{encryption → features/encryption}/providers/aes_gcm_provider_try.rb +1 -1
  91. data/try/{encryption → features/encryption}/providers/xchacha20_poly1305_provider_try.rb +1 -1
  92. data/try/{encryption → features/encryption}/roundtrip_validation_try.rb +1 -1
  93. data/try/{encryption → features/encryption}/secure_memory_handling_try.rb +2 -2
  94. data/try/features/expiration/expiration_try.rb +2 -2
  95. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  96. data/try/features/feature_dependencies_try.rb +1 -1
  97. data/try/features/feature_improvements_try.rb +1 -1
  98. data/try/features/object_identifier/object_identifier_integration_try.rb +1 -1
  99. data/try/features/object_identifier/object_identifier_try.rb +1 -1
  100. data/try/features/quantization/quantization_try.rb +1 -1
  101. data/try/features/real_feature_integration_try.rb +17 -14
  102. data/try/features/relationships/indexing_commands_verification_try.rb +8 -3
  103. data/try/features/relationships/indexing_try.rb +34 -5
  104. data/try/features/relationships/participation_commands_verification_spec.rb +1 -1
  105. data/try/features/relationships/participation_commands_verification_try.rb +4 -4
  106. data/try/features/relationships/participation_performance_improvements_try.rb +1 -1
  107. data/try/features/relationships/participation_reverse_index_try.rb +1 -1
  108. data/try/features/relationships/relationships_api_changes_try.rb +5 -5
  109. data/try/features/relationships/relationships_edge_cases_try.rb +3 -3
  110. data/try/features/relationships/relationships_performance_minimal_try.rb +1 -1
  111. data/try/features/relationships/relationships_performance_simple_try.rb +1 -1
  112. data/try/features/relationships/relationships_performance_try.rb +1 -1
  113. data/try/features/relationships/relationships_performance_working_try.rb +1 -1
  114. data/try/features/relationships/relationships_try.rb +1 -1
  115. data/try/features/safe_dump/safe_dump_advanced_try.rb +1 -1
  116. data/try/features/safe_dump/safe_dump_try.rb +1 -1
  117. data/try/features/transient_fields/redacted_string_try.rb +1 -1
  118. data/try/features/transient_fields/refresh_reset_try.rb +1 -1
  119. data/try/features/transient_fields/single_use_redacted_string_try.rb +1 -1
  120. data/try/features/transient_fields/transient_fields_core_try.rb +1 -1
  121. data/try/features/transient_fields/transient_fields_integration_try.rb +1 -1
  122. data/try/{connection → integration/connection}/fiber_context_preservation_try.rb +4 -4
  123. data/try/{connection → integration/connection}/handler_constraints_try.rb +1 -1
  124. data/try/{core → integration/connection}/isolated_dbclient_try.rb +1 -1
  125. data/try/integration/connection/middleware_reconnect_try.rb +87 -0
  126. data/try/{connection → integration/connection}/operation_mode_guards_try.rb +2 -2
  127. data/try/{connection → integration/connection}/pipeline_fallback_integration_try.rb +13 -13
  128. data/try/{core → integration/connection}/pools_try.rb +1 -1
  129. data/try/{connection → integration/connection}/responsibility_chain_tracking_try.rb +1 -1
  130. data/try/{connection → integration/connection}/transaction_fallback_integration_try.rb +1 -1
  131. data/try/{connection → integration/connection}/transaction_mode_permissive_try.rb +1 -1
  132. data/try/{connection → integration/connection}/transaction_mode_strict_try.rb +1 -1
  133. data/try/{connection → integration/connection}/transaction_mode_warn_try.rb +1 -1
  134. data/try/{connection → integration/connection}/transaction_modes_try.rb +1 -1
  135. data/try/{core → integration}/conventional_inheritance_try.rb +1 -1
  136. data/try/{core → integration}/create_method_try.rb +23 -23
  137. data/try/integration/cross_component_try.rb +1 -1
  138. data/try/integration/data_types/datatype_pipelines_try.rb +104 -0
  139. data/try/integration/data_types/datatype_transactions_try.rb +247 -0
  140. data/try/{core → integration}/database_consistency_try.rb +11 -8
  141. data/try/{core → integration}/familia_extended_try.rb +1 -1
  142. data/try/{core → integration}/familia_members_methods_try.rb +1 -1
  143. data/try/{models → integration/models}/customer_safe_dump_try.rb +6 -2
  144. data/try/{models → integration/models}/customer_try.rb +1 -1
  145. data/try/{models → integration/models}/datatype_base_try.rb +1 -1
  146. data/try/{models → integration/models}/familia_object_try.rb +2 -2
  147. data/try/{core → integration}/persistence_operations_try.rb +163 -11
  148. data/try/integration/relationships_persistence_round_trip_try.rb +441 -0
  149. data/try/{configuration → integration}/scenarios_try.rb +1 -1
  150. data/try/{core → integration}/secure_identifier_try.rb +1 -1
  151. data/try/{core → integration}/verifiable_identifier_try.rb +1 -1
  152. data/try/performance/benchmarks_try.rb +2 -2
  153. data/try/support/benchmarks/deserialization_benchmark.rb +180 -0
  154. data/try/support/benchmarks/deserialization_correctness_test.rb +237 -0
  155. data/try/{helpers → support/helpers}/test_helpers.rb +12 -3
  156. data/try/{core → unit/core}/autoloader_try.rb +1 -1
  157. data/try/{core → unit/core}/base_enhancements_try.rb +1 -9
  158. data/try/{core → unit/core}/connection_try.rb +1 -1
  159. data/try/{core → unit/core}/errors_try.rb +1 -1
  160. data/try/{core → unit/core}/extensions_try.rb +1 -1
  161. data/try/unit/core/familia_logger_try.rb +110 -0
  162. data/try/{core → unit/core}/familia_try.rb +1 -1
  163. data/try/{core → unit/core}/middleware_try.rb +41 -1
  164. data/try/{core → unit/core}/settings_try.rb +1 -1
  165. data/try/{core → unit/core}/time_utils_try.rb +1 -1
  166. data/try/{core → unit/core}/tools_try.rb +1 -1
  167. data/try/{core → unit/core}/utils_try.rb +17 -14
  168. data/try/{data_types → unit/data_types}/boolean_try.rb +2 -2
  169. data/try/{data_types → unit/data_types}/counter_try.rb +1 -1
  170. data/try/{data_types → unit/data_types}/datatype_base_try.rb +1 -1
  171. data/try/{data_types → unit/data_types}/hash_try.rb +1 -1
  172. data/try/{data_types → unit/data_types}/list_try.rb +1 -1
  173. data/try/{data_types → unit/data_types}/lock_try.rb +1 -1
  174. data/try/{data_types → unit/data_types}/sorted_set_try.rb +1 -1
  175. data/try/{data_types → unit/data_types}/sorted_set_zadd_options_try.rb +1 -1
  176. data/try/{data_types → unit/data_types}/string_try.rb +2 -2
  177. data/try/{data_types → unit/data_types}/unsortedset_try.rb +1 -1
  178. data/try/{horreum → unit/horreum}/auto_indexing_on_save_try.rb +33 -17
  179. data/try/unit/horreum/automatic_index_validation_try.rb +253 -0
  180. data/try/{horreum → unit/horreum}/base_try.rb +4 -4
  181. data/try/{horreum → unit/horreum}/class_methods_try.rb +3 -3
  182. data/try/{horreum → unit/horreum}/commands_try.rb +1 -1
  183. data/try/{horreum → unit/horreum}/defensive_initialization_try.rb +1 -1
  184. data/try/{horreum → unit/horreum}/destroy_related_fields_cleanup_try.rb +1 -1
  185. data/try/{horreum → unit/horreum}/enhanced_conflict_handling_try.rb +1 -1
  186. data/try/{horreum → unit/horreum}/field_categories_try.rb +27 -18
  187. data/try/{horreum → unit/horreum}/field_definition_try.rb +1 -1
  188. data/try/{horreum → unit/horreum}/initialization_try.rb +3 -3
  189. data/try/unit/horreum/json_type_preservation_try.rb +248 -0
  190. data/try/{horreum → unit/horreum}/relations_try.rb +5 -5
  191. data/try/{horreum → unit/horreum}/serialization_persistent_fields_try.rb +24 -18
  192. data/try/{horreum → unit/horreum}/serialization_try.rb +6 -6
  193. data/try/{horreum → unit/horreum}/settings_try.rb +1 -1
  194. data/try/unit/horreum/unique_index_edge_cases_try.rb +376 -0
  195. data/try/unit/horreum/unique_index_guard_validation_try.rb +281 -0
  196. data/try/{refinements → unit/refinements}/dear_json_array_methods_try.rb +1 -1
  197. data/try/{refinements → unit/refinements}/dear_json_hash_methods_try.rb +1 -1
  198. data/try/{refinements → unit/refinements}/time_literals_numeric_methods_try.rb +1 -1
  199. data/try/{refinements → unit/refinements}/time_literals_string_methods_try.rb +1 -1
  200. metadata +147 -126
  201. data/lib/familia/distinguisher.rb +0 -85
  202. data/lib/familia/refinements/logger_trace.rb +0 -60
  203. data/try/refinements/logger_trace_methods_try.rb +0 -44
  204. /data/try/{debugging → support/debugging}/README.md +0 -0
  205. /data/try/{debugging → support/debugging}/cache_behavior_tracer.rb +0 -0
  206. /data/try/{debugging → support/debugging}/debug_aad_process.rb +0 -0
  207. /data/try/{debugging → support/debugging}/debug_concealed_internal.rb +0 -0
  208. /data/try/{debugging → support/debugging}/debug_concealed_reveal.rb +0 -0
  209. /data/try/{debugging → support/debugging}/debug_context_aad.rb +0 -0
  210. /data/try/{debugging → support/debugging}/debug_context_simple.rb +0 -0
  211. /data/try/{debugging → support/debugging}/debug_cross_context.rb +0 -0
  212. /data/try/{debugging → support/debugging}/debug_database_load.rb +0 -0
  213. /data/try/{debugging → support/debugging}/debug_encrypted_json_check.rb +0 -0
  214. /data/try/{debugging → support/debugging}/debug_encrypted_json_step_by_step.rb +0 -0
  215. /data/try/{debugging → support/debugging}/debug_exists_lifecycle.rb +0 -0
  216. /data/try/{debugging → support/debugging}/debug_field_decrypt.rb +0 -0
  217. /data/try/{debugging → support/debugging}/debug_fresh_cross_context.rb +0 -0
  218. /data/try/{debugging → support/debugging}/debug_load_path.rb +0 -0
  219. /data/try/{debugging → support/debugging}/debug_method_definition.rb +0 -0
  220. /data/try/{debugging → support/debugging}/debug_method_resolution.rb +0 -0
  221. /data/try/{debugging → support/debugging}/debug_minimal.rb +0 -0
  222. /data/try/{debugging → support/debugging}/debug_provider.rb +0 -0
  223. /data/try/{debugging → support/debugging}/debug_secure_behavior.rb +0 -0
  224. /data/try/{debugging → support/debugging}/debug_string_class.rb +0 -0
  225. /data/try/{debugging → support/debugging}/debug_test.rb +0 -0
  226. /data/try/{debugging → support/debugging}/debug_test_design.rb +0 -0
  227. /data/try/{debugging → support/debugging}/encryption_method_tracer.rb +0 -0
  228. /data/try/{debugging → support/debugging}/provider_diagnostics.rb +0 -0
  229. /data/try/{helpers → support/helpers}/test_cleanup.rb +0 -0
  230. /data/try/{memory → support/memory}/memory_basic_test.rb +0 -0
  231. /data/try/{memory → support/memory}/memory_detailed_test.rb +0 -0
  232. /data/try/{memory → support/memory}/memory_docker_ruby_dump.sh +0 -0
  233. /data/try/{memory → support/memory}/memory_search_for_string.rb +0 -0
  234. /data/try/{memory → support/memory}/test_actual_redactedstring_protection.rb +0 -0
  235. /data/try/{prototypes → support/prototypes}/atomic_saves_v1_context_proxy.rb +0 -0
  236. /data/try/{prototypes → support/prototypes}/atomic_saves_v2_connection_switching.rb +0 -0
  237. /data/try/{prototypes → support/prototypes}/atomic_saves_v3_connection_pool.rb +0 -0
  238. /data/try/{prototypes → support/prototypes}/atomic_saves_v4.rb +0 -0
  239. /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v2_connection_switching_helpers.rb +0 -0
  240. /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
  241. /data/try/{prototypes → support/prototypes}/pooling/README.md +0 -0
  242. /data/try/{prototypes → support/prototypes}/pooling/configurable_stress_test.rb +0 -0
  243. /data/try/{prototypes → support/prototypes}/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
  244. /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_metrics.rb +0 -0
  245. /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_stress_test.rb +0 -0
  246. /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_threading_models.rb +0 -0
  247. /data/try/{prototypes → support/prototypes}/pooling/lib/visualize_stress_results.rb +0 -0
  248. /data/try/{prototypes → support/prototypes}/pooling/pool_siege.rb +0 -0
  249. /data/try/{prototypes → support/prototypes}/pooling/run_stress_tests.rb +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  # These tryouts test the safe dumping functionality.
4
4
 
5
- require_relative '../../helpers/test_helpers'
5
+ require_relative '../../support/helpers/test_helpers'
6
6
 
7
7
  ## By default Familia::Base has no safe_dump_fields method
8
8
  Familia::Base.respond_to?(:safe_dump_fields)
@@ -1,6 +1,6 @@
1
1
  # try/features/safe_dump_try.rb
2
2
 
3
- require_relative '../../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  Familia.debug = false
6
6
 
@@ -1,6 +1,6 @@
1
1
  # try/features/transient_fields/redacted_string_try.rb
2
2
 
3
- require_relative '../../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
 
6
6
  # Create sample sensitive values for testing
@@ -1,7 +1,7 @@
1
1
  # try/features/transient_fields/refresh_reset_try.rb
2
2
  # Test that refresh! properly resets transient fields to nil
3
3
 
4
- require_relative '../../helpers/test_helpers'
4
+ require_relative '../../support/helpers/test_helpers'
5
5
 
6
6
  Familia.debug = false
7
7
 
@@ -1,6 +1,6 @@
1
1
  # try/features/transient_fields/single_use_redacted_string_try.rb
2
2
 
3
- require_relative '../../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  @otp_code = "123456"
6
6
  @auth_token = "temp-auth-token-xyz"
@@ -1,6 +1,6 @@
1
1
  # try/features/transient_fields_core_try.rb
2
2
 
3
- require_relative '../../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  class SecretService < Familia::Horreum
6
6
  feature :transient_fields
@@ -1,6 +1,6 @@
1
1
  # try/features/transient_fields_integration_try.rb
2
2
 
3
- require_relative '../../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  class SecretService < Familia::Horreum
6
6
  feature :transient_fields
@@ -12,7 +12,7 @@
12
12
  # - Operation guards prevent unsafe scenarios before fiber issues arise
13
13
  # - Method aliases work correctly
14
14
 
15
- require_relative '../helpers/test_helpers'
15
+ require_relative '../../support/helpers/test_helpers'
16
16
 
17
17
  ## Transaction method works without previous_conn preservation
18
18
  customer = Customer.new(custid: 'tx_test')
@@ -166,7 +166,7 @@ end
166
166
  ## Operation guards prevent pipeline fiber issues before they occur
167
167
  begin
168
168
  # Ensure we're in strict mode for this test
169
- Familia.configure { |config| config.pipeline_mode = :strict }
169
+ Familia.configure { |config| config.pipelined_mode = :strict }
170
170
 
171
171
  Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
172
172
  Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
@@ -185,7 +185,7 @@ end
185
185
 
186
186
  ## Method aliases work correctly
187
187
  # pipeline alias for pipelined
188
- result1 = Familia.pipeline do |conn|
188
+ result1 = Familia.pipelined do |conn|
189
189
  conn.set('alias_test', 'alias_success')
190
190
  conn.get('alias_test')
191
191
  end
@@ -203,7 +203,7 @@ result1.results.last == 'alias_success' && result2.results.last == 'alias_succes
203
203
  customer = Customer.new(custid: 'alias_test')
204
204
 
205
205
  # pipeline alias
206
- result1 = customer.pipeline do |conn|
206
+ result1 = customer.pipelined do |conn|
207
207
  conn.set('horreum:alias1', 'success1')
208
208
  conn.get('horreum:alias1')
209
209
  end
@@ -8,7 +8,7 @@
8
8
  # - Fresh connections (provider/create) → safe for all operations
9
9
  # - Transaction connections → safe for reentrant transactions only
10
10
 
11
- require_relative '../helpers/test_helpers'
11
+ require_relative '../../support/helpers/test_helpers'
12
12
 
13
13
  ## FiberTransactionHandler constraints
14
14
  Familia::Connection::FiberTransactionHandler.allows_transaction
@@ -5,7 +5,7 @@
5
5
  # Tests for isolated database connections that don't interfere
6
6
  # with the cached connection pool or existing model connections.
7
7
 
8
- require_relative '../helpers/test_helpers'
8
+ require_relative '../../support/helpers/test_helpers'
9
9
 
10
10
  # Clean up any existing test data in all test databases
11
11
  (0..2).each do |db|
@@ -0,0 +1,87 @@
1
+ # try/integration/connection/middleware_reconnect_try.rb
2
+ #
3
+ # Tests for Familia.reconnect! method that refreshes connection pools
4
+ # with current middleware configuration
5
+
6
+ require_relative '../../support/helpers/test_helpers'
7
+ require 'connection_pool'
8
+
9
+ # Disable logging for cleaner test output
10
+ Familia.enable_database_logging = false
11
+
12
+ # Test model for middleware reconnection testing
13
+ class ReconnectTestUser < Familia::Horreum
14
+ identifier_field :user_id
15
+ field :user_id
16
+ field :name
17
+
18
+ def init
19
+ @user_id ||= SecureRandom.hex(4)
20
+ end
21
+ end
22
+
23
+ ## Test 5: Middleware re-registration flag is reset and re-set
24
+ Familia.enable_database_logging = true
25
+ Familia.instance_variable_set(:@middleware_registered, true)
26
+ Familia.reconnect!
27
+ was_registered = Familia.instance_variable_get(:@middleware_registered)
28
+ was_registered
29
+ #=> true
30
+
31
+ ## Test 6: reconnect! safely handles no middleware enabled
32
+ Familia.enable_database_logging = false
33
+ Familia.enable_database_counter = false
34
+ Familia.reconnect!
35
+ chain_cleared = Familia.instance_variable_get(:@connection_chain).nil?
36
+ chain_cleared
37
+ #=> true
38
+
39
+
40
+
41
+ ## Setup: Clean database
42
+ ReconnectTestUser.dbclient.flushdb
43
+ #=> "OK"
44
+
45
+ ## Test 1: Basic reconnect functionality clears chain and increments version
46
+ Familia.enable_database_logging = true
47
+ initial = Familia.middleware_version
48
+ Familia.reconnect!
49
+ chain_cleared = Familia.instance_variable_get(:@connection_chain).nil?
50
+ version_incremented = Familia.middleware_version > initial
51
+ [chain_cleared, version_incremented]
52
+ #=> [true, true]
53
+
54
+ ## Test 2: Reconnect works with connection providers
55
+
56
+ # Create a simple connection provider
57
+ Familia.connection_provider = ->(uri) { Redis.new(url: uri) }
58
+
59
+ ## Reconnect clears chain even with provider
60
+ Familia.reconnect!
61
+ Familia.instance_variable_get(:@connection_chain)
62
+ #=> nil
63
+
64
+ ## Test 3: Verify new connections work after reconnect
65
+
66
+ # Create user to trigger connection
67
+ @user = ReconnectTestUser.new(name: "Bob")
68
+ @user.save
69
+ #=> true
70
+
71
+ ## User should be retrievable
72
+ @retrieved = ReconnectTestUser.find(@user.identifier)
73
+ @retrieved.name
74
+ #=> "Bob"
75
+
76
+ ## Test 4: Multiple reconnects are safe
77
+ Familia.reconnect!
78
+ Familia.reconnect!
79
+ Familia.reconnect!
80
+
81
+ ## Connection chain should still be cleared (will rebuild on next use)
82
+ Familia.instance_variable_get(:@connection_chain)
83
+ #=> nil
84
+
85
+ ## Cleanup
86
+ Familia.enable_database_logging = false
87
+ Familia.connection_provider = nil
@@ -12,7 +12,7 @@
12
12
  # This prevents bugs where middleware/cached connections return "QUEUED" instead
13
13
  # of actual values, breaking conditional logic and business rules.
14
14
 
15
- require_relative '../helpers/test_helpers'
15
+ require_relative '../../support/helpers/test_helpers'
16
16
 
17
17
  ## FiberConnectionHandler blocks transactions in strict mode
18
18
  begin
@@ -36,7 +36,7 @@ end
36
36
  ## FiberConnectionHandler blocks pipelines
37
37
  begin
38
38
  # Ensure we're in strict mode for this test
39
- Familia.configure { |config| config.pipeline_mode = :strict }
39
+ Familia.configure { |config| config.pipelined_mode = :strict }
40
40
 
41
41
  # Simulate middleware connection
42
42
  Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
@@ -1,13 +1,13 @@
1
1
  #
2
2
  # Tests pipeline fallback modes when connection handlers don't support pipelines.
3
- # Validates that pipeline_mode configuration works correctly with cached connections
3
+ # Validates that pipelined_mode configuration works correctly with cached connections
4
4
  # and that the fallback behavior matches transaction fallback patterns.
5
5
  #
6
6
 
7
- require_relative '../helpers/test_helpers'
7
+ require_relative '../../support/helpers/test_helpers'
8
8
 
9
9
  # Store original values
10
- $original_pipeline_mode = Familia.pipeline_mode
10
+ $original_pipelined_mode = Familia.pipelined_mode
11
11
  $original_transaction_mode = Familia.transaction_mode
12
12
 
13
13
  # Test model for pipeline fallback scenarios
@@ -17,7 +17,7 @@ class PipelineFallbackTest < Familia::Horreum
17
17
  end
18
18
 
19
19
  ## Test 1: Strict mode raises error with cached connection
20
- Familia.configure { |c| c.pipeline_mode = :strict }
20
+ Familia.configure { |c| c.pipelined_mode = :strict }
21
21
 
22
22
  # Cache connection at class level (uses DefaultConnectionHandler which doesn't support pipelines)
23
23
  PipelineFallbackTest.instance_variable_set(:@dbclient, Familia.create_dbclient)
@@ -28,7 +28,7 @@ customer.pipelined { |c| c.set('key', 'value') }
28
28
  #=~> /Cannot start pipeline with.*CachedConnectionHandler/
29
29
 
30
30
  ## Test 2: Warn mode falls back successfully with cached connection
31
- Familia.configure { |c| c.pipeline_mode = :warn }
31
+ Familia.configure { |c| c.pipelined_mode = :warn }
32
32
 
33
33
  # Cache connection at class level
34
34
  PipelineFallbackTest.instance_variable_set(:@dbclient, Familia.create_dbclient)
@@ -51,7 +51,7 @@ $warn_result.results[2]
51
51
  #=> 'value1'
52
52
 
53
53
  ## Test 3: Fresh connections still support real pipelines in strict mode
54
- Familia.configure { |c| c.pipeline_mode = :strict }
54
+ Familia.configure { |c| c.pipelined_mode = :strict }
55
55
 
56
56
  # Clear cached class-level connection to force CreateConnectionHandler
57
57
  PipelineFallbackTest.remove_instance_variable(:@dbclient) if PipelineFallbackTest.instance_variable_defined?(:@dbclient)
@@ -70,7 +70,7 @@ $fresh_result.results.size
70
70
  #=> 3
71
71
 
72
72
  ## Test 4: MultiResult format is correct for fallback
73
- Familia.configure { |c| c.pipeline_mode = :permissive }
73
+ Familia.configure { |c| c.pipelined_mode = :permissive }
74
74
 
75
75
  # Cache connection at class level
76
76
  PipelineFallbackTest.instance_variable_set(:@dbclient, Familia.create_dbclient)
@@ -92,7 +92,7 @@ $multi_result.results.class
92
92
  #=> Array
93
93
 
94
94
  ## Test 5: Permissive mode silently falls back
95
- Familia.configure { |c| c.pipeline_mode = :permissive }
95
+ Familia.configure { |c| c.pipelined_mode = :permissive }
96
96
 
97
97
  # Cache connection at class level
98
98
  PipelineFallbackTest.instance_variable_set(:@dbclient, Familia.create_dbclient)
@@ -111,18 +111,18 @@ $permissive_result.results
111
111
  #=> ['OK', 1, '1']
112
112
 
113
113
  ## Test 6: Pipeline mode configuration validation
114
- Familia.configure { |c| c.pipeline_mode = :invalid }
114
+ Familia.configure { |c| c.pipelined_mode = :invalid }
115
115
  #=:> ArgumentError
116
116
  #=~> /Pipeline mode must be :strict, :warn, or :permissive/
117
117
 
118
- ## Test 7: Default pipeline_mode is :warn
119
- Familia.instance_variable_set(:@pipeline_mode, nil)
120
- Familia.pipeline_mode
118
+ ## Test 7: Default pipelined_mode is :warn
119
+ Familia.instance_variable_set(:@pipelined_mode, nil)
120
+ Familia.pipelined_mode
121
121
  #=> :warn
122
122
 
123
123
  ## Cleanup: Restore original values
124
124
  Familia.configure do |c|
125
- c.pipeline_mode = $original_pipeline_mode
125
+ c.pipelined_mode = $original_pipelined_mode
126
126
  c.transaction_mode = $original_transaction_mode
127
127
  end
128
128
  PipelineFallbackTest.remove_instance_variable(:@dbclient) if PipelineFallbackTest.instance_variable_defined?(:@dbclient)
@@ -5,7 +5,7 @@
5
5
  require 'bundler/setup'
6
6
  require 'securerandom'
7
7
  require 'thread'
8
- require_relative '../helpers/test_helpers'
8
+ require_relative '../../support/helpers/test_helpers'
9
9
 
10
10
  # Configure connection pooling via connection_provider
11
11
  require 'connection_pool'
@@ -10,7 +10,7 @@
10
10
  # - Return the connection from the successful handler
11
11
  # - Return nil if no handler provides a connection
12
12
 
13
- require_relative '../helpers/test_helpers'
13
+ require_relative '../../support/helpers/test_helpers'
14
14
 
15
15
  # Setup - clear any existing fiber state
16
16
  Fiber[:familia_connection_handler_class] = nil
@@ -8,7 +8,7 @@
8
8
  # with Familia's existing features when connection handlers don't support
9
9
  # transactions (e.g., cached connections, middleware connections).
10
10
 
11
- require_relative '../helpers/test_helpers'
11
+ require_relative '../../support/helpers/test_helpers'
12
12
 
13
13
  # Setup - store original values
14
14
  $original_transaction_mode = Familia.transaction_mode
@@ -5,7 +5,7 @@
5
5
  #
6
6
  # Permissive mode: Silently uses IndividualCommandProxy for fallback
7
7
 
8
- require_relative '../helpers/test_helpers'
8
+ require_relative '../../support/helpers/test_helpers'
9
9
 
10
10
  # Test class for permissive mode testing
11
11
  class PermissiveModeTestCustomer < Familia::Horreum
@@ -5,7 +5,7 @@
5
5
  #
6
6
  # Strict mode: Raises OperationModeError when transaction unavailable
7
7
 
8
- require_relative '../helpers/test_helpers'
8
+ require_relative '../../support/helpers/test_helpers'
9
9
 
10
10
  # Test class for strict mode testing
11
11
  class StrictModeTestCustomer < Familia::Horreum
@@ -5,7 +5,7 @@
5
5
  #
6
6
  # Warn mode: Logs warning and uses IndividualCommandProxy for fallback
7
7
 
8
- require_relative '../helpers/test_helpers'
8
+ require_relative '../../support/helpers/test_helpers'
9
9
 
10
10
  # Test class for warn mode testing
11
11
  class WarnModeTestCustomer < Familia::Horreum
@@ -10,7 +10,7 @@
10
10
  # The IndividualCommandProxy executes Redis commands immediately instead of queuing
11
11
  # them in a transaction, maintaining the same MultiResult interface for consistency.
12
12
 
13
- require_relative '../helpers/test_helpers'
13
+ require_relative '../../support/helpers/test_helpers'
14
14
 
15
15
  # Setup - ensure clean state
16
16
  @original_transaction_mode = Familia.transaction_mode
@@ -1,4 +1,4 @@
1
- require_relative '../helpers/test_helpers'
1
+ require_relative '../support/helpers/test_helpers'
2
2
 
3
3
  # Define test classes in global namespace
4
4
  class ::TestVehicle < Familia::Horreum
@@ -3,7 +3,7 @@
3
3
  # Comprehensive test coverage for the create method
4
4
  # Tests the correct exception type and error message handling
5
5
 
6
- require_relative '../helpers/test_helpers'
6
+ require_relative '../support/helpers/test_helpers'
7
7
 
8
8
  # Test class for create method behavior
9
9
  class CreateTestModel < Familia::Horreum
@@ -37,7 +37,7 @@ end
37
37
  # =============================================
38
38
 
39
39
  ## create method successfully creates new object
40
- @created_obj = CreateTestModel.create(id: @first_test_id, name: 'Created Object', value: 'test_value')
40
+ @created_obj = CreateTestModel.create!(id: @first_test_id, name: 'Created Object', value: 'test_value')
41
41
  [@created_obj.class, @created_obj.exists?, @created_obj.name]
42
42
  #=> [CreateTestModel, true, 'Created Object']
43
43
 
@@ -55,17 +55,17 @@ end
55
55
  # =============================================
56
56
 
57
57
  ## create method raises RecordExistsError for duplicate
58
- CreateTestModel.create(id: @first_test_id, name: 'Duplicate Attempt')
58
+ CreateTestModel.create!(id: @first_test_id, name: 'Duplicate Attempt')
59
59
  #=!> Familia::RecordExistsError
60
60
 
61
61
  ## RecordExistsError includes the dbkey in the message
62
- CreateTestModel.create(id: @first_test_id, name: 'Another Duplicate')
62
+ CreateTestModel.create!(id: @first_test_id, name: 'Another Duplicate')
63
63
  #=!> Familia::RecordExistsError
64
64
  #==> !!error.message.match(/create_test_model:#{@first_test_id}:object/)
65
65
 
66
66
  ## RecordExistsError message follows consistent format
67
67
  begin
68
- CreateTestModel.create(id: @first_test_id, name: 'Yet Another Duplicate')
68
+ CreateTestModel.create!(id: @first_test_id, name: 'Yet Another Duplicate')
69
69
  false # Should not reach here
70
70
  rescue Familia::RecordExistsError => e
71
71
  e.message.start_with?('Key already exists:')
@@ -74,10 +74,10 @@ end
74
74
 
75
75
  ## RecordExistsError exposes key property for programmatic access
76
76
  @final_test_id = next_test_id
77
- CreateTestModel.create(id: @final_test_id, name: 'Setup for Key Test')
77
+ CreateTestModel.create!(id: @final_test_id, name: 'Setup for Key Test')
78
78
 
79
79
  begin
80
- CreateTestModel.create(id: @final_test_id, name: 'Key Test Duplicate')
80
+ CreateTestModel.create!(id: @final_test_id, name: 'Key Test Duplicate')
81
81
  false # Should not reach here
82
82
  rescue Familia::RecordExistsError => e
83
83
  # Key should be accessible and contain the identifier
@@ -90,22 +90,22 @@ end
90
90
  # =============================================
91
91
 
92
92
  ## create with empty identifier raises NoIdentifier error
93
- CreateTestModel.create(id: '')
93
+ CreateTestModel.create!(id: '')
94
94
  #=!> Familia::NoIdentifier
95
95
 
96
96
  ## create with nil identifier raises NoIdentifier error
97
- CreateTestModel.create(id: nil)
97
+ CreateTestModel.create!(id: nil)
98
98
  #=!> Familia::NoIdentifier
99
99
 
100
100
  ## create with only some fields set
101
101
  @partial_id = next_test_id
102
- @partial_obj = CreateTestModel.create(id: @partial_id, name: 'Partial Object')
102
+ @partial_obj = CreateTestModel.create!(id: @partial_id, name: 'Partial Object')
103
103
  [@partial_obj.exists?, @partial_obj.name, @partial_obj.value]
104
104
  #=> [true, 'Partial Object', nil]
105
105
 
106
106
  ## create with no additional fields (only identifier)
107
107
  @minimal_id = next_test_id
108
- @minimal_obj = CreateTestModel.create(id: @minimal_id)
108
+ @minimal_obj = CreateTestModel.create!(id: @minimal_id)
109
109
  [@minimal_obj.exists?, @minimal_obj.id]
110
110
  #=> [true, @minimal_id]
111
111
 
@@ -115,14 +115,14 @@ CreateTestModel.create(id: nil)
115
115
 
116
116
  ## create is atomic - no partial state on failure
117
117
  @concurrent_id = next_test_id
118
- @first_obj = CreateTestModel.create(id: @concurrent_id, name: 'First')
118
+ @first_obj = CreateTestModel.create!(id: @concurrent_id, name: 'First')
119
119
 
120
120
  # Verify first object exists
121
121
  first_exists = @first_obj.exists?
122
122
 
123
123
  # Attempt to create duplicate should not affect existing object
124
124
  begin
125
- CreateTestModel.create(id: @concurrent_id, name: 'Concurrent Attempt')
125
+ CreateTestModel.create!(id: @concurrent_id, name: 'Concurrent Attempt')
126
126
  false # Should not reach here
127
127
  rescue Familia::RecordExistsError
128
128
  # Original object should be unchanged
@@ -134,7 +134,7 @@ end
134
134
  ## create failure doesn't leave partial data
135
135
  before_failed_create = Familia.dbclient.keys("create_test_model:#{@concurrent_id}:*").length
136
136
  begin
137
- CreateTestModel.create(id: @concurrent_id, name: 'Should Fail')
137
+ CreateTestModel.create!(id: @concurrent_id, name: 'Should Fail')
138
138
  rescue Familia::RecordExistsError
139
139
  # Should not create any additional keys
140
140
  after_failed_create = Familia.dbclient.keys("create_test_model:#{@concurrent_id}:*").length
@@ -148,20 +148,20 @@ end
148
148
 
149
149
  ## Both create and save_if_not_exists raise same error type for duplicates
150
150
  @consistency_id = next_test_id
151
- @consistency_obj = CreateTestModel.create(id: @consistency_id, name: 'Consistency Test')
151
+ @consistency_obj = CreateTestModel.create!(id: @consistency_id, name: 'Consistency Test')
152
152
 
153
153
  # Test create raises RecordExistsError
154
154
  create_error_class = begin
155
- CreateTestModel.create(id: @consistency_id, name: 'Create Duplicate')
155
+ CreateTestModel.create!(id: @consistency_id, name: 'Create Duplicate')
156
156
  nil
157
157
  rescue => e
158
158
  e.class
159
159
  end
160
160
 
161
- # Test save_if_not_exists raises RecordExistsError
161
+ # Test save_if_not_exists! raises RecordExistsError
162
162
  sine_error_class = begin
163
163
  duplicate_obj = CreateTestModel.new(id: @consistency_id, name: 'SINE Duplicate')
164
- duplicate_obj.save_if_not_exists
164
+ duplicate_obj.save_if_not_exists!
165
165
  nil
166
166
  rescue => e
167
167
  e.class
@@ -172,17 +172,17 @@ end
172
172
 
173
173
  ## Both methods have similar error message patterns
174
174
  @error_comparison_id = next_test_id
175
- CreateTestModel.create(id: @error_comparison_id, name: 'Error Comparison')
175
+ CreateTestModel.create!(id: @error_comparison_id, name: 'Error Comparison')
176
176
 
177
177
  create_error_msg = begin
178
- CreateTestModel.create(id: @error_comparison_id, name: 'Create Error')
178
+ CreateTestModel.create!(id: @error_comparison_id, name: 'Create Error')
179
179
  nil
180
180
  rescue => e
181
181
  e.message
182
182
  end
183
183
 
184
184
  sine_error_msg = begin
185
- CreateTestModel.new(id: @error_comparison_id, name: 'SINE Error').save_if_not_exists
185
+ CreateTestModel.new(id: @error_comparison_id, name: 'SINE Error').save_if_not_exists!
186
186
  nil
187
187
  rescue => e
188
188
  e.message
@@ -198,7 +198,7 @@ end
198
198
 
199
199
  ## create works with complex field values
200
200
  @complex_id = next_test_id
201
- @complex_obj = CreateTestModel.create(
201
+ @complex_obj = CreateTestModel.create!(
202
202
  id: @complex_id,
203
203
  name: 'Complex Object',
204
204
  value: { nested: 'data', array: [1, 2, 3] }
@@ -214,7 +214,7 @@ end
214
214
  @consistency_check_id = next_test_id
215
215
 
216
216
  # Create via class method
217
- @class_created = CreateTestModel.create(id: @consistency_check_id, name: 'Class Created')
217
+ @class_created = CreateTestModel.create!(id: @consistency_check_id, name: 'Class Created')
218
218
 
219
219
  # Both class and instance methods should see the object as existing
220
220
  class_sees_exists = CreateTestModel.exists?(@consistency_check_id)
@@ -1,6 +1,6 @@
1
1
  # Test cross-component integration scenarios
2
2
 
3
- require_relative '../helpers/test_helpers'
3
+ require_relative '../support/helpers/test_helpers'
4
4
 
5
5
  class TestUser < Familia::Horreum
6
6
  using Familia::Refinements::StylizeWords
@@ -0,0 +1,104 @@
1
+ # DataType Pipeline Support Tryouts
2
+ #
3
+ # Tests pipeline support for DataType objects. Pipelines provide performance
4
+ # optimization by batching commands without the atomicity guarantee of transactions.
5
+
6
+ require_relative '../../support/helpers/test_helpers'
7
+
8
+ # Setup
9
+ class PipelineTestUser < Familia::Horreum
10
+ logical_database 4
11
+ identifier_field :userid
12
+ field :userid
13
+ field :name
14
+
15
+ sorted_set :scores
16
+ hashkey :profile
17
+ set :tags
18
+ counter :visits
19
+ end
20
+
21
+ @user = PipelineTestUser.new(userid: 'pipe_user_001')
22
+ @user.name = 'Pipeline Tester'
23
+ @user.save
24
+
25
+ ## Parent-owned SortedSet can execute pipeline
26
+ result = @user.scores.pipelined do |pipe|
27
+ pipe.zadd(@user.scores.dbkey, 100, 'p1')
28
+ pipe.zadd(@user.scores.dbkey, 200, 'p2')
29
+ pipe.zcard(@user.scores.dbkey)
30
+ end
31
+ [result.is_a?(MultiResult), @user.scores.members.size]
32
+ #=> [true, 2]
33
+
34
+ ## Parent-owned HashKey can execute pipeline
35
+ result = @user.profile.pipelined do |pipe|
36
+ pipe.hset(@user.profile.dbkey, 'city', 'NYC')
37
+ pipe.hset(@user.profile.dbkey, 'state', 'NY')
38
+ pipe.hgetall(@user.profile.dbkey)
39
+ end
40
+ [result.is_a?(MultiResult), @user.profile.keys.sort]
41
+ #=> [true, ["city", "state"]]
42
+
43
+ ## Standalone SortedSet can execute pipeline
44
+ leaderboard = Familia::SortedSet.new('pipeline:leaderboard')
45
+ leaderboard.delete!
46
+ result = leaderboard.pipelined do |pipe|
47
+ pipe.zadd(leaderboard.dbkey, 100, 'player1')
48
+ pipe.zadd(leaderboard.dbkey, 200, 'player2')
49
+ pipe.zcard(leaderboard.dbkey)
50
+ end
51
+ [result.is_a?(MultiResult), leaderboard.members.size]
52
+ #=> [true, 2]
53
+
54
+ ## Pipeline with direct_access works correctly
55
+ result = @user.profile.pipelined do |pipe_conn|
56
+ pipe_conn.hset(@user.profile.dbkey, 'pipeline_test', 'yes')
57
+
58
+ @user.profile.direct_access do |conn, key|
59
+ conn.object_id == pipe_conn.object_id &&
60
+ conn.hset(key, 'direct_test', 'yes')
61
+ end
62
+ end
63
+ [@user.profile['pipeline_test'], @user.profile['direct_test']]
64
+ #=> ["yes", "yes"]
65
+
66
+ ## Pipeline returns MultiResult with correct structure
67
+ result = @user.scores.pipelined do |pipe|
68
+ pipe.zadd(@user.scores.dbkey, 300, 'p3')
69
+ pipe.zadd(@user.scores.dbkey, 400, 'p4')
70
+ end
71
+ [result.is_a?(MultiResult), result.results.is_a?(Array)]
72
+ #=> [true, true]
73
+
74
+ ## Empty pipeline returns empty MultiResult
75
+ result = @user.scores.pipelined { |pipe| }
76
+ [result.is_a?(MultiResult), result.results.empty?]
77
+ #=> [true, true]
78
+
79
+ ## Multiple DataType operations in single pipeline
80
+ result = @user.scores.pipelined do |pipe|
81
+ pipe.zadd(@user.scores.dbkey, 500, 'multi')
82
+ pipe.hset(@user.profile.dbkey, 'multi', 'pipeline')
83
+ pipe.sadd(@user.tags.dbkey, 'multi_tag')
84
+ end
85
+ [
86
+ result.is_a?(MultiResult),
87
+ @user.scores.member?('multi'),
88
+ @user.profile['multi'],
89
+ @user.tags.member?('multi_tag')
90
+ ]
91
+ #=> [true, true, "pipeline", true]
92
+
93
+ ## Standalone HashKey with logical_database option
94
+ custom = Familia::HashKey.new('pipeline:custom', logical_database: 5)
95
+ custom.delete!
96
+ result = custom.pipelined do |pipe|
97
+ pipe.hset(custom.dbkey, 'key1', 'value1')
98
+ pipe.hget(custom.dbkey, 'key1')
99
+ end
100
+ result.is_a?(MultiResult)
101
+ #=> true
102
+
103
+ # Cleanup
104
+ @user.destroy!