familia 2.0.0.pre15 → 2.0.0.pre17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -2
  3. data/.github/workflows/code-quality.yml +138 -0
  4. data/.github/workflows/code-smells.yml +85 -0
  5. data/.github/workflows/docs.yml +31 -8
  6. data/.gitignore +3 -1
  7. data/.pre-commit-config.yaml +7 -1
  8. data/.reek.yml +98 -0
  9. data/.rubocop.yml +54 -10
  10. data/.talismanrc +9 -0
  11. data/.yardopts +18 -13
  12. data/CHANGELOG.rst +86 -4
  13. data/CLAUDE.md +39 -1
  14. data/Gemfile +6 -5
  15. data/Gemfile.lock +99 -23
  16. data/LICENSE.txt +1 -1
  17. data/README.md +285 -85
  18. data/changelog.d/README.md +2 -2
  19. data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
  20. data/docs/archive/FAMILIA_TECHNICAL.md +42 -42
  21. data/docs/archive/FAMILIA_UPDATE.md +3 -3
  22. data/docs/archive/README.md +3 -2
  23. data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
  24. data/docs/conf.py +29 -0
  25. data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
  26. data/docs/guides/feature-encrypted-fields.md +785 -0
  27. data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
  28. data/docs/guides/feature-external-identifiers.md +637 -0
  29. data/docs/guides/feature-object-identifiers.md +435 -0
  30. data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
  31. data/docs/guides/feature-relationships-methods.md +684 -0
  32. data/docs/guides/feature-relationships.md +200 -0
  33. data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
  34. data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
  35. data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
  36. data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
  37. data/docs/guides/index.md +176 -0
  38. data/docs/guides/{Security-Model.md → security-model.md} +1 -1
  39. data/docs/migrating/v2.0.0-pre.md +1 -1
  40. data/docs/migrating/v2.0.0-pre11.md +2 -2
  41. data/docs/migrating/v2.0.0-pre12.md +2 -2
  42. data/docs/migrating/v2.0.0-pre5.md +33 -12
  43. data/docs/migrating/v2.0.0-pre6.md +2 -2
  44. data/docs/migrating/v2.0.0-pre7.md +8 -8
  45. data/docs/overview.md +624 -20
  46. data/docs/reference/api-technical.md +1365 -0
  47. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
  48. data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
  49. data/examples/autoloader/mega_customer.rb +3 -1
  50. data/examples/encrypted_fields.rb +378 -0
  51. data/examples/json_usage_patterns.rb +144 -0
  52. data/examples/relationships.rb +13 -13
  53. data/examples/safe_dump.rb +7 -7
  54. data/examples/single_connection_transaction_confusions.rb +379 -0
  55. data/lib/familia/base.rb +51 -10
  56. data/lib/familia/connection/handlers.rb +223 -0
  57. data/lib/familia/connection/individual_command_proxy.rb +64 -0
  58. data/lib/familia/connection/middleware.rb +75 -0
  59. data/lib/familia/connection/operation_core.rb +93 -0
  60. data/lib/familia/connection/operations.rb +277 -0
  61. data/lib/familia/connection/pipeline_core.rb +87 -0
  62. data/lib/familia/connection/transaction_core.rb +100 -0
  63. data/lib/familia/connection.rb +60 -186
  64. data/lib/familia/data_type/class_methods.rb +63 -0
  65. data/lib/familia/data_type/commands.rb +53 -51
  66. data/lib/familia/data_type/connection.rb +83 -0
  67. data/lib/familia/data_type/serialization.rb +108 -107
  68. data/lib/familia/data_type/settings.rb +96 -0
  69. data/lib/familia/data_type/types/counter.rb +1 -1
  70. data/lib/familia/data_type/types/hashkey.rb +15 -11
  71. data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
  72. data/lib/familia/data_type/types/lock.rb +3 -2
  73. data/lib/familia/data_type/types/sorted_set.rb +128 -14
  74. data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -9
  75. data/lib/familia/data_type/types/unsorted_set.rb +20 -27
  76. data/lib/familia/data_type.rb +12 -171
  77. data/lib/familia/distinguisher.rb +85 -0
  78. data/lib/familia/encryption/encrypted_data.rb +15 -24
  79. data/lib/familia/encryption/manager.rb +6 -4
  80. data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
  81. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
  82. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
  83. data/lib/familia/encryption/request_cache.rb +7 -7
  84. data/lib/familia/encryption.rb +2 -3
  85. data/lib/familia/errors.rb +9 -3
  86. data/lib/familia/features/autoloader.rb +30 -12
  87. data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
  88. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
  89. data/lib/familia/features/encrypted_fields.rb +71 -66
  90. data/lib/familia/features/expiration/extensions.rb +1 -1
  91. data/lib/familia/features/expiration.rb +31 -26
  92. data/lib/familia/features/external_identifier.rb +57 -19
  93. data/lib/familia/features/object_identifier.rb +134 -25
  94. data/lib/familia/features/quantization.rb +16 -21
  95. data/lib/familia/features/relationships/README.md +97 -0
  96. data/lib/familia/features/relationships/collection_operations.rb +104 -0
  97. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
  98. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +306 -0
  99. data/lib/familia/features/relationships/indexing.rb +182 -256
  100. data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
  101. data/lib/familia/features/relationships/participation/participant_methods.rb +164 -0
  102. data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
  103. data/lib/familia/features/relationships/participation.rb +656 -0
  104. data/lib/familia/features/relationships/participation_relationship.rb +31 -0
  105. data/lib/familia/features/relationships/score_encoding.rb +20 -20
  106. data/lib/familia/features/relationships.rb +65 -266
  107. data/lib/familia/features/safe_dump.rb +127 -130
  108. data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
  109. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
  110. data/lib/familia/features/transient_fields.rb +10 -7
  111. data/lib/familia/features.rb +10 -14
  112. data/lib/familia/field_type.rb +6 -4
  113. data/lib/familia/horreum/connection.rb +297 -0
  114. data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +27 -17
  115. data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +139 -74
  116. data/lib/familia/horreum/{subclass/management.rb → management.rb} +73 -27
  117. data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +108 -185
  118. data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +104 -23
  119. data/lib/familia/horreum/serialization.rb +172 -0
  120. data/lib/familia/horreum/{shared/settings.rb → settings.rb} +2 -1
  121. data/lib/familia/horreum/{core/utils.rb → utils.rb} +2 -1
  122. data/lib/familia/horreum.rb +222 -119
  123. data/lib/familia/json_serializer.rb +0 -1
  124. data/lib/familia/logging.rb +11 -114
  125. data/lib/familia/refinements/dear_json.rb +122 -0
  126. data/lib/familia/refinements/logger_trace.rb +20 -17
  127. data/lib/familia/refinements/stylize_words.rb +65 -0
  128. data/lib/familia/refinements/time_literals.rb +60 -52
  129. data/lib/familia/refinements.rb +2 -1
  130. data/lib/familia/secure_identifier.rb +60 -28
  131. data/lib/familia/settings.rb +83 -7
  132. data/lib/familia/utils.rb +5 -87
  133. data/lib/familia/verifiable_identifier.rb +4 -4
  134. data/lib/familia/version.rb +1 -1
  135. data/lib/familia.rb +72 -14
  136. data/lib/middleware/database_middleware.rb +56 -14
  137. data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
  138. data/try/configuration/scenarios_try.rb +2 -2
  139. data/try/connection/fiber_context_preservation_try.rb +250 -0
  140. data/try/connection/handler_constraints_try.rb +59 -0
  141. data/try/connection/operation_mode_guards_try.rb +208 -0
  142. data/try/connection/pipeline_fallback_integration_try.rb +128 -0
  143. data/try/connection/responsibility_chain_tracking_try.rb +72 -0
  144. data/try/connection/transaction_fallback_integration_try.rb +288 -0
  145. data/try/connection/transaction_mode_permissive_try.rb +153 -0
  146. data/try/connection/transaction_mode_strict_try.rb +98 -0
  147. data/try/connection/transaction_mode_warn_try.rb +131 -0
  148. data/try/connection/transaction_modes_try.rb +249 -0
  149. data/try/core/autoloader_try.rb +120 -2
  150. data/try/core/connection_try.rb +10 -10
  151. data/try/core/conventional_inheritance_try.rb +130 -0
  152. data/try/core/create_method_try.rb +15 -23
  153. data/try/core/database_consistency_try.rb +11 -10
  154. data/try/core/errors_try.rb +11 -14
  155. data/try/core/familia_extended_try.rb +2 -2
  156. data/try/core/familia_members_methods_try.rb +76 -0
  157. data/try/core/familia_try.rb +1 -1
  158. data/try/core/isolated_dbclient_try.rb +165 -0
  159. data/try/core/middleware_try.rb +16 -16
  160. data/try/core/persistence_operations_try.rb +4 -4
  161. data/try/core/pools_try.rb +42 -26
  162. data/try/core/secure_identifier_try.rb +28 -24
  163. data/try/core/time_utils_try.rb +10 -10
  164. data/try/core/tools_try.rb +3 -3
  165. data/try/core/utils_try.rb +2 -2
  166. data/try/data_types/boolean_try.rb +4 -4
  167. data/try/data_types/datatype_base_try.rb +0 -2
  168. data/try/data_types/list_try.rb +10 -10
  169. data/try/data_types/sorted_set_try.rb +5 -5
  170. data/try/data_types/sorted_set_zadd_options_try.rb +625 -0
  171. data/try/data_types/string_try.rb +12 -12
  172. data/try/data_types/unsortedset_try.rb +33 -0
  173. data/try/debugging/cache_behavior_tracer.rb +7 -7
  174. data/try/debugging/debug_aad_process.rb +1 -1
  175. data/try/debugging/debug_concealed_internal.rb +1 -1
  176. data/try/debugging/debug_cross_context.rb +1 -1
  177. data/try/debugging/debug_fresh_cross_context.rb +1 -1
  178. data/try/debugging/encryption_method_tracer.rb +10 -10
  179. data/try/edge_cases/hash_symbolization_try.rb +1 -1
  180. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  181. data/try/encryption/config_persistence_try.rb +2 -2
  182. data/try/encryption/encryption_core_try.rb +19 -19
  183. data/try/encryption/instance_variable_scope_try.rb +1 -1
  184. data/try/encryption/module_loading_try.rb +2 -2
  185. data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
  186. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
  187. data/try/encryption/secure_memory_handling_try.rb +1 -1
  188. data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
  189. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  190. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
  191. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
  192. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
  193. data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
  194. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  195. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  196. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
  197. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
  198. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  199. data/try/features/feature_dependencies_try.rb +3 -3
  200. data/try/features/field_groups_try.rb +244 -0
  201. data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
  202. data/try/features/object_identifier/object_identifier_try.rb +10 -0
  203. data/try/features/quantization/quantization_try.rb +1 -1
  204. data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
  205. data/try/features/relationships/indexing_try.rb +443 -0
  206. data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
  207. data/try/features/relationships/participation_commands_verification_try.rb +105 -0
  208. data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
  209. data/try/features/relationships/participation_reverse_index_try.rb +196 -0
  210. data/try/features/relationships/relationships_api_changes_try.rb +72 -71
  211. data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
  212. data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
  213. data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
  214. data/try/features/relationships/relationships_performance_try.rb +20 -20
  215. data/try/features/relationships/relationships_try.rb +27 -38
  216. data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
  217. data/try/features/transient_fields/refresh_reset_try.rb +3 -1
  218. data/try/features/transient_fields/simple_refresh_test.rb +1 -1
  219. data/try/helpers/test_cleanup.rb +86 -0
  220. data/try/helpers/test_helpers.rb +6 -7
  221. data/try/horreum/auto_indexing_on_save_try.rb +212 -0
  222. data/try/horreum/base_try.rb +3 -2
  223. data/try/horreum/commands_try.rb +3 -1
  224. data/try/horreum/defensive_initialization_try.rb +86 -0
  225. data/try/horreum/destroy_related_fields_cleanup_try.rb +332 -0
  226. data/try/horreum/initialization_try.rb +11 -7
  227. data/try/horreum/relations_try.rb +21 -13
  228. data/try/horreum/serialization_try.rb +12 -11
  229. data/try/horreum/settings_try.rb +2 -0
  230. data/try/integration/cross_component_try.rb +3 -3
  231. data/try/memory/memory_basic_test.rb +1 -1
  232. data/try/memory/memory_docker_ruby_dump.sh +2 -2
  233. data/try/models/customer_safe_dump_try.rb +1 -1
  234. data/try/models/customer_try.rb +13 -15
  235. data/try/models/datatype_base_try.rb +3 -3
  236. data/try/models/familia_object_try.rb +9 -8
  237. data/try/performance/benchmarks_try.rb +2 -2
  238. data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
  239. data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
  240. data/try/prototypes/atomic_saves_v4.rb +1 -1
  241. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
  242. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  243. data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  244. data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
  245. data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
  246. data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
  247. data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
  248. data/try/prototypes/pooling/pool_siege.rb +11 -11
  249. data/try/prototypes/pooling/run_stress_tests.rb +7 -7
  250. data/try/refinements/dear_json_array_methods_try.rb +53 -0
  251. data/try/refinements/dear_json_hash_methods_try.rb +54 -0
  252. data/try/refinements/logger_trace_methods_try.rb +44 -0
  253. data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
  254. data/try/refinements/time_literals_string_methods_try.rb +80 -0
  255. data/try/valkey.conf +26 -0
  256. metadata +92 -52
  257. data/.rubocop_todo.yml +0 -208
  258. data/docs/connection_pooling.md +0 -192
  259. data/docs/guides/Connection-Pooling-Guide.md +0 -437
  260. data/docs/guides/Encrypted-Fields-Overview.md +0 -101
  261. data/docs/guides/Feature-System-Autoloading.md +0 -198
  262. data/docs/guides/Home.md +0 -116
  263. data/docs/guides/Relationships-Guide.md +0 -737
  264. data/docs/guides/relationships-methods.md +0 -266
  265. data/docs/reference/auditing_database_commands.rb +0 -228
  266. data/examples/permissions.rb +0 -240
  267. data/lib/familia/features/relationships/cascading.rb +0 -437
  268. data/lib/familia/features/relationships/membership.rb +0 -497
  269. data/lib/familia/features/relationships/permission_management.rb +0 -264
  270. data/lib/familia/features/relationships/querying.rb +0 -615
  271. data/lib/familia/features/relationships/redis_operations.rb +0 -274
  272. data/lib/familia/features/relationships/tracking.rb +0 -418
  273. data/lib/familia/horreum/core/connection.rb +0 -73
  274. data/lib/familia/horreum/core.rb +0 -21
  275. data/lib/familia/refinements/snake_case.rb +0 -40
  276. data/lib/familia/validation/command_recorder.rb +0 -336
  277. data/lib/familia/validation/expectations.rb +0 -519
  278. data/lib/familia/validation/validation_helpers.rb +0 -443
  279. data/lib/familia/validation/validator.rb +0 -412
  280. data/lib/familia/validation.rb +0 -140
  281. data/try/data_types/set_try.rb +0 -33
  282. data/try/features/relationships/categorical_permissions_try.rb +0 -515
  283. data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
  284. data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
  285. data/try/validation/atomic_operations_try.rb.disabled +0 -320
  286. data/try/validation/command_validation_try.rb.disabled +0 -207
  287. data/try/validation/performance_validation_try.rb.disabled +0 -324
  288. data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
