familia 2.0.0.pre16 → 2.0.0.pre18

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 (250) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -2
  3. data/.github/workflows/{code-smellage.yml → code-smells.yml} +3 -63
  4. data/.gitignore +2 -0
  5. data/.rubocop.yml +6 -0
  6. data/CHANGELOG.rst +82 -0
  7. data/CLAUDE.md +47 -2
  8. data/Gemfile.lock +1 -1
  9. data/README.md +13 -0
  10. data/bin/irb +1 -1
  11. data/docs/archive/FAMILIA_TECHNICAL.md +1 -1
  12. data/docs/guides/core-field-system.md +48 -26
  13. data/docs/migrating/v2.0.0-pre18.md +58 -0
  14. data/docs/overview.md +2 -2
  15. data/docs/qodo-merge-compliance.md +96 -0
  16. data/docs/reference/api-technical.md +1 -1
  17. data/examples/encrypted_fields.rb +1 -1
  18. data/examples/safe_dump.rb +1 -1
  19. data/lib/familia/base.rb +6 -6
  20. data/lib/familia/connection/middleware.rb +58 -4
  21. data/lib/familia/connection.rb +1 -1
  22. data/lib/familia/data_type/class_methods.rb +63 -0
  23. data/lib/familia/data_type/connection.rb +83 -0
  24. data/lib/familia/data_type/{commands.rb → database_commands.rb} +2 -2
  25. data/lib/familia/data_type/serialization.rb +5 -5
  26. data/lib/familia/data_type/settings.rb +96 -0
  27. data/lib/familia/data_type/types/hashkey.rb +2 -1
  28. data/lib/familia/data_type/types/sorted_set.rb +113 -10
  29. data/lib/familia/data_type/types/stringkey.rb +0 -4
  30. data/lib/familia/data_type.rb +8 -195
  31. data/lib/familia/encryption/encrypted_data.rb +12 -2
  32. data/lib/familia/encryption/manager.rb +11 -4
  33. data/lib/familia/features/autoloader.rb +3 -1
  34. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +11 -3
  35. data/lib/familia/features/encrypted_fields.rb +5 -2
  36. data/lib/familia/features/external_identifier.rb +49 -8
  37. data/lib/familia/features/object_identifier.rb +84 -12
  38. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +9 -9
  39. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +45 -26
  40. data/lib/familia/features/relationships/indexing.rb +7 -1
  41. data/lib/familia/features/relationships/participation/participant_methods.rb +6 -2
  42. data/lib/familia/features/safe_dump.rb +2 -3
  43. data/lib/familia/features/transient_fields.rb +7 -2
  44. data/lib/familia/features.rb +6 -1
  45. data/lib/familia/field_type.rb +0 -18
  46. data/lib/familia/horreum/{core/connection.rb → connection.rb} +21 -0
  47. data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +1 -1
  48. data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +102 -56
  49. data/lib/familia/horreum/{subclass/management.rb → management.rb} +18 -15
  50. data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +73 -170
  51. data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +22 -2
  52. data/lib/familia/horreum/serialization.rb +190 -0
  53. data/lib/familia/horreum.rb +39 -14
  54. data/lib/familia/identifier_extractor.rb +60 -0
  55. data/lib/familia/logging.rb +271 -112
  56. data/lib/familia/refinements.rb +0 -1
  57. data/lib/familia/version.rb +1 -1
  58. data/lib/familia.rb +2 -2
  59. data/lib/middleware/{database_middleware.rb → database_logger.rb} +47 -14
  60. data/pr_agent.toml +31 -0
  61. data/pr_compliance_checklist.yaml +45 -0
  62. data/try/edge_cases/empty_identifiers_try.rb +1 -1
  63. data/try/edge_cases/hash_symbolization_try.rb +31 -31
  64. data/try/edge_cases/json_serialization_try.rb +2 -2
  65. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +170 -0
  66. data/try/edge_cases/race_conditions_try.rb +1 -1
  67. data/try/edge_cases/reserved_keywords_try.rb +1 -1
  68. data/try/edge_cases/string_coercion_try.rb +1 -1
  69. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  70. data/try/features/encrypted_fields/aad_protection_try.rb +1 -1
  71. data/try/features/encrypted_fields/concealed_string_core_try.rb +1 -1
  72. data/try/features/encrypted_fields/context_isolation_try.rb +1 -1
  73. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  74. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +1 -1
  75. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +1 -1
  76. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +1 -1
  77. data/try/features/encrypted_fields/error_conditions_try.rb +1 -1
  78. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +1 -1
  79. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  80. data/try/features/encrypted_fields/key_rotation_try.rb +1 -1
  81. data/try/features/encrypted_fields/memory_security_try.rb +1 -1
  82. data/try/features/encrypted_fields/missing_current_key_version_try.rb +1 -1
  83. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  84. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +1 -1
  85. data/try/features/encrypted_fields/thread_safety_try.rb +1 -1
  86. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +1 -1
  87. data/try/{encryption → features/encryption}/config_persistence_try.rb +1 -1
  88. data/try/{encryption/encryption_core_try.rb → features/encryption/core_try.rb} +2 -2
  89. data/try/{encryption → features/encryption}/instance_variable_scope_try.rb +1 -1
  90. data/try/{encryption → features/encryption}/module_loading_try.rb +1 -1
  91. data/try/{encryption → features/encryption}/providers/aes_gcm_provider_try.rb +1 -1
  92. data/try/{encryption → features/encryption}/providers/xchacha20_poly1305_provider_try.rb +1 -1
  93. data/try/{encryption → features/encryption}/roundtrip_validation_try.rb +1 -1
  94. data/try/{encryption → features/encryption}/secure_memory_handling_try.rb +2 -2
  95. data/try/features/expiration/expiration_try.rb +1 -1
  96. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  97. data/try/features/feature_dependencies_try.rb +1 -1
  98. data/try/features/feature_improvements_try.rb +1 -1
  99. data/try/features/field_groups_try.rb +244 -0
  100. data/try/features/object_identifier/object_identifier_integration_try.rb +1 -1
  101. data/try/features/object_identifier/object_identifier_try.rb +1 -1
  102. data/try/features/quantization/quantization_try.rb +1 -1
  103. data/try/features/real_feature_integration_try.rb +17 -14
  104. data/try/features/relationships/indexing_commands_verification_try.rb +8 -3
  105. data/try/features/relationships/indexing_try.rb +16 -1
  106. data/try/features/relationships/participation_commands_verification_spec.rb +1 -1
  107. data/try/features/relationships/participation_commands_verification_try.rb +4 -4
  108. data/try/features/relationships/participation_performance_improvements_try.rb +1 -1
  109. data/try/features/relationships/participation_reverse_index_try.rb +1 -1
  110. data/try/features/relationships/relationships_api_changes_try.rb +1 -1
  111. data/try/features/relationships/relationships_edge_cases_try.rb +3 -3
  112. data/try/features/relationships/relationships_performance_minimal_try.rb +1 -1
  113. data/try/features/relationships/relationships_performance_simple_try.rb +1 -1
  114. data/try/features/relationships/relationships_performance_try.rb +1 -1
  115. data/try/features/relationships/relationships_performance_working_try.rb +1 -1
  116. data/try/features/relationships/relationships_try.rb +1 -1
  117. data/try/features/safe_dump/safe_dump_advanced_try.rb +1 -1
  118. data/try/features/safe_dump/safe_dump_try.rb +1 -1
  119. data/try/features/transient_fields/redacted_string_try.rb +1 -1
  120. data/try/features/transient_fields/refresh_reset_try.rb +3 -1
  121. data/try/features/transient_fields/single_use_redacted_string_try.rb +1 -1
  122. data/try/features/transient_fields/transient_fields_core_try.rb +1 -1
  123. data/try/features/transient_fields/transient_fields_integration_try.rb +1 -1
  124. data/try/{connection → integration/connection}/fiber_context_preservation_try.rb +1 -1
  125. data/try/{connection → integration/connection}/handler_constraints_try.rb +1 -1
  126. data/try/{core → integration/connection}/isolated_dbclient_try.rb +3 -3
  127. data/try/integration/connection/middleware_reconnect_try.rb +87 -0
  128. data/try/{connection → integration/connection}/operation_mode_guards_try.rb +1 -1
  129. data/try/{connection → integration/connection}/pipeline_fallback_integration_try.rb +1 -1
  130. data/try/{core → integration/connection}/pools_try.rb +1 -1
  131. data/try/{connection → integration/connection}/responsibility_chain_tracking_try.rb +1 -1
  132. data/try/{connection → integration/connection}/transaction_fallback_integration_try.rb +1 -1
  133. data/try/{connection → integration/connection}/transaction_mode_permissive_try.rb +1 -1
  134. data/try/{connection → integration/connection}/transaction_mode_strict_try.rb +1 -1
  135. data/try/{connection → integration/connection}/transaction_mode_warn_try.rb +1 -1
  136. data/try/{connection → integration/connection}/transaction_modes_try.rb +1 -1
  137. data/try/{core → integration}/conventional_inheritance_try.rb +1 -1
  138. data/try/{core → integration}/create_method_try.rb +1 -1
  139. data/try/integration/cross_component_try.rb +1 -1
  140. data/try/{core → integration}/database_consistency_try.rb +12 -8
  141. data/try/{core → integration}/familia_extended_try.rb +1 -1
  142. data/try/{core → integration}/familia_members_methods_try.rb +1 -1
  143. data/try/{models → integration/models}/customer_safe_dump_try.rb +1 -1
  144. data/try/{models → integration/models}/customer_try.rb +6 -6
  145. data/try/{models → integration/models}/datatype_base_try.rb +1 -1
  146. data/try/{models → integration/models}/familia_object_try.rb +1 -1
  147. data/try/{core → integration}/persistence_operations_try.rb +1 -1
  148. data/try/integration/relationships_persistence_round_trip_try.rb +441 -0
  149. data/try/{configuration → integration}/scenarios_try.rb +2 -2
  150. data/try/{core → integration}/secure_identifier_try.rb +1 -1
  151. data/try/{core → integration}/verifiable_identifier_try.rb +1 -1
  152. data/try/performance/benchmarks_try.rb +2 -2
  153. data/try/support/benchmarks/deserialization_benchmark.rb +180 -0
  154. data/try/support/benchmarks/deserialization_correctness_test.rb +237 -0
  155. data/try/{helpers → support/helpers}/test_helpers.rb +15 -7
  156. data/try/{memory → support/memory}/memory_docker_ruby_dump.sh +1 -1
  157. data/try/{core → unit/core}/autoloader_try.rb +1 -1
  158. data/try/{core → unit/core}/base_enhancements_try.rb +1 -9
  159. data/try/{core → unit/core}/connection_try.rb +5 -5
  160. data/try/{core → unit/core}/errors_try.rb +4 -4
  161. data/try/{core → unit/core}/extensions_try.rb +1 -1
  162. data/try/unit/core/familia_logger_try.rb +110 -0
  163. data/try/{core → unit/core}/familia_try.rb +2 -2
  164. data/try/{core → unit/core}/middleware_try.rb +41 -1
  165. data/try/{core → unit/core}/settings_try.rb +1 -1
  166. data/try/{core → unit/core}/time_utils_try.rb +1 -1
  167. data/try/{core → unit/core}/tools_try.rb +3 -3
  168. data/try/{core → unit/core}/utils_try.rb +17 -14
  169. data/try/{data_types → unit/data_types}/boolean_try.rb +1 -1
  170. data/try/{data_types → unit/data_types}/counter_try.rb +1 -1
  171. data/try/{data_types → unit/data_types}/datatype_base_try.rb +1 -1
  172. data/try/{data_types → unit/data_types}/hash_try.rb +1 -1
  173. data/try/{data_types → unit/data_types}/list_try.rb +1 -1
  174. data/try/{data_types → unit/data_types}/lock_try.rb +1 -1
  175. data/try/{data_types → unit/data_types}/sorted_set_try.rb +1 -1
  176. data/try/unit/data_types/sorted_set_zadd_options_try.rb +625 -0
  177. data/try/{data_types → unit/data_types}/string_try.rb +1 -1
  178. data/try/{data_types → unit/data_types}/unsortedset_try.rb +1 -1
  179. data/try/unit/horreum/auto_indexing_on_save_try.rb +212 -0
  180. data/try/{horreum → unit/horreum}/base_try.rb +3 -3
  181. data/try/{horreum → unit/horreum}/class_methods_try.rb +1 -1
  182. data/try/{horreum → unit/horreum}/commands_try.rb +3 -1
  183. data/try/unit/horreum/defensive_initialization_try.rb +86 -0
  184. data/try/{horreum → unit/horreum}/destroy_related_fields_cleanup_try.rb +3 -1
  185. data/try/{horreum → unit/horreum}/enhanced_conflict_handling_try.rb +1 -1
  186. data/try/{horreum → unit/horreum}/field_categories_try.rb +27 -18
  187. data/try/{horreum → unit/horreum}/field_definition_try.rb +1 -1
  188. data/try/{horreum → unit/horreum}/initialization_try.rb +2 -2
  189. data/try/unit/horreum/json_type_preservation_try.rb +248 -0
  190. data/try/{horreum → unit/horreum}/relations_try.rb +1 -1
  191. data/try/{horreum → unit/horreum}/serialization_persistent_fields_try.rb +24 -18
  192. data/try/{horreum → unit/horreum}/serialization_try.rb +4 -4
  193. data/try/{horreum → unit/horreum}/settings_try.rb +3 -1
  194. data/try/{refinements → unit/refinements}/dear_json_array_methods_try.rb +1 -1
  195. data/try/{refinements → unit/refinements}/dear_json_hash_methods_try.rb +1 -1
  196. data/try/{refinements → unit/refinements}/time_literals_numeric_methods_try.rb +1 -1
  197. data/try/{refinements → unit/refinements}/time_literals_string_methods_try.rb +1 -1
  198. data/try/valkey.conf +26 -0
  199. metadata +149 -132
  200. data/lib/familia/distinguisher.rb +0 -85
  201. data/lib/familia/horreum/core.rb +0 -21
  202. data/lib/familia/refinements/logger_trace.rb +0 -60
  203. data/try/refinements/logger_trace_methods_try.rb +0 -44
  204. /data/lib/familia/horreum/{shared/settings.rb → settings.rb} +0 -0
  205. /data/lib/familia/horreum/{core/utils.rb → utils.rb} +0 -0
  206. /data/try/{debugging → support/debugging}/README.md +0 -0
  207. /data/try/{debugging → support/debugging}/cache_behavior_tracer.rb +0 -0
  208. /data/try/{debugging → support/debugging}/debug_aad_process.rb +0 -0
  209. /data/try/{debugging → support/debugging}/debug_concealed_internal.rb +0 -0
  210. /data/try/{debugging → support/debugging}/debug_concealed_reveal.rb +0 -0
  211. /data/try/{debugging → support/debugging}/debug_context_aad.rb +0 -0
  212. /data/try/{debugging → support/debugging}/debug_context_simple.rb +0 -0
  213. /data/try/{debugging → support/debugging}/debug_cross_context.rb +0 -0
  214. /data/try/{debugging → support/debugging}/debug_database_load.rb +0 -0
  215. /data/try/{debugging → support/debugging}/debug_encrypted_json_check.rb +0 -0
  216. /data/try/{debugging → support/debugging}/debug_encrypted_json_step_by_step.rb +0 -0
  217. /data/try/{debugging → support/debugging}/debug_exists_lifecycle.rb +0 -0
  218. /data/try/{debugging → support/debugging}/debug_field_decrypt.rb +0 -0
  219. /data/try/{debugging → support/debugging}/debug_fresh_cross_context.rb +0 -0
  220. /data/try/{debugging → support/debugging}/debug_load_path.rb +0 -0
  221. /data/try/{debugging → support/debugging}/debug_method_definition.rb +0 -0
  222. /data/try/{debugging → support/debugging}/debug_method_resolution.rb +0 -0
  223. /data/try/{debugging → support/debugging}/debug_minimal.rb +0 -0
  224. /data/try/{debugging → support/debugging}/debug_provider.rb +0 -0
  225. /data/try/{debugging → support/debugging}/debug_secure_behavior.rb +0 -0
  226. /data/try/{debugging → support/debugging}/debug_string_class.rb +0 -0
  227. /data/try/{debugging → support/debugging}/debug_test.rb +0 -0
  228. /data/try/{debugging → support/debugging}/debug_test_design.rb +0 -0
  229. /data/try/{debugging → support/debugging}/encryption_method_tracer.rb +0 -0
  230. /data/try/{debugging → support/debugging}/provider_diagnostics.rb +0 -0
  231. /data/try/{helpers → support/helpers}/test_cleanup.rb +0 -0
  232. /data/try/{memory → support/memory}/memory_basic_test.rb +0 -0
  233. /data/try/{memory → support/memory}/memory_detailed_test.rb +0 -0
  234. /data/try/{memory → support/memory}/memory_search_for_string.rb +0 -0
  235. /data/try/{memory → support/memory}/test_actual_redactedstring_protection.rb +0 -0
  236. /data/try/{prototypes → support/prototypes}/atomic_saves_v1_context_proxy.rb +0 -0
  237. /data/try/{prototypes → support/prototypes}/atomic_saves_v2_connection_switching.rb +0 -0
  238. /data/try/{prototypes → support/prototypes}/atomic_saves_v3_connection_pool.rb +0 -0
  239. /data/try/{prototypes → support/prototypes}/atomic_saves_v4.rb +0 -0
  240. /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v2_connection_switching_helpers.rb +0 -0
  241. /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
  242. /data/try/{prototypes → support/prototypes}/pooling/README.md +0 -0
  243. /data/try/{prototypes → support/prototypes}/pooling/configurable_stress_test.rb +0 -0
  244. /data/try/{prototypes → support/prototypes}/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
  245. /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_metrics.rb +0 -0
  246. /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_stress_test.rb +0 -0
  247. /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_threading_models.rb +0 -0
  248. /data/try/{prototypes → support/prototypes}/pooling/lib/visualize_stress_results.rb +0 -0
  249. /data/try/{prototypes → support/prototypes}/pooling/pool_siege.rb +0 -0
  250. /data/try/{prototypes → support/prototypes}/pooling/run_stress_tests.rb +0 -0
