familia 2.0.0.pre15 → 2.0.0.pre17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -2
  3. data/.github/workflows/code-quality.yml +138 -0
  4. data/.github/workflows/code-smells.yml +85 -0
  5. data/.github/workflows/docs.yml +31 -8
  6. data/.gitignore +3 -1
  7. data/.pre-commit-config.yaml +7 -1
  8. data/.reek.yml +98 -0
  9. data/.rubocop.yml +54 -10
  10. data/.talismanrc +9 -0
  11. data/.yardopts +18 -13
  12. data/CHANGELOG.rst +86 -4
  13. data/CLAUDE.md +39 -1
  14. data/Gemfile +6 -5
  15. data/Gemfile.lock +99 -23
  16. data/LICENSE.txt +1 -1
  17. data/README.md +285 -85
  18. data/changelog.d/README.md +2 -2
  19. data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
  20. data/docs/archive/FAMILIA_TECHNICAL.md +42 -42
  21. data/docs/archive/FAMILIA_UPDATE.md +3 -3
  22. data/docs/archive/README.md +3 -2
  23. data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
  24. data/docs/conf.py +29 -0
  25. data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
  26. data/docs/guides/feature-encrypted-fields.md +785 -0
  27. data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
  28. data/docs/guides/feature-external-identifiers.md +637 -0
  29. data/docs/guides/feature-object-identifiers.md +435 -0
  30. data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
  31. data/docs/guides/feature-relationships-methods.md +684 -0
  32. data/docs/guides/feature-relationships.md +200 -0
  33. data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
  34. data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
  35. data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
  36. data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
  37. data/docs/guides/index.md +176 -0
  38. data/docs/guides/{Security-Model.md → security-model.md} +1 -1
  39. data/docs/migrating/v2.0.0-pre.md +1 -1
  40. data/docs/migrating/v2.0.0-pre11.md +2 -2
  41. data/docs/migrating/v2.0.0-pre12.md +2 -2
  42. data/docs/migrating/v2.0.0-pre5.md +33 -12
  43. data/docs/migrating/v2.0.0-pre6.md +2 -2
  44. data/docs/migrating/v2.0.0-pre7.md +8 -8
  45. data/docs/overview.md +624 -20
  46. data/docs/reference/api-technical.md +1365 -0
  47. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
  48. data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
  49. data/examples/autoloader/mega_customer.rb +3 -1
  50. data/examples/encrypted_fields.rb +378 -0
  51. data/examples/json_usage_patterns.rb +144 -0
  52. data/examples/relationships.rb +13 -13
  53. data/examples/safe_dump.rb +7 -7
  54. data/examples/single_connection_transaction_confusions.rb +379 -0
  55. data/lib/familia/base.rb +51 -10
  56. data/lib/familia/connection/handlers.rb +223 -0
  57. data/lib/familia/connection/individual_command_proxy.rb +64 -0
  58. data/lib/familia/connection/middleware.rb +75 -0
  59. data/lib/familia/connection/operation_core.rb +93 -0
  60. data/lib/familia/connection/operations.rb +277 -0
  61. data/lib/familia/connection/pipeline_core.rb +87 -0
  62. data/lib/familia/connection/transaction_core.rb +100 -0
  63. data/lib/familia/connection.rb +60 -186
  64. data/lib/familia/data_type/class_methods.rb +63 -0
  65. data/lib/familia/data_type/commands.rb +53 -51
  66. data/lib/familia/data_type/connection.rb +83 -0
  67. data/lib/familia/data_type/serialization.rb +108 -107
  68. data/lib/familia/data_type/settings.rb +96 -0
  69. data/lib/familia/data_type/types/counter.rb +1 -1
  70. data/lib/familia/data_type/types/hashkey.rb +15 -11
  71. data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
  72. data/lib/familia/data_type/types/lock.rb +3 -2
  73. data/lib/familia/data_type/types/sorted_set.rb +128 -14
  74. data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -9
  75. data/lib/familia/data_type/types/unsorted_set.rb +20 -27
  76. data/lib/familia/data_type.rb +12 -171
  77. data/lib/familia/distinguisher.rb +85 -0
  78. data/lib/familia/encryption/encrypted_data.rb +15 -24
  79. data/lib/familia/encryption/manager.rb +6 -4
  80. data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
  81. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
  82. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
  83. data/lib/familia/encryption/request_cache.rb +7 -7
  84. data/lib/familia/encryption.rb +2 -3
  85. data/lib/familia/errors.rb +9 -3
  86. data/lib/familia/features/autoloader.rb +30 -12
  87. data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
  88. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
  89. data/lib/familia/features/encrypted_fields.rb +71 -66
  90. data/lib/familia/features/expiration/extensions.rb +1 -1
  91. data/lib/familia/features/expiration.rb +31 -26
  92. data/lib/familia/features/external_identifier.rb +57 -19
  93. data/lib/familia/features/object_identifier.rb +134 -25
  94. data/lib/familia/features/quantization.rb +16 -21
  95. data/lib/familia/features/relationships/README.md +97 -0
  96. data/lib/familia/features/relationships/collection_operations.rb +104 -0
  97. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
  98. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +306 -0
  99. data/lib/familia/features/relationships/indexing.rb +182 -256
  100. data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
  101. data/lib/familia/features/relationships/participation/participant_methods.rb +164 -0
  102. data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
  103. data/lib/familia/features/relationships/participation.rb +656 -0
  104. data/lib/familia/features/relationships/participation_relationship.rb +31 -0
  105. data/lib/familia/features/relationships/score_encoding.rb +20 -20
  106. data/lib/familia/features/relationships.rb +65 -266
  107. data/lib/familia/features/safe_dump.rb +127 -130
  108. data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
  109. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
  110. data/lib/familia/features/transient_fields.rb +10 -7
  111. data/lib/familia/features.rb +10 -14
  112. data/lib/familia/field_type.rb +6 -4
  113. data/lib/familia/horreum/connection.rb +297 -0
  114. data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +27 -17
  115. data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +139 -74
  116. data/lib/familia/horreum/{subclass/management.rb → management.rb} +73 -27
  117. data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +108 -185
  118. data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +104 -23
  119. data/lib/familia/horreum/serialization.rb +172 -0
  120. data/lib/familia/horreum/{shared/settings.rb → settings.rb} +2 -1
  121. data/lib/familia/horreum/{core/utils.rb → utils.rb} +2 -1
  122. data/lib/familia/horreum.rb +222 -119
  123. data/lib/familia/json_serializer.rb +0 -1
  124. data/lib/familia/logging.rb +11 -114
  125. data/lib/familia/refinements/dear_json.rb +122 -0
  126. data/lib/familia/refinements/logger_trace.rb +20 -17
  127. data/lib/familia/refinements/stylize_words.rb +65 -0
  128. data/lib/familia/refinements/time_literals.rb +60 -52
  129. data/lib/familia/refinements.rb +2 -1
  130. data/lib/familia/secure_identifier.rb +60 -28
  131. data/lib/familia/settings.rb +83 -7
  132. data/lib/familia/utils.rb +5 -87
  133. data/lib/familia/verifiable_identifier.rb +4 -4
  134. data/lib/familia/version.rb +1 -1
  135. data/lib/familia.rb +72 -14
  136. data/lib/middleware/database_middleware.rb +56 -14
  137. data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
  138. data/try/configuration/scenarios_try.rb +2 -2
  139. data/try/connection/fiber_context_preservation_try.rb +250 -0
  140. data/try/connection/handler_constraints_try.rb +59 -0
  141. data/try/connection/operation_mode_guards_try.rb +208 -0
  142. data/try/connection/pipeline_fallback_integration_try.rb +128 -0
  143. data/try/connection/responsibility_chain_tracking_try.rb +72 -0
  144. data/try/connection/transaction_fallback_integration_try.rb +288 -0
  145. data/try/connection/transaction_mode_permissive_try.rb +153 -0
  146. data/try/connection/transaction_mode_strict_try.rb +98 -0
  147. data/try/connection/transaction_mode_warn_try.rb +131 -0
  148. data/try/connection/transaction_modes_try.rb +249 -0
  149. data/try/core/autoloader_try.rb +120 -2
  150. data/try/core/connection_try.rb +10 -10
  151. data/try/core/conventional_inheritance_try.rb +130 -0
  152. data/try/core/create_method_try.rb +15 -23
  153. data/try/core/database_consistency_try.rb +11 -10
  154. data/try/core/errors_try.rb +11 -14
  155. data/try/core/familia_extended_try.rb +2 -2
  156. data/try/core/familia_members_methods_try.rb +76 -0
  157. data/try/core/familia_try.rb +1 -1
  158. data/try/core/isolated_dbclient_try.rb +165 -0
  159. data/try/core/middleware_try.rb +16 -16
  160. data/try/core/persistence_operations_try.rb +4 -4
  161. data/try/core/pools_try.rb +42 -26
  162. data/try/core/secure_identifier_try.rb +28 -24
  163. data/try/core/time_utils_try.rb +10 -10
  164. data/try/core/tools_try.rb +3 -3
  165. data/try/core/utils_try.rb +2 -2
  166. data/try/data_types/boolean_try.rb +4 -4
  167. data/try/data_types/datatype_base_try.rb +0 -2
  168. data/try/data_types/list_try.rb +10 -10
  169. data/try/data_types/sorted_set_try.rb +5 -5
  170. data/try/data_types/sorted_set_zadd_options_try.rb +625 -0
  171. data/try/data_types/string_try.rb +12 -12
  172. data/try/data_types/unsortedset_try.rb +33 -0
  173. data/try/debugging/cache_behavior_tracer.rb +7 -7
  174. data/try/debugging/debug_aad_process.rb +1 -1
  175. data/try/debugging/debug_concealed_internal.rb +1 -1
  176. data/try/debugging/debug_cross_context.rb +1 -1
  177. data/try/debugging/debug_fresh_cross_context.rb +1 -1
  178. data/try/debugging/encryption_method_tracer.rb +10 -10
  179. data/try/edge_cases/hash_symbolization_try.rb +1 -1
  180. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  181. data/try/encryption/config_persistence_try.rb +2 -2
  182. data/try/encryption/encryption_core_try.rb +19 -19
  183. data/try/encryption/instance_variable_scope_try.rb +1 -1
  184. data/try/encryption/module_loading_try.rb +2 -2
  185. data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
  186. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
  187. data/try/encryption/secure_memory_handling_try.rb +1 -1
  188. data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
  189. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  190. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
  191. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
  192. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
  193. data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
  194. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  195. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  196. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
  197. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
  198. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  199. data/try/features/feature_dependencies_try.rb +3 -3
  200. data/try/features/field_groups_try.rb +244 -0
  201. data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
  202. data/try/features/object_identifier/object_identifier_try.rb +10 -0
  203. data/try/features/quantization/quantization_try.rb +1 -1
  204. data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
  205. data/try/features/relationships/indexing_try.rb +443 -0
  206. data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
  207. data/try/features/relationships/participation_commands_verification_try.rb +105 -0
  208. data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
  209. data/try/features/relationships/participation_reverse_index_try.rb +196 -0
  210. data/try/features/relationships/relationships_api_changes_try.rb +72 -71
  211. data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
  212. data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
  213. data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
  214. data/try/features/relationships/relationships_performance_try.rb +20 -20
  215. data/try/features/relationships/relationships_try.rb +27 -38
  216. data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
  217. data/try/features/transient_fields/refresh_reset_try.rb +3 -1
  218. data/try/features/transient_fields/simple_refresh_test.rb +1 -1
  219. data/try/helpers/test_cleanup.rb +86 -0
  220. data/try/helpers/test_helpers.rb +6 -7
  221. data/try/horreum/auto_indexing_on_save_try.rb +212 -0
  222. data/try/horreum/base_try.rb +3 -2
  223. data/try/horreum/commands_try.rb +3 -1
  224. data/try/horreum/defensive_initialization_try.rb +86 -0
  225. data/try/horreum/destroy_related_fields_cleanup_try.rb +332 -0
  226. data/try/horreum/initialization_try.rb +11 -7
  227. data/try/horreum/relations_try.rb +21 -13
  228. data/try/horreum/serialization_try.rb +12 -11
  229. data/try/horreum/settings_try.rb +2 -0
  230. data/try/integration/cross_component_try.rb +3 -3
  231. data/try/memory/memory_basic_test.rb +1 -1
  232. data/try/memory/memory_docker_ruby_dump.sh +2 -2
  233. data/try/models/customer_safe_dump_try.rb +1 -1
  234. data/try/models/customer_try.rb +13 -15
  235. data/try/models/datatype_base_try.rb +3 -3
  236. data/try/models/familia_object_try.rb +9 -8
  237. data/try/performance/benchmarks_try.rb +2 -2
  238. data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
  239. data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
  240. data/try/prototypes/atomic_saves_v4.rb +1 -1
  241. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
  242. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  243. data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  244. data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
  245. data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
  246. data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
  247. data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
  248. data/try/prototypes/pooling/pool_siege.rb +11 -11
  249. data/try/prototypes/pooling/run_stress_tests.rb +7 -7
  250. data/try/refinements/dear_json_array_methods_try.rb +53 -0
  251. data/try/refinements/dear_json_hash_methods_try.rb +54 -0
  252. data/try/refinements/logger_trace_methods_try.rb +44 -0
  253. data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
  254. data/try/refinements/time_literals_string_methods_try.rb +80 -0
  255. data/try/valkey.conf +26 -0
  256. metadata +92 -52
  257. data/.rubocop_todo.yml +0 -208
  258. data/docs/connection_pooling.md +0 -192
  259. data/docs/guides/Connection-Pooling-Guide.md +0 -437
  260. data/docs/guides/Encrypted-Fields-Overview.md +0 -101
  261. data/docs/guides/Feature-System-Autoloading.md +0 -198
  262. data/docs/guides/Home.md +0 -116
  263. data/docs/guides/Relationships-Guide.md +0 -737
  264. data/docs/guides/relationships-methods.md +0 -266
  265. data/docs/reference/auditing_database_commands.rb +0 -228
  266. data/examples/permissions.rb +0 -240
  267. data/lib/familia/features/relationships/cascading.rb +0 -437
  268. data/lib/familia/features/relationships/membership.rb +0 -497
  269. data/lib/familia/features/relationships/permission_management.rb +0 -264
  270. data/lib/familia/features/relationships/querying.rb +0 -615
  271. data/lib/familia/features/relationships/redis_operations.rb +0 -274
  272. data/lib/familia/features/relationships/tracking.rb +0 -418
  273. data/lib/familia/horreum/core/connection.rb +0 -73
  274. data/lib/familia/horreum/core.rb +0 -21
  275. data/lib/familia/refinements/snake_case.rb +0 -40
  276. data/lib/familia/validation/command_recorder.rb +0 -336
  277. data/lib/familia/validation/expectations.rb +0 -519
  278. data/lib/familia/validation/validation_helpers.rb +0 -443
  279. data/lib/familia/validation/validator.rb +0 -412
  280. data/lib/familia/validation.rb +0 -140
  281. data/try/data_types/set_try.rb +0 -33
  282. data/try/features/relationships/categorical_permissions_try.rb +0 -515
  283. data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
  284. data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
  285. data/try/validation/atomic_operations_try.rb.disabled +0 -320
  286. data/try/validation/command_validation_try.rb.disabled +0 -207
  287. data/try/validation/performance_validation_try.rb.disabled +0 -324
  288. data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -0,0 +1,297 @@