data/README.md CHANGED
@@ -1,8 +1,56 @@
1
1
  # Familia - 2.0
2
2
 
3
- **Organize and store Ruby objects in Valkey/Redis. A powerful Ruby ORM (of sorts) for Valkey/Redis.**
3
+ **Organize and store Ruby objects in Valkey/Redis using native database types (an ORM of sorts).**
4
4
 
5
- Familia provides a flexible and feature-rich way to interact with Valkey using Ruby objects. It's designed to make working with Valkey as natural as working with Ruby classes, while offering advanced features for complex data management.
5
+ Familia provides object-oriented access to Valkey/Redis using their database types. Unlike traditional ORMs that map objects to relational tables, Familia maps Ruby objects directly to Valkey's native data structures (strings, lists, sets, sorted sets, hashes) as instance variables.
6
+
7
+ > [!CAUTION]
8
+ > Familia 2 is in pre-release and not ready for production use. (October 2025)
9
+ ## Traditional ORM vs Familia
10
+
11
+ **Traditional ORMs** convert your objects to SQL tables. A product with categories becomes two tables with a join table. Checking if a tag exists requires a query with joins.
12
+
13
+ **Familia** stores your objects using Valkey/Redis data structures directly. A product with categories uses an actual Valkey/Redis list. Checking if a tag exists is a native O(1) Valkey/Redis operation.
14
+
15
+ ```ruby
16
+ # Traditional ORM - everything becomes SQL tables
17
+ class Product < ActiveRecord::Base
18
+ has_and_belongs_to_many :tags # Creates products_tags junction table
19
+ end
20
+
21
+ product.tags.include?(tag) # SELECT * FROM products_tags WHERE ...
22
+
23
+ # Familia - uses Valkey/Redis data types directly
24
+ class Product < Familia::Horreum
25
+ set :tags # Actual Valkey/Redis set
26
+ end
27
+
28
+ product.tags.include?("electronics") # Valkey/Redis SISMEMBER - O(1) operation
29
+ ```
30
+
31
+ ### What This Means in Practice
32
+
33
+ When you define a Familia model, each data type declaration creates the corresponding Valkey/Redis structure:
34
+
35
+ ```ruby
36
+ class Product < Familia::Horreum
37
+ identifier_field :sku
38
+
39
+ field :name, :price # Stored in Valkey/Redis hash
40
+ list :categories # Actual Valkey/Redis list
41
+ set :tags # Actual Valkey/Redis set
42
+ zset :ratings # Actual Valkey/Redis sorted set
43
+ counter :views # Valkey/Redis string with atomic increment
44
+ end
45
+
46
+ # These are Valkey/Redis native operations, not ORM abstractions
47
+ product.categories.push("electronics") # LPUSH
48
+ product.tags.add("popular") # SADD
49
+ product.ratings.add(4.5, "user123") # ZADD with score
50
+ product.views.increment # INCR (atomic)
51
+ ```
52
+
53
+ The performance characteristics you rely on in Valkey/Redis remain unchanged. Set membership is O(1). Sorted sets maintain order automatically. Counters increment atomically without read-modify-write cycles.
6
54
 
