familia 2.0.0.pre14 → 2.0.0.pre16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/code-quality.yml +138 -0
  3. data/.github/workflows/code-smellage.yml +145 -0
  4. data/.github/workflows/docs.yml +31 -8
  5. data/.gitignore +1 -1
  6. data/.pre-commit-config.yaml +7 -1
  7. data/.reek.yml +98 -0
  8. data/.rubocop.yml +48 -10
  9. data/.talismanrc +9 -0
  10. data/.yardopts +18 -13
  11. data/CHANGELOG.rst +66 -6
  12. data/CLAUDE.md +1 -1
  13. data/Gemfile +6 -5
  14. data/Gemfile.lock +99 -23
  15. data/LICENSE.txt +1 -1
  16. data/README.md +285 -85
  17. data/changelog.d/README.md +2 -2
  18. data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
  19. data/docs/archive/FAMILIA_TECHNICAL.md +41 -41
  20. data/docs/archive/FAMILIA_UPDATE.md +3 -3
  21. data/docs/archive/README.md +3 -2
  22. data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
  23. data/docs/conf.py +29 -0
  24. data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
  25. data/docs/guides/feature-encrypted-fields.md +785 -0
  26. data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
  27. data/docs/guides/feature-external-identifiers.md +637 -0
  28. data/docs/guides/feature-object-identifiers.md +435 -0
  29. data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
  30. data/docs/guides/feature-relationships-methods.md +684 -0
  31. data/docs/guides/feature-relationships.md +200 -0
  32. data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
  33. data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
  34. data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
  35. data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
  36. data/docs/guides/index.md +176 -0
  37. data/docs/guides/{Security-Model.md → security-model.md} +1 -1
  38. data/docs/migrating/v2.0.0-pre.md +1 -1
  39. data/docs/migrating/v2.0.0-pre11.md +4 -4
  40. data/docs/migrating/v2.0.0-pre12.md +2 -2
  41. data/docs/migrating/v2.0.0-pre13.md +1 -1
  42. data/docs/migrating/v2.0.0-pre5.md +33 -12
  43. data/docs/migrating/v2.0.0-pre6.md +2 -2
  44. data/docs/migrating/v2.0.0-pre7.md +8 -8
  45. data/docs/overview.md +623 -19
  46. data/docs/reference/api-technical.md +1365 -0
  47. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
  48. data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
  49. data/examples/autoloader/mega_customer.rb +3 -1
  50. data/examples/encrypted_fields.rb +378 -0
  51. data/examples/json_usage_patterns.rb +144 -0
  52. data/examples/relationships.rb +13 -13
  53. data/examples/safe_dump.rb +6 -6
  54. data/examples/single_connection_transaction_confusions.rb +379 -0
  55. data/lib/familia/base.rb +49 -10
  56. data/lib/familia/connection/handlers.rb +223 -0
  57. data/lib/familia/connection/individual_command_proxy.rb +64 -0
  58. data/lib/familia/connection/middleware.rb +75 -0
  59. data/lib/familia/connection/operation_core.rb +93 -0
  60. data/lib/familia/connection/operations.rb +277 -0
  61. data/lib/familia/connection/pipeline_core.rb +87 -0
  62. data/lib/familia/connection/transaction_core.rb +100 -0
  63. data/lib/familia/connection.rb +60 -186
  64. data/lib/familia/data_type/commands.rb +53 -51
  65. data/lib/familia/data_type/serialization.rb +108 -107
  66. data/lib/familia/data_type/types/counter.rb +1 -1
  67. data/lib/familia/data_type/types/hashkey.rb +13 -10
  68. data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
  69. data/lib/familia/data_type/types/lock.rb +3 -2
  70. data/lib/familia/data_type/types/sorted_set.rb +26 -15
  71. data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -5
  72. data/lib/familia/data_type/types/unsorted_set.rb +20 -27
  73. data/lib/familia/data_type.rb +75 -47
  74. data/lib/familia/distinguisher.rb +85 -0
  75. data/lib/familia/encryption/encrypted_data.rb +15 -24
  76. data/lib/familia/encryption/manager.rb +6 -4
  77. data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
  78. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
  79. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
  80. data/lib/familia/encryption/request_cache.rb +7 -7
  81. data/lib/familia/encryption.rb +2 -3
  82. data/lib/familia/errors.rb +9 -3
  83. data/lib/familia/{autoloader.rb → features/autoloader.rb} +49 -23
  84. data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
  85. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
  86. data/lib/familia/features/encrypted_fields.rb +68 -66
  87. data/lib/familia/features/expiration/extensions.rb +61 -0
  88. data/lib/familia/features/expiration.rb +35 -87
  89. data/lib/familia/features/external_identifier.rb +11 -12
  90. data/lib/familia/features/object_identifier.rb +58 -20
  91. data/lib/familia/features/quantization.rb +17 -22
  92. data/lib/familia/features/relationships/README.md +97 -0
  93. data/lib/familia/features/relationships/collection_operations.rb +104 -0
  94. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
  95. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +301 -0
  96. data/lib/familia/features/relationships/indexing.rb +176 -256
  97. data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
  98. data/lib/familia/features/relationships/participation/participant_methods.rb +160 -0
  99. data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
  100. data/lib/familia/features/relationships/participation.rb +656 -0
  101. data/lib/familia/features/relationships/participation_relationship.rb +31 -0
  102. data/lib/familia/features/relationships/score_encoding.rb +20 -20
  103. data/lib/familia/features/relationships.rb +69 -271
  104. data/lib/familia/features/safe_dump.rb +127 -132
  105. data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
  106. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
  107. data/lib/familia/features/transient_fields.rb +5 -5
  108. data/lib/familia/features.rb +21 -21
  109. data/lib/familia/field_type.rb +24 -4
  110. data/lib/familia/horreum/core/connection.rb +229 -26
  111. data/lib/familia/horreum/core/database_commands.rb +27 -17
  112. data/lib/familia/horreum/core/serialization.rb +40 -20
  113. data/lib/familia/horreum/core/utils.rb +2 -1
  114. data/lib/familia/horreum/shared/settings.rb +2 -1
  115. data/lib/familia/horreum/subclass/definition.rb +33 -45
  116. data/lib/familia/horreum/subclass/management.rb +72 -24
  117. data/lib/familia/horreum/subclass/related_fields_management.rb +82 -21
  118. data/lib/familia/horreum.rb +196 -114
  119. data/lib/familia/json_serializer.rb +0 -1
  120. data/lib/familia/logging.rb +11 -114
  121. data/lib/familia/refinements/dear_json.rb +122 -0
  122. data/lib/familia/refinements/logger_trace.rb +20 -17
  123. data/lib/familia/refinements/stylize_words.rb +65 -0
  124. data/lib/familia/refinements/time_literals.rb +60 -52
  125. data/lib/familia/refinements.rb +2 -1
  126. data/lib/familia/secure_identifier.rb +60 -28
  127. data/lib/familia/settings.rb +83 -7
  128. data/lib/familia/utils.rb +5 -87
  129. data/lib/familia/verifiable_identifier.rb +4 -4
  130. data/lib/familia/version.rb +1 -1
  131. data/lib/familia.rb +72 -15
  132. data/lib/middleware/database_middleware.rb +56 -14
  133. data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
  134. data/try/configuration/scenarios_try.rb +1 -1
  135. data/try/connection/fiber_context_preservation_try.rb +250 -0
  136. data/try/connection/handler_constraints_try.rb +59 -0
  137. data/try/connection/operation_mode_guards_try.rb +208 -0
  138. data/try/connection/pipeline_fallback_integration_try.rb +128 -0
  139. data/try/connection/responsibility_chain_tracking_try.rb +72 -0
  140. data/try/connection/transaction_fallback_integration_try.rb +288 -0
  141. data/try/connection/transaction_mode_permissive_try.rb +153 -0
  142. data/try/connection/transaction_mode_strict_try.rb +98 -0
  143. data/try/connection/transaction_mode_warn_try.rb +131 -0
  144. data/try/connection/transaction_modes_try.rb +249 -0
  145. data/try/core/autoloader_try.rb +129 -11
  146. data/try/core/connection_try.rb +7 -7
  147. data/try/core/conventional_inheritance_try.rb +130 -0
  148. data/try/core/create_method_try.rb +15 -23
  149. data/try/core/database_consistency_try.rb +10 -10
  150. data/try/core/errors_try.rb +8 -11
  151. data/try/core/familia_extended_try.rb +2 -2
  152. data/try/core/familia_members_methods_try.rb +76 -0
  153. data/try/core/isolated_dbclient_try.rb +165 -0
  154. data/try/core/middleware_try.rb +16 -16
  155. data/try/core/persistence_operations_try.rb +4 -4
  156. data/try/core/pools_try.rb +42 -26
  157. data/try/core/secure_identifier_try.rb +28 -24
  158. data/try/core/time_utils_try.rb +10 -10
  159. data/try/core/tools_try.rb +1 -1
  160. data/try/core/utils_try.rb +2 -2
  161. data/try/data_types/boolean_try.rb +4 -4
  162. data/try/data_types/datatype_base_try.rb +0 -2
  163. data/try/data_types/list_try.rb +10 -10
  164. data/try/data_types/sorted_set_try.rb +5 -5
  165. data/try/data_types/string_try.rb +12 -12
  166. data/try/data_types/unsortedset_try.rb +33 -0
  167. data/try/debugging/cache_behavior_tracer.rb +7 -7
  168. data/try/debugging/debug_aad_process.rb +1 -1
  169. data/try/debugging/debug_concealed_internal.rb +1 -1
  170. data/try/debugging/debug_cross_context.rb +1 -1
  171. data/try/debugging/debug_fresh_cross_context.rb +1 -1
  172. data/try/debugging/encryption_method_tracer.rb +10 -10
  173. data/try/edge_cases/hash_symbolization_try.rb +1 -1
  174. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  175. data/try/encryption/config_persistence_try.rb +2 -2
  176. data/try/encryption/encryption_core_try.rb +19 -19
  177. data/try/encryption/instance_variable_scope_try.rb +1 -1
  178. data/try/encryption/module_loading_try.rb +2 -2
  179. data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
  180. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
  181. data/try/encryption/secure_memory_handling_try.rb +1 -1
  182. data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
  183. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  184. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
  185. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
  186. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
  187. data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
  188. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  189. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  190. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
  191. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
  192. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  193. data/try/features/feature_dependencies_try.rb +3 -3
  194. data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
  195. data/try/features/object_identifier/object_identifier_try.rb +10 -0
  196. data/try/features/quantization/quantization_try.rb +1 -1
  197. data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
  198. data/try/features/relationships/indexing_try.rb +433 -0
  199. data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
  200. data/try/features/relationships/participation_commands_verification_try.rb +105 -0
  201. data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
  202. data/try/features/relationships/participation_reverse_index_try.rb +196 -0
  203. data/try/features/relationships/relationships_api_changes_try.rb +72 -71
  204. data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
  205. data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
  206. data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
  207. data/try/features/relationships/relationships_performance_try.rb +20 -20
  208. data/try/features/relationships/relationships_try.rb +27 -38
  209. data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
  210. data/try/features/transient_fields/refresh_reset_try.rb +1 -1
  211. data/try/features/transient_fields/simple_refresh_test.rb +1 -1
  212. data/try/helpers/test_cleanup.rb +86 -0
  213. data/try/helpers/test_helpers.rb +3 -3
  214. data/try/horreum/base_try.rb +3 -2
  215. data/try/horreum/commands_try.rb +1 -1
  216. data/try/horreum/destroy_related_fields_cleanup_try.rb +330 -0
  217. data/try/horreum/initialization_try.rb +11 -7
  218. data/try/horreum/relations_try.rb +21 -13
  219. data/try/horreum/serialization_try.rb +12 -11
  220. data/try/integration/cross_component_try.rb +3 -3
  221. data/try/memory/memory_basic_test.rb +1 -1
  222. data/try/memory/memory_docker_ruby_dump.sh +1 -1
  223. data/try/models/customer_safe_dump_try.rb +1 -1
  224. data/try/models/customer_try.rb +8 -10
  225. data/try/models/datatype_base_try.rb +3 -3
  226. data/try/models/familia_object_try.rb +9 -8
  227. data/try/performance/benchmarks_try.rb +2 -2
  228. data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
  229. data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
  230. data/try/prototypes/atomic_saves_v4.rb +1 -1
  231. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
  232. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  233. data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  234. data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
  235. data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
  236. data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
  237. data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
  238. data/try/prototypes/pooling/pool_siege.rb +11 -11
  239. data/try/prototypes/pooling/run_stress_tests.rb +7 -7
  240. data/try/refinements/dear_json_array_methods_try.rb +53 -0
  241. data/try/refinements/dear_json_hash_methods_try.rb +54 -0
  242. data/try/refinements/logger_trace_methods_try.rb +44 -0
  243. data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
  244. data/try/refinements/time_literals_string_methods_try.rb +80 -0
  245. metadata +77 -45
  246. data/.rubocop_todo.yml +0 -208
  247. data/docs/connection_pooling.md +0 -192
  248. data/docs/guides/Connection-Pooling-Guide.md +0 -437
  249. data/docs/guides/Encrypted-Fields-Overview.md +0 -101
  250. data/docs/guides/Feature-System-Autoloading.md +0 -228
  251. data/docs/guides/Home.md +0 -116
  252. data/docs/guides/Relationships-Guide.md +0 -737
  253. data/docs/guides/relationships-methods.md +0 -266
  254. data/docs/reference/auditing_database_commands.rb +0 -228
  255. data/examples/permissions.rb +0 -240
  256. data/lib/familia/features/autoloadable.rb +0 -113
  257. data/lib/familia/features/relationships/cascading.rb +0 -437
  258. data/lib/familia/features/relationships/membership.rb +0 -497
  259. data/lib/familia/features/relationships/permission_management.rb +0 -264
  260. data/lib/familia/features/relationships/querying.rb +0 -615
  261. data/lib/familia/features/relationships/redis_operations.rb +0 -274
  262. data/lib/familia/features/relationships/tracking.rb +0 -418
  263. data/lib/familia/refinements/snake_case.rb +0 -40
  264. data/lib/familia/validation/command_recorder.rb +0 -336
  265. data/lib/familia/validation/expectations.rb +0 -519
  266. data/lib/familia/validation/validation_helpers.rb +0 -443
  267. data/lib/familia/validation/validator.rb +0 -412
  268. data/lib/familia/validation.rb +0 -140
  269. data/try/data_types/set_try.rb +0 -33
  270. data/try/features/autoloadable/autoloadable_try.rb +0 -61
  271. data/try/features/relationships/categorical_permissions_try.rb +0 -515
  272. data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -111
  273. data/try/validation/atomic_operations_try.rb.disabled +0 -320
  274. data/try/validation/command_validation_try.rb.disabled +0 -207
  275. data/try/validation/performance_validation_try.rb.disabled +0 -324
  276. data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -1,266 +0,0 @@
