familia 1.2.1 → 2.0.0.pre2

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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +68 -0
  3. data/.github/workflows/docs.yml +64 -0
  4. data/.gitignore +4 -0
  5. data/.pre-commit-config.yaml +3 -1
  6. data/.rubocop.yml +16 -9
  7. data/.rubocop_todo.yml +177 -31
  8. data/.yardopts +9 -0
  9. data/CLAUDE.md +141 -0
  10. data/Gemfile +16 -2
  11. data/Gemfile.lock +97 -36
  12. data/README.md +39 -23
  13. data/bin/irb +3 -0
  14. data/docs/connection_pooling.md +192 -0
  15. data/familia.gemspec +10 -6
  16. data/lib/familia/base.rb +19 -9
  17. data/lib/familia/connection.rb +232 -65
  18. data/lib/familia/core_ext.rb +1 -1
  19. data/lib/familia/datatype/commands.rb +59 -0
  20. data/lib/familia/{redistype → datatype}/serialization.rb +9 -13
  21. data/lib/familia/{redistype → datatype}/types/hashkey.rb +25 -25
  22. data/lib/familia/{redistype → datatype}/types/list.rb +13 -13
  23. data/lib/familia/{redistype → datatype}/types/sorted_set.rb +20 -20
  24. data/lib/familia/{redistype → datatype}/types/string.rb +22 -21
  25. data/lib/familia/{redistype → datatype}/types/unsorted_set.rb +11 -11
  26. data/lib/familia/datatype.rb +243 -0
  27. data/lib/familia/errors.rb +5 -2
  28. data/lib/familia/features/expiration.rb +33 -34
  29. data/lib/familia/features/quantization.rb +9 -3
  30. data/lib/familia/features/safe_dump.rb +2 -3
  31. data/lib/familia/features.rb +2 -2
  32. data/lib/familia/horreum/class_methods.rb +97 -110
  33. data/lib/familia/horreum/commands.rb +46 -51
  34. data/lib/familia/horreum/connection.rb +82 -0
  35. data/lib/familia/horreum/{relations_management.rb → related_fields_management.rb} +37 -35
  36. data/lib/familia/horreum/serialization.rb +61 -198
  37. data/lib/familia/horreum/settings.rb +6 -17
  38. data/lib/familia/horreum/utils.rb +11 -10
  39. data/lib/familia/horreum.rb +69 -60
  40. data/lib/familia/logging.rb +12 -12
  41. data/lib/familia/multi_result.rb +72 -0
  42. data/lib/familia/refinements.rb +7 -44
  43. data/lib/familia/settings.rb +11 -11
  44. data/lib/familia/utils.rb +123 -90
  45. data/lib/familia/version.rb +4 -21
  46. data/lib/familia.rb +18 -13
  47. data/lib/middleware/database_middleware.rb +150 -0
  48. data/try/configuration/scenarios_try.rb +65 -0
  49. data/try/core/connection_try.rb +58 -0
  50. data/try/core/errors_try.rb +93 -0
  51. data/try/core/extensions_try.rb +26 -0
  52. data/try/{10_familia_try.rb → core/familia_extended_try.rb} +11 -10
  53. data/try/{00_familia_try.rb → core/familia_try.rb} +7 -5
  54. data/try/core/middleware_try.rb +68 -0
  55. data/try/core/refinements_try.rb +39 -0
  56. data/try/core/settings_try.rb +76 -0
  57. data/try/core/tools_try.rb +54 -0
  58. data/try/core/utils_try.rb +189 -0
  59. data/try/{26_redis_bool_try.rb → datatypes/boolean_try.rb} +4 -2
  60. data/try/datatypes/datatype_base_try.rb +69 -0
  61. data/try/{25_redis_type_hash_try.rb → datatypes/hash_try.rb} +5 -3
  62. data/try/{23_redis_type_list_try.rb → datatypes/list_try.rb} +5 -3
  63. data/try/{22_redis_type_set_try.rb → datatypes/set_try.rb} +5 -3
  64. data/try/{21_redis_type_zset_try.rb → datatypes/sorted_set_try.rb} +6 -4
  65. data/try/{24_redis_type_string_try.rb → datatypes/string_try.rb} +8 -8
  66. data/try/edge_cases/empty_identifiers_try.rb +48 -0
  67. data/try/{92_symbolize_try.rb → edge_cases/hash_symbolization_try.rb} +12 -7
  68. data/try/edge_cases/json_serialization_try.rb +85 -0
  69. data/try/edge_cases/race_conditions_try.rb +60 -0
  70. data/try/edge_cases/reserved_keywords_try.rb +59 -0
  71. data/try/{93_string_coercion_try.rb → edge_cases/string_coercion_try.rb} +60 -59
  72. data/try/edge_cases/ttl_side_effects_try.rb +51 -0
  73. data/try/features/expiration_try.rb +86 -0
  74. data/try/features/quantization_try.rb +90 -0
  75. data/try/{35_feature_safedump_try.rb → features/safe_dump_advanced_try.rb} +7 -6
  76. data/try/features/safe_dump_try.rb +137 -0
  77. data/try/{test_helpers.rb → helpers/test_helpers.rb} +25 -60
  78. data/try/{27_redis_horreum_try.rb → horreum/base_try.rb} +39 -14
  79. data/try/horreum/class_methods_try.rb +41 -0
  80. data/try/horreum/commands_try.rb +49 -0
  81. data/try/{29_redis_horreum_initialization_try.rb → horreum/initialization_try.rb} +9 -7
  82. data/try/horreum/relations_try.rb +146 -0
  83. data/try/{28_redis_horreum_serialization_try.rb → horreum/serialization_try.rb} +13 -11
  84. data/try/horreum/settings_try.rb +43 -0
  85. data/try/integration/cross_component_try.rb +46 -0
  86. data/try/{41_customer_safedump_try.rb → models/customer_safe_dump_try.rb} +9 -7
  87. data/try/{40_customer_try.rb → models/customer_try.rb} +21 -18
  88. data/try/models/datatype_base_try.rb +100 -0
  89. data/try/{30_familia_object_try.rb → models/familia_object_try.rb} +18 -16
  90. data/try/performance/benchmarks_try.rb +55 -0
  91. data/try/pooling/README.md +20 -0
  92. data/try/pooling/configurable_stress_test_try.rb +435 -0
  93. data/try/pooling/connection_pool_test_try.rb +273 -0
  94. data/try/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
  95. data/try/pooling/lib/connection_pool_metrics.rb +372 -0
  96. data/try/pooling/lib/connection_pool_stress_test.rb +959 -0
  97. data/try/pooling/lib/connection_pool_threading_models.rb +421 -0
  98. data/try/pooling/lib/visualize_stress_results.rb +434 -0
  99. data/try/pooling/pool_siege_try.rb +509 -0
  100. data/try/pooling/run_stress_tests_try.rb +482 -0
  101. data/try/prototypes/atomic_saves_v1_context_proxy.rb +121 -0
  102. data/try/prototypes/atomic_saves_v2_connection_switching.rb +161 -0
  103. data/try/prototypes/atomic_saves_v3_connection_pool.rb +189 -0
  104. data/try/prototypes/atomic_saves_v4.rb +105 -0
  105. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +124 -0
  106. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
  107. metadata +143 -46
  108. data/.github/workflows/ruby.yml +0 -71
  109. data/VERSION.yml +0 -4
  110. data/lib/familia/redistype/commands.rb +0 -59
  111. data/lib/familia/redistype.rb +0 -228
  112. data/lib/familia/tools.rb +0 -68
  113. data/lib/redis_middleware.rb +0 -109
  114. data/try/20_redis_type_try.rb +0 -70
  115. data/try/91_json_bug_try.rb +0 -86