7
55
  ## Quick Start
8
56
 
@@ -10,7 +58,7 @@ Familia provides a flexible and feature-rich way to interact with Valkey using R
10
58
 
11
59
  ```bash
12
60
  # Add to Gemfile
13
- gem 'familia', '>= 2.0.0'
61
+ gem 'familia', '>= 2.0'
14
62
 
15
63
  # Or install directly
16
64
  gem install familia
@@ -45,19 +93,49 @@ end
45
93
  ### 4. Basic Operations
46
94
 
47
95
  ```ruby
48
- # Create
49
- user = User.new(email: 'alice@example.com', name: 'Alice')
50
- user.save
96
+ # Create and save
97
+ user = User.create(email: 'alice@example.com', name: 'Alice', created_at: Time.now.to_i)
51
98
 
52
- # Find
99
+ # Find by identifier
53
100
  user = User.load('alice@example.com')
54
101
 
55
- # Update
56
- user.name = 'Alice Smith'
102
+ # Update and save
103
+ user.name = 'Alice Windows'
57
104
  user.save
58
105
 
106
+ # Fast update (immediate persistence)
107
+ user.name!('Alice Smith') # Sets and saves immediately
108
+
59
109
  # Check existence
60
110
  User.exists?('alice@example.com') #=> true
111
+
112
+ # Delete
113
+ user.destroy
114
+
115
+ # Conditional save
116
+ user.save_if_not_exists # Only saves if object doesn't exist yet
117
+ ```
118
+
119
+ ### 5. Generated Method Patterns
120
+
121
+ Familia automatically generates methods for fields and data types:
122
+
123
+ ```ruby
124
+ class User < Familia::Horreum
125
+ field :name # → name, name=, name!
126
+ set :tags # → tags, tags=, tags?
127
+ list :history # → history, history=, history?
128
+ end
129
+
130
+ # Field methods
131
+ user.name # Get field value
132
+ user.name = 'Alice' # Set field value
133
+ user.name!('Alice') # Set and save immediately
134
+
135
+ # Data type methods
136
+ user.tags # Get Set instance
137
+ user.tags = new_set # Replace Set instance
138
+ user.tags? # Check if it's a Set type
61
139
  ```
62
140
 
63
141
  ## Prerequisites
@@ -66,13 +144,104 @@ User.exists?('alice@example.com') #=> true
66
144
  - **Valkey/Redis**: 6.0+
67
145
  - **Gems**: `redis` (automatically installed)
68
146
 
147
+ ---
148
+
149
+ ## Core Concepts
150
+
151
+ ### Data Types
152
+
153
+ Familia provides direct mappings to Valkey/Redis native data structures:
154
+
155
+ ```ruby
156
+ class BlogPost < Familia::Horreum
157
+ identifier_field :slug
158
+
159
+ # Basic fields
160
+ field :slug, :title, :content, :published_at
161
+
162
+ # Valkey/Redis data types as instance variables
163
+ string :view_count, default: '0' # Atomic counters
164
+ list :comments # Ordered, allows duplicates
165
+ set :tags # Unique values
166
+ zset :popularity_scores # Scored/ranked data
167
+ hashkey :metadata # Key-value pairs
168
+
169
+ # Advanced field types
170
+ counter :likes # Specialized atomic counter
171
+ end
172
+
173
+ post = BlogPost.create(slug: "hello-world", title: "Hello World")
174
+
175
+ # Work with Valkey/Redis data types naturally
176
+ post.view_count.increment # INCR view_count
177
+ post.comments.push("Great post!") # LPUSH comments
178
+ post.tags.add("ruby", "programming") # SADD tags
179
+ post.popularity_scores.add(4.5, "user123") # ZADD popularity_scores
180
+ post.metadata["author"] = "Alice" # HSET metadata
181
+ post.likes.increment(5) # INCRBY likes 5
182
+ ```
183
+
184
+ ### Features System
185
+
186
+ Enable advanced functionality with Familia's modular feature system:
187
+
188
+ ```ruby
189
+ class User < Familia::Horreum
190
+ # Enable features as needed
191
+ feature :expiration # TTL management
192
+ feature :safe_dump # API-safe serialization
193
+ feature :encrypted_fields # Field-level encryption
194
+ feature :relationships # Object relationships
195
+ feature :object_identifier # Auto-generated IDs
196
+ feature :quantization # Time-based data bucketing
197
+
198
+ identifier_field :email
199
+ field :email, :name, :created_at
200
+
201
+ # Feature-specific functionality
202
+ encrypted_field :api_key # Automatically encrypted
203
+ safe_dump_field :email # Include in safe_dump
204
+ safe_dump_field :name # Include in safe_dump
205
+ default_expiration 30.days # Auto-expire inactive users
206
+ end
207
+
208
+ user = User.create(email: "alice@example.com", api_key: "secret123")
209
+ user.api_key.class # => ConcealedString
210
+ user.api_key.to_s # => "[CONCEALED]" (safe for logs)
211
+ user.safe_dump # => {email: "...", name: "..."}
212
+ ```
213
+
214
+ ### Querying and Finding
215
+
216
+ ```ruby
217
+ # Primary key lookup
218
+ user = User.load("alice@example.com")
219
+
220
+ # Existence checks
221
+ User.exists?("alice@example.com") # => true/false
222
+
223
+ # Bulk operations
224
+ users = User.multiget("alice@example.com", "bob@example.com")
225
+
226
+ # With relationships feature - indexed lookups
227
+ class User < Familia::Horreum
228
+ feature :relationships
229
+ field :email, :username
230
+ class_indexed_by :username, :username_lookup
231
+ end
232
+
233
+ # O(1) indexed finding
234
+ user = User.find_by_username("alice_doe") # Fast hash lookup
235
+ ```
236
+
237
+ ---
69
238
 
70
239
  ## Usage Examples
71
240
 
72
241
  ### Creating and Saving Objects
73
242
 
74
243
  ```ruby
