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
@@ -1,127 +1,128 @@
1
1
  # lib/familia/data_type/serialization.rb
2
2
 
3
- class Familia::DataType
4
- module Serialization
5
- # Serializes a value for storage in Redis.
6
- #
7
- # @param val [Object] The value to be serialized.
8
- # @param strict_values [Boolean] Whether to enforce strict value
9
- # serialization (default: true).
10
- # @return [String, nil] The serialized representation of the value, or nil
11
- # if serialization fails.
12
- #
13
- # @note When a class option is specified, it uses that class's
14
- # serialization method. Otherwise, it relies on Familia.distinguisher for
15
- # serialization.
16
- #
17
- # @example With a class option
18
- # serialize_value(User.new(name: "Cloe"), strict_values: false) #=> '{"name":"Cloe"}'
19
- #
20
- # @example Without a class option
21
- # serialize_value(123) #=> "123"
22
- # serialize_value("hello") #=> "hello"
23
- #
24
- # @raise [Familia::HighRiskFactor] If serialization fails under strict
25
- # mode.
26
- #
27
- def serialize_value(val, strict_values: true)
28
- prepared = nil
3
+ module Familia
4
+ class DataType
5
+ module Serialization
6
+ # Serializes a value for storage in the database.
7
+ #
8
+ # @param val [Object] The value to be serialized.
9
+ # @param strict_values [Boolean] Whether to enforce strict value
10
+ # serialization (default: true).
11
+ # @return [String, nil] The serialized representation of the value, or nil
12
+ # if serialization fails.
13
+ #
14
+ # @note When a class option is specified, it uses that class's
15
+ # serialization method. Otherwise, it relies on Familia.distinguisher for
16
+ # serialization.
17
+ #
18
+ # @example With a class option
19
+ # serialize_value(User.new(name: "Cloe"), strict_values: false) #=> '{"name":"Cloe"}'
20
+ #
21
+ # @example Without a class option
22
+ # serialize_value(123) #=> "123"
23
+ # serialize_value("hello") #=> "hello"
24
+ #
25
+ # @raise [Familia::NotDistinguishableError] If serialization fails under strict
26
+ # mode.
27
+ #
28
+ def serialize_value(val, strict_values: true)
29
+ prepared = nil
29
30
 
30
- Familia.trace :TOREDIS, dbclient, "#{val}<#{val.class}|#{opts[:class]}>", caller(1..1) if Familia.debug?
31
+ Familia.trace :TOREDIS, nil, "#{val}<#{val.class}|#{opts[:class]}>" if Familia.debug?
31
32
 
32
- if opts[:class]
33
- prepared = Familia.distinguisher(opts[:class], strict_values: strict_values)
34
- Familia.ld " from opts[class] <#{opts[:class]}>: #{prepared || '<nil>'}"
35
- end
33
+ if opts[:class]
34
+ prepared = Familia.distinguisher(opts[:class], strict_values: strict_values)
35
+ Familia.ld " from opts[class] <#{opts[:class]}>: #{prepared || '<nil>'}"
36
+ end
36
37
 
37
- if prepared.nil?
38
- # Enforce strict values when no class option is specified
39
- prepared = Familia.distinguisher(val, strict_values: true)
40
- Familia.ld " from <#{val.class}> => <#{prepared.class}>"
41
- end
38
+ if prepared.nil?
39
+ # Enforce strict values when no class option is specified
40
+ prepared = Familia.distinguisher(val, strict_values: true)
41
+ Familia.ld " from <#{val.class}> => <#{prepared.class}>"
42
+ end
43
+
44
+ if Familia.debug?
45
+ Familia.trace :TOREDIS, nil, "#{val}<#{val.class}|#{opts[:class]}> => #{prepared}<#{prepared.class}>"
46
+ end
42
47
 
43
- if Familia.debug?
44
- Familia.trace :TOREDIS, dbclient, "#{val}<#{val.class}|#{opts[:class]}> => #{prepared}<#{prepared.class}>",
45
- caller(1..1)
48
+ Familia.warn "[#{self.class}#serialize_value] nil returned for #{opts[:class]}##{name}" if prepared.nil?
49
+ prepared
46
50
  end
47
51
 
48
- Familia.warn "[#{self.class}#serialize_value] nil returned for #{opts[:class]}##{name}" if prepared.nil?
49
- prepared
50
- end
52
+ # Deserializes multiple values from Valkey/Redis, removing nil values.
53
+ #
54
+ # @param values [Array<String>] The values to deserialize.
55
+ # @return [Array<Object>] Deserialized objects, with nil values removed.
56
+ #
57
+ # @see #deserialize_values_with_nil
58
+ #
59
+ def deserialize_values(*values)
60
+ # Avoid using compact! here. Using compact! as the last expression in the
61
+ # method can unintentionally return nil if no changes are made, which is
62
+ # not desirable. Instead, use compact to ensure the method returns the
63
+ # expected value.
64
+ deserialize_values_with_nil(*values).compact
65
+ end
51
66
 
52
- # Deserializes multiple values from Redis, removing nil values.
53
- #
54
- # @param values [Array<String>] The values to deserialize.
55
- # @return [Array<Object>] Deserialized objects, with nil values removed.
56
- #
57
- # @see #deserialize_values_with_nil
58
- #
59
- def deserialize_values(*values)
60
- # Avoid using compact! here. Using compact! as the last expression in the
61
- # method can unintentionally return nil if no changes are made, which is
62
- # not desirable. Instead, use compact to ensure the method returns the
63
- # expected value.
64
- deserialize_values_with_nil(*values).compact
65
- end
67
+ # Deserializes multiple values from Valkey/Redis, preserving nil values.
68
+ #
69
+ # @param values [Array<String>] The values to deserialize.
70
+ # @return [Array<Object, nil>] Deserialized objects, including nil values.
71
+ #
72
+ # @raise [Familia::Problem] If the specified class doesn't respond to the
73
+ # load method.
74
+ #
75
+ # @note This method attempts to deserialize each value using the specified
76
+ # class's load method. If deserialization fails for a value, it's
77
+ # replaced with nil.
78
+ #
79
+ def deserialize_values_with_nil(*values)
80
+ Familia.ld "deserialize_values: (#{@opts}) #{values}"
81
+ return [] if values.empty?
82
+ return values.flatten unless @opts[:class]
66
83
 
67
- # Deserializes multiple values from Redis, preserving nil values.
68
- #
69
- # @param values [Array<String>] The values to deserialize.
70
- # @return [Array<Object, nil>] Deserialized objects, including nil values.
71
- #
72
- # @raise [Familia::Problem] If the specified class doesn't respond to the
73
- # load method.
74
- #
75
- # @note This method attempts to deserialize each value using the specified
76
- # class's load method. If deserialization fails for a value, it's
77
- # replaced with nil.
78
- #
79
- def deserialize_values_with_nil(*values)
80
- Familia.ld "deserialize_values: (#{@opts}) #{values}"
81
- return [] if values.empty?
82
- return values.flatten unless @opts[:class]
84
+ unless @opts[:class].respond_to?(load_method)
85
+ raise Familia::Problem, "No such method: #{@opts[:class]}##{load_method}"
86
+ end
83
87
 
84
- unless @opts[:class].respond_to?(load_method)
85
- raise Familia::Problem, "No such method: #{@opts[:class]}##{load_method}"
86
- end
88
+ values.collect! do |obj|
89
+ next if obj.nil?
87
90
 
88
- values.collect! do |obj|
89
- next if obj.nil?
91
+ val = @opts[:class].send load_method, obj
92
+ Familia.ld "[#{self.class}#deserialize_values] nil returned for #{@opts[:class]}##{name}" if val.nil?
90
93
 
91
- val = @opts[:class].send load_method, obj
92
- Familia.ld "[#{self.class}#deserialize_values] nil returned for #{@opts[:class]}##{name}" if val.nil?
94
+ val
95
+ rescue StandardError => e
96
+ Familia.info val
97
+ Familia.info "Parse error for #{dbkey} (#{load_method}): #{e.message}"
98
+ Familia.info e.backtrace
99
+ nil
100
+ end
93
101
 
94
- val
95
- rescue StandardError => e
96
- Familia.info val
97
- Familia.info "Parse error for #{dbkey} (#{load_method}): #{e.message}"
98
- Familia.info e.backtrace
99
- nil
102
+ values
100
103
  end
101
104
 
102
- values
103
- end
105
+ # Deserializes a single value from the database.
106
+ #
107
+ # @param val [String, nil] The value to deserialize.
108
+ # @return [Object, nil] The deserialized object, the default value if
109
+ # val is nil, or nil if deserialization fails.
110
+ #
111
+ # @note If no class option is specified, the original value is
112
+ # returned unchanged.
113
+ #
114
+ # NOTE: Currently only the DataType class uses this method. Horreum
115
+ # fields are a newer addition and don't support the full range of
116
+ # deserialization options that DataType supports. It uses serialize_value
117
+ # for serialization since everything becomes a string in Valkey.
118
+ #
119
+ def deserialize_value(val)
120
+ return @opts[:default] if val.nil?
121
+ return val unless @opts[:class]
104
122
 
105
- # Deserializes a single value from the database.
106
- #
107
- # @param val [String, nil] The value to deserialize.
108
- # @return [Object, nil] The deserialized object, the default value if
109
- # val is nil, or nil if deserialization fails.
110
- #
111
- # @note If no class option is specified, the original value is
112
- # returned unchanged.
113
- #
114
- # NOTE: Currently only the DataType class uses this method. Horreum
115
- # fields are a newer addition and don't support the full range of
116
- # deserialization options that DataType supports. It uses serialize_value
117
- # for serialization since everything becomes a string in Valkey.
118
- #
119
- def deserialize_value(val)
120
- return @opts[:default] if val.nil?
121
- return val unless @opts[:class]
122
-
123
- ret = deserialize_values val
124
- ret&.first # return the object or nil
123
+ ret = deserialize_values val
124
+ ret&.first # return the object or nil
125
+ end
125
126
  end
126
127
  end
127
128
  end
@@ -0,0 +1,96 @@
1
+ # lib/familia/data_type/settings.rb
2
+
3
+ module Familia
4
+ class DataType
5
+ # Settings - Instance-level configuration and introspection methods
6
+ #
7
+ # This module provides instance methods for accessing and managing
8
+ # DataType object configuration, parent relationships, and serialization.
9
+ #
10
+ # Key features:
11
+ # * Parent object relationship management
12
+ # * URI and database configuration
13
+ # * Serialization method delegation
14
+ # * Type introspection
15
+ #
16
+ module Settings
17
+ attr_reader :keystring, :opts, :logical_database
18
+ attr_reader :uri
19
+
20
+ alias url uri
21
+
22
+ def class?
23
+ !@opts[:class].to_s.empty? && @opts[:class].is_a?(Familia)
24
+ end
25
+
26
+ def parent_instance?
27
+ parent&.is_a?(Horreum::ParentDefinition)
28
+ end
29
+
30
+ def parent_class?
31
+ parent.is_a?(Class) && parent.ancestors.include?(Familia::Horreum)
32
+ end
33
+
34
+ def parent?
35
+ parent_class? || parent_instance?
36
+ end
37
+
38
+ def parent
39
+ # Return cached ParentDefinition if available
40
+ return @parent if @parent
41
+
42
+ # Return class-level parent if no instance parent
43
+ return self.class.parent unless @parent_ref
44
+
45
+ # Create ParentDefinition dynamically from stored reference.
46
+ # This ensures we get the current identifier value (available after initialization)
47
+ # rather than a stale nil value from initialization time. Cannot cache due to frozen object.
48
+ Horreum::ParentDefinition.from_parent(@parent_ref)
49
+ end
50
+
51
+ def parent=(value)
52
+ case value
53
+ when Horreum::ParentDefinition
54
+ @parent = value
55
+ when nil
56
+ @parent = nil
57
+ @parent_ref = nil
58
+ else
59
+ # Store parent instance reference for lazy ParentDefinition creation.
60
+ # During initialization, the parent's identifier may not be available yet,
61
+ # so we defer ParentDefinition creation until first access for memory efficiency.
62
+ # Note: @parent_ref is not cleared after use because DataType objects are frozen.
63
+ @parent_ref = value
64
+ @parent = nil # Will be created dynamically in parent method
65
+ end
66
+ end
67
+
68
+ def uri
69
+ # Return explicit instance URI if set
70
+ return @uri if @uri
71
+
72
+ # If we have a parent with logical_database, build URI with that database
73
+ if parent && parent.respond_to?(:logical_database) && parent.logical_database
74
+ new_uri = (self.class.uri || Familia.uri).dup
75
+ new_uri.db = parent.logical_database
76
+ new_uri
77
+ else
78
+ # Fall back to class-level URI or global Familia.uri
79
+ self.class.uri || Familia.uri
80
+ end
81
+ end
82
+
83
+ def uri=(value)
84
+ @uri = value
85
+ end
86
+
87
+ def dump_method
88
+ self.class.dump_method
89
+ end
90
+
91
+ def load_method
92
+ self.class.load_method
93
+ end
94
+ end
95
+ end
96
+ end
@@ -1,7 +1,7 @@
1
1
  # lib/familia/data_type/types/counter.rb
2
2
 
3
3
  module Familia
4
- class Counter < String
4
+ class Counter < StringKey
5
5
  def initialize(*args)
6
6
  super
7
7
  @opts[:default] ||= 0
@@ -8,6 +8,8 @@ module Familia
8
8
  dbclient.hlen dbkey
9
9
  end
10
10
  alias size field_count
11
+ alias length field_count
12
+ alias count field_count
11
13
 
12
14
  def empty?
13
15
  field_count.zero?
@@ -22,13 +24,14 @@ module Familia
22
24
  rescue TypeError => e
23
25
  Familia.le "[hset]= #{e.message}"
24
26
  Familia.ld "[hset]= #{dbkey} #{field}=#{val}" if Familia.debug
25
- echo :hset, caller(1..1).first if Familia.debug # logs via echo to the db and back
27
+ echo :hset, Familia.pretty_stack(limit: 1) if Familia.debug # logs via echo to the db and back
26
28
  klass = val.class
27
29
  msg = "Cannot store #{field} => #{val.inspect} (#{klass}) in #{dbkey}"
28
30
  raise e.class, msg
29
31
  end
30
32
  alias put []=
31
33
  alias store []=
34
+ alias add []=
32
35
 
33
36
  def [](field)
34
37
  deserialize_value dbclient.hget(dbkey, field.to_s)
@@ -55,8 +58,8 @@ module Familia
55
58
  end
56
59
 
57
60
  def hgetall
58
- dbclient.hgetall(dbkey).each_with_object({}) do |(k, v), ret|
59
- ret[k] = deserialize_value v
61
+ dbclient.hgetall(dbkey).transform_values do |v|
62
+ deserialize_value v
60
63
  end
61
64
  end
62
65
  alias all hgetall
@@ -73,7 +76,7 @@ module Familia
73
76
  rescue TypeError => e
74
77
  Familia.le "[hsetnx] #{e.message}"
75
78
  Familia.ld "[hsetnx] #{dbkey} #{field}=#{val}" if Familia.debug
76
- echo :hsetnx, caller(1..1).first if Familia.debug # logs via echo to the db and back
79
+ echo :hsetnx, Familia.pretty_stack(limit: 1) if Familia.debug # logs via echo to the db and back
77
80
  klass = val.class
78
81
  msg = "Cannot store #{field} => #{val.inspect} (#{klass}) in #{dbkey}"
79
82
  raise e.class, msg
@@ -92,7 +95,8 @@ module Familia
92
95
  def remove_field(field)
93
96
  dbclient.hdel dbkey, field.to_s
94
97
  end
95
- alias remove remove_field # deprecated
98
+ alias remove remove_field
99
+ alias remove_element remove_field
96
100
 
97
101
  def increment(field, by = 1)
98
102
  dbclient.hincrby(dbkey, field.to_s, by).to_i
@@ -125,15 +129,15 @@ module Familia
125
129
 
126
130
  # The Great Database Refresh-o-matic 3000 for HashKey!
127
131
  #
128
- # This method performs a complete refresh of the hash's state from Redis.
132
+ # This method performs a complete refresh of the hash's state from the database.
129
133
  # It's like giving your hash a memory transfusion - out with the old state,
130
- # in with the fresh data straight from Redis!
134
+ # in with the fresh data straight from Valkey/Redis!
131
135
  #
132
136
  # @note This operation is atomic - it either succeeds completely or fails
133
137
  # safely. Any unsaved changes to the hash will be overwritten.
134
138
  #
135
139
  # @return [void] Returns nothing, but your hash will be sparkling clean
136
- # with all its fields synchronized with Redis.
140
+ # with all its fields synchronized with the database.
137
141
  #
138
142
  # @raise [Familia::KeyNotFoundError] If the dbkey for this hash no
139
143
  # longer exists. Time travelers beware!
@@ -148,7 +152,7 @@ module Familia
148
152
  # puts "Oops! Our hash seems to have vanished into the Database void!"
149
153
  # end
150
154
  def refresh!
151
- Familia.trace :REFRESH, dbclient, uri, caller(1..1) if Familia.debug?
155
+ Familia.trace :REFRESH, nil, uri if Familia.debug?
152
156
  raise Familia::KeyNotFoundError, dbkey unless dbclient.exists(dbkey)
153
157
 
154
158
  fields = hgetall
@@ -167,7 +171,7 @@ module Familia
167
171
  # @return [self] Returns the refreshed hash, ready for more adventures!
168
172
  #
169
173
  # @raise [Familia::KeyNotFoundError] If the dbkey does not exist.
170
- # The hash must exist in Redis-land for this to work!
174
+ # The hash must exist in Valkey/Redis-land for this to work!
171
175
  #
172
176
  # @example Refresh and chain
173
177
  # my_hash.refresh.keys # Refresh and get all keys
@@ -179,7 +183,7 @@ module Familia
179
183
  self
180
184
  end
181
185
 
182
- Familia::DataType.register self, :hash # legacy, deprecated
186
+ Familia::DataType.register self, :hash
183
187
  Familia::DataType.register self, :hashkey
184
188
  end
185
189
  end
@@ -1,20 +1,22 @@
1
- # lib/familia/data_type/types/list.rb
1
+ # lib/familia/data_type/types/listkey.rb
2
2
 
3
3
  module Familia
4
- class List < DataType
4
+ class ListKey < DataType
5
5
  # Returns the number of elements in the list
6
6
  # @return [Integer] number of elements
7
7
  def element_count
8
8
  dbclient.llen dbkey
9
9
  end
10
10
  alias size element_count
11
+ alias length element_count
12
+ alias count element_count
11
13
 
12
14
  def empty?
13
15
  element_count.zero?
14
16
  end
15
17
 
16
18
  def push *values
17
- echo :push, caller(1..1).first if Familia.debug
19
+ echo :push, Familia.pretty_stack(limit: 1) if Familia.debug
18
20
  values.flatten.compact.each { |v| dbclient.rpush dbkey, serialize_value(v) }
19
21
  dbclient.ltrim dbkey, -@opts[:maxlength], -1 if @opts[:maxlength]
20
22
  update_expiration
@@ -23,8 +25,9 @@ module Familia
23
25
  alias append push
24
26
 
25
27
  def <<(val)
26
- push val
28
+ push(val)
27
29
  end
30
+ alias add_element <<
28
31
  alias add <<
29
32
 
30
33
  def unshift *values
@@ -59,6 +62,10 @@ module Familia
59
62
  end
60
63
  alias slice []
61
64
 
65
+ def member?(value)
66
+ !dbclient.lpos(dbkey, serialize_value(value)).nil?
67
+ end
68
+
62
69
  # Removes elements equal to value from the list
63
70
  # @param value The value to remove
64
71
  # @param count [Integer] Number of elements to remove (0 means all)
@@ -78,7 +85,7 @@ module Familia
78
85
  end
79
86
 
80
87
  def members(count = -1)
81
- echo :members, caller(1..1).first if Familia.debug
88
+ echo :members, Familia.pretty_stack(limit: 1) if Familia.debug
82
89
  count -= 1 if count.positive?
83
90
  range 0, count
84
91
  end
@@ -157,5 +164,6 @@ module Familia
157
164
  # end
158
165
 
159
166
  Familia::DataType.register self, :list
167
+ Familia::DataType.register self, :listkey
160
168
  end
161
169
  end
@@ -1,7 +1,7 @@
1
1
  # lib/familia/data_type/types/lock.rb
2
2
 
3
3
  module Familia
4
- class Lock < String
4
+ class Lock < StringKey
5
5
  def initialize(*args)
6
6
  super
7
7
  @opts[:default] = nil
@@ -14,9 +14,10 @@ module Familia
14
14
  def acquire(token = SecureRandom.uuid, ttl: 10)
15
15
  success = setnx(token)
16
16
  # Handle both integer (1/0) and boolean (true/false) return values
17
- return false unless success == 1 || success == true
17
+ return false unless [1, true].include?(success)
18
18
  return del && false if ttl&.<=(0)
19
19
  return del && false if ttl&.positive? && !expire(ttl)
20
+
20
21
  token
21
22
  end
22
23