data/Gemfile.lock CHANGED
@@ -1,84 +1,145 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- familia (1.2.1)
4
+ familia (2.0.0.pre2)
5
+ benchmark
6
+ connection_pool
7
+ csv
8
+ logger
5
9
  redis (>= 4.8.1, < 6.0)
6
10
  stringio (~> 3.1.1)
7
- uri-redis (~> 1.3)
11
+ uri-valkey (~> 1.4)
8
12
 
9
13
  GEM
10
14
  remote: https://rubygems.org/
11
15
  specs:
12
- ast (2.4.2)
16
+ ast (2.4.3)
17
+ base64 (0.3.0)
18
+ benchmark (0.4.1)
13
19
  byebug (11.1.3)
14
20
  coderay (1.1.3)
15
- connection_pool (2.4.1)
21
+ concurrent-ruby (1.3.5)
22
+ connection_pool (2.5.3)
23
+ csv (3.3.5)
24
+ date (3.4.1)
25
+ diff-lcs (1.6.2)
16
26
  drydock (0.6.9)
17
- json (2.7.2)
18
- language_server-protocol (3.17.0.3)
27
+ erb (5.0.2)
28
+ io-console (0.8.1)
29
+ irb (1.15.2)
30
+ pp (>= 0.6.0)
31
+ rdoc (>= 4.0.0)
32
+ reline (>= 0.4.2)
33
+ json (2.13.0)
34
+ kramdown (2.5.1)
35
+ rexml (>= 3.3.9)
36
+ language_server-protocol (3.17.0.5)
37
+ lint_roller (1.1.0)
38
+ logger (1.7.0)
19
39
  method_source (1.1.0)