75
- flower = Flower.create(name: "Red Rose", token: "rrose")
244
+ flower = Flower.create(name: "Red Rose", token: "prose")
76
245
  flower.owners.push("Alice", "Bob")
77
246
  flower.tags.add("romantic")
78
247
  flower.metrics.increment("views", 1)
@@ -83,7 +252,7 @@ flower.save
83
252
  ### Retrieving and Updating Objects
84
253
 
85
254
  ```ruby
86
- rose = Flower.find_by_id("rrose")
255
+ rose = Flower.load("prose")
87
256
  rose.name = "Pink Rose"
88
257
  rose.save
89
258
  ```
@@ -91,9 +260,9 @@ rose.save
91
260
  ### Using Safe Dump
92
261
 
93
262
  ```ruby
94
- user = User.create(username: "rosedog", first_name: "Rose", last_name: "Dog")
263
+ user = User.create(username: "prosedog", first_name: "Rose", last_name: "Dog")
95
264
  user.safe_dump
96
- # => {id: "user:rosedog", username: "rosedog", full_name: "Rose Dog"}
265
+ # => {id: "user:prosedog", username: "prosedog", full_name: "Rose Dog"}
97
266
  ```
98
267
 
99
268
  ### Working with Time-based Data
@@ -106,7 +275,7 @@ metric.counter.increment # Increments the counter for the current hour
106
275
  ### Bulk Operations
