familia 2.0.0.pre15 → 2.0.0.pre16

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 (274) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/code-quality.yml +138 -0
  3. data/.github/workflows/code-smellage.yml +145 -0
  4. data/.github/workflows/docs.yml +31 -8
  5. data/.gitignore +1 -1
  6. data/.pre-commit-config.yaml +7 -1
  7. data/.reek.yml +98 -0
  8. data/.rubocop.yml +48 -10
  9. data/.talismanrc +9 -0
  10. data/.yardopts +18 -13
  11. data/CHANGELOG.rst +64 -4
  12. data/CLAUDE.md +1 -1
  13. data/Gemfile +6 -5
  14. data/Gemfile.lock +99 -23
  15. data/LICENSE.txt +1 -1
  16. data/README.md +285 -85
  17. data/changelog.d/README.md +2 -2
  18. data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
  19. data/docs/archive/FAMILIA_TECHNICAL.md +41 -41
  20. data/docs/archive/FAMILIA_UPDATE.md +3 -3
  21. data/docs/archive/README.md +3 -2
  22. data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
  23. data/docs/conf.py +29 -0
  24. data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
  25. data/docs/guides/feature-encrypted-fields.md +785 -0
  26. data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
  27. data/docs/guides/feature-external-identifiers.md +637 -0
  28. data/docs/guides/feature-object-identifiers.md +435 -0
  29. data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
  30. data/docs/guides/feature-relationships-methods.md +684 -0
  31. data/docs/guides/feature-relationships.md +200 -0
  32. data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
  33. data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
  34. data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
  35. data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
  36. data/docs/guides/index.md +176 -0
  37. data/docs/guides/{Security-Model.md → security-model.md} +1 -1
  38. data/docs/migrating/v2.0.0-pre.md +1 -1
  39. data/docs/migrating/v2.0.0-pre11.md +2 -2
  40. data/docs/migrating/v2.0.0-pre12.md +2 -2
  41. data/docs/migrating/v2.0.0-pre5.md +33 -12
  42. data/docs/migrating/v2.0.0-pre6.md +2 -2
  43. data/docs/migrating/v2.0.0-pre7.md +8 -8
  44. data/docs/overview.md +623 -19
  45. data/docs/reference/api-technical.md +1365 -0
  46. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
  47. data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
  48. data/examples/autoloader/mega_customer.rb +3 -1
  49. data/examples/encrypted_fields.rb +378 -0
  50. data/examples/json_usage_patterns.rb +144 -0
  51. data/examples/relationships.rb +13 -13
  52. data/examples/safe_dump.rb +6 -6
  53. data/examples/single_connection_transaction_confusions.rb +379 -0
  54. data/lib/familia/base.rb +49 -10
  55. data/lib/familia/connection/handlers.rb +223 -0
  56. data/lib/familia/connection/individual_command_proxy.rb +64 -0
  57. data/lib/familia/connection/middleware.rb +75 -0
  58. data/lib/familia/connection/operation_core.rb +93 -0
  59. data/lib/familia/connection/operations.rb +277 -0
  60. data/lib/familia/connection/pipeline_core.rb +87 -0
  61. data/lib/familia/connection/transaction_core.rb +100 -0
  62. data/lib/familia/connection.rb +60 -186
  63. data/lib/familia/data_type/commands.rb +53 -51
  64. data/lib/familia/data_type/serialization.rb +108 -107
  65. data/lib/familia/data_type/types/counter.rb +1 -1
  66. data/lib/familia/data_type/types/hashkey.rb +13 -10
  67. data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
  68. data/lib/familia/data_type/types/lock.rb +3 -2
  69. data/lib/familia/data_type/types/sorted_set.rb +26 -15
  70. data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -5
  71. data/lib/familia/data_type/types/unsorted_set.rb +20 -27
  72. data/lib/familia/data_type.rb +75 -47
  73. data/lib/familia/distinguisher.rb +85 -0
  74. data/lib/familia/encryption/encrypted_data.rb +15 -24
  75. data/lib/familia/encryption/manager.rb +6 -4
  76. data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
  77. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
  78. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
  79. data/lib/familia/encryption/request_cache.rb +7 -7
  80. data/lib/familia/encryption.rb +2 -3
  81. data/lib/familia/errors.rb +9 -3
  82. data/lib/familia/features/autoloader.rb +30 -12
  83. data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
  84. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
  85. data/lib/familia/features/encrypted_fields.rb +66 -64
  86. data/lib/familia/features/expiration/extensions.rb +1 -1
  87. data/lib/familia/features/expiration.rb +31 -26
  88. data/lib/familia/features/external_identifier.rb +9 -12
  89. data/lib/familia/features/object_identifier.rb +56 -19
  90. data/lib/familia/features/quantization.rb +16 -21
  91. data/lib/familia/features/relationships/README.md +97 -0
  92. data/lib/familia/features/relationships/collection_operations.rb +104 -0
  93. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
  94. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +301 -0
  95. data/lib/familia/features/relationships/indexing.rb +176 -256
  96. data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
  97. data/lib/familia/features/relationships/participation/participant_methods.rb +160 -0
  98. data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
  99. data/lib/familia/features/relationships/participation.rb +656 -0
  100. data/lib/familia/features/relationships/participation_relationship.rb +31 -0
  101. data/lib/familia/features/relationships/score_encoding.rb +20 -20
  102. data/lib/familia/features/relationships.rb +65 -266
  103. data/lib/familia/features/safe_dump.rb +127 -130
  104. data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
  105. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
  106. data/lib/familia/features/transient_fields.rb +3 -5
  107. data/lib/familia/features.rb +4 -13
  108. data/lib/familia/field_type.rb +24 -4
  109. data/lib/familia/horreum/core/connection.rb +229 -26
  110. data/lib/familia/horreum/core/database_commands.rb +27 -17
  111. data/lib/familia/horreum/core/serialization.rb +40 -20
  112. data/lib/familia/horreum/core/utils.rb +2 -1
  113. data/lib/familia/horreum/shared/settings.rb +2 -1
  114. data/lib/familia/horreum/subclass/definition.rb +33 -45
  115. data/lib/familia/horreum/subclass/management.rb +72 -24
  116. data/lib/familia/horreum/subclass/related_fields_management.rb +82 -21
  117. data/lib/familia/horreum.rb +196 -114
  118. data/lib/familia/json_serializer.rb +0 -1
  119. data/lib/familia/logging.rb +11 -114
  120. data/lib/familia/refinements/dear_json.rb +122 -0
  121. data/lib/familia/refinements/logger_trace.rb +20 -17
  122. data/lib/familia/refinements/stylize_words.rb +65 -0
  123. data/lib/familia/refinements/time_literals.rb +60 -52
  124. data/lib/familia/refinements.rb +2 -1
  125. data/lib/familia/secure_identifier.rb +60 -28
  126. data/lib/familia/settings.rb +83 -7
  127. data/lib/familia/utils.rb +5 -87
  128. data/lib/familia/verifiable_identifier.rb +4 -4
  129. data/lib/familia/version.rb +1 -1
  130. data/lib/familia.rb +72 -14
  131. data/lib/middleware/database_middleware.rb +56 -14
  132. data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
  133. data/try/configuration/scenarios_try.rb +1 -1
  134. data/try/connection/fiber_context_preservation_try.rb +250 -0
  135. data/try/connection/handler_constraints_try.rb +59 -0
  136. data/try/connection/operation_mode_guards_try.rb +208 -0
  137. data/try/connection/pipeline_fallback_integration_try.rb +128 -0
  138. data/try/connection/responsibility_chain_tracking_try.rb +72 -0
  139. data/try/connection/transaction_fallback_integration_try.rb +288 -0
  140. data/try/connection/transaction_mode_permissive_try.rb +153 -0
  141. data/try/connection/transaction_mode_strict_try.rb +98 -0
  142. data/try/connection/transaction_mode_warn_try.rb +131 -0
  143. data/try/connection/transaction_modes_try.rb +249 -0
  144. data/try/core/autoloader_try.rb +120 -2
  145. data/try/core/connection_try.rb +7 -7
  146. data/try/core/conventional_inheritance_try.rb +130 -0
  147. data/try/core/create_method_try.rb +15 -23
  148. data/try/core/database_consistency_try.rb +10 -10
  149. data/try/core/errors_try.rb +8 -11
  150. data/try/core/familia_extended_try.rb +2 -2
  151. data/try/core/familia_members_methods_try.rb +76 -0
  152. data/try/core/isolated_dbclient_try.rb +165 -0
  153. data/try/core/middleware_try.rb +16 -16
  154. data/try/core/persistence_operations_try.rb +4 -4
  155. data/try/core/pools_try.rb +42 -26
  156. data/try/core/secure_identifier_try.rb +28 -24
  157. data/try/core/time_utils_try.rb +10 -10
  158. data/try/core/tools_try.rb +1 -1
  159. data/try/core/utils_try.rb +2 -2
  160. data/try/data_types/boolean_try.rb +4 -4
  161. data/try/data_types/datatype_base_try.rb +0 -2
  162. data/try/data_types/list_try.rb +10 -10
  163. data/try/data_types/sorted_set_try.rb +5 -5
  164. data/try/data_types/string_try.rb +12 -12
  165. data/try/data_types/unsortedset_try.rb +33 -0
  166. data/try/debugging/cache_behavior_tracer.rb +7 -7
  167. data/try/debugging/debug_aad_process.rb +1 -1
  168. data/try/debugging/debug_concealed_internal.rb +1 -1
  169. data/try/debugging/debug_cross_context.rb +1 -1
  170. data/try/debugging/debug_fresh_cross_context.rb +1 -1
  171. data/try/debugging/encryption_method_tracer.rb +10 -10
  172. data/try/edge_cases/hash_symbolization_try.rb +1 -1
  173. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  174. data/try/encryption/config_persistence_try.rb +2 -2
  175. data/try/encryption/encryption_core_try.rb +19 -19
  176. data/try/encryption/instance_variable_scope_try.rb +1 -1
  177. data/try/encryption/module_loading_try.rb +2 -2
  178. data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
  179. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
  180. data/try/encryption/secure_memory_handling_try.rb +1 -1
  181. data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
  182. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  183. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
  184. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
  185. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
  186. data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
  187. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  188. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  189. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
  190. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
  191. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  192. data/try/features/feature_dependencies_try.rb +3 -3
  193. data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
  194. data/try/features/object_identifier/object_identifier_try.rb +10 -0
  195. data/try/features/quantization/quantization_try.rb +1 -1
  196. data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
  197. data/try/features/relationships/indexing_try.rb +433 -0
  198. data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
  199. data/try/features/relationships/participation_commands_verification_try.rb +105 -0
  200. data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
  201. data/try/features/relationships/participation_reverse_index_try.rb +196 -0
  202. data/try/features/relationships/relationships_api_changes_try.rb +72 -71
  203. data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
  204. data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
  205. data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
  206. data/try/features/relationships/relationships_performance_try.rb +20 -20
  207. data/try/features/relationships/relationships_try.rb +27 -38
  208. data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
  209. data/try/features/transient_fields/refresh_reset_try.rb +1 -1
  210. data/try/features/transient_fields/simple_refresh_test.rb +1 -1
  211. data/try/helpers/test_cleanup.rb +86 -0
  212. data/try/helpers/test_helpers.rb +3 -3
  213. data/try/horreum/base_try.rb +3 -2
  214. data/try/horreum/commands_try.rb +1 -1
  215. data/try/horreum/destroy_related_fields_cleanup_try.rb +330 -0
  216. data/try/horreum/initialization_try.rb +11 -7
  217. data/try/horreum/relations_try.rb +21 -13
  218. data/try/horreum/serialization_try.rb +12 -11
  219. data/try/integration/cross_component_try.rb +3 -3
  220. data/try/memory/memory_basic_test.rb +1 -1
  221. data/try/memory/memory_docker_ruby_dump.sh +1 -1
  222. data/try/models/customer_safe_dump_try.rb +1 -1
  223. data/try/models/customer_try.rb +8 -10
  224. data/try/models/datatype_base_try.rb +3 -3
  225. data/try/models/familia_object_try.rb +9 -8
  226. data/try/performance/benchmarks_try.rb +2 -2
  227. data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
  228. data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
  229. data/try/prototypes/atomic_saves_v4.rb +1 -1
  230. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
  231. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  232. data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  233. data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
  234. data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
  235. data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
  236. data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
  237. data/try/prototypes/pooling/pool_siege.rb +11 -11
  238. data/try/prototypes/pooling/run_stress_tests.rb +7 -7
  239. data/try/refinements/dear_json_array_methods_try.rb +53 -0
  240. data/try/refinements/dear_json_hash_methods_try.rb +54 -0
  241. data/try/refinements/logger_trace_methods_try.rb +44 -0
  242. data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
  243. data/try/refinements/time_literals_string_methods_try.rb +80 -0
  244. metadata +75 -43
  245. data/.rubocop_todo.yml +0 -208
  246. data/docs/connection_pooling.md +0 -192
  247. data/docs/guides/Connection-Pooling-Guide.md +0 -437
  248. data/docs/guides/Encrypted-Fields-Overview.md +0 -101
  249. data/docs/guides/Feature-System-Autoloading.md +0 -198
  250. data/docs/guides/Home.md +0 -116
  251. data/docs/guides/Relationships-Guide.md +0 -737
  252. data/docs/guides/relationships-methods.md +0 -266
  253. data/docs/reference/auditing_database_commands.rb +0 -228
  254. data/examples/permissions.rb +0 -240
  255. data/lib/familia/features/relationships/cascading.rb +0 -437
  256. data/lib/familia/features/relationships/membership.rb +0 -497
  257. data/lib/familia/features/relationships/permission_management.rb +0 -264
  258. data/lib/familia/features/relationships/querying.rb +0 -615
  259. data/lib/familia/features/relationships/redis_operations.rb +0 -274
  260. data/lib/familia/features/relationships/tracking.rb +0 -418
  261. data/lib/familia/refinements/snake_case.rb +0 -40
  262. data/lib/familia/validation/command_recorder.rb +0 -336
  263. data/lib/familia/validation/expectations.rb +0 -519
  264. data/lib/familia/validation/validation_helpers.rb +0 -443
  265. data/lib/familia/validation/validator.rb +0 -412
  266. data/lib/familia/validation.rb +0 -140
  267. data/try/data_types/set_try.rb +0 -33
  268. data/try/features/relationships/categorical_permissions_try.rb +0 -515
  269. data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
  270. data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
  271. data/try/validation/atomic_operations_try.rb.disabled +0 -320
  272. data/try/validation/command_validation_try.rb.disabled +0 -207
  273. data/try/validation/performance_validation_try.rb.disabled +0 -324
  274. data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -16,7 +16,7 @@ end
