familia 2.0.0.pre15 → 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 (274) 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 +64 -4
  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 +2 -2
  40. data/docs/migrating/v2.0.0-pre12.md +2 -2
  41. data/docs/migrating/v2.0.0-pre5.md +33 -12
  42. data/docs/migrating/v2.0.0-pre6.md +2 -2
  43. data/docs/migrating/v2.0.0-pre7.md +8 -8
  44. data/docs/overview.md +623 -19
  45. data/docs/reference/api-technical.md +1365 -0
  46. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
  47. data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
  48. data/examples/autoloader/mega_customer.rb +3 -1
  49. data/examples/encrypted_fields.rb +378 -0
  50. data/examples/json_usage_patterns.rb +144 -0
  51. data/examples/relationships.rb +13 -13
  52. data/examples/safe_dump.rb +6 -6
  53. data/examples/single_connection_transaction_confusions.rb +379 -0
  54. data/lib/familia/base.rb +49 -10
  55. data/lib/familia/connection/handlers.rb +223 -0
  56. data/lib/familia/connection/individual_command_proxy.rb +64 -0
  57. data/lib/familia/connection/middleware.rb +75 -0
  58. data/lib/familia/connection/operation_core.rb +93 -0
  59. data/lib/familia/connection/operations.rb +277 -0
  60. data/lib/familia/connection/pipeline_core.rb +87 -0
  61. data/lib/familia/connection/transaction_core.rb +100 -0
  62. data/lib/familia/connection.rb +60 -186
  63. data/lib/familia/data_type/commands.rb +53 -51
  64. data/lib/familia/data_type/serialization.rb +108 -107
  65. data/lib/familia/data_type/types/counter.rb +1 -1
  66. data/lib/familia/data_type/types/hashkey.rb +13 -10
  67. data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
  68. data/lib/familia/data_type/types/lock.rb +3 -2
  69. data/lib/familia/data_type/types/sorted_set.rb +26 -15
  70. data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -5
  71. data/lib/familia/data_type/types/unsorted_set.rb +20 -27
  72. data/lib/familia/data_type.rb +75 -47
  73. data/lib/familia/distinguisher.rb +85 -0
  74. data/lib/familia/encryption/encrypted_data.rb +15 -24
  75. data/lib/familia/encryption/manager.rb +6 -4
  76. data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
  77. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
  78. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
  79. data/lib/familia/encryption/request_cache.rb +7 -7
  80. data/lib/familia/encryption.rb +2 -3
  81. data/lib/familia/errors.rb +9 -3
  82. data/lib/familia/features/autoloader.rb +30 -12
  83. data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
  84. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
  85. data/lib/familia/features/encrypted_fields.rb +66 -64
  86. data/lib/familia/features/expiration/extensions.rb +1 -1
  87. data/lib/familia/features/expiration.rb +31 -26
  88. data/lib/familia/features/external_identifier.rb +9 -12
  89. data/lib/familia/features/object_identifier.rb +56 -19
  90. data/lib/familia/features/quantization.rb +16 -21
  91. data/lib/familia/features/relationships/README.md +97 -0
  92. data/lib/familia/features/relationships/collection_operations.rb +104 -0
  93. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
  94. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +301 -0
  95. data/lib/familia/features/relationships/indexing.rb +176 -256
  96. data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
  97. data/lib/familia/features/relationships/participation/participant_methods.rb +160 -0
  98. data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
  99. data/lib/familia/features/relationships/participation.rb +656 -0
  100. data/lib/familia/features/relationships/participation_relationship.rb +31 -0
  101. data/lib/familia/features/relationships/score_encoding.rb +20 -20
  102. data/lib/familia/features/relationships.rb +65 -266
  103. data/lib/familia/features/safe_dump.rb +127 -130
  104. data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
  105. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
  106. data/lib/familia/features/transient_fields.rb +3 -5
  107. data/lib/familia/features.rb +4 -13
  108. data/lib/familia/field_type.rb +24 -4
  109. data/lib/familia/horreum/core/connection.rb +229 -26
  110. data/lib/familia/horreum/core/database_commands.rb +27 -17
  111. data/lib/familia/horreum/core/serialization.rb +40 -20
  112. data/lib/familia/horreum/core/utils.rb +2 -1
  113. data/lib/familia/horreum/shared/settings.rb +2 -1
  114. data/lib/familia/horreum/subclass/definition.rb +33 -45
  115. data/lib/familia/horreum/subclass/management.rb +72 -24
  116. data/lib/familia/horreum/subclass/related_fields_management.rb +82 -21
  117. data/lib/familia/horreum.rb +196 -114
  118. data/lib/familia/json_serializer.rb +0 -1
  119. data/lib/familia/logging.rb +11 -114
  120. data/lib/familia/refinements/dear_json.rb +122 -0
  121. data/lib/familia/refinements/logger_trace.rb +20 -17
  122. data/lib/familia/refinements/stylize_words.rb +65 -0
  123. data/lib/familia/refinements/time_literals.rb +60 -52
  124. data/lib/familia/refinements.rb +2 -1
  125. data/lib/familia/secure_identifier.rb +60 -28
  126. data/lib/familia/settings.rb +83 -7
  127. data/lib/familia/utils.rb +5 -87
  128. data/lib/familia/verifiable_identifier.rb +4 -4
  129. data/lib/familia/version.rb +1 -1
  130. data/lib/familia.rb +72 -14
  131. data/lib/middleware/database_middleware.rb +56 -14
  132. data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
  133. data/try/configuration/scenarios_try.rb +1 -1
  134. data/try/connection/fiber_context_preservation_try.rb +250 -0
  135. data/try/connection/handler_constraints_try.rb +59 -0
  136. data/try/connection/operation_mode_guards_try.rb +208 -0
  137. data/try/connection/pipeline_fallback_integration_try.rb +128 -0
  138. data/try/connection/responsibility_chain_tracking_try.rb +72 -0
  139. data/try/connection/transaction_fallback_integration_try.rb +288 -0
  140. data/try/connection/transaction_mode_permissive_try.rb +153 -0
  141. data/try/connection/transaction_mode_strict_try.rb +98 -0
  142. data/try/connection/transaction_mode_warn_try.rb +131 -0
  143. data/try/connection/transaction_modes_try.rb +249 -0
  144. data/try/core/autoloader_try.rb +120 -2
  145. data/try/core/connection_try.rb +7 -7
  146. data/try/core/conventional_inheritance_try.rb +130 -0
  147. data/try/core/create_method_try.rb +15 -23
  148. data/try/core/database_consistency_try.rb +10 -10
  149. data/try/core/errors_try.rb +8 -11
  150. data/try/core/familia_extended_try.rb +2 -2
  151. data/try/core/familia_members_methods_try.rb +76 -0
  152. data/try/core/isolated_dbclient_try.rb +165 -0
  153. data/try/core/middleware_try.rb +16 -16
  154. data/try/core/persistence_operations_try.rb +4 -4
  155. data/try/core/pools_try.rb +42 -26
  156. data/try/core/secure_identifier_try.rb +28 -24
  157. data/try/core/time_utils_try.rb +10 -10
  158. data/try/core/tools_try.rb +1 -1
  159. data/try/core/utils_try.rb +2 -2
  160. data/try/data_types/boolean_try.rb +4 -4
  161. data/try/data_types/datatype_base_try.rb +0 -2
  162. data/try/data_types/list_try.rb +10 -10
  163. data/try/data_types/sorted_set_try.rb +5 -5
  164. data/try/data_types/string_try.rb +12 -12
  165. data/try/data_types/unsortedset_try.rb +33 -0
  166. data/try/debugging/cache_behavior_tracer.rb +7 -7
  167. data/try/debugging/debug_aad_process.rb +1 -1
  168. data/try/debugging/debug_concealed_internal.rb +1 -1
  169. data/try/debugging/debug_cross_context.rb +1 -1
  170. data/try/debugging/debug_fresh_cross_context.rb +1 -1
  171. data/try/debugging/encryption_method_tracer.rb +10 -10
  172. data/try/edge_cases/hash_symbolization_try.rb +1 -1
  173. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  174. data/try/encryption/config_persistence_try.rb +2 -2
  175. data/try/encryption/encryption_core_try.rb +19 -19
  176. data/try/encryption/instance_variable_scope_try.rb +1 -1
  177. data/try/encryption/module_loading_try.rb +2 -2
  178. data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
  179. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
  180. data/try/encryption/secure_memory_handling_try.rb +1 -1
  181. data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
  182. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  183. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
  184. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
  185. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
  186. data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
  187. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  188. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  189. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
  190. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
  191. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  192. data/try/features/feature_dependencies_try.rb +3 -3
  193. data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
  194. data/try/features/object_identifier/object_identifier_try.rb +10 -0
  195. data/try/features/quantization/quantization_try.rb +1 -1
  196. data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
  197. data/try/features/relationships/indexing_try.rb +433 -0
  198. data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
  199. data/try/features/relationships/participation_commands_verification_try.rb +105 -0
  200. data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
  201. data/try/features/relationships/participation_reverse_index_try.rb +196 -0
  202. data/try/features/relationships/relationships_api_changes_try.rb +72 -71
  203. data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
  204. data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
  205. data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
  206. data/try/features/relationships/relationships_performance_try.rb +20 -20
  207. data/try/features/relationships/relationships_try.rb +27 -38
  208. data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
  209. data/try/features/transient_fields/refresh_reset_try.rb +1 -1
  210. data/try/features/transient_fields/simple_refresh_test.rb +1 -1
  211. data/try/helpers/test_cleanup.rb +86 -0
  212. data/try/helpers/test_helpers.rb +3 -3
  213. data/try/horreum/base_try.rb +3 -2
  214. data/try/horreum/commands_try.rb +1 -1
  215. data/try/horreum/destroy_related_fields_cleanup_try.rb +330 -0
  216. data/try/horreum/initialization_try.rb +11 -7
  217. data/try/horreum/relations_try.rb +21 -13
  218. data/try/horreum/serialization_try.rb +12 -11
  219. data/try/integration/cross_component_try.rb +3 -3
  220. data/try/memory/memory_basic_test.rb +1 -1
  221. data/try/memory/memory_docker_ruby_dump.sh +1 -1
  222. data/try/models/customer_safe_dump_try.rb +1 -1
  223. data/try/models/customer_try.rb +8 -10
  224. data/try/models/datatype_base_try.rb +3 -3
  225. data/try/models/familia_object_try.rb +9 -8
  226. data/try/performance/benchmarks_try.rb +2 -2
  227. data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
  228. data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
  229. data/try/prototypes/atomic_saves_v4.rb +1 -1
  230. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
  231. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  232. data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  233. data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
  234. data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
  235. data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
  236. data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
  237. data/try/prototypes/pooling/pool_siege.rb +11 -11
  238. data/try/prototypes/pooling/run_stress_tests.rb +7 -7
  239. data/try/refinements/dear_json_array_methods_try.rb +53 -0
  240. data/try/refinements/dear_json_hash_methods_try.rb +54 -0
  241. data/try/refinements/logger_trace_methods_try.rb +44 -0
  242. data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
  243. data/try/refinements/time_literals_string_methods_try.rb +80 -0
  244. metadata +75 -43
  245. data/.rubocop_todo.yml +0 -208
  246. data/docs/connection_pooling.md +0 -192
  247. data/docs/guides/Connection-Pooling-Guide.md +0 -437
  248. data/docs/guides/Encrypted-Fields-Overview.md +0 -101
  249. data/docs/guides/Feature-System-Autoloading.md +0 -198
  250. data/docs/guides/Home.md +0 -116
  251. data/docs/guides/Relationships-Guide.md +0 -737
  252. data/docs/guides/relationships-methods.md +0 -266
  253. data/docs/reference/auditing_database_commands.rb +0 -228
  254. data/examples/permissions.rb +0 -240
  255. data/lib/familia/features/relationships/cascading.rb +0 -437
  256. data/lib/familia/features/relationships/membership.rb +0 -497
  257. data/lib/familia/features/relationships/permission_management.rb +0 -264
  258. data/lib/familia/features/relationships/querying.rb +0 -615
  259. data/lib/familia/features/relationships/redis_operations.rb +0 -274
  260. data/lib/familia/features/relationships/tracking.rb +0 -418
  261. data/lib/familia/refinements/snake_case.rb +0 -40
  262. data/lib/familia/validation/command_recorder.rb +0 -336
  263. data/lib/familia/validation/expectations.rb +0 -519
  264. data/lib/familia/validation/validation_helpers.rb +0 -443
  265. data/lib/familia/validation/validator.rb +0 -412
  266. data/lib/familia/validation.rb +0 -140
  267. data/try/data_types/set_try.rb +0 -33
  268. data/try/features/relationships/categorical_permissions_try.rb +0 -515
  269. data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
  270. data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
  271. data/try/validation/atomic_operations_try.rb.disabled +0 -320
  272. data/try/validation/command_validation_try.rb.disabled +0 -207
  273. data/try/validation/performance_validation_try.rb.disabled +0 -324
  274. data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -0,0 +1,379 @@