107
276
 
108
277
  ```ruby
109
- Flower.multiget("rrose", "tulip", "daisy")
278
+ Flower.multiget("prose", "tulip", "daisy")
110
279
  ```
111
280
 
112
281
  ### Transactional Operations
@@ -114,113 +283,125 @@ Flower.multiget("rrose", "tulip", "daisy")
114
283
  ```ruby
115
284
  user.transaction do |conn|
116
285
  conn.set("user:#{user.id}:status", "active")
117
- conn.zadd("active_users", Time.now.to_i, user.id)
286
+ conn.zadd("active_users", Familia.now.to_i, user.id)
118
287
  end
119
288
  ```
120
289
 
121
- ### Object Relationships
122
-
123
- Familia includes a powerful relationships system for managing object associations:
290
+ ### Advanced Patterns
124
291
 
292
+ **Time-based Expiration:**
125
293
  ```ruby
126
- class Customer < Familia::Horreum
127
- feature :relationships
128
-
129
- identifier_field :custid
130
- field :custid, :name, :email
131
- set :domains # Collection for related objects
294
+ class Session < Familia::Horreum
295
+ feature :expiration
296
+ default_expiration 24.hours
132
297
 
133
- # Automatic indexing and tracking
134
- class_indexed_by :email, :email_lookup
135
- class_tracked_in :all_customers, score: :created_at
298
+ field :user_id, :token
136
299
  end
137
300
 
138
- class Domain < Familia::Horreum
139
- feature :relationships
301
+ session = Session.create(user_id: "123", token: "abc123")
302
+ session.ttl # Check remaining time
303
+ session.expire_in(1.hour) # Custom expiration
304
+ ```
140
305
 