16
16
  # Clean up any existing test data
17
17
  cleanup_keys = []
18
18
  begin
19
- existing_test_keys = Familia.dbclient.keys('createtestmodel:*')
19
+ existing_test_keys = Familia.dbclient.keys('create_test_model:*')
20
20
  cleanup_keys.concat(existing_test_keys)
21
21
  Familia.dbclient.del(*existing_test_keys) if existing_test_keys.any?
22
22
  rescue => e
@@ -26,16 +26,18 @@ end
26
26
  @test_id_counter = 0
27
27
  def next_test_id
28
28
  @test_id_counter += 1
29
- "create-test-#{Time.now.to_i}-#{@test_id_counter}"
29
+ identifier = "create-test-#{Familia.now.to_i}-#{@test_id_counter}"
30
+ identifier
30
31
  end
31
32
 
33
+ @first_test_id = next_test_id
34
+
32
35
  # =============================================
33
36
  # 1. Basic create method functionality
34
37
  # =============================================
35
38
 
36
39
  ## create method successfully creates new object
37
- @test_id = next_test_id
38
- @created_obj = CreateTestModel.create(id: @test_id, name: 'Created Object', value: 'test_value')
40
+ @created_obj = CreateTestModel.create(id: @first_test_id, name: 'Created Object', value: 'test_value')
39
41
  [@created_obj.class, @created_obj.exists?, @created_obj.name]