1
- # Relationship Methods
2
-
3
- Here are the methods automatically generated for each relationship type in the new clean API:
4
-
5
- ## member_of Relationships
6
-
7
- When you declare:
8
- ```ruby
9
- class Domain < Familia::Horreum
10
- member_of Customer, :domains
11
- end
12
- ```
13
-
14
- **Generated methods on Domain instances:**
15
- - `add_to_customer_domains(customer)` - Add this domain to customer's domains collection
16
- - `remove_from_customer_domains(customer)` - Remove this domain from customer's domains collection
17
- - `in_customer_domains?(customer)` - Check if this domain is in customer's domains collection
18
-
19
- **Collection << operator support:**
20
- ```ruby
21
- customer.domains << domain # Clean Ruby-like syntax (equivalent to domain.add_to_customer_domains(customer))
22
- ```
23
-
24
- The method names follow the pattern: `{action}_to_{lowercase_class_name}_{collection_name}`
25
-
26
- ## tracked_in Relationships
27
-
28
- ### Class-Level Tracking (class_tracked_in)
29
- When you declare:
30
- ```ruby
31
- class Customer < Familia::Horreum
32
- class_tracked_in :all_customers, score: :created_at
33
- end
34
- ```
35
-
36
- **Generated class methods:**
37
- - `Customer.add_to_all_customers(customer)` - Add customer to class-level tracking
38
- - `Customer.remove_from_all_customers(customer)` - Remove customer from class-level tracking
39
- - `Customer.all_customers` - Access the sorted set collection directly
40
-
41
- **Automatic behavior:**
42
- - Objects are automatically added to class-level tracking collections when saved
43
- - No manual calls required for basic tracking
44
-
45
- ### Relationship Tracking (tracked_in with parent class)
46
- When you declare:
47
- ```ruby
48
- class User < Familia::Horreum
49
- tracked_in Team, :active_users, score: :last_seen
50
- end
51
- ```
52
-
53
- **Generated methods:**
54
- - Team instance methods for managing the active_users collection
55
- - Automatic score calculation based on the provided lambda or field
56
-
57
- ## indexed_by Relationships
58
-
59
- The `indexed_by` method creates Redis hash-based indexes for O(1) field lookups with automatic management.
60
-
61
- ### Class-Level Indexing (class_indexed_by)
62
- When you declare:
63
- ```ruby
64
- class Customer < Familia::Horreum
65
- class_indexed_by :email, :email_lookup
66
- end
67
- ```
68
-
69
- **Generated methods:**
70
- - **Instance methods**: `customer.add_to_class_email_lookup`, `customer.remove_from_class_email_lookup`
71
- - **Class methods**: `Customer.email_lookup` (returns hash), `Customer.find_by_email(email)`
72
-
73
- **Automatic behavior:**
74
- - Objects are automatically added to class-level indexes when saved
75
- - Index updates happen transparently on field changes
76
-
77
- Redis key pattern: `customer:email_lookup`
78
-
79
- ### Relationship-Scoped Indexing (indexed_by with parent:)
80
- When you declare:
81
- ```ruby
82
- class Domain < Familia::Horreum
83
- indexed_by :name, :domain_index, parent: Customer
84
- end
85
- ```
86
-
87
- **Generated class methods on Customer:**
88
- - `Customer#find_by_name(domain_name)` - Find domain by name within this customer
89
- - `Customer#find_all_by_name(domain_names)` - Find multiple domains by names
90
-
91
- Redis key pattern: `domain:domain_index` (all stored at class level for consistency)
92
-
93
- ### When to Use Each Context
94
- - **Class-level context (`class_indexed_by`)**: Use for system-wide lookups where the field value should be unique across all instances
95
- - Examples: email addresses, usernames, API keys
96
- - **Relationship context (`parent:` parameter)**: Use for relationship-scoped lookups where the field value is unique within a specific context
97
- - Examples: domain names per customer, project names per team
98
-
99
- ## Complete Example
100
-
101
- From the relationships example file, you can see the new clean API in action:
102
-
103
- ```ruby
104
- # Domain declares membership in Customer collections
105
- class Domain < Familia::Horreum
106
- member_of Customer, :domains
107
- class_tracked_in :active_domains, score: -> { status == 'active' ? Time.now.to_i : 0 }
108
- end
109
-
110
- class Customer < Familia::Horreum
111
- class_indexed_by :email, :email_lookup
112
- class_tracked_in :all_customers, score: :created_at
113
- end
114
- ```
115
-
116
- **Usage with automatic behavior:**
117
- ```ruby
118
- # Create and save objects (automatic indexing and tracking)
119
- customer = Customer.new(email: "admin@acme.com", name: "Acme Corp")
120
- customer.save # Automatically added to email_lookup and all_customers
121
-
122
- domain = Domain.new(name: "acme.com", status: "active")
123
- domain.save # Automatically added to active_domains
124
-
125
- # Clean relationship syntax
126
- customer.domains << domain # Ruby-like collection syntax
127
-
128
- # Query relationships
129
- domain.in_customer_domains?(customer) # => true
130
- customer.domains.member?(domain.identifier) # => true
131
-
132
- # O(1) lookups with automatic management
133
- found_id = Customer.email_lookup.get("admin@acme.com")
134
- ```
135
-
136
- ## Method Naming Conventions
137
-
138
- The relationship system uses consistent naming patterns:
139
- - **member_of**: `{add_to|remove_from|in}_#{parent_class.downcase}_#{collection_name}`
140
- - **class_tracked_in**: `{add_to|remove_from}_#{collection_name}` (class methods)
141
- - **class_indexed_by**: `{add_to|remove_from}_class_#{index_name}` (instance methods)
142
- - **indexed_by with parent**: `{add_to|remove_from}_#{parent_class.downcase}_#{index_name}` (instance methods)
143
-
144
- ## Key Benefits
145
-
146
- - **Automatic management**: Save operations update indexes and tracking automatically
147
- - **Ruby-idiomatic**: Use `<<` operator for natural collection syntax
148
- - **Consistent storage**: All indexes stored at class level for architectural simplicity
149
- - **Clean API**: Removed complex global vs parent conditionals for simpler method generation
150
-
151
-
152
- ## Context Parameter Usage Patterns
153
-
154
- The `context` parameter in `indexed_by` is a fundamental architectural decision that determines index scope and ownership. Here are practical patterns for when to use each approach:
155
-
156
- ### Global Context Pattern
157
- Use `class_indexed_by` when field values should be unique system-wide:
158
-
159
- ```ruby
160
- class User < Familia::Horreum
161
- feature :relationships
162
-
163
- identifier_field :user_id
164
- field :user_id, :email, :username
165
-
166
- # System-wide unique email lookup
167
- class_indexed_by :email, :email_lookup
168
- class_indexed_by :username, :username_lookup
169
- end
170
-
171
- # Usage:
172
- user.add_to_global_email_lookup
173
- found_user_id = User.email_lookup.get("john@example.com")
174
- ```
175
-
176
- **Redis keys generated**: `global:email_lookup`, `global:username_lookup`
177
-
178
- ### Parent Context Pattern
179
- Use `parent: SomeClass` when field values are unique within a specific parent context:
180
-
181
- ```ruby
182
- class Customer < Familia::Horreum
183
- feature :relationships
184
-
185
- identifier_field :custid
186
- field :custid, :name
187
- sorted_set :domains
188
- end
189
-
190
- class Domain < Familia::Horreum
191
- feature :relationships
192
-
193
- identifier_field :domain_id
194
- field :domain_id, :name, :subdomain
195
-
196
- # Domains are unique per customer (customer can't have duplicate domain names)
197
- indexed_by :name, :domain_index, parent: Customer
198
- indexed_by :subdomain, :subdomain_index, parent: Customer
199
- end
200
-
201
- # Usage:
202
- customer = Customer.new(custid: "cust_123")
203
- customer.find_by_name("example.com") # Find domain within this customer
204
- customer.find_all_by_subdomain(["www", "api"]) # Find multiple subdomains
205
- ```
206
-
207
- **Redis keys generated**: `customer:cust_123:domain_index`, `customer:cust_123:subdomain_index`
208
-
209
- ### Mixed Pattern Example
210
- A real-world example showing both patterns:
211
-
212
- ```ruby
213
- class ApiKey < Familia::Horreum
214
- feature :relationships
215
-
216
- identifier_field :key_id
217
- field :key_id, :key_hash, :name, :scope
218
-
219
- # API key hashes must be globally unique
220
- class_indexed_by :key_hash, :global_key_lookup
221
-
222
- # But key names can be reused across different customers
223
- indexed_by :name, :customer_key_lookup, parent: Customer
224
- indexed_by :scope, :scope_lookup, parent: Customer
225
- end
226
-
227
- # Usage examples:
228
- # Global lookup (system-wide unique)
229
- ApiKey.key_lookup.get("sha256:abc123...")
230
-
231
- # Scoped lookup (unique per customer)
232
- customer = Customer.new(custid: "cust_456")
233
- customer.find_by_name("production-api-key")
234
- customer.find_all_by_scope(["read", "write"])
235
- ```
236
-
237
- ### Migrating Guide
238
- If you have existing code with old syntax, here's how to update it:
239
-
240
- ```ruby
241
- # ❌ Old syntax (pre-refactoring)
242
- indexed_by :email_lookup, field: :email
243
- indexed_by :email, :email_lookup, context: :global
244
- tracked_in :global, :all_users, score: :created_at
245
-
246
- # ✅ New syntax - Class-level scope
247
- class_indexed_by :email, :email_lookup
248
- class_tracked_in :all_users, score: :created_at
249
-
250
- # ✅ New syntax - Relationship scope
251
- indexed_by :email, :customer_email_lookup, parent: Customer
252
- tracked_in Customer, :user_activity, score: :last_seen
253
- ```
254
-
255
- **Key Changes**:
256
- 1. **Class-level relationships**: Use `class_` prefix (`class_tracked_in`, `class_indexed_by`)
257
- 2. **Relationship-scoped**: Use `parent:` parameter instead of `:global` symbol
258
- 3. **Automatic management**: Objects automatically added to class-level collections on save
259
- 4. **Clean syntax**: Collections support `<<` operator for Ruby-like relationship building
260
- 5. **Simplified storage**: All indexes stored at class level (parent is conceptual only)
261
-
262
- **Behavioral Changes**:
263
- - Save operations now automatically update indexes and class-level tracking
264
- - No more manual `add_to_*` calls required for basic functionality
265
- - `<<` operator works naturally with all collection types
266
- - Method generation simplified without complex global/parent conditionals
@@ -1,228 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # examples/redis_command_validation_example.rb
4
- #
5
- # Comprehensive example demonstrating Redis command validation for Familia
6
- # This example shows how to validate that Redis operations execute exactly
7
- # as expected, with particular focus on atomic operations.
8
-
9
- require_relative '../lib/familia'
10
- require_relative '../lib/familia/validation'
11
-
12
- # Enable database logging for visibility
13
- Familia.enable_database_logging = true
14
- Familia.enable_database_counter = true
15
-
16
- # Example models for validation demonstration
17
- class Account < Familia::Horreum
18
- identifier_field :account_id
19
- field :account_id
20
- field :balance
21
- field :status
22
- field :last_updated
23
- end
24
-
25
- class TransferService
26
- def self.atomic_transfer(from_account, to_account, amount)
27
- # Proper atomic implementation using Familia transaction
28
- from_balance = from_account.balance.to_i - amount
29
- to_balance = to_account.balance.to_i + amount
30
-
31
- Familia.transaction do |conn|
32
- conn.hset(from_account.dbkey, 'balance', from_balance.to_s)
33
- conn.hset(to_account.dbkey, 'balance', to_balance.to_s)
34
- conn.hset(from_account.dbkey, 'last_updated', Time.now.to_i.to_s)
35
- conn.hset(to_account.dbkey, 'last_updated', Time.now.to_i.to_s)
36
- end
37
-
38
- # Update local state
39
- from_account.balance = from_balance.to_s
40
- to_account.balance = to_balance.to_s
41
- end
42
-
43
- def self.non_atomic_transfer(from_account, to_account, amount)
44
- # Non-atomic implementation (BAD - for demonstration)
45
- from_account.balance = (from_account.balance.to_i - amount).to_s
46
- to_account.balance = (to_account.balance.to_i + amount).to_s
47
-
48
- from_account.save
49
- to_account.save
50
- end
51
- end
52
-
53
- puts '🧪 Redis Command Validation Framework Demo'
54
- puts '=' * 50
55
-
56
- # Clean up any existing test data
57
- cleanup_keys = Familia.dbclient.keys('account:*')
58
- Familia.dbclient.del(*cleanup_keys) if cleanup_keys.any?
59
-
60
- # Example 1: Basic Command Recording
61
- puts "\n1. Basic Command Recording"
62
- puts '-' * 30
63
-
64
- CommandRecorder = Familia::Validation::CommandRecorder
65
- CommandRecorder.start_recording
66
-
67
- account = Account.new(account_id: 'acc001', balance: '1000', status: 'active')
68
- account.save
69
-
70
- commands = CommandRecorder.stop_recording
71
- puts "Recorded #{commands.command_count} commands:"
72
- commands.commands.each { |cmd| puts " #{cmd}" }
73
-
74
- # Example 2: Transaction Detection
75
- puts "\n2. Transaction Detection"
76
- puts '-' * 30
77
-
78
- CommandRecorder.start_recording
79
-
80
- acc1 = Account.new(account_id: 'acc002', balance: '2000')
81
- acc2 = Account.new(account_id: 'acc003', balance: '500')
82
- acc1.save
83
- acc2.save
84
-
85
- TransferService.atomic_transfer(acc1, acc2, 500)
86
-
87
- commands = CommandRecorder.stop_recording
88
- puts "Commands executed: #{commands.command_count}"
89
- puts "Transactions detected: #{commands.transaction_count}"
90
-
91
- if commands.transaction_blocks.any?
92
- tx = commands.transaction_blocks.first
93
- puts "Transaction commands: #{tx.command_count}"
94
- tx.commands.each { |cmd| puts " [TX] #{cmd}" }
95
- end
96
-
97
- # Example 3: Validation with Expectations DSL
98
- puts "\n3. Command Validation with Expectations"
99
- puts '-' * 30
100
-
101
- begin
102
- validator = Familia::Validation::Validator.new
103
-
104
- # This should pass - we expect the exact Redis commands
105
- result = validator.validate do |expect|
106
- expect.transaction do |tx|
107
- tx.hset('account:acc004:object', 'balance', '1500')
108
- .hset('account:acc005:object', 'balance', '1000')
109
- .hset('account:acc004:object', 'last_updated', Familia::Validation::ArgumentMatcher.new(:any_string))
110
- .hset('account:acc005:object', 'last_updated', Familia::Validation::ArgumentMatcher.new(:any_string))
111
- end
112
-
113
- # Execute the operation
114
- acc4 = Account.new(account_id: 'acc004', balance: '2000')
115
- acc5 = Account.new(account_id: 'acc005', balance: '500')
116
- acc4.save
117
- acc5.save
118
-
119
- TransferService.atomic_transfer(acc4, acc5, 500)
120
- end
121
-
122
- puts "Validation result: #{result.valid? ? 'PASS ✅' : 'FAIL ❌'}"
123
- puts "Summary: #{result.summary}"
124
- rescue StandardError => e
125
- puts "Validation demo encountered error: #{e.message}"
126
- puts 'This is expected as the framework needs Redis middleware integration'
127
- end
128
-
129
- # Example 4: Performance Analysis
130
- puts "\n4. Performance Analysis"
131
- puts '-' * 30
132
-
133
- begin
134
- commands = Familia::Validation.capture_commands do
135
- # Create multiple accounts
136
- accounts = []
137
- (1..5).each do |i|
138
- account = Account.new(account_id: "perf#{i}", balance: '1000')
139
- account.save
140
- accounts << account
141
- end
142
-
143
- # Perform operations
144
- accounts[0].balance = '1100'
145
- accounts[0].save
146
- end
147
-
148
- analyzer = Familia::Validation::PerformanceAnalyzer.new(commands)
149
- analysis = analyzer.analyze
150
-
151
- puts 'Performance Analysis:'
152
- puts " Total Commands: #{analysis[:total_commands]}"
153
- puts " Command Types: #{analysis[:command_type_breakdown].keys.join(', ')}"
154
- puts " Efficiency Score: #{analysis[:efficiency_score]}/100"
155
- rescue StandardError => e
156
- puts "Performance analysis encountered error: #{e.message}"
157
- end
158
-
159
- # Example 5: Atomicity Validation
160
- puts "\n5. Atomicity Validation"
161
- puts '-' * 30
162
-
163
- begin
164
- # Test atomic vs non-atomic operations
165
- acc6 = Account.new(account_id: 'acc006', balance: '3000')
166
- acc7 = Account.new(account_id: 'acc007', balance: '1000')
167
- acc6.save
168
- acc7.save
169
-
170
- # This should detect that atomic operations are properly used
171
- validator = Familia::Validation::Validator.new(strict_atomicity: true)
172
-
173
- commands = validator.capture_redis_commands do
174
- TransferService.atomic_transfer(acc6, acc7, 1000)
175
- end
176
-
177
- atomicity_validator = Familia::Validation::AtomicityValidator.new(commands)
178
- result = atomicity_validator.validate
179
-
180
- puts "Atomicity validation: #{result.valid? ? 'PASS ✅' : 'FAIL ❌'}"
181
- rescue StandardError => e
182
- puts "Atomicity validation encountered error: #{e.message}"
183
- end
184
-
185
- puts "\n6. Framework Architecture Overview"
186
- puts '-' * 30
187
- puts "
188
- The Redis Command Validation Framework provides:
189
-
190
- 🔍 Command Recording
191
- - Captures all Redis commands with full context
192
- - Tracks transaction boundaries (MULTI/EXEC)
193
- - Records timing and performance metrics
194
-
195
- 📝 Expectations DSL
196
- - Fluent API for defining expected command sequences
197
- - Support for pattern matching and flexible ordering
198
- - Transaction and pipeline validation
199
-
200
- ✅ Validation Engine
201
- - Compares actual vs expected commands
202
- - Validates atomicity of operations
203
- - Provides detailed mismatch reports
204
-
205
- 🧪 Test Helpers
206
- - Integration with tryouts framework
207
- - Methods like assert_redis_commands, assert_atomic_operation
208
- - Automatic setup and cleanup
209
-
210
- ⚡ Performance Analysis
211
- - Command efficiency scoring
212
- - N+1 pattern detection
213
- - Transaction overhead analysis
214
-
215
- Key Benefits:
216
- • Brass-tacks Redis command validation
217
- • Atomic operation verification
218
- • Performance optimization insights
219
- • Clear diagnostic messages
220
- • Thread-safe operation
221
- "
222
-
223
- # Cleanup
224
- cleanup_keys = Familia.dbclient.keys('account:*')
225
- Familia.dbclient.del(*cleanup_keys) if cleanup_keys.any?
226
-
227
- puts "\n🎉 Demo complete! The validation framework is ready for use."
228
- puts ' See try/validation/ for comprehensive test examples.'