141
- identifier_field :domain_id
142
- field :domain_id, :name, :status
306
+ **Encrypted Fields:**
307
+ ```ruby
308
+ class SecureData < Familia::Horreum
309
+ feature :encrypted_fields
143
310
 
144
- # Bidirectional membership
145
- member_of Customer, :domains
311
+ field :name
312
+ encrypted_field :credit_card, :ssn
146
313
  end
147
314
 
148
- # Clean, Ruby-like syntax
149
- customer = Customer.new(custid: "cust123", email: "admin@acme.com")
150
- customer.save # Automatically indexed and tracked
151
-
152
- domain = Domain.new(domain_id: "dom456", name: "acme.com")
153
- customer.domains << domain # Clean collection syntax
154
-
155
- # Fast O(1) lookups
156
- found_customer = Customer.find_by_email("admin@acme.com")
315
+ data = SecureData.create(name: "Alice", credit_card: "4111-1111-1111-1234")
316
+ data.credit_card.reveal # => "4111-1111-1111-1234"
317
+ data.credit_card.to_s # => "[CONCEALED]"
157
318
  ```
158
319
 
159
- ## Advanced Features
320
+ ## Configuration
160
321
 
161
- ### Relationships and Associations
322
+ ### Basic Setup
162
323
 
163
- Familia provides three types of relationships with automatic management:
324
+ ```ruby
325
+ # config/initializers/familia.rb (Rails)
326
+ require 'familia'
164
327
 
165
- - **`member_of`** - Bidirectional membership with clean `<<` operator support
166
- - **`indexed_by`** - O(1) hash-based field lookups (class-level or relationship-scoped)
167
- - **`tracked_in`** - Scored collections for rankings, time-series, and analytics
328
+ # Basic configuration
329
+ Familia.uri = 'redis://localhost:6379/0'
168
330
 
169
- All relationships support automatic indexing and tracking - objects are automatically added to class-level collections when saved, with no manual management required.
331
+ # Production configuration
332
+ Familia.configure do |config|
333
+ config.redis_uri = ENV['REDIS_URL']
334
+ config.debug = ENV['FAMILIA_DEBUG'] == 'true'
335
+ end
336
+ ```
170
337
 
171
- ## Organizing Complex Models
338
+ ### Connection Pooling
172
339
 
173
- For large applications, you can organize model complexity using custom features and the Feature Autoloading System:
340
+ ```ruby
341
+ require 'connection_pool'
174
342
 
175
- ### Feature Autoloading System
343
+ Familia.connection_provider = lambda do |uri|
344
+ ConnectionPool.new(size: 10, timeout: 5) do
345
+ Redis.new(url: uri)
346
+ end.with { |conn| yield conn if block_given?; conn }
347
+ end
348
+ ```
176
349
 
177
- Familia automatically discovers and loads feature-specific configuration files, enabling clean separation between core model definitions and feature configurations:
350
+ ### Encryption Setup
178
351
 
179
352
  ```ruby
180
- # app/models/user.rb - Clean model definition
181
- class User < Familia::Horreum
182
- field :name, :email, :password
183
- feature :safe_dump # Configuration auto-loaded
184
- end
185
-
186
- # app/models/user/safe_dump_extensions.rb - Automatically discovered
187
- class User
188
- safe_dump_fields :name, :email # password excluded for security
353
+ Familia.configure do |config|
354
+ config.encryption_keys = {
355
+ v1: ENV['FAMILIA_ENCRYPTION_KEY_V1'],
356
+ v2: ENV['FAMILIA_ENCRYPTION_KEY_V2']
357
+ }
358
+ config.current_key_version = :v2
189
359
  end
190
360
  ```
191
361
 
192
- Extension files follow the pattern: `{model_name}/{feature_name}_*.rb`
362
+ ## Organizing Complex Models
193
363
 
194
- ### Self-Registering Features
364
+ For large applications, you can organize model complexity using custom features and the Feature Autoloading System:
195
365
 
196
- ```ruby
197
- # app/features/customer_management.rb
198
- module MyApp::Features::CustomerManagement
199
- Familia::Base.add_feature(self, :customer_management)
366
+ ### Feature Organization with Autoloader
200
367
 
201
- def self.included(base)
202
- base.extend(ClassMethods)
203
- end
368
+ For large applications, organize features into modular files using the autoloader:
204
369
 
205
- module ClassMethods
206
- def create_with_validation(attrs)
207
- # Complex creation logic
208
- end
370
+ ```ruby
371
+ # app/models/customer.rb - Main model file
372
+ class Customer < Familia::Horreum
373
+ module Features
374
+ include Familia::Features::Autoloader
375
+ # Automatically loads all .rb files from app/models/customer/features/
209
376
  end
210
377
 
211
- def complex_business_method
212
- # Instance methods
378
+ identifier_field :custid
379
+ field :custid, :name, :email
380
+ feature :safe_dump # Feature configuration loaded automatically
381
+ end
382
+
383
+ # app/models/customer/features/notifications.rb - Automatically loaded
384
+ module Customer::Features::Notifications
385
+ def send_welcome_email
386
+ NotificationService.send_template(
387
+ email: email,
388
+ template: 'customer_welcome',
389
+ variables: { name: name, custid: custid }
390
+ )
213
391
  end