40
42
  #=> [CreateTestModel, true, 'Created Object']
41
43
 
@@ -53,27 +55,17 @@ end
53
55
  # =============================================
54
56
 
55
57
  ## create method raises RecordExistsError for duplicate
56
- begin
57
- CreateTestModel.create(id: @test_id, name: 'Duplicate Attempt')
58
- false # Should not reach here
59
- rescue => e
60
- e.class
61
- end
62
- #=> Familia::RecordExistsError
58
+ CreateTestModel.create(id: @first_test_id, name: 'Duplicate Attempt')
59
+ #=!> Familia::RecordExistsError
63
60
 
64
61
  ## RecordExistsError includes the dbkey in the message
65
- begin
66
- CreateTestModel.create(id: @test_id, name: 'Another Duplicate')
67
- false # Should not reach here
68
- rescue Familia::RecordExistsError => e
69
- expected_dbkey = "createtestmodel:#{@test_id}:object"
70
- e.message.include?(expected_dbkey)
71
- end
72
- #=> true
62
+ CreateTestModel.create(id: @first_test_id, name: 'Another Duplicate')
63
+ #=!> Familia::RecordExistsError
64
+ #==> !!error.message.match(/create_test_model:#{@first_test_id}:object/)
73
65
 
74
66
  ## RecordExistsError message follows consistent format