@@ -0,0 +1,248 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../support/helpers/test_helpers'
4
+
5
+ Familia.debug = false
6
+
7
+ # Setup: Define test model with various field types
8
+ class JsonTestModel < Familia::Horreum
9
+ identifier_field :model_id
10
+ field :model_id
11
+ field :name # String
12
+ field :age # Integer
13
+ field :active # Boolean
14
+ field :score # Float
15
+ field :metadata # Hash
16
+ field :tags # Array
17
+ field :nested_data # Hash with nested structures
18
+ field :badge_number # String that looks numeric
19
+ field :status # String that looks boolean
20
+ field :placeholder # String that looks like null
21
+ end
22
+
23
+ @test_id = "json_test_#{Familia.now.to_i}"
24
+
25
+ ## String fields preserve String type
26
+ @model = JsonTestModel.new(model_id: @test_id, name: "Test User")
27
+ @model.save
28
+ @loaded = JsonTestModel.find(@test_id)
29
+ @loaded.name
30
+ #=> "Test User"
31
+
32
+ ## String field returns String class
33
+ @loaded.name.class
34
+ #=> String
35
+
36
+ ## Integer fields preserve Integer type (not String)
37
+ @model = JsonTestModel.new(model_id: @test_id, age: 35)
38
+ @model.save
39
+ @loaded = JsonTestModel.find(@test_id)
40
+ @loaded.age
41
+ #=> 35
42
+
43
+ ## Integer field returns Integer class (not String)
44
+ @loaded.age.class
45
+ #=> Integer
46
+
47
+ ## Boolean true preserves TrueClass type (not String "true")
48
+ @model = JsonTestModel.new(model_id: @test_id, active: true)
49
+ @model.save
50
+ @loaded = JsonTestModel.find(@test_id)
51
+ @loaded.active
52
+ #=> true
53
+
54
+ ## Boolean true returns TrueClass
55
+ @loaded.active.class
56
+ #=> TrueClass
57
+
58
+ ## Boolean false preserves FalseClass type (not String "false")
59
+ @model = JsonTestModel.new(model_id: @test_id, active: false)
60
+ @model.save
61
+ @loaded = JsonTestModel.find(@test_id)
62
+ @loaded.active
63
+ #=> false
64
+
65
+ ## Boolean false returns FalseClass
66
+ @loaded.active.class
67
+ #=> FalseClass
68
+
69
+ ## Float fields preserve Float type
70
+ @model = JsonTestModel.new(model_id: @test_id, score: 98.6)
71
+ @model.save
72
+ @loaded = JsonTestModel.find(@test_id)
73
+ @loaded.score
74
+ #=> 98.6
75
+
76
+ ## Float field returns Float class
77
+ @loaded.score.class
78
+ #=> Float
79
+
80
+ ## Hash fields preserve Hash structure
81
+ @model = JsonTestModel.new(model_id: @test_id, metadata: { "key" => "value", "count" => 42 })
82
+ @model.save
83
+ @loaded = JsonTestModel.find(@test_id)
84
+ @loaded.metadata
85
+ #=> {"key"=>"value", "count"=>42}
86
+
87
+ ## Hash field returns Hash class
88
+ @loaded.metadata.class
89
+ #=> Hash
90
+
91
+ ## Array fields preserve Array structure and element types
92
+ @model = JsonTestModel.new(model_id: @test_id, tags: ["ruby", "redis", 123, true])
93
+ @model.save
94
+ @loaded = JsonTestModel.find(@test_id)
95
+ @loaded.tags
96
+ #=> ["ruby", "redis", 123, true]
97
+
98
+ ## Array field returns Array class
99
+ @loaded.tags.class
100
+ #=> Array
101
+
102
+ ## Array preserves Integer element type
103
+ @loaded.tags[2].class
104
+ #=> Integer
105
+
106
+ ## Array preserves Boolean element type
107
+ @loaded.tags[3].class
108
+ #=> TrueClass
109
+
110
+ ## Nested Hash structures preserve types at all levels
111
+ @nested = {
112
+ "user" => {
113
+ "name" => "John",
114
+ "age" => 30,
115
+ "active" => true,
116
+ "roles" => ["admin", "user"]
117
+ },
118
+ "count" => 5
119
+ }
120
+ @model = JsonTestModel.new(model_id: @test_id, nested_data: @nested)
121
+ @model.save
122
+ @loaded = JsonTestModel.find(@test_id)
123
+ @loaded.nested_data["user"]["age"].class
124
+ #=> Integer
125
+
126
+ ## Nested Hash Boolean preserved
127
+ @loaded.nested_data["user"]["active"].class
128
+ #=> TrueClass
129
+
130
+ ## Nested Hash top-level Integer preserved
131
+ @loaded.nested_data["count"].class
132
+ #=> Integer
133
+ ## Round-trip consistency: save → load → save → load maintains types
134
+ @model = JsonTestModel.new(
135
+ model_id: @test_id,
136
+ name: "Round Trip",
137
+ age: 42,
138
+ active: true,
139
+ score: 99.9,
140
+ tags: [1, 2, 3]
141
+ )
142
+ @model.save
143
+ @first_load = JsonTestModel.find(@test_id)
144
+ @first_load.save # Re-save without modification
145
+ @second_load = JsonTestModel.find(@test_id)
146
+ [@second_load.age.class, @second_load.active.class, @second_load.tags[0].class]
147
+ #=> [Integer, TrueClass, Integer]
148
+
149
+ ## batch_update preserves types
150
+ @model = JsonTestModel.new(model_id: @test_id)
151
+ @model.batch_update(name: "Batch", age: 50, active: false, tags: ["one", 2])
152
+ @loaded = JsonTestModel.find(@test_id)
153
+ [@loaded.name, @loaded.age, @loaded.active, @loaded.tags[1]]
154
+ #=> ["Batch", 50, false, 2]
155
+
156
+ ## batch_update types verification
157
+ [@loaded.age.class, @loaded.active.class, @loaded.tags[1].class]
158
+ #=> [Integer, FalseClass, Integer]
159
+
160
+ ## refresh! maintains type preservation
161
+ @model = JsonTestModel.new(model_id: @test_id, age: 25, active: true)
162
+ @model.save
163
+ # Modify directly in Redis to simulate external change
164
+ @model.dbclient.hset(@model.dbkey, "age", Familia::JsonSerializer.dump(30))
165
+ @model.refresh!
166
+ [@model.age, @model.age.class]
167
+ #=> [30, Integer]
168
+
169
+ ## to_h returns string keys (external API compatibility)
170
+ @model = JsonTestModel.new(model_id: @test_id, name: "API Test", age: 40)
171
+ @hash = @model.to_h
172
+ @hash.keys.first.class
173
+ #=> String
174
+
175
+ ## to_h has string key for name field
176
+ @hash.key?("name")
177
+ #=> true
178
+
179
+ ## Nil values are handled correctly
180
+ @model = JsonTestModel.new(model_id: @test_id, name: "Nil Test", age: nil, active: nil)
181
+ @model.save
182
+ @loaded = JsonTestModel.find(@test_id)
183
+ [@loaded.age, @loaded.active]
184
+ #=> [nil, nil]
185
+
186
+ ## Empty string preserved correctly
187
+ @model = JsonTestModel.new(model_id: @test_id, name: "")
188
+ @model.save
189
+ @loaded = JsonTestModel.find(@test_id)
190
+ @loaded.name
191
+ #=> ""
192
+
193
+ ## Empty string has String class
194
+ @loaded.name.class
195
+ #=> String
196
+
197
+ ## String fields with numeric values preserve String type (badge "007")
198
+ @model = JsonTestModel.new(model_id: @test_id, badge_number: "007")
199
+ @model.save
200
+ @loaded = JsonTestModel.find(@test_id)
201
+ @loaded.badge_number
202
+ #=> "007"
203
+
204
+ ## Badge number preserves String class (not Integer)
205
+ @loaded.badge_number.class
206
+ #=> String
207
+
208
+ ## String "true" stays String (not Boolean)
209
+ @model = JsonTestModel.new(model_id: @test_id, status: "true")
210
+ @model.save
211
+ @loaded = JsonTestModel.find(@test_id)
212
+ [@loaded.status, @loaded.status.class]
213
+ #=> ["true", String]
214
+
215
+ ## String "false" stays String (not Boolean)
216
+ @model = JsonTestModel.new(model_id: @test_id, status: "false")
217
+ @model.save
218
+ @loaded = JsonTestModel.find(@test_id)
219
+ [@loaded.status, @loaded.status.class]
220
+ #=> ["false", String]
221
+
222
+ ## String "null" stays String (not nil)
223
+ @model = JsonTestModel.new(model_id: @test_id, placeholder: "null")
224
+ @model.save
225
+ @loaded = JsonTestModel.find(@test_id)
226
+ [@loaded.placeholder, @loaded.placeholder.class]
227
+ #=> ["null", String]
228
+
229
+ ## Numeric string "123" preserves String type
230
+ @model = JsonTestModel.new(model_id: @test_id, badge_number: "123")
231
+ @model.save
232
+ @loaded = JsonTestModel.find(@test_id)
233
+ [@loaded.badge_number, @loaded.badge_number.class]
234
+ #=> ["123", String]
235
+
236
+ ## Zero values preserved with correct types
237
+ @model = JsonTestModel.new(model_id: @test_id, age: 0, score: 0.0)
238
+ @model.save
239
+ @loaded = JsonTestModel.find(@test_id)
240
+ [@loaded.age, @loaded.score]
241
+ #=> [0, 0.0]
242
+
243
+ ## Zero Integer and Float have correct classes
244
+ [@loaded.age.class, @loaded.score.class]
245
+ #=> [Integer, Float]
246
+
247
+ # Teardown: Clean up test data
248
+ JsonTestModel.destroy!(@test_id) if JsonTestModel.exists?(@test_id)
@@ -1,7 +1,7 @@
1
1
  # try/horreum/relations_try.rb