214
392
  end
215
393
 
216
- # models/customer.rb
217
- class Customer < Familia::Horreum
218
- field :email, :name
219
- feature :customer_management # Clean model definition
394
+ # app/models/customer/features/safe_dump_extensions.rb - Feature-specific config
395
+ module Customer::Features::SafeDumpExtensions
396
+ def self.included(base)
397
+ base.safe_dump_field :custid
398
+ base.safe_dump_field :name
399
+ base.safe_dump_field :email
400
+ end
220
401
  end
221
402
  ```
222
403
 
223
- These approaches keep complex models organized while maintaining Familia's clean, declarative style. For detailed migration information, see the [migration guides](docs/migrating/).
404
+ This approach keeps complex models organized while maintaining clean, declarative style.
224
405
 
225
406
  ## AI Development Assistance
226
407
 
@@ -234,10 +415,29 @@ This version of Familia was developed with assistance from AI tools. The followi
234
415
 
235
416
  I remain responsible for all design decisions and the final code. I believe in being transparent about development tools, especially as AI becomes more integrated into our workflows as developers.
236
417
 
237
- ## Epilogue
418
+ ## Links
238
419
 
239
- For more information, visit:
240
420
  - [Github Repository](https://github.com/delano/familia)
241
421
  - [RubyGems Page](https://rubygems.org/gems/familia)
242
422
 
243
- Contributions are welcome! Feel free to submit a Pull Request.
423
+ ## Documentation
424
+
425
+ For comprehensive guides and detailed technical information:
426
+
427
+ - **[Overview Guide](docs/overview.md)** - Conceptual understanding and getting started
428
+ - **[Technical Reference](docs/reference/api-technical.md)** - Implementation details and advanced patterns
429
+ - **[Migration Guides](docs/migrating/)** - Upgrading from previous versions
430
+
431
+ ## Contributing
432
+
433
+ Contributions are welcome! Please feel free to submit a Pull Request.
434
+
435
+ 1. Fork the repository
436
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
437
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
438
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
439
+ 5. Open a Pull Request
440
+
441
+ ## License
442
+
443
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -53,8 +53,8 @@ git commit
53
53
  - **One Fragment Per Change:** Keep each fragment focused on a single feature, fix, or improvement.
54
54
  - **Documenting AI Assistance:** If a change involved significant AI assistance, place it in its own fragment. This ensures the `### AI Assistance` section clearly corresponds to the single change described in that fragment.
55
55
  - **Write for a Human Audience:** Describe the *impact* of the change, not just the implementation details.
56
- - **Good:** "Improved the performance and stability of Redis connections under high load."
57
- - **Bad:** "Refactored the `RedisManager`."
56
+ - **Good:** "Improved the performance and stability of Database connections under high load."
57
+ - **Bad:** "Refactored the `DatabaseManager`."
58
58
  - **Be Specific:** Avoid generic messages like "fixed a bug." Clearly state what was fixed.
59
59
  - **Include Context:** Reference issue or pull request numbers to provide a link to the discussion and implementation details. `scriv` will automatically create links for them.
60
60
  - **Example:** `- Fixed a bug where users could not reset their passwords. PR #123`
@@ -16,11 +16,11 @@ Generated methods on Domain instances:
16
16
 
17
17
  The method names follow the pattern: {action}_to_{lowercase_class_name}_{collection_name}
18
18
 
19
- tracked_in Relationships
19
+ participates_in Relationships
20
20
 
21
21
  When you declare:
22
22
  class Customer < Familia::Horreum
23
- tracked_in :all_customers, type: :sorted_set, score: :created_at
23
+ participates_in :all_customers, type: :sorted_set, score: :created_at
24
24
  end
25
25
 
26
26
  Generated class methods:
@@ -29,18 +29,18 @@ Generated class methods:
29
29
  - Customer.all_customers - Access the sorted set collection directly
30
30
 
31
31
  For scoped tracking (with class prefix):
32
- tracked_in :global, :active_users, score: :last_seen
32
+ participates_in :global, :active_users, score: :last_seen
33
33
  Generates: Customer.add_to_active_users(customer) and Customer.active_users
34
34
 
35
35
  indexed_by Relationships
36
36
 
37
- The `indexed_by` method creates Redis hash-based indexes for O(1) field lookups. The `context` parameter determines index ownership and scope.
37
+ The `indexed_by` method creates Valkey/Redis hash-based indexes for O(1) field lookups. The `context` parameter determines index ownership and scope.
38
38
 
39
39
  **Global Context (Shared Index)**
40
40
  When you declare:
41
41
  ```ruby
42
42
  class Customer < Familia::Horreum
43
- indexed_by :email, :email_lookup, context: :global
43
+ indexed_by :email, :email_lookup, target: :global
44
44
  end
45
45
  ```
46
46
 
@@ -49,13 +49,13 @@ Generated class methods:
49
49
  - Customer.remove_from_email_lookup(customer) - Remove customer from global email index
50
50
  - Customer.email_lookup - Access the global hash index directly (supports .get(email))
51
51
 
52
- Redis key pattern: `global:email_lookup`
52
+ Valkey/Redis key pattern: `global:email_lookup`
53
53
 
54
54
  **Class Context (Per-Instance Index)**
55
55
  When you declare:
