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,130 @@
1
+ require_relative '../helpers/test_helpers'
2
+
3
+ # Define test classes in global namespace
4
+ class ::TestVehicle < Familia::Horreum
5
+ identifier_field :vin
6
+ field :vin
7
+ field :make
8
+ field :model
9
+ field :year
10
+ feature :expiration
11
+ list :maintenance_log
12
+ set :tags
13
+ end
14
+
15
+ class ::TestCar < ::TestVehicle
16
+ field :doors
17
+ field :fuel_type
18
+ end
19
+
20
+ class ::TestElectricCar < ::TestCar
21
+ field :battery_capacity
22
+ field :range_miles
23
+ end
24
+
25
+ class ::TestMotorcycle < ::TestVehicle
26
+ field :engine_cc
27
+ field :has_sidecar
28
+ end
29
+
30
+ class ::TestBaseModel < Familia::Horreum
31
+ end
32
+
33
+ class ::TestConcreteModel < ::TestBaseModel
34
+ identifier_field :id
35
+ field :name
36
+ end
37
+
38
+ ## Creates a parent class with various configurations
39
+ @vehicle = TestVehicle.new(vin: 'ABC123', make: 'Toyota', model: 'Camry', year: 2020)
40
+
41
+ ## Parent class has expected configuration
42
+ TestVehicle.identifier_field
43
+ #=> :vin
44
+
45
+ ## Parent class has fields defined
46
+ TestVehicle.fields
47
+ #=> [:vin, :make, :model, :year]
48
+
49
+ ## Parent class has features enabled
50
+ TestVehicle.features_enabled
51
+ #=> [:expiration]
52
+
53
+ ## Child class inherits parent identifier_field
54
+ TestCar.identifier_field
55
+ #=> :vin
56
+
57
+ ## Child class inherits parent fields and adds its own
58
+ TestCar.fields
59
+ #=> [:vin, :make, :model, :year, :doors, :fuel_type]
60
+
61
+ ## Child class inherits parent features
62
+ TestCar.features_enabled
63
+ #=> [:expiration]
64
+
65
+ ## Child instance works with inherited configuration
66
+ @car = TestCar.new(vin: 'DEF456', make: 'Honda', model: 'Civic', year: 2021, doors: 4, fuel_type: 'gasoline')
67
+ @car.identifier
68
+ #=> "DEF456"
69
+
70
+ ## Child instance can access inherited fields
71
+ @car.make
72
+ #=> "Honda"
73
+
74
+ ## Child instance can access new fields
75
+ @car.doors
76
+ #=> 4
77
+
78
+ ## Child instance inherits DataType relationships
79
+ @car.maintenance_log.class
80
+ #=> Familia::ListKey
81
+
82
+ ## Child instance can use inherited DataType relationships
83
+ @car.tags << 'reliable'
84
+ @car.tags.members
85
+ #=> ["reliable"]
86
+
87
+ ## Grandchild inherits all ancestor configuration
88
+ TestElectricCar.identifier_field
89
+ #=> :vin
90
+
91
+ ## Grandchild inherits all ancestor fields
92
+ TestElectricCar.fields
93
+ #=> [:vin, :make, :model, :year, :doors, :fuel_type, :battery_capacity, :range_miles]
94
+
95
+ ## Grandchild inherits all ancestor features
96
+ TestElectricCar.features_enabled
97
+ #=> [:expiration]
98
+
99
+ ## Grandchild instance works correctly
100
+ @electric = TestElectricCar.new(vin: 'TESLA123', make: 'Tesla', model: 'Model 3', doors: 4, battery_capacity: 75)
101
+ @electric.identifier
102
+ #=> "TESLA123"
103
+
104
+ ## Parent and child classes remain independent after inheritance
105
+ TestVehicle.fields.size
106
+ #=> 4
107
+
108
+ ## Child class has correct field count
109
+ TestCar.fields.size
110
+ #=> 6
111
+
112
+ ## Grandchild class has correct field count
113
+ TestElectricCar.fields.size
114
+ #=> 8
115
+
116
+ ## Parent field count unchanged after adding new child
117
+ TestVehicle.fields.size
118
+ #=> 4
119
+
120
+ ## New child class has correct field count
121
+ TestMotorcycle.fields.size
122
+ #=> 6
123
+
124
+ ## Child of empty parent inherits correctly
125
+ TestConcreteModel.identifier_field
126
+ #=> :id
127
+
128
+ ## Child of empty parent has correct fields
129
+ TestConcreteModel.fields
130
+ #=> [:name]
@@ -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
@@ -214,6 +214,7 @@ exists_after_batch = @batch_obj.exists?
214
214
 
215
215
  ## Transient fields don't affect exists? behavior
216
216
  class TransientConsistencyTest < Familia::Horreum
217
+ feature :transient_fields
217
218
  identifier_field :id
218
219
  field :id
219
220
  field :name
@@ -26,33 +26,30 @@ 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
- test_uri = URI.parse('redis://localhost:6379')
43
+ test_uri = URI.parse('redis://localhost:2525')
47
44
  begin
48
45
  raise Familia::NotConnected.new(test_uri)
49
46
  rescue Familia::NotConnected => e
50
47
  e.uri.to_s
51
48
  end
52
- #=> "redis://localhost"
49
+ #=> "redis://localhost:2525"
53
50
 
54
51
  ## NotConnected error has custom message
55
- test_uri = URI.parse('redis://localhost:6379')
52
+ test_uri = URI.parse('redis://localhost:2525')
56
53
  begin
57
54
  raise Familia::NotConnected.new(test_uri)
58
55
  rescue Familia::NotConnected => e
@@ -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
@@ -12,7 +12,7 @@ Familia.uri
12
12
 
13
13
  ## Familia has a uri as a string
14
14
  Familia.uri.to_s
15
- #=> 'redis://127.0.0.1'
15
+ #=> 'redis://127.0.0.1:2525'
16
16
 
17
17
  ## Familia has a url, an alias to uri
18
18
  Familia.url.eql?(Familia.uri)
@@ -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..2).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:2525/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