2
2
  # Test Horreum Database type relations functionality
3
3
 
4
- require_relative '../helpers/test_helpers'
4
+ require_relative '../../support/helpers/test_helpers'
5
5
 
6
6
  Familia.debug = false
7
7
 
@@ -1,36 +1,42 @@
1
1
  # try/horreum/serialization_persistent_fields_try.rb
2
2
 
3
- require_relative '../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  Familia.debug = false
6
6
 
7
- # Test class with mixed field categories for serialization
7
+ # Test class with mixed field types for serialization
8
8
  class SerializationCategoryTest < Familia::Horreum
9
+ feature :transient_fields
10
+
9
11
  identifier_field :id
10
12
  field :id
11
13
  field :name # persistent by default
12
- field :email, category: :encrypted # persistent, encrypted category
13
- field :tryouts_cache_data, category: :transient # should be excluded from serialization
14
- field :description, category: :persistent # explicitly persistent
15
- field :temp_settings, category: :transient # should be excluded
16
- field :metadata, category: :persistent # explicitly persistent
14
+ field :email # persistent field
15
+ transient_field :tryouts_cache_data # should be excluded from serialization
16
+ field :description # explicitly persistent
17
+ transient_field :temp_settings # should be excluded
18
+ field :metadata # explicitly persistent
17
19
  end
