familia 2.0.0.pre15 → 2.0.0.pre17

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 (288) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -2
  3. data/.github/workflows/code-quality.yml +138 -0
  4. data/.github/workflows/code-smells.yml +85 -0
  5. data/.github/workflows/docs.yml +31 -8
  6. data/.gitignore +3 -1
  7. data/.pre-commit-config.yaml +7 -1
  8. data/.reek.yml +98 -0
  9. data/.rubocop.yml +54 -10
  10. data/.talismanrc +9 -0
  11. data/.yardopts +18 -13
  12. data/CHANGELOG.rst +86 -4
  13. data/CLAUDE.md +39 -1
  14. data/Gemfile +6 -5
  15. data/Gemfile.lock +99 -23
  16. data/LICENSE.txt +1 -1
  17. data/README.md +285 -85
  18. data/changelog.d/README.md +2 -2
  19. data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
  20. data/docs/archive/FAMILIA_TECHNICAL.md +42 -42
  21. data/docs/archive/FAMILIA_UPDATE.md +3 -3
  22. data/docs/archive/README.md +3 -2
  23. data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
  24. data/docs/conf.py +29 -0
  25. data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
  26. data/docs/guides/feature-encrypted-fields.md +785 -0
  27. data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
  28. data/docs/guides/feature-external-identifiers.md +637 -0
  29. data/docs/guides/feature-object-identifiers.md +435 -0
  30. data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
  31. data/docs/guides/feature-relationships-methods.md +684 -0
  32. data/docs/guides/feature-relationships.md +200 -0
  33. data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
  34. data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
  35. data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
  36. data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
  37. data/docs/guides/index.md +176 -0
  38. data/docs/guides/{Security-Model.md → security-model.md} +1 -1
  39. data/docs/migrating/v2.0.0-pre.md +1 -1
  40. data/docs/migrating/v2.0.0-pre11.md +2 -2
  41. data/docs/migrating/v2.0.0-pre12.md +2 -2
  42. data/docs/migrating/v2.0.0-pre5.md +33 -12
  43. data/docs/migrating/v2.0.0-pre6.md +2 -2
  44. data/docs/migrating/v2.0.0-pre7.md +8 -8
  45. data/docs/overview.md +624 -20
  46. data/docs/reference/api-technical.md +1365 -0
  47. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
  48. data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
  49. data/examples/autoloader/mega_customer.rb +3 -1
  50. data/examples/encrypted_fields.rb +378 -0
  51. data/examples/json_usage_patterns.rb +144 -0
  52. data/examples/relationships.rb +13 -13
  53. data/examples/safe_dump.rb +7 -7
  54. data/examples/single_connection_transaction_confusions.rb +379 -0
  55. data/lib/familia/base.rb +51 -10
  56. data/lib/familia/connection/handlers.rb +223 -0
  57. data/lib/familia/connection/individual_command_proxy.rb +64 -0
  58. data/lib/familia/connection/middleware.rb +75 -0
  59. data/lib/familia/connection/operation_core.rb +93 -0
  60. data/lib/familia/connection/operations.rb +277 -0
  61. data/lib/familia/connection/pipeline_core.rb +87 -0
  62. data/lib/familia/connection/transaction_core.rb +100 -0
  63. data/lib/familia/connection.rb +60 -186
  64. data/lib/familia/data_type/class_methods.rb +63 -0
  65. data/lib/familia/data_type/commands.rb +53 -51
  66. data/lib/familia/data_type/connection.rb +83 -0
  67. data/lib/familia/data_type/serialization.rb +108 -107
  68. data/lib/familia/data_type/settings.rb +96 -0
  69. data/lib/familia/data_type/types/counter.rb +1 -1
  70. data/lib/familia/data_type/types/hashkey.rb +15 -11
  71. data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
  72. data/lib/familia/data_type/types/lock.rb +3 -2
  73. data/lib/familia/data_type/types/sorted_set.rb +128 -14
  74. data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -9
  75. data/lib/familia/data_type/types/unsorted_set.rb +20 -27
  76. data/lib/familia/data_type.rb +12 -171
  77. data/lib/familia/distinguisher.rb +85 -0
  78. data/lib/familia/encryption/encrypted_data.rb +15 -24
  79. data/lib/familia/encryption/manager.rb +6 -4
  80. data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
  81. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
  82. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
  83. data/lib/familia/encryption/request_cache.rb +7 -7
  84. data/lib/familia/encryption.rb +2 -3
  85. data/lib/familia/errors.rb +9 -3
  86. data/lib/familia/features/autoloader.rb +30 -12
  87. data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
  88. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
  89. data/lib/familia/features/encrypted_fields.rb +71 -66
  90. data/lib/familia/features/expiration/extensions.rb +1 -1
  91. data/lib/familia/features/expiration.rb +31 -26
  92. data/lib/familia/features/external_identifier.rb +57 -19
  93. data/lib/familia/features/object_identifier.rb +134 -25
  94. data/lib/familia/features/quantization.rb +16 -21
  95. data/lib/familia/features/relationships/README.md +97 -0
  96. data/lib/familia/features/relationships/collection_operations.rb +104 -0
  97. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
  98. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +306 -0
  99. data/lib/familia/features/relationships/indexing.rb +182 -256
  100. data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
  101. data/lib/familia/features/relationships/participation/participant_methods.rb +164 -0
  102. data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
  103. data/lib/familia/features/relationships/participation.rb +656 -0
  104. data/lib/familia/features/relationships/participation_relationship.rb +31 -0
  105. data/lib/familia/features/relationships/score_encoding.rb +20 -20
  106. data/lib/familia/features/relationships.rb +65 -266
  107. data/lib/familia/features/safe_dump.rb +127 -130
  108. data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
  109. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
  110. data/lib/familia/features/transient_fields.rb +10 -7
  111. data/lib/familia/features.rb +10 -14
  112. data/lib/familia/field_type.rb +6 -4
  113. data/lib/familia/horreum/connection.rb +297 -0
  114. data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +27 -17
  115. data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +139 -74
  116. data/lib/familia/horreum/{subclass/management.rb → management.rb} +73 -27
  117. data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +108 -185
  118. data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +104 -23
  119. data/lib/familia/horreum/serialization.rb +172 -0
  120. data/lib/familia/horreum/{shared/settings.rb → settings.rb} +2 -1
  121. data/lib/familia/horreum/{core/utils.rb → utils.rb} +2 -1
  122. data/lib/familia/horreum.rb +222 -119
  123. data/lib/familia/json_serializer.rb +0 -1
  124. data/lib/familia/logging.rb +11 -114
  125. data/lib/familia/refinements/dear_json.rb +122 -0
  126. data/lib/familia/refinements/logger_trace.rb +20 -17
  127. data/lib/familia/refinements/stylize_words.rb +65 -0
  128. data/lib/familia/refinements/time_literals.rb +60 -52
  129. data/lib/familia/refinements.rb +2 -1
  130. data/lib/familia/secure_identifier.rb +60 -28
  131. data/lib/familia/settings.rb +83 -7
  132. data/lib/familia/utils.rb +5 -87
  133. data/lib/familia/verifiable_identifier.rb +4 -4
  134. data/lib/familia/version.rb +1 -1
  135. data/lib/familia.rb +72 -14
  136. data/lib/middleware/database_middleware.rb +56 -14
  137. data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
  138. data/try/configuration/scenarios_try.rb +2 -2
  139. data/try/connection/fiber_context_preservation_try.rb +250 -0
  140. data/try/connection/handler_constraints_try.rb +59 -0
  141. data/try/connection/operation_mode_guards_try.rb +208 -0
  142. data/try/connection/pipeline_fallback_integration_try.rb +128 -0
  143. data/try/connection/responsibility_chain_tracking_try.rb +72 -0
  144. data/try/connection/transaction_fallback_integration_try.rb +288 -0
  145. data/try/connection/transaction_mode_permissive_try.rb +153 -0
  146. data/try/connection/transaction_mode_strict_try.rb +98 -0
  147. data/try/connection/transaction_mode_warn_try.rb +131 -0
  148. data/try/connection/transaction_modes_try.rb +249 -0
  149. data/try/core/autoloader_try.rb +120 -2
  150. data/try/core/connection_try.rb +10 -10
  151. data/try/core/conventional_inheritance_try.rb +130 -0
  152. data/try/core/create_method_try.rb +15 -23
  153. data/try/core/database_consistency_try.rb +11 -10
  154. data/try/core/errors_try.rb +11 -14
  155. data/try/core/familia_extended_try.rb +2 -2
  156. data/try/core/familia_members_methods_try.rb +76 -0
  157. data/try/core/familia_try.rb +1 -1
  158. data/try/core/isolated_dbclient_try.rb +165 -0
  159. data/try/core/middleware_try.rb +16 -16
  160. data/try/core/persistence_operations_try.rb +4 -4
  161. data/try/core/pools_try.rb +42 -26
  162. data/try/core/secure_identifier_try.rb +28 -24
  163. data/try/core/time_utils_try.rb +10 -10
  164. data/try/core/tools_try.rb +3 -3
  165. data/try/core/utils_try.rb +2 -2
  166. data/try/data_types/boolean_try.rb +4 -4
  167. data/try/data_types/datatype_base_try.rb +0 -2
  168. data/try/data_types/list_try.rb +10 -10
  169. data/try/data_types/sorted_set_try.rb +5 -5
  170. data/try/data_types/sorted_set_zadd_options_try.rb +625 -0
  171. data/try/data_types/string_try.rb +12 -12
  172. data/try/data_types/unsortedset_try.rb +33 -0
  173. data/try/debugging/cache_behavior_tracer.rb +7 -7
  174. data/try/debugging/debug_aad_process.rb +1 -1
  175. data/try/debugging/debug_concealed_internal.rb +1 -1
  176. data/try/debugging/debug_cross_context.rb +1 -1
  177. data/try/debugging/debug_fresh_cross_context.rb +1 -1
  178. data/try/debugging/encryption_method_tracer.rb +10 -10
  179. data/try/edge_cases/hash_symbolization_try.rb +1 -1
  180. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  181. data/try/encryption/config_persistence_try.rb +2 -2
  182. data/try/encryption/encryption_core_try.rb +19 -19
  183. data/try/encryption/instance_variable_scope_try.rb +1 -1
  184. data/try/encryption/module_loading_try.rb +2 -2
  185. data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
  186. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
  187. data/try/encryption/secure_memory_handling_try.rb +1 -1
  188. data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
  189. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  190. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
  191. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
  192. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
  193. data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
  194. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  195. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  196. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
  197. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
  198. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  199. data/try/features/feature_dependencies_try.rb +3 -3
  200. data/try/features/field_groups_try.rb +244 -0
  201. data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
  202. data/try/features/object_identifier/object_identifier_try.rb +10 -0
  203. data/try/features/quantization/quantization_try.rb +1 -1
  204. data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
  205. data/try/features/relationships/indexing_try.rb +443 -0
  206. data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
  207. data/try/features/relationships/participation_commands_verification_try.rb +105 -0
  208. data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
  209. data/try/features/relationships/participation_reverse_index_try.rb +196 -0
  210. data/try/features/relationships/relationships_api_changes_try.rb +72 -71
  211. data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
  212. data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
  213. data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
  214. data/try/features/relationships/relationships_performance_try.rb +20 -20
  215. data/try/features/relationships/relationships_try.rb +27 -38
  216. data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
  217. data/try/features/transient_fields/refresh_reset_try.rb +3 -1
  218. data/try/features/transient_fields/simple_refresh_test.rb +1 -1
  219. data/try/helpers/test_cleanup.rb +86 -0
  220. data/try/helpers/test_helpers.rb +6 -7
  221. data/try/horreum/auto_indexing_on_save_try.rb +212 -0
  222. data/try/horreum/base_try.rb +3 -2
  223. data/try/horreum/commands_try.rb +3 -1
  224. data/try/horreum/defensive_initialization_try.rb +86 -0
  225. data/try/horreum/destroy_related_fields_cleanup_try.rb +332 -0
  226. data/try/horreum/initialization_try.rb +11 -7
  227. data/try/horreum/relations_try.rb +21 -13
  228. data/try/horreum/serialization_try.rb +12 -11
  229. data/try/horreum/settings_try.rb +2 -0
  230. data/try/integration/cross_component_try.rb +3 -3
  231. data/try/memory/memory_basic_test.rb +1 -1
  232. data/try/memory/memory_docker_ruby_dump.sh +2 -2
  233. data/try/models/customer_safe_dump_try.rb +1 -1
  234. data/try/models/customer_try.rb +13 -15
  235. data/try/models/datatype_base_try.rb +3 -3
  236. data/try/models/familia_object_try.rb +9 -8
  237. data/try/performance/benchmarks_try.rb +2 -2
  238. data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
  239. data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
  240. data/try/prototypes/atomic_saves_v4.rb +1 -1
  241. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
  242. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  243. data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  244. data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
  245. data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
  246. data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
  247. data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
  248. data/try/prototypes/pooling/pool_siege.rb +11 -11
  249. data/try/prototypes/pooling/run_stress_tests.rb +7 -7
  250. data/try/refinements/dear_json_array_methods_try.rb +53 -0
  251. data/try/refinements/dear_json_hash_methods_try.rb +54 -0
  252. data/try/refinements/logger_trace_methods_try.rb +44 -0
  253. data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
  254. data/try/refinements/time_literals_string_methods_try.rb +80 -0
  255. data/try/valkey.conf +26 -0
  256. metadata +92 -52
  257. data/.rubocop_todo.yml +0 -208
  258. data/docs/connection_pooling.md +0 -192
  259. data/docs/guides/Connection-Pooling-Guide.md +0 -437
  260. data/docs/guides/Encrypted-Fields-Overview.md +0 -101
  261. data/docs/guides/Feature-System-Autoloading.md +0 -198
  262. data/docs/guides/Home.md +0 -116
  263. data/docs/guides/Relationships-Guide.md +0 -737
  264. data/docs/guides/relationships-methods.md +0 -266
  265. data/docs/reference/auditing_database_commands.rb +0 -228
  266. data/examples/permissions.rb +0 -240
  267. data/lib/familia/features/relationships/cascading.rb +0 -437
  268. data/lib/familia/features/relationships/membership.rb +0 -497
  269. data/lib/familia/features/relationships/permission_management.rb +0 -264
  270. data/lib/familia/features/relationships/querying.rb +0 -615
  271. data/lib/familia/features/relationships/redis_operations.rb +0 -274
  272. data/lib/familia/features/relationships/tracking.rb +0 -418
  273. data/lib/familia/horreum/core/connection.rb +0 -73
  274. data/lib/familia/horreum/core.rb +0 -21
  275. data/lib/familia/refinements/snake_case.rb +0 -40
  276. data/lib/familia/validation/command_recorder.rb +0 -336
  277. data/lib/familia/validation/expectations.rb +0 -519
  278. data/lib/familia/validation/validation_helpers.rb +0 -443
  279. data/lib/familia/validation/validator.rb +0 -412
  280. data/lib/familia/validation.rb +0 -140
  281. data/try/data_types/set_try.rb +0 -33
  282. data/try/features/relationships/categorical_permissions_try.rb +0 -515
  283. data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
  284. data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
  285. data/try/validation/atomic_operations_try.rb.disabled +0 -320
  286. data/try/validation/command_validation_try.rb.disabled +0 -207
  287. data/try/validation/performance_validation_try.rb.disabled +0 -324
  288. data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -0,0 +1,98 @@