75
67
  begin
76
- CreateTestModel.create(id: @test_id, name: 'Yet Another Duplicate')
68
+ CreateTestModel.create(id: @first_test_id, name: 'Yet Another Duplicate')
77
69
  false # Should not reach here
78
70
  rescue Familia::RecordExistsError => e
79
71
  e.message.start_with?('Key already exists:')
@@ -140,12 +132,12 @@ end
140
132
  #=> true
141
133
 
142
134
  ## create failure doesn't leave partial data
143
- before_failed_create = Familia.dbclient.keys("createtestmodel:#{@concurrent_id}:*").length
135
+ before_failed_create = Familia.dbclient.keys("create_test_model:#{@concurrent_id}:*").length
144
136
  begin
145
137
  CreateTestModel.create(id: @concurrent_id, name: 'Should Fail')
146
138
  rescue Familia::RecordExistsError
147
139
  # Should not create any additional keys
148
- after_failed_create = Familia.dbclient.keys("createtestmodel:#{@concurrent_id}:*").length
140
+ after_failed_create = Familia.dbclient.keys("create_test_model:#{@concurrent_id}:*").length
149
141
  after_failed_create == before_failed_create
150
142
  end
151
143
  #=> true
@@ -236,5 +228,5 @@ instance_sees_exists = @class_created.exists?
236
228
  # =============================================
237
229
 
238
230
  # Clean up all test data
239
- test_keys = Familia.dbclient.keys('createtestmodel:*')
231
+ test_keys = Familia.dbclient.keys('create_test_model:*')
240
232
  Familia.dbclient.del(*test_keys) if test_keys.any?
@@ -28,14 +28,14 @@ end
28
28
  @test_id_counter = 0
29
29
  def next_test_id
30
30
  @test_id_counter += 1
31
- "consistency-#{Time.now.to_i}-#{@test_id_counter}"
31
+ "consistency-#{Familia.now.to_i}-#{@test_id_counter}"
32
32
  end
33
33
 
34
34
  # =============================================
35
35
  # 1. Database Consistency Verification
36
36
  # =============================================
37
37
 
38
- ## Redis key structure follows expected pattern
38
+ ## Valkey/Redis key structure follows expected pattern
39
39
  @key_test = ConsistencyTestModel.new(id: next_test_id, name: 'Key Test')
40
40
  @key_test.save
41
41
  dbkey = @key_test.dbkey
@@ -139,7 +139,7 @@ end
139
139
  @empty_hash.save
140
140
 
141
141
  # Manually remove all fields to create an empty hash
142
- # First add a temp field then remove it, which creates empty hash in some Redis versions
142
+ # First add a temp field then remove it, which creates empty hash in some Valkey/Redis versions
143
143
  Familia.dbclient.hset(@empty_hash.dbkey, 'temp_field', 'temp_value')
144
144
  Familia.dbclient.hdel(@empty_hash.dbkey, 'temp_field')
145
145
  # Now remove all remaining fields to create truly empty hash
@@ -159,17 +159,17 @@ obj_exists_without_check = @empty_hash.exists?(check_size: false)
159
159
  @tx_test = ConsistencyTestModel.new(id: next_test_id, name: 'Transaction Test')
160
160
  @tx_test.save
161
161
 
162
- # Verify transaction doesn't interfere with exists? calls
163
- result = @tx_test.transaction do |conn|
164
- # During transaction, exists? should still work
165
- exists_in_tx = @tx_test.exists?
162
+ # Verify transaction works and doesn't corrupt object state
163
+ exists_before_tx = @tx_test.exists?
164
+ multi_result = @tx_test.transaction do |conn|
165
+ # Update a field within transaction
166
166
  conn.hset(@tx_test.dbkey, 'active', 'true')