56
56
  ```ruby
57
57
  class Domain < Familia::Horreum
58
- indexed_by :name, :domain_index, context: Customer
58
+ indexed_by :name, :domain_index, target: Customer
59
59
  end
60
60
  ```
61
61
 
@@ -63,7 +63,7 @@ Generated class methods on Customer:
63
63
  - Customer.find_by_name(domain_name) - Find domain by name within this customer
64
64
  - Customer.find_all_by_name(domain_names) - Find multiple domains by names
65
65
 
66
- Redis key pattern: `customer:123:domain_index` (per customer instance)
66
+ Valkey/Redis key pattern: `customer:123:domain_index` (per customer instance)
67
67
 
68
68
  **When to Use Each Context**
69
69
  - **Global context (`:global`)**: Use for system-wide lookups where the field value should be unique across all instances
@@ -85,7 +85,7 @@ domain.add_to_customer_domains(customer.custid) # Add to relationship
85
85
  domain.remove_from_customer_domains(customer.custid) # Remove from relationship
86
86
  domain.in_customer_domains?(customer.custid) # Query membership
87
87
 
88
- # For tracked_in relationships:
88
+ # For participates_in relationships:
89
89
  Customer.add_to_all_customers(customer) # Class method
90
90
  Customer.all_customers.range(0, -1) # Direct collection access
91
91
 
@@ -97,10 +97,10 @@ Method Naming Conventions
97
97
 
98
98
  The relationship system uses consistent naming patterns:
99
99
  - member_of: {add_to|remove_from|in}_#{parent_class.downcase}_#{collection_name}
100
- - tracked_in: {add_to|remove_from}_#{collection_name} (class methods)
100
+ - participates_in: {add_to|remove_from}_#{collection_name} (class methods)
101
101
  - indexed_by: {add_to|remove_from}_#{index_name} (class methods)
102
102
 
103
- This automatic method generation creates a clean, predictable API that handles both the Redis operations and maintains referential consistency
103
+ This automatic method generation creates a clean, predictable API that handles both the db operations and maintains referential consistency
104
104
  across related objects.
105
105
 
106
106
 
@@ -119,8 +119,8 @@ class User < Familia::Horreum
119
119
  field :user_id, :email, :username
120
120
 
121
121
  # System-wide unique email lookup
122
- indexed_by :email, :email_lookup, context: :global
123
- indexed_by :username, :username_lookup, context: :global
122
+ indexed_by :email, :email_lookup, target: :global
123
+ indexed_by :username, :username_lookup, target: :global
124
124
  end
125
125
 
126
126
  # Usage:
@@ -128,7 +128,7 @@ User.add_to_email_lookup(user)
128
128
  found_user_id = User.email_lookup.get("john@example.com")
129
129
  ```
130
130
 
131
- **Redis keys generated**: `global:email_lookup`, `global:username_lookup`
131
+ **Valkey/Redis keys generated**: `global:email_lookup`, `global:username_lookup`
132
132
 
133
133
  ### Class Context Pattern
134
134
  Use `context: SomeClass` when field values are unique within a specific parent context:
@@ -149,8 +149,8 @@ class Domain < Familia::Horreum
149
149
  field :domain_id, :name, :subdomain
150
150
 
151
151
  # Domains are unique per customer (customer can't have duplicate domain names)
152
- indexed_by :name, :domain_index, context: Customer
153
- indexed_by :subdomain, :subdomain_index, context: Customer
152
+ indexed_by :name, :domain_index, target: Customer
153
+ indexed_by :subdomain, :subdomain_index, target: Customer
154
154
  end
155
155
 
156
156
  # Usage:
@@ -159,7 +159,7 @@ customer.find_by_name("example.com") # Find domain within this custome
159
159
  customer.find_all_by_subdomain(["www", "api"]) # Find multiple subdomains
160
160
  ```
161
161
 
162
- **Redis keys generated**: `customer:cust_123:domain_index`, `customer:cust_123:subdomain_index`
162
+ **Valkey/Redis keys generated**: `customer:cust_123:domain_index`, `customer:cust_123:subdomain_index`
163
163
 
164
164
  ### Mixed Pattern Example
165
165
  A real-world example showing both patterns:
@@ -172,11 +172,11 @@ class ApiKey < Familia::Horreum
172
172
  field :key_id, :key_hash, :name, :scope
173
173
 
174
174
  # API key hashes must be globally unique
175
- indexed_by :key_hash, :global_key_lookup, context: :global
175
+ indexed_by :key_hash, :global_key_lookup, target: :global
176
176
 
177
177
  # But key names can be reused across different customers
178
- indexed_by :name, :customer_key_lookup, context: Customer
179
- indexed_by :scope, :scope_lookup, context: Customer
178
+ indexed_by :name, :customer_key_lookup, target: Customer
179
+ indexed_by :scope, :scope_lookup, target: Customer
180
180
  end
181
181
 
182
182
  # Usage examples:
@@ -197,10 +197,10 @@ If you have existing code with incorrect syntax, here's how to fix it:
197
197
  indexed_by :email_lookup, field: :email
198
198
 
199
199
  # ✅ New correct syntax - Global scope
200
- indexed_by :email, :email_lookup, context: :global
200
+ indexed_by :email, :email_lookup, target: :global
201
201
 
202
202
  # ✅ New correct syntax - Class scope
203
- indexed_by :email, :customer_email_lookup, context: Customer
203
+ indexed_by :email, :customer_email_lookup, target: Customer
204
204
  ```
205
205
 
206
206
  **Key Differences**: