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,72 @@
1
+ # ResponsibilityChain Handler Tracking Tryouts
2
+ #
3
+ # Tests that the ResponsibilityChain correctly tracks which handler provided
4
+ # each connection by setting Fiber[:familia_connection_handler_class]. This enables
5
+ # operation mode guards to enforce constraints based on connection source.
6
+ #
7
+ # The chain should:
8
+ # - Try handlers in order until one returns a connection
9
+ # - Set Fiber[:familia_connection_handler_class] to the successful handler's class
10
+ # - Return the connection from the successful handler
11
+ # - Return nil if no handler provides a connection
12
+
13
+ require_relative '../helpers/test_helpers'
14
+
15
+ # Setup - clear any existing fiber state
16
+ Fiber[:familia_connection_handler_class] = nil
17
+ Fiber[:familia_connection] = nil
18
+
19
+ ## Chain tracks CreateConnectionHandler for normal connections
20
+ customer = Customer.new
21
+ customer.custid = 'tracking_test'
22
+ connection = customer.dbclient
23
+ Fiber[:familia_connection_handler_class]
24
+ #=> Familia::Connection::CreateConnectionHandler
25
+
26
+ ## Chain tracks FiberConnectionHandler for middleware connections
27
+ begin
28
+ test_conn = Customer.create_dbclient
29
+ Fiber[:familia_connection] = [test_conn, Familia.middleware_version]
30
+
31
+ customer = Customer.new
32
+ customer.custid = 'fiber_test'
33
+ connection = customer.dbclient
34
+ Fiber[:familia_connection_handler_class]
35
+ ensure
36
+ Fiber[:familia_connection] = nil
37
+ Fiber[:familia_connection_handler_class] = nil
38
+ end
39
+ #=> Familia::Connection::FiberConnectionHandler
40
+
41
+ ## Chain tracks ProviderConnectionHandler for connection providers
42
+ original_provider = Familia.connection_provider
43
+ begin
44
+ Familia.connection_provider = ->(uri) { Redis.new(url: uri) }
45
+
46
+ customer = Customer.new
47
+ customer.custid = 'provider_test'
48
+ connection = customer.dbclient
49
+ Fiber[:familia_connection_handler_class]
50
+ ensure
51
+ Familia.connection_provider = original_provider
52
+ Fiber[:familia_connection_handler_class] = nil
53
+ end
54
+ #=> Familia::Connection::ProviderConnectionHandler
55
+
56
+ ## Chain returns nil when no handler provides connection
57
+ # Create a mock chain with handlers that return nil
58
+ chain = Familia::Connection::ResponsibilityChain.new
59
+ chain.add_handler(Familia::Connection::FiberTransactionHandler.new)
60
+ chain.add_handler(Familia::Connection::FiberConnectionHandler.new)
61
+
62
+ result = chain.handle('test_uri')
63
+ result.nil?
64
+ #=> true
65
+
66
+ ## Chain sets handler class even when tracking connections
67
+ customer = Customer.new
68
+ customer.custid = 'final_test'
69
+ conn = customer.dbclient
70
+ handler_class = Fiber[:familia_connection_handler_class]
71
+ handler_class == Familia::Connection::CreateConnectionHandler && !conn.nil?
72
+ #=> true
@@ -0,0 +1,288 @@
1
+ # Transaction Fallback Integration Tryouts
2
+ #
3
+ # Tests real-world scenarios where transaction fallback is needed, particularly
4
+ # focusing on save operations, relationship updates, and other high-level
5
+ # operations that internally use transactions.
6
+ #
7
+ # These tests verify that the transaction mode configuration works seamlessly
8
+ # with Familia's existing features when connection handlers don't support
9
+ # transactions (e.g., cached connections, middleware connections).
10
+
11
+ require_relative '../helpers/test_helpers'
12
+
13
+ # Setup - store original values
14
+ $original_transaction_mode = Familia.transaction_mode
15
+ $original_provider = Familia.connection_provider
16
+
17
+ # Create test classes for integration testing
18
+ class IntegrationTestUser < Familia::Horreum
19
+ identifier_field :user_id
20
+ field :user_id
21
+ field :name
22
+ field :email
23
+ field :status
24
+ list :activity_log
25
+ set :tags
26
+ zset :scores
27
+ end
28
+
29
+ class IntegrationTestSession < Familia::Horreum
30
+ identifier_field :session_id
31
+ field :session_id
32
+ field :user_id
33
+ field :created_at
34
+ field :expires_at
35
+ end
36
+
37
+ ## Save operation works with transaction fallback in warn mode
38
+ begin
39
+ Familia.configure { |config| config.transaction_mode = :warn }
40
+
41
+ # Force CachedConnectionHandler
42
+ IntegrationTestUser.instance_variable_set(:@dbclient, Familia.create_dbclient)
43
+
44
+ user = IntegrationTestUser.new(
45
+ user_id: 'save_test_001',
46
+ name: 'Test User',
47
+ email: 'test@example.com',
48
+ status: 'active'
49
+ )
50
+
51
+ # Save internally uses transactions for atomicity
52
+ result = user.save
53
+
54
+ # Should complete successfully using individual commands
55
+ result && user.exists?
56
+ ensure
57
+ IntegrationTestUser.remove_instance_variable(:@dbclient)
58
+ Familia.configure { |config| config.transaction_mode = :strict }
59
+ end
60
+ #=> true
61
+
62
+ ## Save operation works with transaction fallback in permissive mode
63
+ begin
64
+ Familia.configure { |config| config.transaction_mode = :permissive }
65
+
66
+ # Force CachedConnectionHandler
67
+ IntegrationTestUser.instance_variable_set(:@dbclient, Familia.create_dbclient)
68
+
69
+ user = IntegrationTestUser.new(
70
+ user_id: 'save_test_002',
71
+ name: 'Permissive User',
72
+ email: 'permissive@example.com'
73
+ )
74
+
75
+ # Should save silently using individual commands
76
+ result = user.save
77
+
78
+ result && user.exists?
79
+ ensure
80
+ IntegrationTestUser.remove_instance_variable(:@dbclient)
81
+ Familia.configure { |config| config.transaction_mode = :strict }
82
+ end
83
+ #=> true
84
+
85
+ ## Multiple field updates work with fallback
86
+ begin
87
+ Familia.configure { |config| config.transaction_mode = :permissive }
88
+ IntegrationTestUser.instance_variable_set(:@dbclient, Familia.create_dbclient)
89
+
90
+ user = IntegrationTestUser.new(user_id: 'update_test_001')
91
+ user.save
92
+
93
+ # Update multiple fields - should use individual commands
94
+ user.name = 'Updated Name'
95
+ user.email = 'updated@example.com'
96
+ user.status = 'updated'
97
+
98
+ result = user.save
99
+
100
+ # Verify all fields were updated
101
+ reloaded_user = IntegrationTestUser.load('update_test_001')
102
+ result &&
103
+ reloaded_user.name == 'Updated Name' &&
104
+ reloaded_user.email == 'updated@example.com' &&
105
+ reloaded_user.status == 'updated'
106
+ ensure
107
+ IntegrationTestUser.remove_instance_variable(:@dbclient)
108
+ Familia.configure { |config| config.transaction_mode = :strict }
109
+ end
110
+ #=> true
111
+
112
+ ## Data type operations work with transaction fallback
113
+
114
+ Familia.configure { |config| config.transaction_mode = :permissive }
115
+ IntegrationTestUser.instance_variable_set(:@dbclient, Familia.create_dbclient)
116
+
117
+ user = IntegrationTestUser.new(user_id: 'datatype_test_001')
118
+ user.transaction { |conn| conn.set('test', 'value') }
119
+
120
+ # Test list operations
121
+ user.activity_log.add('user_created')
122
+ user.activity_log.add('profile_updated')
123
+
124
+ # Test set operations
125
+ user.tags.add('premium')
126
+ user.tags.add('verified')
127
+
128
+ # Test sorted set operations
129
+ user.scores.add('game_score', 100)
130
+ user.scores.add('quiz_score', 85)
131
+
132
+ # Verify all operations worked
133
+ results = [
134
+ user.activity_log.size,
135
+ user.tags.include?('premium'),
136
+ user.scores.score('game_score')
137
+ ]
138
+
139
+ user.destroy!
140
+
141
+ # Clean up test data
142
+ IntegrationTestUser.remove_instance_variable(:@dbclient)
143
+ Familia.configure { |config| config.transaction_mode = :strict }
144
+
145
+ results
146
+ #=> [2, true, 100.0]
147
+
148
+ ## Connection provider integration with transaction modes
149
+ begin
150
+ Familia.configure { |config| config.transaction_mode = :warn }
151
+
152
+ # Set up connection provider that doesn't support transactions
153
+ test_connections = {}
154
+ Familia.connection_provider = lambda do |uri|
155
+ test_connections[uri] ||= Familia.create_dbclient(uri)
156
+ end
157
+
158
+ user = IntegrationTestUser.new(
159
+ user_id: 'provider_test_001',
160
+ name: 'Provider Test User'
161
+ )
162
+
163
+ # Should work with connection provider
164
+ result = user.save
165
+
166
+ result && user.exists?
167
+ ensure
168
+ Familia.connection_provider = $original_provider
169
+ Familia.configure { |config| config.transaction_mode = :strict }
170
+ end
171
+ #=> true
172
+
173
+ ## Mixed connection handlers in same operation
174
+ begin
175
+ Familia.configure { |config| config.transaction_mode = :permissive }
176
+
177
+ # User class with cached connection (fallback mode)
178
+ IntegrationTestUser.instance_variable_set(:@dbclient, Familia.create_dbclient)
179
+
180
+ # Session class without cached connection (normal mode)
181
+ user = IntegrationTestUser.new(user_id: 'mixed_test_001')
182
+ session = IntegrationTestSession.new(
183
+ session_id: 'sess_mixed_001',
184
+ user_id: 'mixed_test_001',
185
+ created_at: Time.now.to_i
186
+ )
187
+
188
+ # Both should save successfully despite different connection handlers
189
+ user_saved = user.save
190
+ session_saved = session.save
191
+
192
+ user_saved && session_saved && user.exists? && session.exists?
193
+ ensure
194
+ IntegrationTestUser.remove_instance_variable(:@dbclient)
195
+ Familia.configure { |config| config.transaction_mode = :strict }
196
+ end
197
+ #=> true
198
+
199
+ ## Transaction mode changes during runtime
200
+ begin
201
+ IntegrationTestUser.instance_variable_set(:@dbclient, Familia.create_dbclient)
202
+
203
+ user = IntegrationTestUser.new(user_id: 'runtime_test_001')
204
+
205
+ # Start in strict mode - should fail
206
+ Familia.configure { |config| config.transaction_mode = :strict }
207
+ strict_failed = false
208
+ begin
209
+ user.transaction { |conn| conn.set('test_key', 'test_value') }
210
+ rescue Familia::OperationModeError
211
+ strict_failed = true
212
+ end
213
+
214
+ # Switch to permissive mode - should work
215
+ Familia.configure { |config| config.transaction_mode = :permissive }
216
+ result = user.transaction { |conn| conn.set('test_key', 'test_value') }
217
+ permissive_worked = result.successful?
218
+
219
+ strict_failed && permissive_worked
220
+ ensure
221
+ IntegrationTestUser.remove_instance_variable(:@dbclient)
222
+ Familia.configure { |config| config.transaction_mode = :strict }
223
+ end
224
+ #=> true
225
+
226
+ ## Large batch operations with transaction fallback
227
+ begin
228
+ Familia.configure { |config| config.transaction_mode = :permissive }
229
+ IntegrationTestUser.instance_variable_set(:@dbclient, Familia.create_dbclient)
230
+
231
+ user = IntegrationTestUser.new(user_id: 'batch_test_001')
232
+
233
+ # Simulate a large batch operation that would normally use transactions
234
+ result = user.transaction do |conn|
235
+ # Add multiple activity log entries
236
+ (1..10).each do |i|
237
+ conn.rpush(user.activity_log.dbkey, "activity_#{i}")
238
+ end
239
+
240
+ # Add multiple tags
241
+ %w[tag1 tag2 tag3 tag4 tag5].each do |tag|
242
+ conn.sadd(user.tags.dbkey, tag)
243
+ end
244
+
245
+ # Add user fields
246
+ conn.hset(user.dbkey, 'batch_processed', 'true')
247
+ end
248
+
249
+ # Should complete successfully and return MultiResult
250
+ result.is_a?(MultiResult) && result.successful?
251
+ ensure
252
+ IntegrationTestUser.remove_instance_variable(:@dbclient)
253
+ Familia.configure { |config| config.transaction_mode = :strict }
254
+ end
255
+ #=> true
256
+
257
+ ## Error handling in individual command mode
258
+ begin
259
+ Familia.configure { |config| config.transaction_mode = :permissive }
260
+ IntegrationTestUser.instance_variable_set(:@dbclient, Familia.create_dbclient)
261
+
262
+ user = IntegrationTestUser.new(user_id: 'error_test_001')
263
+
264
+ # Execute commands where some might fail
265
+ result = user.transaction do |conn|
266
+ conn.hset(user.dbkey, 'field1', 'value1') # Should succeed
267
+
268
+ # This might fail but shouldn't stop other commands
269
+ begin
270
+ conn.incr('non_numeric_key') # Might fail if key exists as string
271
+ rescue => e
272
+ # Individual commands can handle their own errors
273
+ end
274
+
275
+ conn.hset(user.dbkey, 'field2', 'value2') # Should succeed
276
+ end
277
+
278
+ # Should return MultiResult even with mixed success/failure
279
+ result.is_a?(MultiResult)
280
+ ensure
281
+ IntegrationTestUser.remove_instance_variable(:@dbclient)
282
+ Familia.configure { |config| config.transaction_mode = :strict }
283
+ end
284
+ #=> true
285
+
286
+ # Cleanup - restore original settings
287
+ Familia.configure { |config| config.transaction_mode = $original_transaction_mode }
288
+ Familia.connection_provider = $original_provider
@@ -0,0 +1,153 @@
1
+ # Transaction Mode: Permissive Tryouts
2
+ #
3
+ # Tests permissive transaction mode behavior where operations silently
4
+ # execute commands individually when transactions are unavailable.
5
+ #
6
+ # Permissive mode: Silently uses IndividualCommandProxy for fallback
7
+
8
+ require_relative '../helpers/test_helpers'
9
+
10
+ # Test class for permissive mode testing
11
+ class PermissiveModeTestCustomer < Familia::Horreum
12
+ identifier_field :custid
13
+ field :custid
14
+ field :name
15
+ field :status
16
+ end
17
+
18
+ ## Permissive mode can be configured
19
+ Familia.configure { |config| config.transaction_mode = :permissive }
20
+ Familia.transaction_mode
21
+ #=> :permissive
22
+
23
+ ## Permissive mode silently executes individual commands with CachedConnectionHandler
24
+ begin
25
+ # Force CachedConnectionHandler
26
+ PermissiveModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
27
+
28
+ customer = PermissiveModeTestCustomer.new(custid: 'permissive_test')
29
+ result = customer.transaction do |conn|
30
+ # Should be IndividualCommandProxy
31
+ conn.class == Familia::Connection::IndividualCommandProxy &&
32
+ conn.hset(customer.dbkey, 'name', 'Permissive Mode Works') &&
33
+ conn.hget(customer.dbkey, 'name')
34
+ end
35
+
36
+ result.is_a?(MultiResult) && result.results.last == 'Permissive Mode Works'
37
+ ensure
38
+ PermissiveModeTestCustomer.remove_instance_variable(:@dbclient)
39
+ end
40
+ #=> true
41
+
42
+ ## Permissive mode works silently with FiberConnectionHandler
43
+ begin
44
+ # Simulate middleware connection
45
+ Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
46
+ Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
47
+ customer = PermissiveModeTestCustomer.new(custid: 'fiber_permissive_test')
48
+
49
+ result = customer.transaction do |conn|
50
+ conn.class == Familia::Connection::IndividualCommandProxy &&
51
+ conn.hset(customer.dbkey, 'source', 'fiber_permissive') &&
52
+ conn.hget(customer.dbkey, 'source')
53
+ end
54
+
55
+ result.is_a?(MultiResult) && result.results.last == 'fiber_permissive'
56
+ ensure
57
+ Fiber[:familia_connection] = nil
58
+ Fiber[:familia_connection_handler_class] = nil
59
+ end
60
+ #=> true
61
+
62
+ ## Permissive mode still uses normal transactions when available
63
+ begin
64
+ customer = PermissiveModeTestCustomer.new(custid: 'normal_permissive_test')
65
+ result = customer.transaction do |conn|
66
+ # Should be Redis::MultiConnection for normal transactions
67
+ conn.class == Redis::MultiConnection &&
68
+ conn.hset(customer.dbkey, 'type', 'normal in permissive mode') &&
69
+ conn.hget(customer.dbkey, 'type')
70
+ end
71
+ result.is_a?(MultiResult) && result.results.last == 'normal in permissive mode'
72
+ end
73
+ #=> true
74
+
75
+ ## Permissive mode handles complex operations silently
76
+ begin
77
+ PermissiveModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
78
+
79
+ customer = PermissiveModeTestCustomer.new(custid: 'complex_permissive_test')
80
+ result = customer.transaction do |conn|
81
+ # Multiple operations that would normally be atomic
82
+ conn.hset(customer.dbkey, 'status', 'processing')
83
+ conn.hset(customer.dbkey, 'updated_at', Time.now.to_i)
84
+ conn.hset(customer.dbkey, 'version', '1.0')
85
+ conn.hget(customer.dbkey, 'status')
86
+ end
87
+
88
+ result.is_a?(MultiResult) && result.results.last == 'processing'
89
+ ensure
90
+ PermissiveModeTestCustomer.remove_instance_variable(:@dbclient)
91
+ end
92
+ #=> true
93
+
94
+ ## Permissive mode works with nested calls
95
+ begin
96
+ PermissiveModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
97
+
98
+ customer = PermissiveModeTestCustomer.new(custid: 'nested_permissive_test')
99
+
100
+ outer_result = customer.transaction do |outer_conn|
101
+ outer_conn.hset(customer.dbkey, 'outer', 'value')
102
+
103
+ inner_result = customer.transaction do |inner_conn|
104
+ inner_conn.hset(customer.dbkey, 'inner', 'nested_value')
105
+ end
106
+
107
+ # Both should return MultiResult
108
+ inner_result.is_a?(MultiResult)
109
+ end
110
+
111
+ outer_result.is_a?(MultiResult)
112
+ ensure
113
+ PermissiveModeTestCustomer.remove_instance_variable(:@dbclient)
114
+ end
115
+ #=> true
116
+
117
+ ## Batch operations work silently in permissive mode
118
+ begin
119
+ PermissiveModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
120
+
121
+ customer = PermissiveModeTestCustomer.new(custid: 'batch_permissive_test')
122
+
123
+ # Large batch that would normally be atomic
124
+ result = customer.transaction do |conn|
125
+ 10.times do |i|
126
+ conn.hset(customer.dbkey, "field_#{i}", "value_#{i}")
127
+ end
128
+ conn.hget(customer.dbkey, 'field_9')
129
+ end
130
+
131
+ result.is_a?(MultiResult) && result.results.last == 'value_9'
132
+ ensure
133
+ PermissiveModeTestCustomer.remove_instance_variable(:@dbclient)
134
+ end
135
+ #=> true
136
+
137
+ ## Save operations work in permissive mode
138
+ begin
139
+ PermissiveModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
140
+
141
+ customer = PermissiveModeTestCustomer.new(
142
+ custid: 'save_permissive_test',
143
+ name: 'Permissive Save User',
144
+ status: 'active'
145
+ )
146
+
147
+ # Should save successfully using individual commands
148
+ save_result = customer.save
149
+ save_result && customer.exists?
150
+ ensure
151
+ PermissiveModeTestCustomer.remove_instance_variable(:@dbclient)
152
+ end
153
+ #=> true
@@ -0,0 +1,98 @@
1
+ # Transaction Mode: Strict Tryouts
2
+ #
3
+ # Tests strict transaction mode behavior where operations fail fast
4
+ # when connection handlers don't support transactions.
5
+ #
6
+ # Strict mode: Raises OperationModeError when transaction unavailable
7
+
8
+ require_relative '../helpers/test_helpers'
9
+
10
+ # Test class for strict mode testing
11
+ class StrictModeTestCustomer < Familia::Horreum
12
+ identifier_field :custid
13
+ field :custid
14
+ field :name
15
+ end
16
+
17
+ ## Strict mode can be configured
18
+ Familia.configure { |config| config.transaction_mode = :strict }
19
+ Familia.transaction_mode
20
+ #=> :strict
21
+
22
+ ## Strict mode raises error with CachedConnectionHandler
23
+
24
+ # Force CachedConnectionHandler
25
+ StrictModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
26
+
27
+ customer = StrictModeTestCustomer.new(custid: 'strict_test')
28
+ customer.transaction do |conn|
29
+ conn.hset(customer.dbkey, 'name', 'Should Not Work')
30
+ end
31
+
32
+ #=:> Familia::OperationModeError
33
+ #=~> /Cannot start transaction with/
34
+ #=~> /CachedConnectionHandler/
35
+
36
+ ## Clear the dbclient instance var
37
+ StrictModeTestCustomer.remove_instance_variable(:@dbclient)
38
+ #=*>
39
+
40
+ ## Strict mode raises error with FiberConnectionHandler
41
+ begin
42
+ # Simulate middleware connection
43
+ Fiber[:familia_connection] = [Customer.create_dbclient, Familia.middleware_version]
44
+ Fiber[:familia_connection_handler_class] = Familia::Connection::FiberConnectionHandler
45
+ customer = StrictModeTestCustomer.new(custid: 'fiber_test')
46
+ customer.transaction { |conn| conn.set('test', 'value') }
47
+ false
48
+ rescue Familia::OperationModeError => e
49
+ e.message.include?('FiberConnectionHandler')
50
+ ensure
51
+ Fiber[:familia_connection] = nil
52
+ Fiber[:familia_connection_handler_class] = nil
53
+ end
54
+ #=> true
55
+
56
+ ## Strict mode allows normal transactions with CreateConnectionHandler
57
+ begin
58
+ customer = StrictModeTestCustomer.new(custid: 'normal_test')
59
+ result = customer.transaction do |conn|
60
+ conn.hset(customer.dbkey, 'type', 'normal transaction')
61
+ conn.hget(customer.dbkey, 'type')
62
+ end
63
+ result.is_a?(MultiResult) && result.results.last == 'normal transaction'
64
+ end
65
+ #=> true
66
+
67
+ ## Strict mode works with ProviderConnectionHandler
68
+ original_provider = Familia.connection_provider
69
+ begin
70
+ Familia.connection_provider = ->(uri) { Redis.new(url: uri) }
71
+ customer = StrictModeTestCustomer.new(custid: 'provider_test')
72
+ result = customer.transaction do |conn|
73
+ conn.hset(customer.dbkey, 'source', 'provider')
74
+ conn.hget(customer.dbkey, 'source')
75
+ end
76
+ result.is_a?(MultiResult) && result.results.last == 'provider'
77
+ ensure
78
+ Familia.connection_provider = original_provider
79
+ end
80
+ #=> true
81
+
82
+ ## Global transactions respect strict mode with cached connections
83
+ begin
84
+ # Set a cached connection on the Familia module itself would be complex
85
+ # Instead test that cached connections on models affect their transactions
86
+ StrictModeTestCustomer.instance_variable_set(:@dbclient, Familia.create_dbclient)
87
+
88
+ customer = StrictModeTestCustomer.new(custid: 'global_strict_test')
89
+ customer.transaction do |conn|
90
+ conn.set('should_fail', 'value')
91
+ end
92
+ false
93
+ rescue Familia::OperationModeError
94
+ true
95
+ ensure
96
+ StrictModeTestCustomer.remove_instance_variable(:@dbclient)
97
+ end
98
+ #=> true