familia 2.0.0.pre14 → 2.0.0.pre16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/code-quality.yml +138 -0
  3. data/.github/workflows/code-smellage.yml +145 -0
  4. data/.github/workflows/docs.yml +31 -8
  5. data/.gitignore +1 -1
  6. data/.pre-commit-config.yaml +7 -1
  7. data/.reek.yml +98 -0
  8. data/.rubocop.yml +48 -10
  9. data/.talismanrc +9 -0
  10. data/.yardopts +18 -13
  11. data/CHANGELOG.rst +66 -6
  12. data/CLAUDE.md +1 -1
  13. data/Gemfile +6 -5
  14. data/Gemfile.lock +99 -23
  15. data/LICENSE.txt +1 -1
  16. data/README.md +285 -85
  17. data/changelog.d/README.md +2 -2
  18. data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
  19. data/docs/archive/FAMILIA_TECHNICAL.md +41 -41
  20. data/docs/archive/FAMILIA_UPDATE.md +3 -3
  21. data/docs/archive/README.md +3 -2
  22. data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
  23. data/docs/conf.py +29 -0
  24. data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
  25. data/docs/guides/feature-encrypted-fields.md +785 -0
  26. data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
  27. data/docs/guides/feature-external-identifiers.md +637 -0
  28. data/docs/guides/feature-object-identifiers.md +435 -0
  29. data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
  30. data/docs/guides/feature-relationships-methods.md +684 -0
  31. data/docs/guides/feature-relationships.md +200 -0
  32. data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
  33. data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
  34. data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
  35. data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
  36. data/docs/guides/index.md +176 -0
  37. data/docs/guides/{Security-Model.md → security-model.md} +1 -1
  38. data/docs/migrating/v2.0.0-pre.md +1 -1
  39. data/docs/migrating/v2.0.0-pre11.md +4 -4
  40. data/docs/migrating/v2.0.0-pre12.md +2 -2
  41. data/docs/migrating/v2.0.0-pre13.md +1 -1
  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 +623 -19
  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 +6 -6
  54. data/examples/single_connection_transaction_confusions.rb +379 -0
  55. data/lib/familia/base.rb +49 -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/commands.rb +53 -51
  65. data/lib/familia/data_type/serialization.rb +108 -107
  66. data/lib/familia/data_type/types/counter.rb +1 -1
  67. data/lib/familia/data_type/types/hashkey.rb +13 -10
  68. data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
  69. data/lib/familia/data_type/types/lock.rb +3 -2
  70. data/lib/familia/data_type/types/sorted_set.rb +26 -15
  71. data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -5
  72. data/lib/familia/data_type/types/unsorted_set.rb +20 -27
  73. data/lib/familia/data_type.rb +75 -47
  74. data/lib/familia/distinguisher.rb +85 -0
  75. data/lib/familia/encryption/encrypted_data.rb +15 -24
  76. data/lib/familia/encryption/manager.rb +6 -4
  77. data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
  78. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
  79. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
  80. data/lib/familia/encryption/request_cache.rb +7 -7
  81. data/lib/familia/encryption.rb +2 -3
  82. data/lib/familia/errors.rb +9 -3
  83. data/lib/familia/{autoloader.rb → features/autoloader.rb} +49 -23
  84. data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
  85. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
  86. data/lib/familia/features/encrypted_fields.rb +68 -66
  87. data/lib/familia/features/expiration/extensions.rb +61 -0
  88. data/lib/familia/features/expiration.rb +35 -87
  89. data/lib/familia/features/external_identifier.rb +11 -12
  90. data/lib/familia/features/object_identifier.rb +58 -20
  91. data/lib/familia/features/quantization.rb +17 -22
  92. data/lib/familia/features/relationships/README.md +97 -0
  93. data/lib/familia/features/relationships/collection_operations.rb +104 -0
  94. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
  95. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +301 -0
  96. data/lib/familia/features/relationships/indexing.rb +176 -256
  97. data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
  98. data/lib/familia/features/relationships/participation/participant_methods.rb +160 -0
  99. data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
  100. data/lib/familia/features/relationships/participation.rb +656 -0
  101. data/lib/familia/features/relationships/participation_relationship.rb +31 -0
  102. data/lib/familia/features/relationships/score_encoding.rb +20 -20
  103. data/lib/familia/features/relationships.rb +69 -271
  104. data/lib/familia/features/safe_dump.rb +127 -132
  105. data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
  106. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
  107. data/lib/familia/features/transient_fields.rb +5 -5
  108. data/lib/familia/features.rb +21 -21
  109. data/lib/familia/field_type.rb +24 -4
  110. data/lib/familia/horreum/core/connection.rb +229 -26
  111. data/lib/familia/horreum/core/database_commands.rb +27 -17
  112. data/lib/familia/horreum/core/serialization.rb +40 -20
  113. data/lib/familia/horreum/core/utils.rb +2 -1
  114. data/lib/familia/horreum/shared/settings.rb +2 -1
  115. data/lib/familia/horreum/subclass/definition.rb +33 -45
  116. data/lib/familia/horreum/subclass/management.rb +72 -24
  117. data/lib/familia/horreum/subclass/related_fields_management.rb +82 -21
  118. data/lib/familia/horreum.rb +196 -114
  119. data/lib/familia/json_serializer.rb +0 -1
  120. data/lib/familia/logging.rb +11 -114
  121. data/lib/familia/refinements/dear_json.rb +122 -0
  122. data/lib/familia/refinements/logger_trace.rb +20 -17
  123. data/lib/familia/refinements/stylize_words.rb +65 -0
  124. data/lib/familia/refinements/time_literals.rb +60 -52
  125. data/lib/familia/refinements.rb +2 -1
  126. data/lib/familia/secure_identifier.rb +60 -28
  127. data/lib/familia/settings.rb +83 -7
  128. data/lib/familia/utils.rb +5 -87
  129. data/lib/familia/verifiable_identifier.rb +4 -4
  130. data/lib/familia/version.rb +1 -1
  131. data/lib/familia.rb +72 -15
  132. data/lib/middleware/database_middleware.rb +56 -14
  133. data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
  134. data/try/configuration/scenarios_try.rb +1 -1
  135. data/try/connection/fiber_context_preservation_try.rb +250 -0
  136. data/try/connection/handler_constraints_try.rb +59 -0
  137. data/try/connection/operation_mode_guards_try.rb +208 -0
  138. data/try/connection/pipeline_fallback_integration_try.rb +128 -0
  139. data/try/connection/responsibility_chain_tracking_try.rb +72 -0
  140. data/try/connection/transaction_fallback_integration_try.rb +288 -0
  141. data/try/connection/transaction_mode_permissive_try.rb +153 -0
  142. data/try/connection/transaction_mode_strict_try.rb +98 -0
  143. data/try/connection/transaction_mode_warn_try.rb +131 -0
  144. data/try/connection/transaction_modes_try.rb +249 -0
  145. data/try/core/autoloader_try.rb +129 -11
  146. data/try/core/connection_try.rb +7 -7
  147. data/try/core/conventional_inheritance_try.rb +130 -0
  148. data/try/core/create_method_try.rb +15 -23
  149. data/try/core/database_consistency_try.rb +10 -10
  150. data/try/core/errors_try.rb +8 -11
  151. data/try/core/familia_extended_try.rb +2 -2
  152. data/try/core/familia_members_methods_try.rb +76 -0
  153. data/try/core/isolated_dbclient_try.rb +165 -0
  154. data/try/core/middleware_try.rb +16 -16
  155. data/try/core/persistence_operations_try.rb +4 -4
  156. data/try/core/pools_try.rb +42 -26
  157. data/try/core/secure_identifier_try.rb +28 -24
  158. data/try/core/time_utils_try.rb +10 -10
  159. data/try/core/tools_try.rb +1 -1
  160. data/try/core/utils_try.rb +2 -2
  161. data/try/data_types/boolean_try.rb +4 -4
  162. data/try/data_types/datatype_base_try.rb +0 -2
  163. data/try/data_types/list_try.rb +10 -10
  164. data/try/data_types/sorted_set_try.rb +5 -5
  165. data/try/data_types/string_try.rb +12 -12
  166. data/try/data_types/unsortedset_try.rb +33 -0
  167. data/try/debugging/cache_behavior_tracer.rb +7 -7
  168. data/try/debugging/debug_aad_process.rb +1 -1
  169. data/try/debugging/debug_concealed_internal.rb +1 -1
  170. data/try/debugging/debug_cross_context.rb +1 -1
  171. data/try/debugging/debug_fresh_cross_context.rb +1 -1
  172. data/try/debugging/encryption_method_tracer.rb +10 -10
  173. data/try/edge_cases/hash_symbolization_try.rb +1 -1
  174. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  175. data/try/encryption/config_persistence_try.rb +2 -2
  176. data/try/encryption/encryption_core_try.rb +19 -19
  177. data/try/encryption/instance_variable_scope_try.rb +1 -1
  178. data/try/encryption/module_loading_try.rb +2 -2
  179. data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
  180. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
  181. data/try/encryption/secure_memory_handling_try.rb +1 -1
  182. data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
  183. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  184. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
  185. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
  186. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
  187. data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
  188. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  189. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  190. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
  191. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
  192. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  193. data/try/features/feature_dependencies_try.rb +3 -3
  194. data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
  195. data/try/features/object_identifier/object_identifier_try.rb +10 -0
  196. data/try/features/quantization/quantization_try.rb +1 -1
  197. data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
  198. data/try/features/relationships/indexing_try.rb +433 -0
  199. data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
  200. data/try/features/relationships/participation_commands_verification_try.rb +105 -0
  201. data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
  202. data/try/features/relationships/participation_reverse_index_try.rb +196 -0
  203. data/try/features/relationships/relationships_api_changes_try.rb +72 -71
  204. data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
  205. data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
  206. data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
  207. data/try/features/relationships/relationships_performance_try.rb +20 -20
  208. data/try/features/relationships/relationships_try.rb +27 -38
  209. data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
  210. data/try/features/transient_fields/refresh_reset_try.rb +1 -1
  211. data/try/features/transient_fields/simple_refresh_test.rb +1 -1
  212. data/try/helpers/test_cleanup.rb +86 -0
  213. data/try/helpers/test_helpers.rb +3 -3
  214. data/try/horreum/base_try.rb +3 -2
  215. data/try/horreum/commands_try.rb +1 -1
  216. data/try/horreum/destroy_related_fields_cleanup_try.rb +330 -0
  217. data/try/horreum/initialization_try.rb +11 -7
  218. data/try/horreum/relations_try.rb +21 -13
  219. data/try/horreum/serialization_try.rb +12 -11
  220. data/try/integration/cross_component_try.rb +3 -3
  221. data/try/memory/memory_basic_test.rb +1 -1
  222. data/try/memory/memory_docker_ruby_dump.sh +1 -1
  223. data/try/models/customer_safe_dump_try.rb +1 -1
  224. data/try/models/customer_try.rb +8 -10
  225. data/try/models/datatype_base_try.rb +3 -3
  226. data/try/models/familia_object_try.rb +9 -8
  227. data/try/performance/benchmarks_try.rb +2 -2
  228. data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
  229. data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
  230. data/try/prototypes/atomic_saves_v4.rb +1 -1
  231. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
  232. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  233. data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  234. data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
  235. data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
  236. data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
  237. data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
  238. data/try/prototypes/pooling/pool_siege.rb +11 -11
  239. data/try/prototypes/pooling/run_stress_tests.rb +7 -7
  240. data/try/refinements/dear_json_array_methods_try.rb +53 -0
  241. data/try/refinements/dear_json_hash_methods_try.rb +54 -0
  242. data/try/refinements/logger_trace_methods_try.rb +44 -0
  243. data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
  244. data/try/refinements/time_literals_string_methods_try.rb +80 -0
  245. metadata +77 -45
  246. data/.rubocop_todo.yml +0 -208
  247. data/docs/connection_pooling.md +0 -192
  248. data/docs/guides/Connection-Pooling-Guide.md +0 -437
  249. data/docs/guides/Encrypted-Fields-Overview.md +0 -101
  250. data/docs/guides/Feature-System-Autoloading.md +0 -228
  251. data/docs/guides/Home.md +0 -116
  252. data/docs/guides/Relationships-Guide.md +0 -737
  253. data/docs/guides/relationships-methods.md +0 -266
  254. data/docs/reference/auditing_database_commands.rb +0 -228
  255. data/examples/permissions.rb +0 -240
  256. data/lib/familia/features/autoloadable.rb +0 -113
  257. data/lib/familia/features/relationships/cascading.rb +0 -437
  258. data/lib/familia/features/relationships/membership.rb +0 -497
  259. data/lib/familia/features/relationships/permission_management.rb +0 -264
  260. data/lib/familia/features/relationships/querying.rb +0 -615
  261. data/lib/familia/features/relationships/redis_operations.rb +0 -274
  262. data/lib/familia/features/relationships/tracking.rb +0 -418
  263. data/lib/familia/refinements/snake_case.rb +0 -40
  264. data/lib/familia/validation/command_recorder.rb +0 -336
  265. data/lib/familia/validation/expectations.rb +0 -519
  266. data/lib/familia/validation/validation_helpers.rb +0 -443
  267. data/lib/familia/validation/validator.rb +0 -412
  268. data/lib/familia/validation.rb +0 -140
  269. data/try/data_types/set_try.rb +0 -33
  270. data/try/features/autoloadable/autoloadable_try.rb +0 -61
  271. data/try/features/relationships/categorical_permissions_try.rb +0 -515
  272. data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -111
  273. data/try/validation/atomic_operations_try.rb.disabled +0 -320
  274. data/try/validation/command_validation_try.rb.disabled +0 -207
  275. data/try/validation/performance_validation_try.rb.disabled +0 -324
  276. data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -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