20
- parallel (1.25.1)
21
- parser (3.3.4.0)
40
+ minitest (5.25.5)
41
+ parallel (1.27.0)
42
+ parser (3.3.8.0)
22
43
  ast (~> 2.4.1)
23
44
  racc
45
+ pp (0.6.2)
46
+ prettyprint
47
+ prettyprint (0.2.0)
48
+ prism (1.4.0)
24
49
  pry (0.14.2)
25
50
  coderay (~> 1.1)
26
51
  method_source (~> 1.0)
27
52
  pry-byebug (3.10.1)
28
53
  byebug (~> 11.0)
29
54
  pry (>= 0.13, < 0.15)
30
- racc (1.8.0)
55
+ psych (5.2.6)
56
+ date
57
+ stringio
58
+ racc (1.8.1)
31
59
  rainbow (3.1.1)
32
- redis (5.2.0)
60
+ rdoc (6.14.2)
61
+ erb
62
+ psych (>= 4.0.0)
63
+ redis (5.4.1)
33
64
  redis-client (>= 0.22.0)
34
- redis-client (0.22.2)
65
+ redis-client (0.25.1)
35
66
  connection_pool
36
- regexp_parser (2.9.2)
37
- rexml (3.3.2)
38
- strscan
39
- rubocop (1.65.0)
67
+ regexp_parser (2.10.0)
68
+ reline (0.6.2)
69
+ io-console (~> 0.5)
70
+ rexml (3.4.1)
71
+ rspec (3.13.1)
72
+ rspec-core (~> 3.13.0)
73
+ rspec-expectations (~> 3.13.0)
74
+ rspec-mocks (~> 3.13.0)
75
+ rspec-core (3.13.5)
76
+ rspec-support (~> 3.13.0)
77
+ rspec-expectations (3.13.5)
78
+ diff-lcs (>= 1.2.0, < 2.0)
79
+ rspec-support (~> 3.13.0)
80
+ rspec-mocks (3.13.5)
81
+ diff-lcs (>= 1.2.0, < 2.0)
82
+ rspec-support (~> 3.13.0)
83
+ rspec-support (3.13.4)
84
+ rubocop (1.78.0)
40
85
  json (~> 2.3)
41
- language_server-protocol (>= 3.17.0)
86
+ language_server-protocol (~> 3.17.0.2)
87
+ lint_roller (~> 1.1.0)
42
88
  parallel (~> 1.10)
43
89
  parser (>= 3.3.0.2)
44
90
  rainbow (>= 2.2.2, < 4.0)