1
+ #!/usr/bin/env ruby
2
+ # examples/single_connection_transaction_confusions.rb
3
+
4
+ # Redis Single Connection Mode Confusions
5
+ #
6
+ # This file demonstrates why mixing Redis operation modes on a single connection
7
+ # causes subtle but critical failures in production applications.
8
+ #
9
+ # Key Concepts:
10
+ # - Normal mode: Commands execute immediately, return actual values
11
+ # - MULTI mode: Commands return "QUEUED", execute atomically on EXEC
12
+ # - Pipeline mode: Commands return Futures, execute in batch
13
+ #
14
+ # Production Impact:
15
+ # - Conditional logic breaks when expecting values but getting "QUEUED"
16
+ # - Business rules fail silently when code assumes immediate execution
17
+ # - Nested operations cause Redis protocol errors
18
+ # - Race conditions emerge from incorrect mode assumptions
19
+ #
20
+ # Key Insight: Connection source should determine which operations are
21
+ # allowed. This is how we prevent bugs like expecting values but getting "QUEUED".
22
+ #
23
+ # Summary of Behaviors:
24
+ #
25
+ # | Handler | Transaction | Pipeline | Ad-hoc Commands |
26
+ # |---------|------------|----------|-----------------|
27
+ # | **FiberTransaction** | Reentrant (same conn) | Error | Use transaction conn |
28
+ # | **FiberConnection** | Error | Error | ✓ Allowed |
29
+ # | **Provider** | ✓ New checkout | ✓ New checkout | ✓ New checkout |
30
+ # | **Default** | ✓ With guards | ✓ With guards | ✓ Check mode |
31
+ # | **Create** | ✓ Fresh conn | ✓ Fresh conn | ✓ Fresh conn |
32
+ #
33
+ #
34
+ # Usage:
35
+ #
36
+ # $ irb
37
+ # load './single_connection_transaction_confusions.rb'
38
+ # demo1_multi_queues_commands
39
+ # demo2_nested_multi_fails
40
+ # ...
41
+
42
+ require 'redis'
43
+
44
+ # Global Redis connection for demonstrations
45
+ $redis = Redis.new(host: 'localhost', port: 6379)
46
+
47
+ # Service class for demonstration 5
48
+ class OrderService
49
+ def initialize(redis_conn)
50
+ @redis = redis_conn
51
+ end
52
+
53
+ # This method assumes immediate command execution
54
+ def process_order(order_id)
55
+ # Step 1: Set processing status
56
+ @redis.set("order:#{order_id}:status", 'processing')
57
+
58
+ # Step 2: Verify status before proceeding (CRITICAL CHECK)
59
+ status = @redis.get("order:#{order_id}:status")
60
+
61
+ # Step 3: Conditional business logic
62
+ if status == 'processing'
63
+ @redis.set("order:#{order_id}:confirmed", 'true')
64
+ puts "✅ Order #{order_id}: Status confirmed, order processed"
65
+ else
66
+ puts "❌ Order #{order_id}: Status check failed - got #{status.inspect}"
67
+ # In production: would trigger error handling, alerts, etc.
68
+ end
69
+ end
70
+ end
71
+
72
+ def setup_redis_demo
73
+ # Sets up clean Redis state for demonstrations
74
+ $redis.flushdb
75
+ $redis.set('counter', '10')
76
+ $redis.set('name', 'Alice')
77
+ $redis.set('score', '50')
78
+ $redis.set('value1', '100')
79
+
80
+ puts '=== Redis Demo Environment Ready ==='
81
+ puts "counter: #{$redis.get('counter')}"
82
+ puts "name: #{$redis.get('name')}"
83
+ puts "score: #{$redis.get('score')}"
84
+ puts
85
+ end
86
+
87
+ def demo1_multi_queues_commands
88
+ # CRITICAL: Commands inside MULTI don't execute immediately
89
+ #
90
+ # Problem: Business logic expecting immediate results gets "QUEUED" instead
91
+ # Impact: Conditional statements, calculations, and validations all break
92
+
93
+ puts '=== Demo 1: Commands inside MULTI return "QUEUED", not values ==='
94
+
95
+ $redis.multi do |conn|
96
+ puts 'Inside MULTI block - all commands are queued...'
97
+
98
+ # These increment counter but return "QUEUED"
99
+ conn.incr('counter')
100
+ result = conn.get('counter')
101
+
102
+ puts "❌ GET inside MULTI returns: #{result.inspect} (expected: '11')"
103
+ puts "❌ Direct redis.get still shows: #{$redis.get('counter')} (expected: '11')"
104
+
105
+ # This condition will ALWAYS be false during MULTI
106
+ if result == '11'
107
+ puts '✅ Counter validation passed'
108
+ else
109
+ puts "⚠️ Counter validation failed - got #{result.inspect}, not '11'"
110
+ end
111
+
112
+ conn.set('name', 'Bob')
113
+ end
114
+
115
+ puts "\n📊 After MULTI/EXEC completes:"
116
+ puts "counter: #{$redis.get('counter')} (now executed)"
117
+ puts "name: #{$redis.get('name')} (now executed)"
118
+ puts "\n💡 Lesson: Never use command results inside MULTI for logic\n\n"
119
+ end
120
+
121
+ def demo2_nested_multi_fails
122
+ # CRITICAL: Redis protocol forbids nested MULTI commands
123
+ #
124
+ # Problem: Code reuse becomes impossible when methods assume fresh connections
125
+ # Impact: Shared utilities break when called from within transactions
126
+
127
+ puts '=== Demo 2: Nested MULTI causes Redis protocol errors ==='
128
+
129
+ begin
130
+ $redis.multi do |outer_conn|
131
+ puts 'In outer MULTI transaction...'
132
+ outer_conn.incr('counter')
133
+
134
+ # This breaks the Redis protocol - MULTI calls cannot be nested
135
+ $redis.multi do |inner_conn|
136
+ puts 'Attempting inner MULTI...'
137
+ inner_conn.incr('counter')
138
+ end
139
+ end
140
+ rescue Redis::CommandError => e
141
+ puts "❌ Redis Error: #{e.message}"
142
+ puts '💡 This makes shared connection patterns dangerous'
143
+ end
144
+
145
+ puts "📊 Counter after error: #{$redis.get('counter')} (partial execution)\n\n"
146
+ end
147
+
148
+ def demo3_pipeline_multi_confusion
149
+ # COMPLEX: Pipeline and MULTI modes create execution order confusion
150
+ #
151
+ # Problem: Commands execute in unexpected batches with mixed return types
152
+ # Impact: Debugging becomes nearly impossible with interleaved operations
153
+
154
+ puts '=== Demo 3: Pipeline + MULTI creates execution chaos ==='
155
+
156
+ $redis.set('counter', '20')
157
+
158
+ results = $redis.pipelined do |pipeline|
159
+ # Returns Future object, not value
160
+ pipeline.get('counter')
161
+
162
+ # MULTI/EXEC block executes inside pipeline
163
+ pipeline.multi do |multi|
164
+ multi.incr('counter') # Will be 21
165
+ multi.incr('counter') # Will be 22
166
+ end
167
+
168
+ # This sees the final value after MULTI completes
169
+ pipeline.get('counter')
170
+ end
171
+
172
+ puts "📊 Pipeline results: #{results.inspect}"
173
+ puts '🔍 Analysis:'
174
+ puts " - results[0]: Initial value ('20')"
175
+ puts " - results[1]: MULTI/EXEC result (['21', '22'])"
176
+ puts " - results[2]: Final value ('22')"
177
+ puts "\n💡 Mixed return types make result processing error-prone\n\n"
178
+ end
179
+
180
+ def demo4_interrupted_multi_cleanup
181
+ # RECOVERY: Redis auto-DISCARD on exceptions, but application state unclear
182
+ #
183
+ # Problem: Partial transaction state is invisible to application code
184
+ # Impact: Error recovery logic may make incorrect assumptions about data state
185
+
186
+ puts '=== Demo 4: Exception handling in MULTI transactions ==='
187
+
188
+ $redis.set('value1', '100')
189
+ original_value = $redis.get('value1')
190
+
191
+ begin
192
+ $redis.multi do |conn|
193
+ conn.incr('value1') # Queued but not executed
194
+
195
+ # Simulate business logic error
196
+ raise StandardError, 'Payment validation failed'
197
+
198
+ conn.incr('value1') # Never reached
199
+ end
200
+ rescue StandardError => e
201
+ puts "🔥 Exception caught: #{e.message}"
202
+
203
+ # Redis automatically DISCARD on exception
204
+ current_value = $redis.get('value1')
205
+ puts "📊 Value after exception: #{current_value} (#{current_value == original_value ? 'unchanged ✅' : 'modified ❌'})"
206
+ end
207
+
208
+ puts "💡 Redis auto-DISCARD protects consistency but masks partial state\n\n"
209
+ end
210
+
211
+ def demo5_shared_connection_logic_failure
212
+ # PRODUCTION BUG: Business logic fails silently with shared connections
213
+ #
214
+ # Problem: Service classes can't detect when they're called inside transactions
215
+ # Impact: Critical business rules execute incorrectly without visible errors
216
+
217
+ puts '=== Demo 5: Shared connection breaks business logic ==='
218
+
219
+ service = OrderService.new($redis)
220
+
221
+ puts '📈 Normal operation:'
222
+ service.process_order(1)
223
+ puts " Result: #{$redis.get('order:1:confirmed')}"
224
+
225
+ puts "\n🔄 Inside MULTI transaction:"
226
+ $redis.multi do |conn|
227
+ # Same service class, but now connection is in MULTI mode
228
+ transaction_service = OrderService.new(conn)
229
+ transaction_service.process_order(2) # Logic fails silently
230
+ end
231
+ puts " Result: #{$redis.get('order:2:confirmed')} (business logic bypassed!)"
232
+
233
+ puts "\n💡 Service classes need connection mode awareness for safety\n\n"
234
+ end
235
+
236
+ def demo6_pipeline_future_confusion
237
+ # TIMING: Pipeline returns Future objects that don't behave like values
238
+ #
239
+ # Problem: Conditional logic using Future objects produces unexpected results
240
+ # Impact: Business rules execute incorrectly based on Future truthiness
241
+
242
+ puts '=== Demo 6: Pipeline Futures break conditional logic ==='
243
+
244
+ $redis.set('score', '30')
245
+
246
+ puts '❌ Broken approach - using Future in conditional:'
247
+ begin
248
+ $redis.pipelined do |pipeline|
249
+ current_score = pipeline.get('score') # Returns Redis::Future
250
+
251
+ puts " Future object: #{current_score.class}"
252
+ puts " Future value (immediate): #{current_score.inspect}"
253
+
254
+ # This will cause an error - Future doesn't have to_i method
255
+ if current_score.to_i > 40
256
+ pipeline.set('achievement', 'high_score')
257
+ puts ' ❌ Achievement awarded (incorrect - score is 30!)'
258
+ end
259
+ end
260
+ rescue NoMethodError => e
261
+ puts " ❌ Error: #{e.message}"
262
+ puts " 💡 Future objects don't behave like values!"
263
+ end
264
+
265
+ puts "\n✅ Correct approach - wait for pipeline completion:"
266
+ results = $redis.pipelined do |pipeline|
267
+ pipeline.get('score')
268
+ pipeline.exists('achievement')
269
+ end
270
+
271
+ current_score = results[0].to_i
272
+ has_achievement = results[1]
273
+
274
+ puts " Actual score: #{current_score}"
275
+ puts " Has achievement: #{has_achievement} (incorrect from above)"
276
+
277
+ puts "\n💡 Always extract values from pipeline results before logic\n\n"
278
+ end
279
+
280
+ def demo7_mode_switching_catastrophe
281
+ # CATASTROPHIC: Methods that switch connection modes mid-operation
282
+ #
283
+ # Problem: Utility functions work in normal mode but break in MULTI mode
284
+ # Impact: Subtle bugs that only appear when code paths intersect
285
+
286
+ puts '=== Demo 7: Mode switching breaks utility functions ==='
287
+
288
+ # Utility function that assumes immediate execution
289
+ def update_stats(redis_conn, key, increment = 10)
290
+ # Read current value
291
+ current = redis_conn.get(key).to_i
292
+ puts " 📖 Read current value: #{current}"
293
+
294
+ # Business logic
295
+ new_value = current + increment
296
+ puts " 🧮 Calculated new value: #{new_value}"
297
+
298
+ # Write new value
299
+ redis_conn.set(key, new_value)
300
+
301
+ # Verification read (common pattern for critical updates)
302
+ verified = redis_conn.get(key)
303
+ puts " ✅ Verification read: #{verified}"
304
+
305
+ verified
306
+ rescue NoMethodError => e
307
+ puts " ❌ Error: #{e.message}"
308
+ puts ' 💡 Function expects immediate values but got Future objects!'
309
+ "ERROR: #{e.message}"
310
+ end
311
+
312
+ $redis.set('stats', '5')
313
+
314
+ puts '📈 Normal mode - utility function works:'
315
+ result = update_stats($redis, 'stats')
316
+ puts " Final result: #{result} ✅"
317
+
318
+ puts "\n🔄 MULTI mode - same function breaks:"
319
+ $redis.set('stats', '5') # Reset
320
+
321
+ $redis.multi do |conn|
322
+ result = update_stats(conn, 'stats')
323
+ puts " Final result: #{result.inspect} ❌ (expected: '15')"
324
+ end
325
+
326
+ # Check what actually happened
327
+ actual_value = $redis.get('stats')
328
+ puts " Actual final value: #{actual_value}"
329
+
330
+ puts "\n💡 Utility functions need explicit mode handling or connection isolation\n\n"
331
+ end
332
+
333
+ def summary_and_solutions
334
+ # Summary of problems and production-ready solutions
335
+
336
+ puts '=== Summary: Redis Connection Mode Problems ==='
337
+ puts
338
+ puts '🔴 PROBLEMS:'
339
+ puts ' 1. MULTI mode returns "QUEUED" instead of values'
340
+ puts ' 2. Pipeline mode returns Futures instead of values'
341
+ puts ' 3. Nested MULTI operations cause protocol errors'
342
+ puts ' 4. Business logic fails silently with wrong assumptions'
343
+ puts ' 5. Conditional statements break with mode confusion'
344
+ puts ' 6. Error recovery becomes unpredictable'
345
+ puts
346
+ puts '🟢 SOLUTIONS:'
347
+ puts ' 1. Connection pooling - fresh connection per operation type'
348
+ puts ' 2. Mode-aware service classes with explicit connection requirements'
349
+ puts ' 3. Never share connections between normal and transactional code'
350
+ puts ' 4. Use connection decorators that enforce mode contracts'
351
+ puts ' 5. Implement connection mode detection and validation'
352
+ puts ' 6. Design APIs that make mode requirements explicit'
353
+ puts
354
+ puts '📚 PRODUCTION PATTERNS:'
355
+ puts ' - Repository pattern with mode-specific connections'
356
+ puts ' - Command/Query separation with dedicated connection pools'
357
+ puts ' - Transaction boundaries clearly defined at service layer'
358
+ puts ' - Connection mode validation in development/testing'
359
+ puts
360
+ end
361
+
362
+ # Main execution - run all demonstrations
363
+ if __FILE__ == $PROGRAM_NAME
364
+ setup_redis_demo
365
+ demo1_multi_queues_commands
366
+ demo2_nested_multi_fails
367
+ demo3_pipeline_multi_confusion
368
+ demo4_interrupted_multi_cleanup
369
+ demo5_shared_connection_logic_failure
370
+ demo6_pipeline_future_confusion
371
+ demo7_mode_switching_catastrophe
372
+ summary_and_solutions
373
+
374
+ puts '🎯 Demo complete! Load in IRB to run individual methods:'
375
+ puts " load './single_connection_transaction_confusions.rb'"
376
+ puts ' demo1_multi_queues_commands'
377
+ puts ' demo2_nested_multi_fails'
378
+ puts ' # ... etc'
379
+ end
data/lib/familia/base.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # lib/familia/base.rb
2
2
 