1
+ # lib/familia/horreum/connection.rb
2
+
3
+ module Familia
4
+ class Horreum
5
+ # Connection - Mixed instance and class-level methods for Valkey connection management
6
+ # Provides connection handling, transactions, and URI normalization for both
7
+ # class-level operations (e.g., Customer.dbclient) and instance-level operations
8
+ # (e.g., customer.dbclient)
9
+ module Connection
10
+ attr_reader :uri
11
+
12
+ # Normalizes various URI formats to a consistent URI object
13
+ # Considers the class/instance logical_database when uri is nil or Integer
14
+ def normalize_uri(uri)
15
+ case uri
16
+ when Integer
17
+ new_uri = Familia.uri.dup
18
+ new_uri.db = uri
19
+ new_uri
20
+ when ->(obj) { obj.is_a?(String) || obj.instance_of?(::String) }
21
+ URI.parse(uri)
22
+ when URI
23
+ uri
24
+ when nil
25
+ # Use logical_database if available, otherwise fall back to Familia.uri
26
+ if respond_to?(:logical_database) && logical_database
27
+ new_uri = Familia.uri.dup
28
+ new_uri.db = logical_database
29
+ new_uri
30
+ else
31
+ Familia.uri
32
+ end
33
+ else
34
+ raise ArgumentError, "Invalid URI type: #{uri.class.name}"
35
+ end
36
+ end
37
+
38
+ # Creates a new Database connection instance using the class/instance configuration
39
+ def create_dbclient(uri = nil)
40
+ parsed_uri = normalize_uri(uri)
41
+ Familia.create_dbclient(parsed_uri)
42
+ end
43
+
44
+ # Returns the Database connection for the class using Chain of Responsibility pattern.
45
+ #
46
+ # This method uses a chain of handlers to resolve connections in priority order:
47
+ # 1. FiberTransactionHandler - Fiber[:familia_transaction] (active transaction)
48
+ # 2. DefaultConnectionHandler - Horreum model class-level @dbclient
49
+ # 3. GlobalFallbackHandler - Familia.dbclient(uri || logical_database) (global fallback)
50
+ #
51
+ # @return [Redis] the Database connection instance.
52
+ #
53
+ def dbclient(uri = nil)
54
+ @class_connection_chain ||= build_connection_chain
55
+ @class_connection_chain.handle(uri)
56
+ end
57
+
58
+ def connect(*)
59
+ create_dbclient(*)
60
+ end
61
+
62
+ def uri=(uri)
63
+ @uri = normalize_uri(uri)
64
+ end
65
+ alias url uri
66
+ alias url= uri=
67
+
68
+ # Perform a sacred Database transaction ritual.
69
+ #
70
+ # This method creates a protective circle around your Database operations,
71
+ # ensuring they all succeed or fail together. It's like a group hug for your
72
+ # data operations, but with more ACID properties.
73
+ #
74
+ # @yield [conn] A block where you can perform your Database incantations.
75
+ # @yieldparam conn [Redis] A Database connection in multi mode.
76
+ #
77
+ # @example Performing a Database rain dance
78
+ # transaction do |conn|
79
+ # conn.set("weather", "rainy")
80
+ # conn.set("mood", "melancholic")
81
+ # end
82
+ #
83
+ # @note This method works with the global Familia.transaction context when available
84
+ #
85
+ # Executes a Redis transaction (MULTI/EXEC) using this object's connection context.
86
+ #
87
+ # Provides atomic execution of multiple Redis commands with automatic connection
88
+ # management and operation mode enforcement. Uses the object's database and
89
+ # connection settings. Returns a MultiResult object for consistency with global methods.
90
+ #
91
+ # @param [Proc] block The block containing Redis commands to execute atomically
92
+ # @yield [Redis] conn The Redis connection configured for transaction mode
93
+ # @return [MultiResult] Result object with success status and command results
94
+ #
95
+ # @raise [Familia::OperationModeError] When called with incompatible connection handlers
96
+ # (e.g., FiberConnectionHandler or DefaultConnectionHandler that don't support transactions)
97
+ #
98
+ # @example Basic instance transaction
99
+ # customer = Customer.new(custid: 'cust_123')
100
+ # result = customer.transaction do |conn|
101
+ # conn.hset(customer.dbkey, 'name', 'John Doe')
102
+ # conn.hset(customer.dbkey, 'email', 'john@example.com')
103
+ # conn.hget(customer.dbkey, 'name')
104
+ # end
105
+ # result.successful? # => true
106
+ # result.results # => ["OK", "OK", "John Doe"]
107
+ #
108
+ # @example Using with object's database context
109
+ # class Customer < Familia::Horreum
110
+ # logical_database 5 # Use database 5
111
+ # field :name
112
+ # field :email
113
+ # end
114
+ #
115
+ # customer = Customer.new(custid: 'cust_456')
116
+ # result = customer.transaction do |conn|
117
+ # # Commands automatically execute in database 5
118
+ # conn.hset(customer.dbkey, 'status', 'active')
119
+ # conn.sadd('active_customers', customer.identifier)
120
+ # end
121
+ # result.successful? # => true
122
+ #
123
+ # @example Reentrant behavior with global transactions
124
+ # customer = Customer.new(custid: 'cust_789')
125
+ #
126
+ # # When called within a global transaction, reuses the transaction connection
127
+ # result = Familia.transaction do |global_conn|
128
+ # global_conn.set('global_key', 'value')
129
+ #
130
+ # # This reuses the same transaction connection
131
+ # customer.transaction do |local_conn|
132
+ # local_conn.hset(customer.dbkey, 'updated', Time.now.to_i)
133
+ # 'local_return_value' # Returned directly in nested context
134
+ # end
135
+ # end
136
+ #
137
+ # @note Connection Inheritance:
138
+ # - Uses object's logical_database setting if configured
139
+ # - Inherits class-level database settings
140
+ # - Falls back to instance-level dbclient if set
141
+ # - Uses global connection chain as final fallback
142
+ #
143
+ # @note Transaction Context:
144
+ # - When called outside global transaction: Creates local MultiResult
145
+ # - When called inside global transaction: Yields to existing transaction
146
+ # - Maintains proper Fiber-local state for nested calls
147
+ #
148
+ # @see Familia.transaction For global transaction method
149
+ # @see MultiResult For details on the return value structure
150
+ # @see #batch_update For similar atomic field updates with MultiResult
151
+ def transaction(&)
152
+ ensure_relatives_initialized!
153
+ Familia::Connection::TransactionCore.execute_transaction(-> { dbclient }, &)
154
+ end
155
+ alias multi transaction
156
+
157
+ # Executes Redis commands in a pipeline using this object's connection context.
158
+ #
159
+ # Batches multiple Redis commands together and sends them in a single network
160
+ # round-trip for improved performance. Uses the object's database and connection
161
+ # settings. Returns a MultiResult object for consistency with global methods.
162
+ #
163
+ # @param [Proc] block The block containing Redis commands to execute in pipeline
164
+ # @yield [Redis] conn The Redis connection configured for pipelined mode
165
+ # @return [MultiResult] Result object with success status and command results
166
+ #
167
+ # @raise [Familia::OperationModeError] When called with incompatible connection handlers
168
+ # (e.g., FiberConnectionHandler or CachedConnectionHandler that don't support pipelines)
169
+ #
170
+ # @example Basic instance pipeline
171
+ # customer = Customer.new(custid: 'cust_123')
172
+ # result = customer.pipelined do |conn|
173
+ # conn.hset(customer.dbkey, 'last_login', Time.now.to_i)
174
+ # conn.hincrby(customer.dbkey, 'login_count', 1)
175
+ # conn.sadd('recent_logins', customer.identifier)
176
+ # conn.hget(customer.dbkey, 'login_count')
177
+ # end
178
+ # result.successful? # => true
179
+ # result.results # => ["OK", 15, "OK", "15"]
180
+ # result.results.last # => "15" (new login count)
181
+ #
182
+ # @example Performance optimization for object operations
183
+ # user = User.new(userid: 'user_456')
184
+ #
185
+ # # Instead of multiple round-trips:
186
+ # # user.save # Round-trip 1
187
+ # # user.tags.add('premium') # Round-trip 2
188
+ # # user.sessions.clear # Round-trip 3
189
+ #
190
+ # # Use pipeline for single round-trip:
191
+ # result = user.pipelined do |conn|
192
+ # conn.hmset(user.dbkey, user.to_h_for_storage)
193
+ # conn.sadd(user.tags.dbkey, 'premium')
194
+ # conn.del(user.sessions.dbkey)
195
+ # end
196
+ # # All operations completed in one network round-trip
197
+ #
198
+ # @example Using with object's database context
199
+ # class Session < Familia::Horreum
200
+ # logical_database 3 # Use database 3
201
+ # field :user_id
202
+ # field :expires_at
203
+ # end
204
+ #
205
+ # session = Session.new(session_id: 'sess_789')
206
+ # result = session.pipelined do |conn|
207
+ # # Commands automatically execute in database 3
208
+ # conn.hset(session.dbkey, 'user_id', 'user_123')
209
+ # conn.hset(session.dbkey, 'expires_at', 1.hour.from_now.to_i)
210
+ # conn.expire(session.dbkey, 3600)
211
+ # end
212
+ #
213
+ # @example Reentrant behavior with global pipelines
214
+ # customer = Customer.new(custid: 'cust_abc')
215
+ #
216
+ # # When called within a global pipeline, reuses the pipeline connection
217
+ # result = Familia.pipelined do |global_conn|
218
+ # global_conn.set('global_counter', 0)
219
+ #
220
+ # # This reuses the same pipeline connection
221
+ # customer.pipelined do |local_conn|
222
+ # local_conn.hset(customer.dbkey, 'updated', Time.now.to_i)
223
+ # Redis::Future.new # Returns Redis::Future in nested context
224
+ # end
225
+ # end
226
+ #
227
+ # @note Connection Inheritance:
228
+ # - Uses object's logical_database setting if configured
229
+ # - Inherits class-level database settings
230
+ # - Falls back to instance-level dbclient if set
231
+ # - Uses global connection chain as final fallback
232
+ #
233
+ # @note Pipeline Context:
234
+ # - When called outside global pipeline: Creates local MultiResult
235
+ # - When called inside global pipeline: Yields to existing pipeline
236
+ # - Maintains proper Fiber-local state for nested calls
237
+ #
238
+ # @note Performance Considerations:
239
+ # - Best for multiple independent operations on the same object
240
+ # - Reduces network latency by batching commands
241
+ # - Commands execute independently (some may succeed, others fail)
242
+ #
243
+ # @see Familia.pipelined For global pipeline method
244
+ # @see MultiResult For details on the return value structure
245
+ # @see Familia.transaction For atomic command execution
246
+ def pipelined(&block)
247
+ ensure_relatives_initialized!
248
+ Familia::Connection::PipelineCore.execute_pipeline(-> { dbclient }, &block)
249
+ end
250
+ alias pipeline pipelined
251
+
252
+ private
253
+
254
+ # Ensures that related fields have been initialized before entering transactions or pipelines.
255
+ #
256
+ # This prevents Redis::Future errors when lazy initialization would occur inside
257
+ # transaction/pipeline blocks. When commands execute inside transactions, Redis returns
258
+ # Future objects that don't respond to standard methods, causing cryptic NoMethodError.
259
+ #
260
+ # @raise [RuntimeError] if instance has relations but they haven't been initialized
261
+ # @note Skips check for class methods - they create temporary instances internally
262
+ # @note Uses singleton class to avoid polluting instance variables
263
+ def ensure_relatives_initialized!
264
+ return if is_a?(Class) # Class methods handle their own instances
265
+ return unless self.class.respond_to?(:relations?) && self.class.relations?
266
+ return if singleton_class.instance_variable_defined?(:"@relatives_initialized")
267
+
268
+ raise "#{self.class} has related fields but they haven't been initialized. " \
269
+ "Did you override initialize without calling super? " \
270
+ "Related fields: #{self.class.related_fields.keys.join(', ')}"
271
+ end
272
+
273
+ # Builds the class-level connection chain with handlers in priority order
274
+ def build_connection_chain
275
+ # Cache handlers at class level to avoid creating new instances per model instance
276
+ @fiber_connection_handler ||= Familia::Connection::FiberConnectionHandler.new
277
+ @provider_connection_handler ||= Familia::Connection::ProviderConnectionHandler.new
278
+
279
+ # Determine the appropriate class context
280
+ # When called from instance: self is instance, self.class is the model class
281
+ # When called from class: self is the model class
282
+ klass = self.is_a?(Class) ? self : self.class
283
+
284
+ # Always check class first for @dbclient since instance-level connections were removed
285
+ @cached_connection_handler ||= Familia::Connection::CachedConnectionHandler.new(klass)
286
+ @create_connection_handler ||= Familia::Connection::CreateConnectionHandler.new(klass)
287
+
288
+ Familia::Connection::ResponsibilityChain.new
289
+ .add_handler(Familia::Connection::FiberTransactionHandler.instance)
290
+ .add_handler(@fiber_connection_handler)
291
+ .add_handler(@provider_connection_handler)
292
+ .add_handler(@cached_connection_handler)
293
+ .add_handler(@create_connection_handler)
294
+ end
295
+ end
296
+ end
297
+ end
@@ -1,13 +1,13 @@
1
1
  # lib/familia/horreum/database_commands.rb
2
2
 
3
3
  module Familia
4
- # InstanceMethods - Module containing instance-level methods for Familia
4
+ # Familia::Horreum
5
5
  #
6
6
  # This module is included in classes that include Familia, providing
7
7
  # instance-level functionality for Database operations and object management.
8
8
  #
9
9
  class Horreum
10
- # Methods that call Database commands (InstanceMethods)
10
+ # DatabaseCommands - Instance-level methods for horreum models that call Database commands
11
11
  #
12
12
  # NOTE: There is no hgetall for Horreum. This is because Horreum
13
13
  # is a single hash in Database that we aren't meant to have be working
@@ -20,11 +20,11 @@ module Familia
20
20
  dbclient.move dbkey, logical_database
21
21
  end
22
22
 
23
- # Checks if the calling object's key exists in Redis.
23
+ # Checks if the calling object's key exists in the database.
24
24
  #
25
25
  # @param check_size [Boolean] When true (default), also verifies the hash has a non-zero size.
26
26
  # When false, only checks key existence regardless of content.
27
- # @return [Boolean] Returns `true` if the key exists in Redis. When `check_size` is true,
27
+ # @return [Boolean] Returns `true` if the key exists in the database. When `check_size` is true,
28
28
  # also requires the hash to have at least one field.
29
29
  #
30
30
  # @example Check existence with size validation (default behavior)
@@ -49,6 +49,7 @@ module Familia
49
49
  dbclient.hlen dbkey
50
50
  end
51
51
  alias size field_count
52
+ alias length field_count
52
53
 
53
54
  # Sets a timeout on key. After the timeout has expired, the key will
54
55
  # automatically be deleted. Returns 1 if the timeout was set, 0 if key
@@ -56,19 +57,19 @@ module Familia
56
57
  #
57
58
  def expire(default_expiration = nil)
58
59
  default_expiration ||= self.class.default_expiration
59
- Familia.trace :EXPIRE, dbclient, default_expiration, caller(1..1) if Familia.debug?
60
+ Familia.trace :EXPIRE, nil, default_expiration if Familia.debug?
60
61
  dbclient.expire dbkey, default_expiration.to_i
61
62
  end
62
63
 
63
64
  # Retrieves the remaining time to live (TTL) for the object's dbkey.
64
65
  #
65
- # This method accesses the ovjects Database client to obtain the TTL of `dbkey`.
66
+ # This method accesses the objects Database client to obtain the TTL of `dbkey`.
66
67
  # If debugging is enabled, it logs the TTL retrieval operation using `Familia.trace`.
67
68
  #
68
69
  # @return [Integer] The TTL of the key in seconds. Returns -1 if the key does not exist
69
70
  # or has no associated expire time.
70
71
  def current_expiration
71
- Familia.trace :CURRENT_EXPIRATION, dbclient, uri, caller(1..1) if Familia.debug?
72
+ Familia.trace :CURRENT_EXPIRATION, nil, uri if Familia.debug?
72
73
  dbclient.ttl dbkey
73
74
  end
74
75
 
@@ -77,32 +78,32 @@ module Familia
77
78
  # @param field [String] The field to remove from the hash.
78
79
  # @return [Integer] The number of fields that were removed from the hash (0 or 1).
79
80
  def remove_field(field)
80
- Familia.trace :HDEL, dbclient, field, caller(1..1) if Familia.debug?
81
+ Familia.trace :HDEL, nil, field if Familia.debug?
81
82
  dbclient.hdel dbkey, field
82
83
  end
83
84
  alias remove remove_field # deprecated
84
85
 
85
86
  def data_type
86
- Familia.trace :DATATYPE, dbclient, uri, caller(1..1) if Familia.debug?
87
+ Familia.trace :DATATYPE, nil, uri if Familia.debug?
87
88
  dbclient.type dbkey(suffix)
88
89
  end
89
90
 
90
91
  # For parity with DataType#hgetall
91
92
  def hgetall
92
- Familia.trace :HGETALL, dbclient, uri, caller(1..1) if Familia.debug?
93
+ Familia.trace :HGETALL, nil, uri if Familia.debug?
93
94
  dbclient.hgetall dbkey(suffix)
94
95
  end
95
96
  alias all hgetall
96
97
 
97
98
  def hget(field)
98
- Familia.trace :HGET, dbclient, field, caller(1..1) if Familia.debug?
99
+ Familia.trace :HGET, nil, field if Familia.debug?
99
100
  dbclient.hget dbkey(suffix), field
100
101
  end
101
102
 
102
103
  # @return The number of fields that were added to the hash. If the
103
104
  # field already exists, this will return 0.
104
105
  def hset(field, value)
105
- Familia.trace :HSET, dbclient, field, caller(1..1) if Familia.debug?
106
+ Familia.trace :HSET, nil, field if Familia.debug?
106
107
  dbclient.hset dbkey, field, value
107
108
  end
108
109
 
@@ -115,18 +116,18 @@ module Familia
115
116
  # @return [Integer] 1 if the field is a new field in the hash and the value was set,
116
117
  # 0 if the field already exists in the hash and no operation was performed
117
118
  def hsetnx(field, value)
118
- Familia.trace :HSETNX, dbclient, field, caller(1..1) if Familia.debug?
119
+ Familia.trace :HSETNX, nil, field if Familia.debug?
119
120
  dbclient.hsetnx dbkey, field, value
120
121
  end
121
122
 
122
123
  def hmset(hsh = {})