18
20
 
19
21
  # Class with all transient fields
20
22
  class AllTransientSerializationTest < Familia::Horreum
23
+ feature :transient_fields
24
+
21
25
  identifier_field :id
22
26
  field :id
23
- field :temp1, category: :transient
24
- field :temp2, category: :transient
27
+ transient_field :temp1
28
+ transient_field :temp2
25
29
  end
26
30
 
27
- # Mixed categories with aliased fields
31
+ # Mixed field types with aliased fields
28
32
  class AliasedSerializationTest < Familia::Horreum
33
+ feature :transient_fields
34
+
29
35
  identifier_field :id
30
36
  field :id
31
- field :internal_name, as: :display_name, category: :persistent
32
- field :temp_cache, as: :cache, category: :transient
33
- field :user_data, as: :data, category: :encrypted
37
+ field :internal_name, as: :display_name
38
+ transient_field :temp_cache, as: :cache
39
+ field :user_data, as: :data
34
40
  end
35
41
 
36
42
  # Setup test instance with all field types
@@ -66,7 +72,7 @@ end
66
72
  @hash_result.key?("name")
67
73
  #=> true
68
74
 
69
- ## to_h includes encrypted persistent fields
75
+ ## to_h includes regular persistent fields
70
76
  @hash_result.key?("email")
71
77
  #=> true
72
78
 
@@ -82,9 +88,9 @@ end
82
88
  @hash_result.key?(:temp_settings)
83
89
  #=> false
84
90
 
85
- ## to_h serializes complex values correctly
86
- @hash_result["metadata"]
87
- #=:> String
91
+ ## to_h returns actual Ruby values (Hash, not serialized JSON string)
92
+ @hash_result["metadata"].class
93
+ #=> Hash
88
94
 
89
95
  ## to_a excludes transient fields
90
96
  @array_result = @serialization_test.to_a
@@ -133,7 +139,7 @@ SerializationCategoryTest.persistent_fields.include?(:email)
133
139
  @all_transient.to_a
134
140
  #=> ["transient_test_1"]
135
141
 
136
- ## Aliased fields serialization uses original field names
142
+ ## Aliased fields serialization uses original field names (transient excluded)
137
143
  @aliased_hash = @aliased_test.to_h
138
144
  @aliased_hash.keys.sort
139
145
  #=> ["id", "internal_name", "user_data"]
@@ -1,6 +1,6 @@
1
1
  # try/horreum/serialization_try.rb
2
2
 
3
- require_relative '../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  Familia.debug = false
6
6
 
@@ -103,9 +103,9 @@ Familia.dbclient.set('debug:ending_save_if_not_exists_tests', Familia.now.to_s)
103
103
  [@customer.name, @customer.email]
104
104
  #=> ["Bob Jones", "jane@example.com"]
105
105
 
106
- ## serialize_value handles strings
106
+ ## serialize_value handles strings with JSON encoding
107
107
  @customer.serialize_value('test string')
108
- #=> "test string"
108
+ #=> "\"test string\""
109
109
 
110
110
  ## serialize_value handles numbers
111
111
  @customer.serialize_value(42)
@@ -121,7 +121,7 @@ Familia.dbclient.set('debug:ending_save_if_not_exists_tests', Familia.now.to_s)
121
121
 
122
122
  ## deserialize_value handles JSON strings back to objects
123
123
  @customer.deserialize_value('{"key":"value","num":123}')
124
- #=> {:key=>"value", :num=>123}
124
+ #=> {"key"=>"value", "num"=>123}
125
125
 
126
126
  ## deserialize_value handles JSON arrays
127
127
  @customer.deserialize_value('[1,2,"three"]')
@@ -1,6 +1,8 @@
1
+ # try/horreum/settings_try.rb
2
+
1
3
  # Test Horreum settings
2
4
 
3
- require_relative '../helpers/test_helpers'
5
+ require_relative '../../support/helpers/test_helpers'
4
6
 
5
7
  ## database selection inheritance
6
8
  user_class = Class.new(Familia::Horreum) do
@@ -1,6 +1,6 @@
1
1
  # try/refinements/dear_json_array_methods_try.rb
2
2
 
3
- require_relative '../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  class TestArrayWithDearJson < Array
6
6
  include Familia::Refinements::DearJsonArrayMethods
@@ -1,6 +1,6 @@
1
1
  # try/refinements/dear_json_hash_methods_try.rb
2
2
 
3
- require_relative '../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  class TestHashWithDearJson < Hash
6
6
  include Familia::Refinements::DearJsonHashMethods
@@ -1,6 +1,6 @@
1
1
  # try/refinements/time_literals_numeric_methods_try.rb
2
2
 
3
- require_relative '../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  class TestNumericWithTimeLiterals
6
6
  include Familia::Refinements::TimeLiterals::NumericMethods
@@ -1,6 +1,6 @@
1
1
  # try/refinements/time_literals_string_methods_try.rb
2
2
 
3
- require_relative '../helpers/test_helpers'
3
+ require_relative '../../support/helpers/test_helpers'
4
4
 
5
5
  class TestStringWithTimeLiterals < String
6
6
  include Familia::Refinements::TimeLiterals::StringMethods
data/try/valkey.conf ADDED
@@ -0,0 +1,26 @@
1
+ # try/test-valkey.conf
2
+
3
+ # Familia - Tryouts Valkey Config
4
+ # 2025-10-01
5
+ #
6
+ # Usage:
7
+ #
8
+ # $ valkey-server try/valkey.conf
9
+ #
10
+
11
+ dir ./data
12
+
13
+ enable-debug-command yes
14
+
15
+ #requirepass CHANGEME
16
+
17
+ bind 127.0.0.1
18
+ port 2525
19
+ databases 10
20
+
21
+ timeout 4
22
+ daemonize no
23
+ loglevel notice
24
+
25
+ # Disable RDB persistence for tests DB
26
+ save ""