3
- #
4
3
  module Familia
5
4
  # A common module for Familia::DataType and Familia::Horreum to include.
6
5
  #
@@ -14,7 +13,6 @@ module Familia
14
13
  # @see Familia::DataType
15
14
  #
16
15
  module Base
17
-
18
16
  using Familia::Refinements::TimeLiterals
19
17
 
20
18
  @features_available = nil
@@ -35,12 +33,12 @@ module Familia
35
33
 
36
34
  def add_feature(klass, feature_name, depends_on: [])
37
35
  @features_available ||= {}
38
- Familia.trace :ADD_FEATURE, klass, feature_name, caller(1..1) if Familia.debug?
36
+ Familia.trace :ADD_FEATURE, klass, feature_name if Familia.debug?
39
37
 
40
38
  # Create field definition object
41
39
  feature_def = FeatureDefinition.new(
42
40
  name: feature_name,
43
- depends_on: depends_on,
41
+ depends_on: depends_on
44
42
  )
45
43
 
46
44
  # Track field definitions after defining field methods
@@ -66,6 +64,49 @@ module Familia
66
64
  "#<#{self.class}:0x#{object_id.to_s(16)}>"
67
65
  end
68
66
 
67
+ # Prepares the object for JSON serialization by converting it to a hash.
68
+ # This method provides the data preparation step in the standard Ruby JSON
69
+ # pattern: to_json → as_json → JSON serialization.
70
+ #
71
+ # Implementing classes can override this method to customize their JSON
72
+ # representation. For Horreum objects, this delegates to to_h which returns
73
+ # only the public fields. For DataType objects, this returns the raw value.
74
+ #
75
+ # @param options [Hash] Optional parameters for customizing JSON output
76
+ # @return [Hash, Object] JSON-serializable representation of the object
77
+ #
78
+ def as_json(options = nil)
79
+ if respond_to?(:to_h)
80
+ # Horreum objects - return their field hash
81
+ to_h
82
+ elsif respond_to?(:members)
83
+ # DataType objects (List, Set, etc.) - return their members
84
+ members
85
+ elsif respond_to?(:value)
86
+ # String-like objects or simple values
87
+ value
88
+ else
89
+ # Fallback for objects that don't have standard value methods
90
+ # This ensures we don't expose internal state accidentally
91
+ { class: self.class.name, id: respond_to?(:identifier) ? identifier : object_id }
92
+ end
93
+ end
94
+
95
+ # Converts the object to a JSON string using Familia's JsonSerializer.
96
+ # This method completes the standard Ruby JSON pattern by calling as_json
97
+ # to prepare the data, then using JsonSerializer.dump for serialization.
98
+ #
99
+ # This maintains security by ensuring all JSON serialization goes through
100
+ # Familia's controlled JsonSerializer (OJ in strict mode) rather than
101
+ # potentially unsafe serialization methods.
102
+ #
103
+ # @param options [Hash] Optional parameters passed to as_json
104
+ # @return [String] JSON string representation of the object
105
+ #
106
+ def to_json(options = nil)
107
+ Familia::JsonSerializer.dump(as_json(options))
108
+ end
109
+
69
110
  # Module-level methods for Familia::Base itself
70
111
  class << self
71
112
  attr_reader :features_available, :feature_definitions
@@ -73,12 +114,12 @@ module Familia
73
114
 
74
115
  def add_feature(klass, feature_name, depends_on: [])
75
116
  @features_available ||= {}
76
- Familia.trace :ADD_FEATURE, klass, feature_name, caller(1..1) if Familia.debug?
117
+ Familia.trace :ADD_FEATURE, klass, feature_name if Familia.debug?
77
118
 
78
119
  # Create field definition object
79
120
  feature_def = FeatureDefinition.new(
80
121
  name: feature_name,
81
- depends_on: depends_on,
122
+ depends_on: depends_on
82
123
  )
83
124
 
84
125
  # Track field definitions after defining field methods
@@ -99,9 +140,7 @@ module Familia
99
140
  next unless ancestor.respond_to?(:features_available)
100
141
  next unless ancestor.features_available
101
142
 
102
- if ancestor.features_available.key?(feature_name)
103
- return ancestor.features_available[feature_name]
104
- end
143
+ return ancestor.features_available[feature_name] if ancestor.features_available.key?(feature_name)
105
144
  end
106
145
 
107
146
  nil
@@ -109,7 +148,7 @@ module Familia
109
148
  end
110
149
 
111
150
  def generate_id
112
- @identifier ||= Familia.generate_id # rubocop:disable Naming/MemoizedInstanceVariableName
151
+ @identifier ||= Familia.generate_id
113
152
  end
114
153
 
115
154
  def uuid