45
- regexp_parser (>= 2.4, < 3.0)
46
- rexml (>= 3.2.5, < 4.0)
47
- rubocop-ast (>= 1.31.1, < 2.0)
91
+ regexp_parser (>= 2.9.3, < 3.0)
92
+ rubocop-ast (>= 1.45.1, < 2.0)
48
93
  ruby-progressbar (~> 1.7)
49
- unicode-display_width (>= 2.4.0, < 3.0)
50
- rubocop-ast (1.31.3)
51
- parser (>= 3.3.1.0)
52
- rubocop-performance (1.21.1)
53
- rubocop (>= 1.48.1, < 2.0)
54
- rubocop-ast (>= 1.31.1, < 2.0)
55
- rubocop-thread_safety (0.5.1)
56
- rubocop (>= 0.90.0)
94
+ unicode-display_width (>= 2.4.0, < 4.0)
95
+ rubocop-ast (1.46.0)
96
+ parser (>= 3.3.7.2)
97
+ prism (~> 1.4)
98
+ rubocop-performance (1.25.0)
99
+ lint_roller (~> 1.1)
100
+ rubocop (>= 1.75.0, < 2.0)
101
+ rubocop-ast (>= 1.38.0, < 2.0)
102
+ rubocop-thread_safety (0.7.3)
103
+ lint_roller (~> 1.1)
104
+ rubocop (~> 1.72, >= 1.72.1)
105
+ rubocop-ast (>= 1.44.0, < 2.0)
106
+ ruby-prof (1.7.2)
107
+ base64
57
108
  ruby-progressbar (1.13.0)
109
+ stackprof (0.2.27)
58
110
  storable (0.10.0)
59
- stringio (3.1.1)
60
- strscan (3.1.0)
111
+ stringio (3.1.7)
61
112
  sysinfo (0.10.0)
62
113
  drydock (< 1.0)
63
114
  storable (~> 0.10)
64
- tryouts (2.4.1)
65
- sysinfo (~> 0.10)
66
- unicode-display_width (2.5.0)
67
- uri-redis (1.3.0)
115
+ tryouts (3.1.1)
116
+ minitest (~> 5.0)
117
+ rspec (~> 3.0)
118
+ sysinfo (>= 0.8, < 1.0)
119
+ unicode-display_width (3.1.4)
120
+ unicode-emoji (~> 4.0, >= 4.0.4)
121
+ unicode-emoji (4.0.4)
122
+ uri-valkey (1.4.0)
123
+ yard (0.9.37)
68
124
 
69
125
  PLATFORMS
70
- arm64-darwin-23
71
126
  arm64-darwin-24
72
127
  ruby
73
128
 
74
129
  DEPENDENCIES
75
130
  byebug (~> 11.0)
131
+ concurrent-ruby (~> 1.3.5)
76
132
  familia!
133
+ irb (~> 1.15.2)
134
+ kramdown
77
135
  pry-byebug (~> 3.10.1)
78
136
  rubocop
79
137
  rubocop-performance
80
138
  rubocop-thread_safety
81
- tryouts (~> 2.4)
139
+ ruby-prof
140
+ stackprof
141
+ tryouts (~> 3.1.1)
142
+ yard (~> 0.9)
82
143
 
83
144
  BUNDLED WITH
84
- 2.5.13
145
+ 2.6.2
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # Familia - 1.1.0-rc1 (November 2024)
1
+ # Familia - 2.0.0
2
2
 
3
- **Organize and store Ruby objects in Redis. A powerful Ruby ORM (of sorts) for Redis.**
3
+ **Organize and store Ruby objects in Valkey/Redis. A powerful Ruby ORM (of sorts) for Valkey/Redis.**
4
4
 
5
- Familia provides a flexible and feature-rich way to interact with Redis using Ruby objects. It's designed to make working with Redis as natural as working with Ruby classes, while offering advanced features for complex data management.
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.
6
6
 
7
7
  ## Installation
8
8
 
@@ -18,11 +18,11 @@ Get it in one of the following ways:
18
18
 
19
19
  ### 1. Defining Horreum Classes