1
+ # Transaction Mode: Strict Tryouts
2
+ #
3
+ # Tests strict transaction mode behavior where operations fail fast
4
+ # when connection handlers don't support transactions.
5
+ #
6
+ # Strict mode: Raises OperationModeError when transaction unavailable
7
+
8
+ require_relative '../helpers/test_helpers'
9
+
10
+ # Test class for strict mode testing
11
+ class StrictModeTestCustomer < Familia::Horreum
12
+ identifier_field :custid
13
+ field :custid
14
+ field :name
15
+ end
16
+
17
+ ## Strict mode can be configured
18
+ Familia.configure { |config| config.transaction_mode = :strict }
19
+ Familia.transaction_mode
20
+ #=> :strict
21
+
22
+ ## Strict mode raises error with CachedConnectionHandler
23
+
24
+ # Force CachedConnectionHandler
25
+ StrictModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
26
+
27
+ customer = StrictModeTestCustomer.new(custid: 'strict_test')
28
+ customer.transaction do |conn|
29
+ conn.hset(customer.dbkey, 'name', 'Should Not Work')
30
+ end
31
+
32
+ #=:> Familia::OperationModeError
33
+ #=~> /Cannot start transaction with/
34
+ #=~> /CachedConnectionHandler/
35
+
36
+ ## Clear the dbclient instance var
37
+ StrictModeTestCustomer.remove_instance_variable(:@dbclient)
38
+ #=*>
39
+
40
+ ## Strict mode raises error with FiberConnectionHandler
41
+ begin
42
+ # Simulate middleware connection
43
+ Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
44
+ Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
45
+ customer = StrictModeTestCustomer.new(custid: 'fiber_test')
46
+ customer.transaction { |conn| conn.set('test', 'value') }
47
+ false
48
+ rescue Familia::OperationModeError => e
49
+ e.message.include?('FiberConnectionHandler')
50
+ ensure
51
+ Fiber[:familia_connection] = nil
52
+ Fiber[:familia_connection_handler_class] = nil
53
+ end
54
+ #=> true
55
+
56
+ ## Strict mode allows normal transactions with CreateConnectionHandler
57
+ begin
58
+ customer = StrictModeTestCustomer.new(custid: 'normal_test')
59
+ result = customer.transaction do |conn|
60
+ conn.hset(customer.dbkey, 'type', 'normal transaction')
61
+ conn.hget(customer.dbkey, 'type')
62
+ end
63
+ result.is_a?(MultiResult) && result.results.last == 'normal transaction'
64
+ end
65
+ #=> true
66
+
67
+ ## Strict mode works with ProviderConnectionHandler
68
+ original_provider = Familia.connection_provider
69
+ begin
70
+ Familia.connection_provider = ->(uri) { Redis.new(url: uri) }
71
+ customer = StrictModeTestCustomer.new(custid: 'provider_test')
72
+ result = customer.transaction do |conn|
73
+ conn.hset(customer.dbkey, 'source', 'provider')
74
+ conn.hget(customer.dbkey, 'source')
75
+ end
76
+ result.is_a?(MultiResult) && result.results.last == 'provider'
77
+ ensure
78
+ Familia.connection_provider = original_provider
79
+ end
80
+ #=> true
81
+
82
+ ## Global transactions respect strict mode with cached connections
83
+ begin
84
+ # Set a cached connection on the Familia module itself would be complex
85
+ # Instead test that cached connections on models affect their transactions
86
+ StrictModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
87
+
88
+ customer = StrictModeTestCustomer.new(custid: 'global_strict_test')
89
+ customer.transaction do |conn|
90
+ conn.set('should_fail', 'value')
91
+ end
92
+ false
93
+ rescue Familia::OperationModeError
94
+ true
95
+ ensure
96
+ StrictModeTestCustomer.remove_instance_variable(:@dbclient)
97
+ end
98
+ #=> true
@@ -0,0 +1,131 @@
1
+ # Transaction Mode: Warn Tryouts
2
+ #
3
+ # Tests warn transaction mode behavior where operations log a warning
4
+ # and execute commands individually when transactions are unavailable.
5
+ #
6
+ # Warn mode: Logs warning and uses IndividualCommandProxy for fallback
7
+
8
+ require_relative '../helpers/test_helpers'
9
+
10
+ # Test class for warn mode testing
11
+ class WarnModeTestCustomer < Familia::Horreum
12
+ identifier_field :custid
13
+ field :custid
14
+ field :name
15
+ field :email
16
+ end
17
+
18
+ ## Warn mode can be configured
19
+ Familia.configure { |config| config.transaction_mode = :warn }
20
+ Familia.transaction_mode
21
+ #=> :warn
22
+
23
+ ## Warn mode executes individual commands with CachedConnectionHandler
24
+ begin
25
+ # Force CachedConnectionHandler
26
+ WarnModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
27
+
28
+ customer = WarnModeTestCustomer.new(custid: 'warn_test')
29
+ result = customer.transaction do |conn|
30
+ # Should be IndividualCommandProxy
31
+ conn.class == Familia::Connection::IndividualCommandProxy &&
32
+ conn.hset(customer.dbkey, 'name', 'Warn Mode Works') &&
33
+ conn.hget(customer.dbkey, 'name')
34
+ end
35
+
36
+ result.is_a?(MultiResult) && result.results.last == 'Warn Mode Works'
37
+ ensure
38
+ WarnModeTestCustomer.remove_instance_variable(:@dbclient)
39
+ end
40
+ #=> true
41
+
42
+ ## Warn mode executes individual commands with FiberConnectionHandler
43
+ begin
44
+ # Simulate middleware connection
45
+ Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
46
+ Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
47
+ customer = WarnModeTestCustomer.new(custid: 'fiber_warn_test')
48
+
49
+ result = customer.transaction do |conn|
50
+ conn.class == Familia::Connection::IndividualCommandProxy &&
51
+ conn.hset(customer.dbkey, 'source', 'fiber_warn') &&
52
+ conn.hget(customer.dbkey, 'source')
53
+ end
54
+
55
+ result.is_a?(MultiResult) && result.results.last == 'fiber_warn'
56
+ ensure
57
+ Fiber[:familia_connection] = nil
58
+ Fiber[:familia_connection_handler_class] = nil
59
+ end
60
+ #=> true
61
+
62
+ ## Warn mode still uses normal transactions with CreateConnectionHandler
63
+ begin
64
+ customer = WarnModeTestCustomer.new(custid: 'normal_warn_test')
65
+ result = customer.transaction do |conn|
66
+ # Should be Redis::MultiConnection for normal transactions
67
+ conn.class == Redis::MultiConnection &&
68
+ conn.hset(customer.dbkey, 'type', 'normal in warn mode') &&
69
+ conn.hget(customer.dbkey, 'type')
70
+ end
71
+ result.is_a?(MultiResult) && result.results.last == 'normal in warn mode'
72
+ end
73
+ #=> true
74
+
75
+ ## IndividualCommandProxy collects results correctly in warn mode
76
+ begin
77
+ WarnModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
78
+
79
+ customer = WarnModeTestCustomer.new(custid: 'proxy_warn_test')
80
+ result = customer.transaction do |conn|
81
+ conn.hset(customer.dbkey, 'field1', 'value1')
82
+ conn.hset(customer.dbkey, 'field2', 'value2')
83
+ conn.hget(customer.dbkey, 'field1')
84
+ conn.hget(customer.dbkey, 'field2')
85
+ end
86
+
87
+ # Check that results are collected properly
88
+ result.is_a?(MultiResult) &&
89
+ result.results.size == 4 &&
90
+ result.results.include?('value1') &&
91
+ result.results.include?('value2')
92
+ ensure
93
+ WarnModeTestCustomer.remove_instance_variable(:@dbclient)
94
+ end
95
+ #=> true
96
+
97
+ ## Save operations work in warn mode with fallback
98
+ begin
99
+ WarnModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
100
+
101
+ customer = WarnModeTestCustomer.new(
102
+ custid: 'save_warn_test',
103
+ name: 'Save Test User',
104
+ email: 'save@example.com'
105
+ )
106
+
107
+ # Save should work using individual commands
108
+ save_result = customer.save
109
+ save_result && customer.exists?
110
+ ensure
111
+ WarnModeTestCustomer.remove_instance_variable(:@dbclient)
112
+ end
113
+ #=> true
114
+
115
+ ## Model transactions respect warn mode with cached connections
116
+ begin
117
+ # Test that cached connections on models respect warn mode
118
+ WarnModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
119
+
120
+ customer = WarnModeTestCustomer.new(custid: 'model_warn_test')
121
+ result = customer.transaction do |conn|
122
+ conn.class == Familia::Connection::IndividualCommandProxy &&
123
+ conn.hset(customer.dbkey, 'mode', 'warn_fallback') &&
124
+ conn.hget(customer.dbkey, 'mode')
125
+ end
126
+
127
+ result.is_a?(MultiResult) && result.results.last == 'warn_fallback'
128
+ ensure
129
+ WarnModeTestCustomer.remove_instance_variable(:@dbclient)
130
+ end
131
+ #=> true
@@ -0,0 +1,249 @@
1
+ # Transaction Modes Tryouts
2
+ #
3
+ # Tests the configurable transaction mode system that provides graceful fallback
4
+ # when connection handlers don't support transactions. Three modes available:
5
+ #
6
+ # - :strict (default): Raise OperationModeError when transaction unavailable
7
+ # - :warn: Log warning and execute commands individually with IndividualCommandProxy
8
+ # - :permissive: Silently execute commands individually
9
+ #
10
+ # The IndividualCommandProxy executes Redis commands immediately instead of queuing
11
+ # them in a transaction, maintaining the same MultiResult interface for consistency.
12
+
13
+ require_relative '../helpers/test_helpers'
14
+
15
+ # Setup - ensure clean state
16
+ @original_transaction_mode = Familia.transaction_mode
17
+ @test_customer_class = nil
18
+
19
+ # Create a test customer class for isolation
20
+ class TransactionModeTestCustomer < Familia::Horreum
21
+ identifier_field :custid
22
+ field :custid
23
+ field :name
24
+ field :email
25
+ end
26
+
27
+ ## Default transaction mode is warn (user-friendly)
28
+ Familia.transaction_mode
29
+ #=> :warn
30
+
31
+ ## Transaction mode can be configured to warn
32
+ Familia.configure do |config|
33
+ config.transaction_mode = :warn
34
+ end
35
+ Familia.transaction_mode
36
+ #=> :warn
37
+
38
+ ## Transaction mode can be configured to permissive
39
+ Familia.configure do |config|
40
+ config.transaction_mode = :permissive
41
+ end
42
+ Familia.transaction_mode
43
+ #=> :permissive
44
+
45
+ ## Reset to strict mode for remaining tests
46
+ Familia.configure { |config| config.transaction_mode = :strict }
47
+
48
+ ## Strict mode raises error with CachedConnectionHandler
49
+ begin
50
+ # Ensure we're in strict mode first
51
+ Familia.configure { |config| config.transaction_mode = :strict }
52
+
53
+ # Force CachedConnectionHandler by setting @dbclient
54
+ TransactionModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
55
+
56
+ customer = TransactionModeTestCustomer.new(custid: 'strict_test')
57
+ customer.transaction do |conn|
58
+ conn.hset(customer.dbkey, 'name', 'Should Not Work')
59
+ end
60
+ false # Should not reach here
61
+ rescue Familia::OperationModeError => e
62
+ e.message.include?('Cannot start transaction with') && e.message.include?('CachedConnectionHandler')
63
+ ensure
64
+ # Clean up cached connection
65
+ TransactionModeTestCustomer.remove_instance_variable(:@dbclient)
66
+ end
67
+ #=> true
68
+
69
+ ## Warn mode logs warning and executes individual commands
70
+ begin
71
+ Familia.configure { |config| config.transaction_mode = :warn }
72
+
73
+ # Force CachedConnectionHandler
74
+ TransactionModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
75
+
76
+ customer = TransactionModeTestCustomer.new(custid: 'warn_test')
77
+
78
+ # Capture log output would be ideal, but test the core functionality
79
+ result = customer.transaction do |conn|
80
+ # This should be an IndividualCommandProxy
81
+ conn.class == Familia::Connection::IndividualCommandProxy &&
82
+ conn.hset(customer.dbkey, 'name', 'Warn Mode Works') &&
83
+ conn.hget(customer.dbkey, 'name')
84
+ end
85
+
86
+ # Should return MultiResult with individual command results
87
+ result.is_a?(MultiResult) && result.results.last == 'Warn Mode Works'
88
+ ensure
89
+ TransactionModeTestCustomer.remove_instance_variable(:@dbclient)
90
+ Familia.configure { |config| config.transaction_mode = :strict }
91
+ end
92
+ #=> true
93
+
94
+ ## Permissive mode silently executes individual commands
95
+ begin
96
+ Familia.configure { |config| config.transaction_mode = :permissive }
97
+
98
+ # Force CachedConnectionHandler
99
+ TransactionModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
100
+
101
+ customer = TransactionModeTestCustomer.new(custid: 'permissive_test')
102
+
103
+ result = customer.transaction do |conn|
104
+ # Should be IndividualCommandProxy
105
+ conn.class == Familia::Connection::IndividualCommandProxy &&
106
+ conn.hset(customer.dbkey, 'email', 'permissive@example.com') &&
107
+ conn.hget(customer.dbkey, 'email')
108
+ end
109
+
110
+ # Should return MultiResult
111
+ result.is_a?(MultiResult) && result.results.last == 'permissive@example.com'
112
+ ensure
113
+ TransactionModeTestCustomer.remove_instance_variable(:@dbclient)
114
+ Familia.configure { |config| config.transaction_mode = :strict }
115
+ end
116
+ #=> true
117
+
118
+ ## Normal transactions still work with CreateConnectionHandler
119
+ begin
120
+ customer = TransactionModeTestCustomer.new(custid: 'normal_test')
121
+
122
+ result = customer.transaction do |conn|
123
+ # Should be Redis::MultiConnection for normal transactions
124
+ conn.class == Redis::MultiConnection &&
125
+ conn.hset(customer.dbkey, 'type', 'normal transaction') &&
126
+ conn.hget(customer.dbkey, 'type')
127
+ end
128
+
129
+ result.is_a?(MultiResult) && result.results.last == 'normal transaction'
130
+ end
131
+ #=> true
132
+
133
+ ## IndividualCommandProxy collects results correctly
134
+ begin
135
+ Familia.configure { |config| config.transaction_mode = :permissive }
136
+ TransactionModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
137
+
138
+ customer = TransactionModeTestCustomer.new(custid: 'proxy_test')
139
+
140
+ result = customer.transaction do |conn|
141
+ conn.hset(customer.dbkey, 'field1', 'value1')
142
+ conn.hget(customer.dbkey, 'field1')
143
+ end
144
+
145
+ # Check that results are collected and it's a MultiResult
146
+ result.is_a?(MultiResult) && result.results.size >= 2
147
+ ensure
148
+ TransactionModeTestCustomer.remove_instance_variable(:@dbclient)
149
+ Familia.configure { |config| config.transaction_mode = :strict }
150
+ end
151
+ #=> true
152
+
153
+ ## MultiResult success detection works with individual commands
154
+ begin
155
+ Familia.configure { |config| config.transaction_mode = :permissive }
156
+ TransactionModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
157
+
158
+ customer = TransactionModeTestCustomer.new(custid: 'success_test')
159
+
160
+ result = customer.transaction do |conn|
161
+ conn.hset(customer.dbkey, 'status', 'active') # Returns 1
162
+ end
163
+
164
+ # Should be successful since 1 is considered success
165
+ result.successful?
166
+ ensure
167
+ TransactionModeTestCustomer.remove_instance_variable(:@dbclient)
168
+ Familia.configure { |config| config.transaction_mode = :strict }
169
+ end
170
+ #=> true
171
+
172
+ ## Global transaction methods also respect transaction modes
173
+ begin
174
+ Familia.configure { |config| config.transaction_mode = :permissive }
175
+
176
+ # Force a handler that doesn't support transactions
177
+ original_provider = Familia.connection_provider
178
+ test_connection = Familia.create_dbclient
179
+ Familia.connection_provider = ->(_uri) { test_connection }
180
+
181
+ # Global transaction should also fallback
182
+ result = Familia.transaction do |conn|
183
+ conn.set('global_test_key', 'global_test_value')
184
+ conn.get('global_test_key')
185
+ end
186
+
187
+ result.is_a?(MultiResult) && result.results.last == 'global_test_value'
188
+ ensure
189
+ Familia.connection_provider = original_provider
190
+ Familia.configure { |config| config.transaction_mode = :strict }
191
+ end
192
+ #=> true
193
+
194
+ ## Transaction fallback preserves connection context
195
+ begin
196
+ Familia.configure { |config| config.transaction_mode = :permissive }
197
+
198
+ # Test with logical database setting
199
+ class DatabaseTestCustomer < Familia::Horreum
200
+ logical_database 5
201
+ identifier_field :custid
202
+ field :custid
203
+ end
204
+
205
+ DatabaseTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
206
+ customer = DatabaseTestCustomer.new(custid: 'db_test')
207
+
208
+ result = customer.transaction do |conn|
209
+ # Commands should execute on the correct database
210
+ conn.set('db_test_key', 'db_test_value')
211
+ conn.get('db_test_key')
212
+ end
213
+
214
+ result.results.last == 'db_test_value'
215
+ ensure
216
+ DatabaseTestCustomer.remove_instance_variable(:@dbclient) if DatabaseTestCustomer.instance_variable_defined?(:@dbclient)
217
+ Familia.configure { |config| config.transaction_mode = :strict }
218
+ end
219
+ #=> true
220
+
221
+ ## Transaction modes work with nested calls
222
+ begin
223
+ Familia.configure { |config| config.transaction_mode = :permissive }
224
+ TransactionModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
225
+
226
+ customer = TransactionModeTestCustomer.new(custid: 'nested_test')
227
+
228
+ # Test that nested transactions work
229
+ outer_result = customer.transaction do |outer_conn|
230
+ outer_conn.hset(customer.dbkey, 'outer', 'value')
231
+
232
+ inner_result = customer.transaction do |inner_conn|
233
+ inner_conn.hset(customer.dbkey, 'inner', 'nested')
234
+ end
235
+
236
+ # Inner transaction should return MultiResult
237
+ inner_result.is_a?(MultiResult)
238
+ end
239
+
240
+ # Outer transaction should also return MultiResult
241
+ outer_result.is_a?(MultiResult)
242
+ ensure
243
+ TransactionModeTestCustomer.remove_instance_variable(:@dbclient)
244
+ Familia.configure { |config| config.transaction_mode = :strict }
245
+ end
246
+ #=> true
247
+
248
+ # Cleanup - restore original transaction mode
249
+ Familia.configure { |config| config.transaction_mode = @original_transaction_mode }
@@ -1,10 +1,30 @@
1
1
  # try/core/autoloader_try.rb
2
2
 
3
+ # Tests for Familia::Features::Autoloader
4
+ #
5
+ # TESTING STRATEGY:
6
+ # Autoloading is inherently tricky to test because:
7
+ # 1. Files are loaded once and cached by Ruby's require system
8
+ # 2. We need to simulate different directory structures
9
+ # 3. We need to verify that files are actually loaded (not just found)
10
+ #
11
+ # SOLUTION:
12
+ # Use temporary directories with Dir.mktmpdir to create isolated test
13
+ # environments and write test files that set global variables when
14
+ # loaded ($test_feature_loaded = true). Globals are reset before each
15
+ # test and FileUtils.rm_rf to clean up temp directories after each test.
16
+ #
17
+ # This approach allows us to:
18
+ # - Test actual file loading behavior (not just glob patterns) that verify
19
+ # the directory patterns that autoloader.included generates and also the
20
+ # exclusion logic works correctly.
21
+
3
22
  require_relative '../../lib/familia'
4
23
  require 'fileutils'
5
24
  require 'tmpdir'
6
25
 
7
- # Create test directory structure for Autoloader testing
26
+ # SETUP: Create test directory structure for basic autoloader testing
27
+ # This simulates the lib/familia/features/ directory structure
8
28
  @test_dir = Dir.mktmpdir('familia_autoloader_test')
9
29
  @features_dir = File.join(@test_dir, 'features')
10
30
  @test_file1 = File.join(@features_dir, 'test_feature1.rb')
@@ -52,6 +72,7 @@ $test_feature1_loaded && $test_feature2_loaded
52
72
  #=> true
53
73
 
54
74
  ## Test that autoload_files respects exclusions (using fresh files)
75
+ # Create a separate test environment to avoid conflicts with cached requires
55
76
  @exclude_test_dir = Dir.mktmpdir('familia_autoloader_exclude_test')
56
77
  @exclude_features_dir = File.join(@exclude_test_dir, 'features')
57
78
  @include_file = File.join(@exclude_features_dir, 'include_me.rb')
@@ -74,6 +95,7 @@ $include_me_loaded && !$exclude_me_loaded
74
95
  #=> true
75
96
 
76
97
  ## Test autoload_files with array of patterns (using fresh files)
98
+ # Test that multiple glob patterns can be processed in a single call
77
99
  @pattern_test_dir = Dir.mktmpdir('familia_autoloader_pattern_test')
78
100
  @pattern_dir1 = File.join(@pattern_test_dir, 'dir1')
79
101
  @pattern_dir2 = File.join(@pattern_test_dir, 'dir2')
@@ -102,11 +124,107 @@ $pattern1_loaded && $pattern2_loaded
102
124
  include Familia::Features::Autoloader
103
125
  end
104
126
 
105
- # The Features module already includes Autoloader, so test indirectly
127
+ ## The Features module already includes Autoloader, so test indirectly
106
128
  Familia::Features.ancestors.include?(Familia::Features::Autoloader)
107
129
  #=> true
108
130
 
131
+ ## Test normalize_to_config_name method exists
132
+ # This method was added to fix issues with namespaced classes after commit d319d9d
133
+ # moved the namespace splitting logic from snake_case to config_name
134
+ Familia::Features::Autoloader.respond_to?(:normalize_to_config_name)
135
+ #=> true
136
+
137
+ ## Test normalize_to_config_name with simple class name
138
+ Familia::Features::Autoloader.normalize_to_config_name('Customer')
139
+ #=> 'customer'
140
+
141
+ ## Test normalize_to_config_name with PascalCase class name
142
+ Familia::Features::Autoloader.normalize_to_config_name('ApiTestUser')
143
+ #=> 'api_test_user'
144
+
145
+ ## Test normalize_to_config_name with namespaced class name (single level)
146
+ Familia::Features::Autoloader.normalize_to_config_name('V2::Customer')
147
+ #=> 'customer'
148
+
149
+ ## Test normalize_to_config_name with deeply namespaced class name
150
+ Familia::Features::Autoloader.normalize_to_config_name('My::Deep::Nested::Module::ApiTestUser')
151
+ #=> 'api_test_user'
152
+
153
+ ## Test normalize_to_config_name with leading double colon
154
+ Familia::Features::Autoloader.normalize_to_config_name('::Customer')
155
+ #=> 'customer'
156
+
157
+ ## Test normalize_to_config_name handles edge case with anonymous class representation
158
+ Familia::Features::Autoloader.normalize_to_config_name('#<Class:0x0001991a8>::ApiTestUser')
159
+ #=> 'api_test_user'
160
+
161
+ ## Test that autoloader directory patterns work with namespaced classes
162
+ # This tests the core fix: ensuring that namespaced classes like TestNamespace::ApiTestModule
163
+ # correctly generate directory patterns using only the demodularized name (api_test_module)
164
+ # rather than the full namespaced name
165
+ # Create a test directory structure that simulates what would happen
166
+ # when a namespaced class includes the autoloader
167
+ @namespace_pattern_test_dir = Dir.mktmpdir('familia_autoloader_namespace_test')
168
+ @base_path = @namespace_pattern_test_dir
169
+ @config_name = 'api_test_module' # This would be the result of normalize_to_config_name
170
+
171
+ # Create directory structure for different patterns
172
+ @features_global_dir = File.join(@base_path, 'features')
173
+ @features_config_dir = File.join(@base_path, @config_name, 'features')
174
+ @features_file = File.join(@base_path, @config_name, 'features.rb')
175
+
176
+ FileUtils.mkdir_p(@features_global_dir)
177
+ FileUtils.mkdir_p(@features_config_dir)
178
+
179
+ # Write test files for each pattern
180
+ @global_feature = File.join(@features_global_dir, 'global_feature.rb')
181
+ @config_feature = File.join(@features_config_dir, 'config_feature.rb')
182
+
183
+ File.write(@global_feature, '$global_feature_loaded = true')
184
+ File.write(@config_feature, '$config_feature_loaded = true')
185
+ File.write(@features_file, '$features_file_loaded = true')
186
+
187
+ # These are the exact patterns that autoloader.included generates:
188
+ # 1. Global features dir: base_path/features/*.rb
189
+ # 2. Config-specific features dir: base_path/config_name/features/*.rb
190
+ # 3. Config-specific features file: base_path/config_name/features.rb
191
+ @dir_patterns = [
192
+ File.join(@base_path, 'features', '*.rb'),
193
+ File.join(@base_path, @config_name, 'features', '*.rb'),
194
+ File.join(@base_path, @config_name, 'features.rb'),
195
+ ]
196
+ # Verify all three patterns are correctly constructed
197
+ @dir_patterns.length
198
+ #=> 3
199
+
200
+ # Reset test flags - critical for testing actual file loading behavior
201
+ $global_feature_loaded = false
202
+ $config_feature_loaded = false
203
+ $features_file_loaded = false
204
+
205
+ ## Test that global features pattern matches correctly
206
+ Dir.glob(@dir_patterns[0]).length
207
+ #=> 1
208
+
209
+ ## Test that config-specific features pattern matches correctly
210
+ Dir.glob(@dir_patterns[1]).length
211
+ #=> 1
212
+
213
+ ## Test that config-specific features.rb file exists
214
+ File.exist?(@dir_patterns[2])
215
+ #=> true
216
+
217
+ ## Test loading all patterns simulates autoloader.included behavior
218
+ # This simulates what happens when a class includes Familia::Features::Autoloader
219
+ # All three file patterns should be processed and their contents loaded
220
+ Familia::Features::Autoloader.autoload_files(@dir_patterns)
221
+
222
+ # Verify all three test files were actually loaded (not just found)
223
+ $global_feature_loaded && $config_feature_loaded && $features_file_loaded
224
+ #=> true
225
+
109
226
  # Cleanup test files and directories
110
227
  FileUtils.rm_rf(@test_dir)
111
228
  FileUtils.rm_rf(@exclude_test_dir)
112
229
  FileUtils.rm_rf(@pattern_test_dir)
230
+ FileUtils.rm_rf(@namespace_pattern_test_dir)
@@ -12,31 +12,31 @@ Familia.uri
12
12
 
13
13
  ## Default URI points to localhost database server
14
14
  Familia.uri.to_s
15
- #=> "redis://127.0.0.1"
15
+ #=> "redis://127.0.0.1:2525"
16
16
 
17
17
  ## Can parse URI from string
18
- uri = URI.parse('redis://localhost:6379/1')
18
+ uri = URI.parse('redis://localhost:2525/1')
19
19
  uri.host
20
20
  #=> "localhost"
21
21
 
22
22
  ## Can establish Database connection
23
+ Familia.create_dbclient
24
+ #=:> Redis
25
+
26
+ ## Can establish Database connection with deprecated method
23
27
  Familia.connect
24
28
  #=:> Redis
25
29
 
26
- ## Can connect to different URI
30
+ ## Can create connection to different URI
27
31
  ## Doesn't confirm the logical DB number, dbclient.options raises an error?
28
- test_uri = 'redis://localhost:6379/2'
29
- Familia.connect(test_uri)
32
+ test_uri = 'redis://localhost:2525/2'
33
+ Familia.create_dbclient(test_uri)
30
34
  #=:> Redis
31
35
 
32
36
  ## Database client responds to basic commands
33
37
  Familia.dbclient.ping
34
38
  #=> "PONG"
35
39
 
36
- ## Multiple connections are managed separately
37
- Familia.database_clients.size >= 1
38
- #=> true
39
-
40
40
  ## Can enable Database logging
41
41
  Familia.enable_database_logging = true
42
42
  Familia.enable_database_logging
@@ -48,7 +48,7 @@ Familia.enable_database_counter
48
48
  #=> true
49
49
 
50
50
  ## Middleware gets registered when enabled
51
- dbclient = Familia.connect('redis://localhost:6379/3')
51
+ dbclient = Familia.create_dbclient('redis://localhost:2525/2')
52
52
  dbclient.ping
53
53
  #=> "PONG"
54
54