167
- exists_in_tx
167
+ conn.hset(@tx_test.dbkey, 'processed', 'true')
168
168
  end
169
169
 
170
170
  exists_after_tx = @tx_test.exists?
171
- [result, exists_after_tx]
172
- #=> [[0], true]
171
+ [exists_before_tx, multi_result.successful?, exists_after_tx]
172
+ #=> [true, true, true]
173
173
 
174
174
  # =============================================
175
175
  # 4. Performance Consistency
@@ -26,21 +26,18 @@ rescue Familia::NonUniqueKey => e
26
26
  end
27
27
  #=> Familia::NonUniqueKey
28
28
 
29
- ## HighRiskFactor error stores value
29
+ ## NotDistinguishableError error stores value
30
30
  begin
31
- raise Familia::HighRiskFactor.new('dangerous_value')
32
- rescue Familia::HighRiskFactor => e
31
+ raise Familia::NotDistinguishableError.new('dangerous_value')
32
+ rescue Familia::NotDistinguishableError => e
33
33
  e.value
34
34
  end
35
35
  #=> "dangerous_value"
36
36
 
37
- ## HighRiskFactor error has custom message
38
- begin
39
- raise Familia::HighRiskFactor.new(123)
40
- rescue Familia::HighRiskFactor => e
41
- e.message.include?('High risk factor')
42
- end
43
- #=> true
37
+ ## NotDistinguishableError error has custom message
38
+ raise Familia::NotDistinguishableError, 'A customized message'
39
+ #=:> Familia::NotDistinguishableError
40
+ #=~> /A customized message/
44
41
 
45
42
  ## NotConnected error stores URI
46
43
  test_uri = URI.parse('redis://localhost:6379')
@@ -105,7 +102,7 @@ Familia::RecordExistsError.superclass
105
102
  [
106
103
  Familia::NoIdentifier,
107
104
  Familia::NonUniqueKey,
108
- Familia::HighRiskFactor,
105
+ Familia::NotDistinguishableError,
109
106
  Familia::NotConnected,
110
107
  Familia::KeyNotFoundError,
111
108
  Familia::RecordExistsError
@@ -7,7 +7,7 @@ require_relative '../helpers/test_helpers'
7
7
  ## Has all datatype relativess
8
8
  registered_types = Familia::DataType.registered_types.keys
9
9
  registered_types.collect(&:to_s).sort
10
- #=> ["counter", "hash", "hashkey", "list", "lock", "set", "sorted_set", "string", "zset"]
10
+ #=> ["counter", "hash", "hashkey", "list", "listkey", "lock", "set", "sorted_set", "string", "stringkey", "unsorted_set", "zset"]
11
11
 
12
12
  ## Familia created class methods for datatype list class
13
13
  Familia::Horreum::DefinitionMethods.public_method_defined? :list?
@@ -36,7 +36,7 @@ Bone.list? :owners
36
36
  ## A Familia object can get a specific datatype relatives def
37
37
  definition = Bone.list :owners
38
38
  definition.klass
39
- #=> Familia::List
39
+ #=> Familia::ListKey
40
40
 
41
41
  ## Familia.now
42
42
  parsed_time = Familia.now(Time.parse('2011-04-10 20:56:20 UTC').utc)
@@ -0,0 +1,76 @@
1
+ # try/core/familia_members_methods_try.rb
2
+
3
+ require_relative '../helpers/test_helpers'
4
+
5
+ # Tests for new methods: demodularize, familia_name, and resolve_class
6
+
7
+ # Use the testable methods from the refactored module
8
+ String.include(Familia::Refinements::StylizeWordsMethods)
9
+
10
+ ## demodularize removes module namespace from simple class name
11
+ 'Customer'.demodularize
12
+ #=> 'Customer'
13
+
14
+ ## demodularize removes module namespace from nested class name
15
+ 'V2::Customer'.demodularize
16
+ #=> 'Customer'
17
+
18
+ ## demodularize handles deep nesting
19
+ 'My::Deep::Nested::Module::Customer'.demodularize
20
+ #=> 'Customer'
21
+
22
+ ## demodularize handles single colon edge case
23
+ '::Customer'.demodularize
24
+ #=> 'Customer'
25
+
26
+ ## demodularize returns original string when no modules
27
+ 'SimpleClass'.demodularize
28
+ #=> 'SimpleClass'
29
+
30
+ ## familia_name returns demodularized class name for Customer
31
+ Customer.familia_name
32
+ #=> 'Customer'
33
+
34
+ ## familia_name returns demodularized class name for Session
35
+ Session.familia_name
36
+ #=> 'Session'
37
+
38
+ ## familia_name returns demodularized class name for Bone
39
+ Bone.familia_name
40
+ #=> 'Bone'
41
+
42
+ ## resolve_class returns the same class when given a Class
43
+ Familia.resolve_class(Customer)
44
+ #=> Customer
45
+
46
+ ## resolve_class finds class by string name
47
+ Familia.resolve_class('Customer')
48
+ #=> Customer
49
+
50
+ ## resolve_class finds class by symbol name
51
+ Familia.resolve_class(:Customer)
52
+ #=> Customer
53
+
54
+ ## resolve_class handles CamelCase string conversion
55
+ Familia.resolve_class('CustomDomain')
56
+ #=> CustomDomain
57
+
58
+ ## resolve_class handles snake_case symbol conversion
59
+ Familia.resolve_class(:CustomDomain)
60
+ #=> CustomDomain
61
+
62
+ ## resolve_class raises error for invalid input
63
+ begin
64
+ Familia.resolve_class(123)
65
+ rescue ArgumentError => e
66
+ e.message
67
+ end
68
+ #=> "Expected Class, String, or Symbol, got Integer"
69
+
70
+ ## resolve_class returns nil for unknown class name
71
+ Familia.resolve_class('NonExistentClass')
72
+ #=> nil
73
+
74
+ ## resolve_class returns nil for unknown symbol
75
+ Familia.resolve_class(:NonExistentClass)
76
+ #=> nil
@@ -0,0 +1,165 @@
1
+ # try/core/isolated_dbclient_try.rb
2
+
3
+ # Tryouts: Isolated connection functionality
4
+ #
5
+ # Tests for isolated database connections that don't interfere
6
+ # with the cached connection pool or existing model connections.
7
+
8
+ require_relative '../helpers/test_helpers'
9
+
10
+ # Clean up any existing test data in all test databases
11
+ (0..15).each do |db|
12
+ Familia.with_isolated_dbclient(db) do |client|
13
+ client.flushdb
14
+ end
15
+ end
16
+
17
+ ## isolated_dbclient creates a new uncached connection
18
+ client1 = Familia.isolated_dbclient(0)
19
+ client2 = Familia.isolated_dbclient(0)
20
+ different_objects = client1.object_id != client2.object_id
21
+ client1.close
22
+ client2.close
23
+ different_objects
24
+ #=> true
25
+
26
+ ## isolated_dbclient connects to the correct database
27
+ Familia.with_isolated_dbclient(5) do |client|
28
+ client.set("test_key", "test_value")
29
+ end
30
+
31
+ # Verify the key was set in database 5
32
+ found_in_db5 = Familia.with_isolated_dbclient(5) do |client|
33
+ client.get("test_key") == "test_value"
34
+ end
35
+
36
+ # Verify the key is NOT in database 0
37
+ not_found_in_db0 = Familia.with_isolated_dbclient(0) do |client|
38
+ client.get("test_key").nil?
39
+ end
40
+
41
+ found_in_db5 && not_found_in_db0
42
+ #=> true
43
+
44
+ ## isolated_dbclient doesn't affect cached connections
45
+ # Set up a cached connection
46
+ regular_client = Familia.dbclient(0)
47
+ regular_client.set("cached_key", "cached_value")
48
+
49
+ # Use isolated connection on same database
50
+ isolated_result = Familia.with_isolated_dbclient(0) do |client|
51
+ client.set("isolated_key", "isolated_value")
52
+ client.get("cached_key")
53
+ end
54
+
55
+ # Both keys should be accessible
56
+ cached_accessible = regular_client.get("cached_key") == "cached_value"
57
+ isolated_accessible = regular_client.get("isolated_key") == "isolated_value"
58
+
59
+ cached_accessible && isolated_accessible && isolated_result == "cached_value"
60
+ #=> true
61
+
62
+ ## with_isolated_dbclient properly manages connection lifecycle
63
+ # Test by verifying functionality rather than relying on GC/ObjectSpace
64
+ captured_clients = []
65
+
66
+ 5.times do |i|
67
+ Familia.with_isolated_dbclient(i) do |client|
68
+ captured_clients << client
69
+ client.set("temp_key_#{i}", "temp_value_#{i}")
70
+ # Verify connection works inside block
71
+ client.ping
72
+ end
73
+ end
74
+
75
+ # Verify all connections worked and created distinct objects
76
+ all_worked = captured_clients.size == 5
77
+ all_distinct = captured_clients.map(&:object_id).uniq.size == 5
78
+ keys_set = Familia.with_isolated_dbclient(0) { |c| c.exists?("temp_key_0") }
79
+
80
+ all_worked && all_distinct && keys_set
81
+ #=> true
82
+
83
+ ## with_isolated_dbclient handles exceptions gracefully
84
+ exception_raised = false
85
+ database_state_correct = false
86
+
87
+ begin
88
+ Familia.with_isolated_dbclient(0) do |client|
89
+ client.set("before_error", "value")
90
+ raise "Test exception"
91
+ # This line should not be reached
92
+ client.set("after_error", "should_not_be_set")
93
+ end
94
+ rescue => e
95
+ exception_raised = (e.message == "Test exception")
96
+ end
97
+
98
+ # Verify the database state after the exception was caught
99
+ if exception_raised
100
+ database_state_correct = Familia.with_isolated_dbclient(0) do |client|
101
+ client.get("before_error") == "value" && client.get("after_error").nil?
102
+ end
103
+ end
104
+
105
+ exception_raised && database_state_correct
106
+ #=> true
107
+
108
+ ## isolated connections don't interfere with model connections
109
+ class TestModel < Familia::Horreum
110
+ logical_database 3
111
+ identifier_field :name
112
+ field :name
113
+ end
114
+
115
+ # Create a model instance
116
+ test_model = TestModel.new(name: "test")
117
+ test_model.save
118
+
119
+ # Use isolated connection to scan a different database
120
+ scan_result = Familia.with_isolated_dbclient(5) do |client|
121
+ client.keys("*")
122
+ end
123
+
124
+ # Model should still work correctly
125
+ model_accessible = test_model.exists? && test_model.name == "test"
126
+ # Don't rely on database 5 being empty since previous tests may have written to it
127
+ # Just verify the model still works correctly
128
+ scan_result_valid = scan_result.is_a?(Array)
129
+
130
+ model_accessible && scan_result_valid
131
+ #=> true
132
+
133
+ # Clean up test model and class
134
+ test_model.delete!
135
+ Familia.unload_member(TestModel)
136
+
137
+ ## isolated_dbclient with Integer argument
138
+ client = Familia.isolated_dbclient(7)
139
+ client.set("db_test", "seven")
140
+ result = client.get("db_test")
141
+ client.close
142
+ result
143
+ #=> "seven"
144
+
145
+ ## isolated_dbclient with String URI argument
146
+ client = Familia.isolated_dbclient("redis://localhost:6379/8")
147
+ client.set("uri_test", "eight")
148
+ result = client.get("uri_test")
149
+ client.close
150
+ result
151
+ #=> "eight"
152
+
153
+ ## isolated_dbclient with nil uses default
154
+ default_db = Familia.uri.db || 0
155
+ client = Familia.isolated_dbclient(nil)
156
+ client.set("default_test", "default_value")
157
+
158
+ # Verify it's in the expected database
159
+ verification = Familia.with_isolated_dbclient(default_db) do |verify_client|
160
+ verify_client.get("default_test")
161
+ end
162
+
163
+ client.close
164
+ verification
165
+ #=> "default_value"
@@ -1,11 +1,11 @@
1
1
  # try/core/middleware_try.rb
2
2
 
3
- # Test Redis middleware components
4
- # Mock Redis client with middleware for testing
3
+ # Test Valkey/Redis middleware components
4
+ # Mock Valkey/Redis client with middleware for testing
5
5
 
6
6
  require_relative '../helpers/test_helpers'
7
7
 
8
- class MockRedis
8
+ class MockDatabase
9
9
  attr_reader :logged_commands
10
10
 
11
11
  def initialize
@@ -19,40 +19,40 @@ class MockRedis
19
19
  private
20
20
 
21
21
  def log_command(cmd, *args)
22
- start_time = Time.now
22
+ start_time = Familia.now
23
23
  result = yield
24
- duration = Time.now - start_time
24
+ duration = Familia.now - start_time
25
25
  @logged_commands << { command: cmd, args: args, duration: duration }
26
26
  result
27
27
  end
28
28
  end
29
29
 
30
- ## MockRedis can log commands with timing
31
- redis = MockRedis.new
32
- result = redis.get("test_key")
33
- [result, redis.logged_commands.length, redis.logged_commands.first[:command]]
30
+ ## MockDatabase can log commands with timing
31
+ dbclient = MockDatabase.new
32
+ result = dbclient.get("test_key")
33
+ [result, dbclient.logged_commands.length, dbclient.logged_commands.first[:command]]
34
34
  #=> ["test_value", 1, "GET"]
35
35
 
36
- ## RedisCommandCounter tracks command metrics (if available)
36
+ ## DatabaseCommandCounter tracks command metrics (if available)
37
37
  begin
38
- counter = RedisCommandCounter.new
38
+ counter = DatabaseCommandCounter.new
39
39
  counter.increment("GET")
40
40
  counter.increment("SET")
41
41
  counter.increment("GET")
42
42
  [counter.count("GET"), counter.count("SET"), counter.total]
43
43
  rescue NameError
44
- # Skip if RedisCommandCounter not available
44
+ # Skip if DatabaseCommandCounter not available
45
45
  [2, 1, 3]
46
46
  end
47
47
  #=> [2, 1, 3]
48
48
 
49
49
  ## Command counting utility works (if available)
50
50
  begin
51
- redis = Familia.dbclient
51
+ dbclient = Familia.dbclient
52
52
  count = count_commands do
53
- redis.set("test_key", "value")
54
- redis.get("test_key")
55
- redis.del("test_key")
53
+ dbclient.set("test_key", "value")
54
+ dbclient.get("test_key")
55
+ dbclient.del("test_key")
56
56
  end
57
57
  count >= 3
58
58
  rescue NameError, NoMethodError
@@ -26,7 +26,7 @@ end
26
26
  @test_id_counter = 0
27
27
  def next_test_id
28
28
  @test_id_counter += 1
29
- "test-#{Time.now.to_i}-#{@test_id_counter}"
29
+ "test-#{Familia.now.to_i}-#{@test_id_counter}"
30
30
  end
31
31
 
32
32
  # =============================================
@@ -95,11 +95,11 @@ second_save = @idempotent_obj.save
95
95
 
96
96
  ## Save with partial field data
97
97
  @partial_obj = PersistenceTestModel.new(id: next_test_id)
98
- @partial_obj.name = 'Only Name Set'
98
+ @partial_obj.name = 'Only Name UnsortedSet'
99
99
  # value field is nil/unset
100
100
  result = @partial_obj.save
101
101
  [result, @partial_obj.exists?, @partial_obj.name]
102
- #=> [true, true, 'Only Name Set']
102
+ #=> [true, true, 'Only Name UnsortedSet']
103
103
 
104
104
  # =============================================
105
105
  # 3. save_if_not_exists Method Coverage
@@ -141,7 +141,7 @@ end
141
141
  # 4. create Method Coverage (MISSING from current tests)
142
142
  # =============================================
143
143
 
144
- # NOTE: create method tests disabled due to Redis::Future bug
144
+ # NOTE: create method tests disabled due to Valkey/Redis::Future bug
145
145
  # This would be high-priority coverage but needs the create method bug fixed first
146
146
 
147
147
  ## create method alternative: manual creation simulation
@@ -51,10 +51,28 @@ class PoolTestSession < Familia::Horreum
51
51
 
52
52
  def init
53
53
  @session_id ||= SecureRandom.hex(8)
54
- @created_at ||= Time.now.to_i
54
+ @created_at ||= Familia.now.to_i
55
55
  end
56
56
  end
57
57
 
58
+ class PoolTestAccountDB1 < Familia::Horreum
59
+ logical_database 1
60
+ identifier_field :account_id
61
+ field :account_id
62
+ field :balance, on_conflict: :skip
63
+ field :holder_name
64
+
65
+ def init
66
+ @account_id ||= SecureRandom.hex(6)
67
+ @balance = @balance.to_f if @balance
68
+ end
69
+
70
+ def balance
71
+ @balance&.to_f
72
+ end
73
+ end
74
+
75
+
58
76
  ## Clean up before tests
59
77
  PoolTestAccount.dbclient.flushdb
60
78
  #=> "OK"
@@ -79,23 +97,6 @@ Familia.connection_provider.is_a?(Proc)
79
97
  #=> true
80
98
 
81
99
  ## Test 5: Account in DB 1 via class configuration
82
- class PoolTestAccountDB1 < Familia::Horreum
83
- self.logical_database = 1
84
- identifier_field :account_id
85
- field :account_id
86
- field :balance, on_conflict: :skip
87
- field :holder_name
88
-
89
- def init
90
- @account_id ||= SecureRandom.hex(6)
91
- @balance = @balance.to_f if @balance
92
- end
93
-
94
- def balance
95
- @balance&.to_f
96
- end
97
- end
98
-
99
100
  @account_db1 = PoolTestAccountDB1.new(balance: 750, holder_name: "Charlie")
100
101
  @account_db1.save
101
102
  #=> true
@@ -140,8 +141,8 @@ threads.each(&:join)
140
141
  # Test that transaction connection is available
141
142
  conn.ping
142
143
  end
143
- # Transaction returns array with results
144
- @transfer_result.first
144
+ # Transaction returns MultiResult with success status and results
145
+ @transfer_result.results.first
145
146
  #=> "PONG"
146
147
 
147
148
  ## Test 12: Transaction block executes properly
@@ -149,19 +150,19 @@ end
149
150
  [@account_a.balance, @account_b.balance]
150
151
  #=> [1000.0, 500.0]
151
152
 
152
- ## Test 13: with_connection method
153
- @connection_test_result = Familia.with_connection do |conn|
153
+ ## Test 13: with_dbclient method
154
+ @connection_test_result = Familia.with_dbclient do |conn|
154
155
  conn.set("test_key_#{SecureRandom.hex(4)}", "test_value")
155
156
  end
156
157
  @connection_test_result
157
158
  #=> "OK"
158
159
 
159
160
  ## Test 14: Pipeline operations with connection pool
160
- @pipeline_results = Familia.pipeline do |conn|
161
+ @pipeline_results = Familia.pipelined do |conn|
161
162
  conn.ping
162
163
  end
163
164
  # Pipeline executes successfully
164
- @pipeline_results.first
165
+ @pipeline_results.results.first
165
166
  #=> "PONG"
166
167
 
167
168
  ## Test 15: Multi/EXEC operations with connection pool
@@ -169,7 +170,7 @@ end
169
170
  conn.ping
170
171
  end
171
172
  # Multi/EXEC executes successfully
172
- @multi_results.first
173
+ @multi_results.results.first
173
174
  #=> "PONG"
174
175
 
175
176
  ## Test 16: Error handling in transactions
@@ -208,7 +209,7 @@ timeout_mutex = Mutex.new
208
209
  3.times do |i|
209
210
  timeout_threads << Thread.new do
210
211
  begin
211
- result = Familia.with_connection do |conn|
212
+ result = Familia.with_dbclient do |conn|
212
213
  sleep(0.1) # Brief hold
213
214
  conn.ping
214
215
  end
@@ -270,4 +271,19 @@ Familia.connection_provider = original_provider
270
271
  @captured_uris.any? { |uri| uri.include?('redis://') }
271
272
  #=> true
272
273
 
274
+ ## Check PoolTestAccountDB1 config name
275
+ PoolTestAccountDB1.config_name
276
+ #=> 'pool_test_account_db1'
277
+
278
+
273
279
  puts "Connection pool tests completed successfully!"
280
+
281
+ # Teardown
282
+ Familia.connection_provider = nil
283
+ Fiber[:familia_connection] = nil
284
+ Fiber[:familia_connection_handler_class] = nil
285
+ Fiber[:familia_transaction] = nil
286
+ Fiber[:familia_pipeline] = nil
287
+ Fiber[:familia_key_cache] = nil
288
+ Fiber[:familia_request_cache] = nil
289
+ Fiber[:familia_request_cache_enabled] = nil