20
20
 
21
- Familia uses the concept of "Horreum" classes to represent Redis-backed objects:
21
+ Familia uses the concept of "Horreum" classes to represent Valkey-compatible objects:
22
22
 
23
23
  ```ruby
24
24
  class Flower < Familia::Horreum
25
- identifier :token
25
+ identifier_field :token
26
26
  field :name
27
27
  list :owners
28
28
  set :tags
@@ -38,20 +38,20 @@ You can define identifiers in various ways:
38
38
 
39
39
  ```ruby
40
40
  class User < Familia::Horreum
41
- identifier :email
41
+ identifier_field :email
42
42
  # or
43
- identifier -> (user) { "user:#{user.email}" }
43
+ identifier_field -> (user) { "user:#{user.email}" }
44
44
  # or
45
- identifier [:type, :email]
45
+ identifier_field [:type, :email]
46
46
 
47
47
  field :email
48
48
  field :type
49
49
  end
50
50
  ```
51
51
 
52
- ### 3. Redis Data Types
52
+ ### 3. Data Types
53
53
 
54
- Familia supports various Redis data types:
54
+ Familia supports various Valkey-compatible data types:
55
55
 
56
56
  ```ruby
57
57
  class Product < Familia::Horreum
@@ -63,9 +63,9 @@ class Product < Familia::Horreum
63
63
  end
64
64
  ```
65
65
 
66
- ### 4. Class-level Redis Types
66
+ ### 4. Class-level Valkey-compatible Types
67
67
 
68
- You can also define Redis types at the class level:
68
+ You can also define Valkey-compatible types at the class level:
69
69
 
70
70
  ```ruby
71
71
  class Customer < Familia::Horreum
@@ -78,12 +78,12 @@ end
78
78
 
79
79
  ### 5. Automatic Expiration
80
80
 
81
- Use the expiration feature to set TTL for objects:
81
+ Use the expiration feature to set default TTL for objects:
82
82
 
83
83
  ```ruby
84
84
  class Session < Familia::Horreum
85
85
  feature :expiration
86
- ttl 180.minutes
86
+ default_expiration 180.minutes
87
87
  end
88
88
  ```
89
89
 
@@ -110,7 +110,7 @@ Use quantization for time-based metrics:
110
110
  ```ruby
111
111
  class DailyMetric < Familia::Horreum
112
112
  feature :quantization
113
- string :counter, ttl: 1.day, quantize: [10.minutes, '%H:%M']
113
+ string :counter, default_expiration: 1.day, quantize: [10.minutes, '%H:%M']
114
114
  end
115
115
  ```
116
116
 
@@ -140,13 +140,6 @@ class Customer < Familia::Horreum
140
140
  verified && !reset_requested
141
141
  end
142
142
  end
143
-
144
- class Session < Familia::Horreum
145
- def external_identifier
146
- elements = [ipaddress || 'UNKNOWNIP', custid || 'anon']
147
- @external_identifier ||= Familia.generate_sha_hash(elements)
148
- end
149
- end
150
143
  ```
151
144
  ### 10. Open-ended Serialization
152
145
 
@@ -171,6 +164,29 @@ user.transaction do |conn|
171
164
  end
172
165
  ```
173
166
 
167
+ ### 12. Connection Management and Pooling
168
+
169
+ Familia supports custom connection providers for advanced scenarios like connection pooling:
170
+
171
+ ```ruby
172
+ # Using connection_pool gem for thread-safe pooling
173
+ require 'connection_pool'
174
+
175
+ # Create pools for each logical database
176
+ pools = {
177
+ "redis://localhost:6379/0" => ConnectionPool.new(size: 10) { Redis.new(db: 0) },
178
+ "redis://localhost:6379/1" => ConnectionPool.new(size: 5) { Redis.new(db: 1) }
179
+ }
180
+
181
+ # Configure Familia to use the pools
182
+ Familia.connection_provider = lambda do |uri|
183
+ pool = pools[uri] || pools["redis://localhost:6379/0"]
184
+ pool.with { |conn| conn }
185
+ end
186
+ ```
187
+
188
+ See the [Connection Pooling Guide](docs/connection_pooling.md) for detailed examples.
189
+
174
190
 
175
191
  ## Usage Examples
176
192
 
@@ -225,7 +241,7 @@ end
225
241
 
226
242
  ## Conclusion
227
243
 
228
- Familia provides a powerful and flexible way to work with Redis in Ruby applications. Its features like automatic expiration, safe dumping, and quantization make it suitable for a wide range of use cases, from simple key-value storage to complex time-series data management.
244
+ Familia provides a powerful and flexible way to work with Valkey-compatible in Ruby applications. Its features like automatic expiration, safe dumping, and quantization make it suitable for a wide range of use cases, from simple key-value storage to complex time-series data management.
229
245
 
230
246
  For more information, visit:
231
247
  - [Github Repository](https://github.com/delano/familia)
data/bin/irb ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ irb -Ilib -r familia -Itry -r helpers/test_helpers
@@ -0,0 +1,192 @@
1
+ # Connection Pooling with Familia
2
+
3
+ Familia uses a connection provider pattern for efficient connection pooling. This guide shows how to configure pools for optimal performance with multiple logical databases.
4
+
5
+ ## Key Concepts
6
+
7
+ - **Connection Provider Contract**: Your provider MUST return connections already on the correct logical database. Familia will NOT issue SELECT commands.
8
+ - **URI-based Selection**: Familia passes normalized URIs (e.g., `redis://localhost:6379/2`) encoding the logical database.
9
+ - **One Pool Per Database**: Each unique logical database requires its own connection pool.
10
+
11
+ ## Basic Setup
12
+
13
+ ### Simple Connection Pool
14
+
15
+ ```ruby
16
+ require 'connection_pool'
17
+
18
+ class MyApp
19
+ @pools = {}
20
+
21
+ Familia.connection_provider = lambda do |uri|
22
+ parsed = URI.parse(uri)
23
+ pool_key = "#{parsed.host}:#{parsed.port}/#{parsed.db || 0}"
24
+
25
+ @pools[pool_key] ||= ConnectionPool.new(size: 10, timeout: 5) do
26
+ Redis.new(
27
+ host: parsed.host,
28
+ port: parsed.port,
29
+ db: parsed.db || 0
30
+ )
31
+ end
32
+
33
+ @pools[pool_key].with { |conn| conn }
34
+ end
35
+ end
36
+ ```
37
+
38
+ ### Multi-Database Configuration
39
+
40
+ ```ruby
41
+ class MyApp
42
+ POOL_CONFIGS = {
43
+ 0 => { size: 20 }, # Main database
44
+ 1 => { size: 5 }, # Analytics
45
+ 2 => { size: 10 } # Cache
46
+ }.freeze
47
+
48
+ @pools = {}
49
+
50
+ Familia.connection_provider = lambda do |uri|
51
+ parsed = URI.parse(uri)
52
+ db = parsed.db || 0
53
+ pool_key = "#{parsed.host}:#{parsed.port}/#{db}"
54
+
55
+ @pools[pool_key] ||= begin
56
+ config = POOL_CONFIGS[db] || { size: 5 }
57
+ ConnectionPool.new(timeout: 5, **config) do
58
+ Redis.new(host: parsed.host, port: parsed.port, db: db)
59
+ end
60
+ end
61
+
62
+ @pools[pool_key].with { |conn| conn }
63
+ end
64
+ end
65
+ ```
66
+
67
+ ### Production Setup with Roda
68
+
69
+ ```ruby
70
+ # config/familia.rb
71
+ class FamiliaPoolManager
72
+ include Singleton
73
+
74
+ def initialize
75
+ @pools = {}
76
+ end
77
+
78
+ def get_connection(uri)
79
+ parsed = URI.parse(uri)
80
+ pool_key = "#{parsed.host}:#{parsed.port}/#{parsed.db || 0}"
81
+
82
+ @pools[pool_key] ||= ConnectionPool.new(
83
+ size: pool_size_for_environment,
84
+ timeout: 5
85
+ ) do
86
+ Redis.new(
87
+ host: parsed.host,
88
+ port: parsed.port,
89
+ db: parsed.db || 0,
90
+ timeout: 1,
91
+ reconnect_attempts: 3
92
+ )
93
+ end
94
+
95
+ @pools[pool_key].with { |conn| conn }
96
+ end
97
+
98
+ private
99
+
100
+ def pool_size_for_environment
101
+ if defined?(Sidekiq)
102
+ Sidekiq.options[:concurrency] + 2
103
+ else
104
+ ENV.fetch('WEB_CONCURRENCY', 5).to_i + 2
105
+ end
106
+ end
107
+ end
108
+
109
+ # Configure at application startup
110
+ Familia.connection_provider = lambda do |uri|
111
+ FamiliaPoolManager.instance.get_connection(uri)
112
+ end
113
+
114
+ # In your Roda app
115
+ class App < Roda
116
+ plugin :hooks
117
+
118
+ before do
119
+ # Familia pools are automatically used via connection_provider
120
+ end
121
+ end
122
+ ```
123
+
124
+ ## Model Configuration
125
+
126
+ Configure models to use different logical databases:
127
+
128
+ ```ruby
129
+ class Customer < Familia::Horreum
130
+ self.logical_database = 0 # Main application data
131
+ field :name, :email
132
+ end
133
+
134
+ class Analytics < Familia::Horreum
135
+ self.logical_database = 1 # Analytics data
136
+ field :event_type, :timestamp
137
+ end
138
+
139
+ class Session < Familia::Horreum
140
+ self.logical_database = 2 # Session/cache data
141
+ feature :expiration
142
+ default_expiration 1.hour
143
+ field :user_id, :data
144
+ end
145
+ ```
146
+
147
+ ## Performance Benefits
148
+
149
+ Without connection pooling, each operation triggers database switches:
150
+ ```
151
+ SET key value # Connection on DB 0
152
+ SELECT 2 # Switch to DB 2
153
+ SET key2 value2 # Now on DB 2
154
+ SELECT 0 # Switch back
155
+ ```
156
+
157
+ With proper pooling, connections stay on the correct database:
158
+ ```
159
+ SET key value # Connection already on DB 0
160
+ SET key2 value2 # Different connection, already on DB 2
161
+ ```
162
+
163
+ ## Pool Sizing Guidelines
164
+
165
+ - **Web Applications**: `threads + 2`
166
+ - **Background Jobs**: `concurrency + 2`
167
+ - **High Traffic DBs**: Scale up based on usage patterns
168
+
169
+ ## Testing and Debugging
170
+
171
+ Enable debug mode to verify correct database selection:
172
+ ```ruby
173
+ Familia.debug = true
174
+ ```
175
+
176
+ Test concurrent access:
177
+ ```ruby
178
+ threads = 10.times.map do |i|
179
+ Thread.new do
180
+ 100.times { |j| MyModel.create(value: "test-#{i}-#{j}") }
181
+ end
182
+ end
183
+ threads.each(&:join)
184
+ ```
185
+
186
+ ## Best Practices
187
+
188
+ - Return connections already on the correct database
189
+ - Use one pool per unique logical database
190
+ - Implement thread-safe pool creation
191
+ - Monitor pool usage and adjust sizes accordingly
192
+ - Use the `connection_pool` gem for production reliability
data/familia.gemspec CHANGED
@@ -1,12 +1,12 @@
1
- # frozen_string_literal: true
1
+ # lib/familia/settings.rb
2
2
 
3
3
  require_relative 'lib/familia/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'familia'
7
- spec.version = Familia::VERSION.to_s
8
- spec.summary = 'An ORM for Redis in Ruby.'
9
- spec.description = "Familia: #{spec.summary}. Organize and store ruby objects in Redis"
7
+ spec.version = Familia::VERSION
8
+ spec.summary = 'An ORM for Valkey-compatible databases in Ruby.'
9
+ spec.description = "Familia: #{spec.summary}. Organize and store ruby objects in Valkey/Redis"
10
10
  spec.authors = ['Delano Mandelbaum']
11
11
  spec.email = 'gems@solutious.com'
12
12
  spec.homepage = 'https://github.com/delano/familia'
@@ -17,11 +17,15 @@ Gem::Specification.new do |spec|
17
17
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
18
  spec.require_paths = ['lib']
19
19
 
20
- spec.required_ruby_version = Gem::Requirement.new('>= 2.7.8')
20
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.4')
21
21
 
22
+ spec.add_dependency 'benchmark'
23
+ spec.add_dependency 'connection_pool'
24
+ spec.add_dependency 'csv'
25
+ spec.add_dependency 'logger'
22
26
  spec.add_dependency 'redis', '>= 4.8.1', '< 6.0'
23
27
  spec.add_dependency 'stringio', '~> 3.1.1'
24
- spec.add_dependency 'uri-redis', '~> 1.3'
28
+ spec.add_dependency 'uri-valkey', '~> 1.4'
25
29
 
26
30
  spec.metadata['rubygems_mfa_required'] = 'true'
27
31
  end
data/lib/familia/base.rb CHANGED
@@ -1,8 +1,8 @@
1
- # frozen_string_literal: true
1
+ # lib/familia/base.rb
2
2
 
3
3
  #
4
4
  module Familia
5
- # A common module for Familia::RedisType and Familia::Horreum to include.
5
+ # A common module for Familia::DataType and Familia::Horreum to include.
6
6
  #
7
7
  # This allows us to use a single comparison to check if a class is a
8
8
  # Familia class. e.g.
@@ -11,13 +11,23 @@ module Familia
11
11
  # klass.ancestors.member?(Familia::Base) # => true
12
12
  #
13
13
  # @see Familia::Horreum
14
- # @see Familia::RedisType
14
+ # @see Familia::DataType
15
15
  #
16
16
  module Base
17
17
  @features = nil
18
18
  @dump_method = :to_json
19
19
  @load_method = :from_json
20
20
 
21
+ # Returns a string representation of the object. Implementing classes
22
+ # are welcome to override this method to provide a more meaningful
23
+ # representation. Using this as a default via super is recommended.
24
+ #
25
+ # @return [String] A string representation of the object. Never nil.
26
+ #
27
+ def to_s
28
+ "#<#{self.class}:0x#{object_id.to_s(16)}>"
29
+ end
30
+
21
31
  class << self
22
32
  attr_reader :features
23
33
  attr_accessor :dump_method, :load_method
@@ -34,23 +44,23 @@ module Familia
34
44
  # with the :expiration feature's implementation.
35
45
  #
36
46
  # This is a no-op implementation that gets overridden by features like
37
- # :expiration. It accepts an optional ttl parameter to maintain interface
47
+ # :expiration. It accepts an optional default_expiration parameter to maintain interface
38
48
  # compatibility with the overriding implementations.
39
49
  #
40
- # @param ttl [Integer, nil] Time To Live in seconds (ignored in base implementation)
50
+ # @param default_expiration [Integer, nil] Time To Live in seconds (ignored in base implementation)
41
51
  # @return [nil] Always returns nil
42
52
  #
43
53
  # @note This is a no-op implementation. Classes that need expiration
44
54
  # functionality should include the :expiration feature.
45
55
  #
46
- def update_expiration(ttl: nil)
47
- Familia.ld "[update_expiration] Feature not enabled for #{self.class}. Key: #{rediskey} (caller: #{caller(1..1)})"
56
+ def update_expiration(default_expiration: nil)
57
+ Familia.ld "[update_expiration] Feature not enabled for #{self.class}. Key: #{dbkey} (caller: #{caller(1..1)})"
48
58
  nil
49
59
  end
50
60
 
51
61
  def generate_id
52
- @key ||= Familia.generate_id
53
- @key
62
+ @identifier ||= Familia.generate_id
63
+ @identifier
54
64
  end
55
65
 
56
66
  def uuid