@@ -125,15 +128,15 @@ module Familia
125
128
 
126
129
  # The Great Database Refresh-o-matic 3000 for HashKey!
127
130
  #
128
- # This method performs a complete refresh of the hash's state from Redis.
131
+ # This method performs a complete refresh of the hash's state from the database.
129
132
  # It's like giving your hash a memory transfusion - out with the old state,
130
- # in with the fresh data straight from Redis!
133
+ # in with the fresh data straight from Valkey/Redis!
131
134
  #
132
135
  # @note This operation is atomic - it either succeeds completely or fails
133
136
  # safely. Any unsaved changes to the hash will be overwritten.
134
137
  #
135
138
  # @return [void] Returns nothing, but your hash will be sparkling clean
136
- # with all its fields synchronized with Redis.
139
+ # with all its fields synchronized with the database.
137
140
  #
138
141
  # @raise [Familia::KeyNotFoundError] If the dbkey for this hash no
139
142
  # longer exists. Time travelers beware!
@@ -148,7 +151,7 @@ module Familia
148
151
  # puts "Oops! Our hash seems to have vanished into the Database void!"
149
152
  # end
150
153
  def refresh!
151
- Familia.trace :REFRESH, dbclient, uri, caller(1..1) if Familia.debug?
154
+ Familia.trace :REFRESH, nil, uri if Familia.debug?
152
155
  raise Familia::KeyNotFoundError, dbkey unless dbclient.exists(dbkey)
153
156
 
154
157
  fields = hgetall
@@ -167,7 +170,7 @@ module Familia
167
170
  # @return [self] Returns the refreshed hash, ready for more adventures!
168
171
  #
169
172
  # @raise [Familia::KeyNotFoundError] If the dbkey does not exist.
170
- # The hash must exist in Redis-land for this to work!
173
+ # The hash must exist in Valkey/Redis-land for this to work!
171
174
  #
172
175
  # @example Refresh and chain
173
176
  # my_hash.refresh.keys # Refresh and get all keys
@@ -179,7 +182,7 @@ module Familia
179
182
  self
180
183
  end
181
184
 
182
- Familia::DataType.register self, :hash # legacy, deprecated
185
+ Familia::DataType.register self, :hash
183
186
  Familia::DataType.register self, :hashkey
184
187
  end
185
188
  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
 
@@ -8,6 +8,8 @@ module Familia
8
8
  dbclient.zcard 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?
@@ -24,14 +26,13 @@ module Familia
24
26
  # @return [Integer] Returns 1 if the element is new and added, 0 if the
25
27
  # element already existed and the score was updated.
26
28
  #
27
- # @example
28
- # sorted_set << "new_element"
29
+ # @example sorted_set << "new_element"
29
30
  #
30
31
  # @note This is a non-standard operation for sorted sets as it doesn't allow
31
32
  # specifying a custom score. Use `add` or `[]=` for more control.
32
33
  #
33
34
  def <<(val)
34
- add(Time.now.to_i, val)
35
+ add(val)
35
36
  end
36
37
 
37
38
  # NOTE: The argument order is the reverse of #add. We do this to
@@ -42,14 +43,24 @@ module Familia
42
43
  # obj.metrics[VALUE] # => SCORE
43
44
  #
44
45
  def []=(val, score)
45
- add score, val
46
- end
47
-
48
- def add(score, val)
46
+ add val, score
47
+ end
48
+
49
+ def add(val, score = nil)
50
+ # TODO: Support some or all of the ZADD options.
51
+ # XX: Only update existing elements. Don't add new ones.
52
+ # NX: Only add new elements. Don't update existing ones.
53
+ # LT: Only update if new score < current score. Doesn't prevent adding.
54
+ # GT: Only update if new score > current score. Doesn't prevent adding.
55
+ # CH: Return total changed elements (new + updated) instead of just new.
56
+ # INCR: Acts like ZINCRBY. Only one score-element pair allowed.
57
+ # Note: GT, LT and NX options are mutually exclusive.
58
+ score ||= Familia.now
49
59
  ret = dbclient.zadd dbkey, score, serialize_value(val)
50
60
  update_expiration
51
61
  ret
52
62
  end
63
+ alias add_element add
53
64
 
54
65
  def score(val)
55
66
  ret = dbclient.zscore dbkey, serialize_value(val, strict_values: false)
@@ -58,7 +69,7 @@ module Familia
58
69
  alias [] score
59
70
 
60
71
  def member?(val)
61
- Familia.trace :MEMBER, dbclient, "#{val}<#{val.class}>", caller(1..1) if Familia.debug?
72
+ Familia.trace :MEMBER, nil, "#{val}<#{val.class}>" if Familia.debug?
62
73
  !rank(val).nil?
63
74
  end
64
75
  alias include? member?
@@ -132,7 +143,7 @@ module Familia
132
143
  end
133
144
 
134
145
  def range(sidx, eidx, opts = {})
135
- echo :range, caller(1..1).first if Familia.debug
146
+ echo :range, Familia.pretty_stack(limit: 1) if Familia.debug
136
147
  elements = rangeraw(sidx, eidx, opts)
137
148
  deserialize_values(*elements)
138
149
  end
@@ -149,7 +160,7 @@ module Familia
149
160
  end
150
161
 
151
162
  def revrange(sidx, eidx, opts = {})
152
- echo :revrange, caller(1..1).first if Familia.debug
163
+ echo :revrange, Familia.pretty_stack(limit: 1) if Familia.debug
153
164
  elements = revrangeraw(sidx, eidx, opts)
154
165
  deserialize_values(*elements)
155
166
  end
@@ -160,25 +171,25 @@ module Familia
160
171
 
161
172
  # e.g. obj.metrics.rangebyscore (now-12.hours), now, :limit => [0, 10]
162
173
  def rangebyscore(sscore, escore, opts = {})
163
- echo :rangebyscore, caller(1..1).first if Familia.debug
174
+ echo :rangebyscore, Familia.pretty_stack(limit: 1) if Familia.debug
164
175
  elements = rangebyscoreraw(sscore, escore, opts)
165
176
  deserialize_values(*elements)
166
177
  end
167
178
 
168
179
  def rangebyscoreraw(sscore, escore, opts = {})
169
- echo :rangebyscoreraw, caller(1..1).first if Familia.debug
180
+ echo :rangebyscoreraw, Familia.pretty_stack(limit: 1) if Familia.debug
170
181
  dbclient.zrangebyscore(dbkey, sscore, escore, **opts)
171
182
  end
172
183
 
173
184
  # e.g. obj.metrics.revrangebyscore (now-12.hours), now, :limit => [0, 10]
174
185
  def revrangebyscore(sscore, escore, opts = {})
175
- echo :revrangebyscore, caller(1..1).first if Familia.debug
186
+ echo :revrangebyscore, Familia.pretty_stack(limit: 1) if Familia.debug
176
187
  elements = revrangebyscoreraw(sscore, escore, opts)
177
188
  deserialize_values(*elements)
178
189
  end
179
190
 
180
191
  def revrangebyscoreraw(sscore, escore, opts = {})
181
- echo :revrangebyscoreraw, caller(1..1).first if Familia.debug
192
+ echo :revrangebyscoreraw, Familia.pretty_stack(limit: 1) if Familia.debug
182
193
  opts[:with_scores] = true if opts[:withscores]
183
194
  dbclient.zrevrangebyscore(dbkey, sscore, escore, opts)
184
195
  end
@@ -207,7 +218,7 @@ module Familia
207
218
  # @param value The value to remove from the sorted set
208
219
  # @return [Integer] The number of members that were removed (0 or 1)
209
220
  def remove_element(value)
210
- Familia.trace :REMOVE_ELEMENT, dbclient, "#{value}<#{value.class}>", caller(1..1) if Familia.debug?
221
+ Familia.trace :REMOVE_ELEMENT, nil, "#{value}<#{value.class}>" if Familia.debug?
211
222
  # We use `strict_values: false` here to allow for the deletion of values
212
223
  # that are in the sorted set. If it's a horreum object, the value is
213
224
  # the identifier and not a serialized version of the object. So either
@@ -1,7 +1,7 @@
1
- # lib/familia/data_type/types/string.rb
1
+ # lib/familia/data_type/types/stringkey.rb
2
2
 
3
3
  module Familia
4
- class String < DataType
4
+ class StringKey < DataType
5
5
  def init; end
6
6
 
7
7
  # Returns the number of elements in the list
@@ -10,13 +10,14 @@ module Familia
10
10
  to_s.size
11
11
  end
12
12
  alias size char_count
13
+ alias length char_count
13
14
 
14
15
  def empty?
15
16
  char_count.zero?
16
17
  end
17
18
 
18
19
  def value
19
- echo :value, caller(0..0) if Familia.debug
20
+ echo :value, Familia.pretty_stack(limit: 1) if Familia.debug
20
21
  dbclient.setnx dbkey, @opts[:default] if @opts[:default]
21
22
  deserialize_value dbclient.get(dbkey)
22
23
  end
@@ -30,7 +31,7 @@ module Familia
30
31
  end
31
32
 
32
33
  def to_i
33
- value&.to_i || 0
34
+ value.to_i
34
35
  end
35
36
 
36
37
  def value=(val)
@@ -118,9 +119,10 @@ module Familia
118
119
  end
119
120
 
120
121
  Familia::DataType.register self, :string
122
+ Familia::DataType.register self, :stringkey
121
123
  end
122
124
  end
123
125
 
124
- # Both subclass String
126
+ # Both subclass StringKey
125
127
  require_relative 'lock'
126
128
  require_relative 'counter'
@@ -1,13 +1,17 @@
1
1
  # lib/familia/data_type/types/unsorted_set.rb
2
2
 
3
3
  module Familia
4
- class Set < DataType
4
+ # Familia::UnsortedSet
5
+ #
6
+ class UnsortedSet < DataType
5
7
  # Returns the number of elements in the unsorted set
6
8
  # @return [Integer] number of elements
7
9
  def element_count
8
10
  dbclient.scard dbkey
9
11
  end
10
12
  alias size element_count
13
+ alias length element_count
14
+ alias count element_count
11
15
 
12
16
  def empty?
13
17
  element_count.zero?
@@ -18,13 +22,14 @@ module Familia
18
22
  update_expiration
19
23
  self
20
24
  end
25
+ alias add_element add
21
26
 
22
27
  def <<(v)
23
28
  add v
24
29
  end
25
30
 
26
31
  def members
27
- echo :members, caller(1..1).first if Familia.debug
32
+ echo :members, Familia.pretty_stack(limit: 1) if Familia.debug
28
33
  elements = membersraw
29
34
  deserialize_values(*elements)
30
35
  end
@@ -92,35 +97,23 @@ module Familia
92
97
  dbclient.smove dbkey, dstkey, val
93
98
  end
94
99
 
95
- def random
96
- deserialize_value randomraw
100
+ # Get one or more random members from the set
101
+ # @param count [Integer] Number of random members to return (default: 1)
102
+ # @return [Array] Array of deserialized random members
103
+ def sample(count = 1)
104
+ deserialize_values(*sampleraw(count))
97
105
  end
106
+ alias random sample
98
107
 
99
- def randomraw
100
- dbclient.srandmember(dbkey)
108
+ # Get one or more random members from the set without deserialization
109
+ # @param count [Integer] Number of random members to return (default: 1)
110
+ # @return [Array] Array of raw random members
111
+ def sampleraw(count = 1)
112
+ dbclient.srandmember(dbkey, count) || []
101
113
  end
102
-
103
- ## Make the value stored at KEY identical to the given list
104
- # define_method :"#{name}_sync" do |*latest|
105
- # latest = latest.flatten.compact
106
- # # Do nothing if we're given an empty Array.
107
- # # Otherwise this would clear all current values
108
- # if latest.empty?
109
- # false
110
- # else
111
- # # Convert to a list of index values if we got the actual objects
112
- # latest = latest.collect { |obj| obj.index } if klass === latest.first
113
- # current = send("#{name_plural}raw")
114
- # added = latest-current
115
- # removed = current-latest
116
- # #Familia.info "#{self.index}: adding: #{added}"
117
- # added.each { |v| self.send("add_#{name_singular}", v) }
118
- # #Familia.info "#{self.index}: removing: #{removed}"
119
- # removed.each { |v| self.send("remove_#{name_singular}", v) }
120
- # true
121
- # end
122
- # end
114
+ alias random sampleraw
123
115
 
124
116
  Familia::DataType.register self, :set
117
+ Familia::DataType.register self, :unsorted_set
125
118
  end
126
119
  end
@@ -9,7 +9,7 @@ module Familia
9
9
  # DataType - Base class for Database data type wrappers
10
10
  #
11
11
  # This class provides common functionality for various Database data types
12
- # such as String, List, Set, SortedSet, and HashKey.
12
+ # such as String, List, UnsortedSet, SortedSet, and HashKey.
13
13
  #
14
14
  # @abstract Subclass and implement Database data type specific methods
15
15
  class DataType
@@ -19,30 +19,38 @@ module Familia
19
19
  using Familia::Refinements::TimeLiterals
20
20
 
21
21
  @registered_types = {}
22
- @valid_options = %i[class parent default_expiration default logical_database dbkey dbclient suffix prefix]
22
+ @valid_options = %i[class parent default_expiration default logical_database dbkey dbclient suffix prefix].freeze
23
23
  @logical_database = nil
24
24
 
25
25
  feature :expiration
26
26
  feature :quantization
27
27
 
28
28
  class << self
29
- attr_reader :registered_types, :valid_options, :has_relations
30
- attr_accessor :parent
31
- attr_writer :logical_database, :uri
29
+ attr_reader :registered_types, :valid_options, :has_related_fields
32
30
  end
33
31
 
34
32
  # DataType::ClassMethods
35
33
  #
36
34
  module ClassMethods
35
+ attr_accessor :parent, :suffix, :prefix, :uri
36
+ attr_writer :logical_database
37
+
37
38
  # To be called inside every class that inherits DataType
38
39
  # +methname+ is the term used for the class and instance methods
39
40
  # that are created for the given +klass+ (e.g. set, list, etc)
40
41
  def register(klass, methname)
41
- Familia.trace :REGISTER, nil, "[#{self}] Registering #{klass} as #{methname.inspect}", caller(1..1) if Familia.debug?
42
+ Familia.trace :REGISTER, nil, "[#{self}] Registering #{klass} as #{methname.inspect}" if Familia.debug?
42
43
 
43
44
  @registered_types[methname] = klass
44
45
  end
45
46
 
47
+ # Get the registered type class from a given method name
48
+ # +methname+ is the method name used to register the class (e.g. :set, :list, etc)
49
+ # Returns the registered class or nil if not found
50
+ def registered_type(methname)
51
+ @registered_types[methname]
52
+ end
53
+
46
54
  def logical_database(val = nil)
47
55
  @logical_database = val unless val.nil?
48
56
  @logical_database || parent&.logical_database
@@ -54,11 +62,10 @@ module Familia
54
62
  end
55
63
 
56
64
  def inherited(obj)
57
- Familia.trace :DATATYPE, nil, "#{obj} is my kinda type", caller(1..1) if Familia.debug?
65
+ Familia.trace :DATATYPE, nil, "#{obj} is my kinda type" if Familia.debug?
58
66
  obj.logical_database = logical_database
59
67
  obj.default_expiration = default_expiration # method added via Features::Expiration
60
68
  obj.uri = uri
61
- obj.parent = self
62
69
  super
63
70
  end
64
71
 
@@ -67,13 +74,14 @@ module Familia
67
74
  end
68
75
 
69
76
  def relations?
70
- @has_relations ||= false # rubocop:disable ThreadSafety/ClassInstanceVariable
77
+ @has_related_fields ||= false
71
78
  end
72
79
  end
73
80
  extend ClassMethods
74
81
 
75
- attr_reader :keystring, :opts
76
- attr_writer :dump_method, :load_method
82
+ attr_reader :keystring, :opts, :uri, :logical_database
83
+
84
+ alias url uri
77
85
 
78
86
  # +keystring+: If parent is set, this will be used as the suffix
79
87
  # for dbkey. Otherwise this becomes the value of the key.
@@ -94,10 +102,6 @@ module Familia
94
102
  #
95
103
  # :default => the default value (String-only)
96
104
  #
97
- # :logical_database => the logical database index to use (ignored if :dbclient is used).
98
- #
99
- # :dbclient => an instance of database client.
100
- #
101
105
  # :dbkey => a hardcoded key to use instead of the deriving the from
102
106
  # the name and parent (e.g. a derived key: customer:custid:secret_counter).
103
107
  #
@@ -111,28 +115,23 @@ module Familia
111
115
  @keystring = @keystring.join(Familia.delim) if @keystring.is_a?(Array)
112
116
 
113
117
  # Remove all keys from the opts that are not in the allowed list
114
- @opts = opts || {}
115
- @opts = DataType.valid_keys_only(@opts)
118
+ @opts = DataType.valid_keys_only(opts || {})
116
119
 
117
120
  # Apply the options to instance method setters of the same name
118
121
  @opts.each do |k, v|
119
- # Bewarde logging :parent instance here implicitly calls #to_s which for
120
- # some classes could include the identifier which could still be nil at
121
- # this point. This would result in a Familia::Problem being raised. So
122
- # to be on the safe-side here until we have a better understanding of
123
- # the issue, we'll just log the class name for each key-value pair.
124
- Familia.trace :SETTING, nil, " [setting] #{k} #{v.class}", caller(1..1) if Familia.debug?
125
122
  send(:"#{k}=", v) if respond_to? :"#{k}="
126
123
  end
127
124
 
128
125
  init if respond_to? :init
129
126
  end
130
127
 
128
+ # TODO: Replace with Chain of Responsibility pattern
131
129
  def dbclient
132
130
  return Fiber[:familia_transaction] if Fiber[:familia_transaction]
133
131
  return @dbclient if @dbclient
134
132
 
135
- parent? ? parent.dbclient : Familia.dbclient(opts[:logical_database])
133
+ # Delegate to parent if present, otherwise fall back to Familia
134
+ parent ? parent.dbclient : Familia.dbclient(opts[:logical_database])
136
135
  end
137
136
 
138
137
  # Produces the full dbkey for this object.
@@ -189,12 +188,19 @@ module Familia
189
188
  !@opts[:class].to_s.empty? && @opts[:class].is_a?(Familia)
190
189
  end
191
190
 
191
+ # Provides a structured way to "gear down" to run db commands that are
192
+ # not implemented in our DataType classes since we intentionally don't
193
+ # have a method_missing method.
194
+ def direct_access
195
+ yield(dbclient, dbkey)
196
+ end
197
+
192
198
  def parent_instance?
193
- parent.is_a?(Familia::Horreum)
199
+ parent&.is_a?(Horreum::ParentDefinition)
194
200
  end
195
201
 
196
202
  def parent_class?
197
- parent.is_a?(Class) && parent <= Familia::Horreum
203
+ parent.is_a?(Class) && parent.ancestors.include?(Familia::Horreum)
198
204
  end
199
205
 
200
206
  def parent?
@@ -202,47 +208,69 @@ module Familia
202
208
  end
203
209
 
204
210
  def parent
205
- @opts[:parent]
206
- end
211
+ # Return cached ParentDefinition if available
212
+ return @parent if @parent
207
213
 
214
+ # Return class-level parent if no instance parent
215
+ return self.class.parent unless @parent_ref
208
216
 
209
- def logical_database
210
- @opts[:logical_database] || self.class.logical_database
217
+ # Create ParentDefinition dynamically from stored reference.
218
+ # This ensures we get the current identifier value (available after initialization)
219
+ # rather than a stale nil value from initialization time. Cannot cache due to frozen object.
220
+ Horreum::ParentDefinition.from_parent(@parent_ref)
221
+ end
222
+
223
+ def parent=(value)
224
+ case value
225
+ when Horreum::ParentDefinition
226
+ @parent = value
227
+ when nil
228
+ @parent = nil
229
+ @parent_ref = nil
230
+ else
231
+ # Store parent instance reference for lazy ParentDefinition creation.
232
+ # During initialization, the parent's identifier may not be available yet,
233
+ # so we defer ParentDefinition creation until first access for memory efficiency.
234
+ # Note: @parent_ref is not cleared after use because DataType objects are frozen.
235
+ @parent_ref = value
236
+ @parent = nil # Will be created dynamically in parent method
237
+ end
211
238
  end
212
239
 
213
240
  def uri
214
- # If a specific URI is set in opts, use it
215
- return @opts[:uri] if @opts[:uri]
216
-
217
- # If parent has a DB set, create a URI with that DB
218
- if parent? && parent.respond_to?(:logical_database) && parent.logical_database
219
- base_uri = self.class.uri || Familia.uri
220
- if base_uri
221
- uri_with_db = base_uri.dup
222
- uri_with_db.db = parent.logical_database
223
- return uri_with_db
224
- end
241
+ # Return explicit instance URI if set
242
+ return @uri if @uri
243
+
244
+ # If we have a parent with logical_database, build URI with that database
245
+ if parent && parent.respond_to?(:logical_database) && parent.logical_database
246
+ new_uri = (self.class.uri || Familia.uri).dup
247
+ new_uri.db = parent.logical_database
248
+ new_uri
249
+ else
250
+ # Fall back to class-level URI or global Familia.uri
251
+ self.class.uri || Familia.uri
225
252
  end
253
+ end
226
254
 
227
- # Otherwise fall back to class URI
228
- self.class.uri
255
+ def uri=(value)
256
+ @uri = value
229
257
  end
230
258
 
231
259
  def dump_method
232
- @dump_method || self.class.dump_method
260
+ self.class.dump_method
233
261
  end
234
262
 
235
263
  def load_method
236
- @load_method || self.class.load_method
264
+ self.class.load_method
237
265
  end
238
266
 
239
267
  include Commands
240
268
  include Serialization
241
269
  end
242
270
 
243
- require_relative 'data_type/types/list'
271
+ require_relative 'data_type/types/listkey'
244
272
  require_relative 'data_type/types/unsorted_set'
245
273
  require_relative 'data_type/types/sorted_set'
246
274
  require_relative 'data_type/types/hashkey'
247
- require_relative 'data_type/types/string'
275
+ require_relative 'data_type/types/stringkey'
248
276
  end