123
124
  hsh ||= to_h
124
- Familia.trace :HMSET, dbclient, hsh, caller(1..1) if Familia.debug?
125
+ Familia.trace :HMSET, nil, hsh if Familia.debug?
125
126
  dbclient.hmset dbkey(suffix), hsh
126
127
  end
127
128
 
128
129
  def hkeys
129
- Familia.trace :HKEYS, dbclient, 'uri', caller(1..1) if Familia.debug?
130
+ Familia.trace :HKEYS, nil, 'uri' if Familia.debug?
130
131
  dbclient.hkeys dbkey(suffix)
131
132
  end
132
133
 
@@ -169,14 +170,23 @@ module Familia
169
170
  end
170
171
  alias has_key? key?
171
172
 
172
- # Deletes the entire dbkey
173
+ # Deletes the dbkey for this horreum :object.
174
+ #
175
+ # It does not delete the related fields keys. See destroy!
176
+ #
173
177
  # @return [Boolean] true if the key was deleted, false otherwise
174
178
  def delete!
175
- Familia.trace :DELETE!, dbclient, uri, caller(1..1) if Familia.debug?
179
+ Familia.trace :DELETE!, nil, uri if Familia.debug?
180
+
181
+ # Delete the main object key
176
182
  ret = dbclient.del dbkey
177
183
  ret.positive?
178
184
  end
179
185
  alias clear delete!
186
+
187
+ def echo(*args)
188
+ dbclient.echo "[#{self.class}] #{args.join(' ')}"
189
+ end
180
190
  end
181
191
  end
182
192
  end