redis-client-namespace 0.1.1 → 0.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e13d5a883c2e2dd430843640140758704e128296ddd3ff65c7eddae15a6b98e
4
- data.tar.gz: 22f7e62d0ccc6d1d2c4b0c888f0985a4e431ede0eb4730f356f7a1a788a1796f
3
+ metadata.gz: 1b936795d1c5a2100eccec3e4d0bda4f909dc2c7afa4c6d6585a690efe9d352b
4
+ data.tar.gz: b86791a8d99b72f3869ec42b1e3fa424b343fb63673abedf9b20d1c2685249ed
5
5
  SHA512:
6
- metadata.gz: d46cda192df4c1623c8eeb92ec9b2ade00a59a9f26908a115083d29066129d6a80f96e8f6f814d60973ef419e570d0ce68f5772659e1ff133548ed16acd29fc3
7
- data.tar.gz: 626cea93d4bc9e245ced597235cab8b833a05ad2bdcc3c25aae06dce41e5131df59d55b76186b3fced8cf40167ed95cee0c0b75fbe54fa3f6ba52d43e88deb71
6
+ metadata.gz: 5b18227306bb697a65ebd78583cc7ac6b1f87ca4281cc82d121f0df9299bfb10e237d56b9e70f31b6ecbcacf749b95f32049dc699c8f0be7bfe62e7b004fe1a5
7
+ data.tar.gz: 279dc732d7b2a9a692769040e0b6b8ef2de12dbab83638d550d1a4a7e5e47fd5b62cd007e2df8fa00e1f3a7fb8d2706450aa40b865944b966adb3048de1ecaa1
data/.rubocop.yml CHANGED
@@ -47,13 +47,16 @@ Naming/FileName:
47
47
  - 'lib/redis-client-namespace.rb'
48
48
 
49
49
  # Test helper method for processing Redis commands.json - complexity is justified
50
+ # Also exclude trimed_result method which handles multiple command types
50
51
  Metrics/AbcSize:
51
52
  Exclude:
52
53
  - 'spec/redis_client/namespace/command_builder_auto_spec.rb'
54
+ - 'lib/redis_client/namespace/command_builder.rb'
53
55
 
54
56
  Metrics/CyclomaticComplexity:
55
57
  Exclude:
56
58
  - 'spec/redis_client/namespace/command_builder_auto_spec.rb'
59
+ - 'lib/redis_client/namespace/command_builder.rb'
57
60
 
58
61
  Metrics/MethodLength:
59
62
  Exclude:
@@ -61,4 +64,5 @@ Metrics/MethodLength:
61
64
 
62
65
  Metrics/PerceivedComplexity:
63
66
  Exclude:
64
- - 'spec/redis_client/namespace/command_builder_auto_spec.rb'
67
+ - 'spec/redis_client/namespace/command_builder_auto_spec.rb'
68
+ - 'lib/redis_client/namespace/command_builder.rb'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2025-08-01
4
+
5
+ ### Added
6
+ - Added `RedisClient::Namespace::Middleware` for full namespace support including result processing
7
+ - Added automatic removal of namespace prefixes from command results (KEYS, SCAN, BLPOP, BRPOP)
8
+ - Middleware approach allows proper handling of both command transformation and result trimming
9
+ - Added comprehensive Sidekiq integration tests with Sidekiq::Launcher
10
+
11
+ ### Changed
12
+ - **BREAKING**: Middleware approach is now the recommended way to use RedisClient::Namespace
13
+ - Updated README to focus on middleware usage with comprehensive examples
14
+ - Updated Sidekiq integration examples to use middleware approach
15
+
16
+ ### Deprecated
17
+ - Using `RedisClient::Namespace` as a command_builder is now deprecated
18
+ - The command_builder approach cannot process results to remove namespace prefixes
19
+ - Users should migrate to the middleware approach for complete namespace functionality
20
+
21
+ ### Technical Changes
22
+ - Refactored `namespaced_command` method as a static method in `CommandBuilder` module
23
+ - Added `trimed_result` method for processing command results
24
+ - Enhanced middleware implementation to handle both single commands and pipelined operations
25
+
3
26
  ## [0.1.1] - 2025-07-29
4
27
 
5
28
  - Add `RedisClient::Namespace.command_builder` class method for conditional namespacing based on environment variables
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A Redis namespace extension for [redis-client](https://github.com/redis-rb/redis-client) gem that automatically prefixes Redis keys with a namespace, enabling multi-tenancy and key isolation in Redis applications.
4
4
 
5
- This gem works by wrapping `RedisClient::CommandBuilder` and intercepting Redis commands to transparently add namespace prefixes to keys before they are sent to Redis.
5
+ This gem works by implementing a RedisClient middleware that intercepts Redis commands to transparently add namespace prefixes to keys before they are sent to Redis and removes them from results (with some limitations for certain commands like Pub/Sub events).
6
6
 
7
7
  ## Motivation
8
8
 
@@ -11,10 +11,7 @@ This gem was created to provide namespace support for [Sidekiq](https://github.c
11
11
  ## Features
12
12
 
13
13
  - **Transparent key namespacing**: Automatically prefixes Redis keys with a configurable namespace
14
- - **Comprehensive command support**: Supports all Redis commands with intelligent key detection
15
- - **Customizable separator**: Configure the namespace separator (default: `:`)
16
- - **Nested namespaces**: Support for nested command builders with multiple namespace levels
17
- - **Zero configuration**: Works out of the box with sensible defaults
14
+ - **Comprehensive command support**: Supports most Redis commands with intelligent key detection
18
15
  - **High performance**: Minimal overhead with efficient command transformation
19
16
  - **Thread-safe**: Safe for use in multi-threaded applications
20
17
 
@@ -40,70 +37,68 @@ gem install redis-client-namespace
40
37
 
41
38
  ## Usage
42
39
 
43
- ### Basic Usage
40
+ ### with RedisClient
41
+
42
+ RedisClient::Namespace is implemented as a [RedisClient middleware](https://github.com/redis-rb/redis-client#instrumentation-and-middlewares) that transparently handles both command transformation and result processing:
44
43
 
45
44
  ```ruby
46
45
  require 'redis-client-namespace'
47
46
 
48
- # Create a namespaced command builder
49
- namespace = RedisClient::Namespace.new("myapp")
50
-
51
- # Use with redis-client
52
- client = RedisClient.config(command_builder: namespace).new_client
47
+ # Configure RedisClient with the namespace middleware
48
+ client = RedisClient.config(
49
+ middlewares: [RedisClient::Namespace::Middleware],
50
+ custom: { namespace: "myapp" }
51
+ ).new_client
53
52
 
54
53
  # All commands will be automatically namespaced
55
54
  client.call("SET", "user:123", "john") # Actually sets "myapp:user:123"
56
55
  client.call("GET", "user:123") # Actually gets "myapp:user:123"
57
- client.call("DEL", "user:123", "user:456") # Actually deletes "myapp:user:123", "myapp:user:456"
56
+
57
+ # Commands that return keys will have the namespace automatically removed
58
+ client.call("KEYS", "*") # Returns ["user:123"] instead of ["myapp:user:123"]
59
+ client.call("SCAN", 0, "MATCH", "user:*") # Returns keys without namespace prefix
58
60
  ```
59
61
 
60
62
  ### Custom Separator
61
63
 
62
64
  ```ruby
63
65
  # Use a custom separator
64
- namespace = RedisClient::Namespace.new("myapp", separator: "-")
65
- client = RedisClient.config(command_builder: namespace).new_client
66
+ client = RedisClient.config(
67
+ middlewares: [RedisClient::Namespace::Middleware],
68
+ custom: { namespace: "myapp", separator: "-" }
69
+ ).new_client
66
70
 
67
71
  client.call("SET", "user:123", "john") # Actually sets "myapp-user:123"
68
72
  ```
69
73
 
70
- ### Nested Namespaces
74
+ ### with Redis (`redis-rb`)
71
75
 
72
- ```ruby
73
- # Create nested namespaces
74
- parent = RedisClient::Namespace.new("myapp")
75
- child = RedisClient::Namespace.new("jobs", parent_command_builder: parent)
76
-
77
- client = RedisClient.config(command_builder: child).new_client
78
- client.call("SET", "queue", "important") # Actually sets "jobs:myapp:queue"
79
- ```
80
-
81
- ### with `Redis` ([`redis-rb`](https://github.com/redis/redis-rb))
82
-
83
- This gem also works with the [redis](https://github.com/redis/redis-rb) gem through its `command_builder` option:
76
+ This gem also works with the [redis](https://github.com/redis/redis-rb) gem through its middleware support:
84
77
 
85
78
  ```ruby
86
79
  require 'redis'
87
80
  require 'redis-client-namespace'
88
81
 
89
- # Create a namespaced command builder
90
- namespace = RedisClient::Namespace.new("myapp")
91
-
92
- # Use with redis-rb
93
- redis = Redis.new(host: 'localhost', port: 6379, command_builder: namespace)
82
+ # Use with redis-rb via middleware
83
+ redis = Redis.new(
84
+ host: 'localhost',
85
+ port: 6379,
86
+ middlewares: [RedisClient::Namespace::Middleware],
87
+ custom: { namespace: "myapp", separator: ":" }
88
+ )
94
89
 
95
90
  # All commands will be automatically namespaced
96
91
  redis.set("user:123", "john") # Actually sets "myapp:user:123"
97
92
  redis.get("user:123") # Actually gets "myapp:user:123"
98
93
  redis.del("user:123", "user:456") # Actually deletes "myapp:user:123", "myapp:user:456"
99
94
 
100
- # Works with all Redis commands
95
+ # Works with most Redis commands
101
96
  redis.lpush("queue", ["job1", "job2"]) # Actually pushes to "myapp:queue"
102
97
  redis.hset("config", "timeout", "30") # Actually sets in "myapp:config"
103
98
  redis.sadd("tags", "ruby", "rails") # Actually adds to "myapp:tags"
104
99
 
105
- # Pattern matching
106
- redis.keys("user:*") # Actually searches for "myapp:user:*"
100
+ # Pattern matching with automatic namespace removal
101
+ redis.keys("user:*") # Returns ["user:123"] instead of ["myapp:user:123"]
107
102
  ```
108
103
 
109
104
  ### Sidekiq Integration
@@ -114,19 +109,19 @@ This gem is particularly useful for Sidekiq applications that need namespace iso
114
109
  # In your Sidekiq configuration
115
110
  require 'redis-client-namespace'
116
111
 
117
- namespace = RedisClient::Namespace.new("sidekiq_production")
118
-
119
112
  Sidekiq.configure_server do |config|
120
113
  config.redis = {
121
114
  url: 'redis://redis:6379/1',
122
- command_builder: namespace,
115
+ middlewares: [RedisClient::Namespace::Middleware],
116
+ custom: { namespace: "sidekiq_production", separator: ":" }
123
117
  }
124
118
  end
125
119
 
126
120
  Sidekiq.configure_client do |config|
127
121
  config.redis = {
128
122
  url: 'redis://redis:6379/1',
129
- command_builder: namespace,
123
+ middlewares: [RedisClient::Namespace::Middleware],
124
+ custom: { namespace: "sidekiq_production", separator: ":" }
130
125
  }
131
126
  end
132
127
  ```
@@ -148,6 +143,32 @@ RedisClient::Namespace supports the vast majority of Redis commands with intelli
148
143
 
149
144
  The gem automatically detects which arguments are keys and applies the namespace prefix accordingly.
150
145
 
146
+ ### Limitations
147
+
148
+ #### Unknown Commands
149
+
150
+ Commands not explicitly supported by the gem will generate a warning and be passed through without namespace transformation. This ensures compatibility but means namespace isolation may not work for newer or less common Redis commands.
151
+
152
+ #### Pub/Sub Events
153
+
154
+ When using the Middleware approach, Pub/Sub subscribe events (via `pubsub.next_event`) are not automatically processed to remove namespace prefixes from channel names. This is because the middleware doesn't have access to intercept the `next_event` method.
155
+
156
+ If you're using Pub/Sub with the middleware approach, you'll need to manually handle namespace removal:
157
+
158
+ ```ruby
159
+ # With middleware approach
160
+ pubsub = client.pubsub
161
+ pubsub.call("SUBSCRIBE", "channel1") # Subscribes to "myapp:channel1"
162
+
163
+ # You need to manually remove the namespace prefix from received events
164
+ event = pubsub.next_event
165
+ if event && event[0] == "message"
166
+ channel = event[1].delete_prefix("myapp:") # Remove namespace manually
167
+ message = event[2]
168
+ end
169
+ ```
170
+
171
+
151
172
  ## Advanced Features
152
173
 
153
174
  ### Pattern Matching
@@ -155,11 +176,14 @@ The gem automatically detects which arguments are keys and applies the namespace
155
176
  For commands like `SCAN` and `KEYS`, the namespace is automatically applied to patterns:
156
177
 
157
178
  ```ruby
158
- namespace = RedisClient::Namespace.new("myapp")
159
- client = RedisClient.config(command_builder: namespace).new_client
179
+ client = RedisClient.config(
180
+ middlewares: [RedisClient::Namespace::Middleware],
181
+ custom: { namespace: "myapp", separator: ":" }
182
+ ).new_client
160
183
 
161
184
  # This will scan for "myapp:user:*" pattern
162
185
  client.call("SCAN", 0, "MATCH", "user:*")
186
+ # Returns keys without the namespace prefix for easier handling
163
187
  ```
164
188
 
165
189
  ### Complex Commands
@@ -178,20 +202,21 @@ client.call("EVAL", "return redis.call('get', KEYS[1])", 1, "mykey")
178
202
 
179
203
  ## Configuration Options
180
204
 
205
+ When using the middleware approach, configure via the `custom` option:
206
+
181
207
  - `namespace`: The namespace prefix to use (required)
182
208
  - `separator`: The separator between namespace and key (default: `":"`)
183
- - `parent_command_builder`: Parent command builder for nested namespaces (default: `RedisClient::CommandBuilder`)
184
209
 
185
210
  ## Thread Safety
186
211
 
187
- RedisClient::Namespace is **thread-safe** and can be used in multi-threaded applications without additional synchronization. The implementation:
212
+ RedisClient::Namespace is **thread-safe** and can be used in multi-threaded applications without additional synchronization. The middleware implementation:
188
213
 
189
- - Uses immutable instance variables (`@namespace`, `@separator`, `@parent_command_builder`) that are set once during initialization
190
- - Never modifies shared state during command processing
191
- - Creates new command arrays for each operation without mutating the original
214
+ - Reads configuration from `redis_config.custom` without maintaining any state
215
+ - Uses class methods in `CommandBuilder` that don't modify shared state
216
+ - Each call receives its own command array from RedisClient, avoiding shared mutable state
192
217
  - Uses frozen constants for strategy and command mappings
193
218
 
194
- Each `generate` call is completely independent, making it safe to use the same namespace instance across multiple threads.
219
+ Each middleware call is completely independent, making it safe to use the same middleware across multiple threads and connections.
195
220
 
196
221
  ## Performance
197
222
 
@@ -25,8 +25,13 @@ plain_redis = Redis.new(host: REDIS_HOST, port: REDIS_PORT, db: REDIS_DB)
25
25
  redis_namespace = Redis::Namespace.new("bench_old", redis: plain_redis)
26
26
 
27
27
  # redis-client-namespace (new approach)
28
- namespace_builder = RedisClient::Namespace.new("bench_new")
29
- redis_client_namespace = Redis.new(host: REDIS_HOST, port: REDIS_PORT, db: REDIS_DB, command_builder: namespace_builder)
28
+ redis_client_namespace = Redis.new(
29
+ host: REDIS_HOST,
30
+ port: REDIS_PORT,
31
+ db: REDIS_DB,
32
+ middlewares: [RedisClient::Namespace::Middleware],
33
+ custom: { namespace: "bench_new" }
34
+ )
30
35
 
31
36
  # Clean up before benchmark
32
37
  plain_redis.flushdb
data/benchmark/results.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Benchmark Results: redis-namespace vs redis-client-namespace
2
2
 
3
- Date: 2025-07-29
3
+ Date: 2025-07-31 (Updated with middleware implementation)
4
4
 
5
5
  ## Test Environment
6
6
 
@@ -12,7 +12,7 @@ Date: 2025-07-29
12
12
 
13
13
  ## Summary
14
14
 
15
- The benchmarks show that `redis-client-namespace` performs comparably to `redis-namespace` across all tested operations. In most cases, the performance difference falls within the margin of error, indicating that the new implementation introduces minimal overhead.
15
+ The benchmarks show that `redis-client-namespace` with the new middleware-based architecture performs excellently compared to `redis-namespace`. The middleware implementation has achieved performance parity or slight improvements across all tested operations, with performance differences consistently falling within the margin of error. This demonstrates that the middleware pattern successfully maintains high performance while providing cleaner architecture.
16
16
 
17
17
  ## Detailed Results
18
18
 
@@ -20,67 +20,71 @@ The benchmarks show that `redis-client-namespace` performs comparably to `redis-
20
20
 
21
21
  | Operation | redis-namespace | redis-client-namespace | Comparison |
22
22
  |-----------|----------------|----------------------|------------|
23
- | SET | 2,045.7 i/s | 2,093.9 i/s | Same-ish (within error) |
24
- | GET | 2,516.3 i/s | 2,473.3 i/s | Same-ish (within error) |
23
+ | SET | 4,153.9 i/s | 4,206.5 i/s | Same-ish (within error) |
24
+ | GET | 4,213.9 i/s | 4,283.6 i/s | Same-ish (within error) |
25
25
 
26
26
  ### Multiple Key Operations
27
27
 
28
28
  | Operation | redis-namespace | redis-client-namespace | Comparison |
29
29
  |-----------|----------------|----------------------|------------|
30
- | MGET (10 keys) | 2,386.6 i/s | 2,440.0 i/s | Same-ish (within error) |
30
+ | MGET (10 keys) | 3,993.6 i/s | 4,011.5 i/s | Same-ish (within error) |
31
31
 
32
32
  ### List Operations
33
33
 
34
34
  | Operation | redis-namespace | redis-client-namespace | Comparison |
35
35
  |-----------|----------------|----------------------|------------|
36
- | LPUSH | 2,264.6 i/s | 2,198.1 i/s | Same-ish (within error) |
37
- | LRANGE | 64.4 i/s | 67.7 i/s | Same-ish (within error) |
36
+ | LPUSH | 4,004.2 i/s | 4,042.7 i/s | Same-ish (within error) |
37
+ | LRANGE | 52.5 i/s | 53.0 i/s | Same-ish (within error) |
38
38
 
39
39
  ### Hash Operations
40
40
 
41
41
  | Operation | redis-namespace | redis-client-namespace | Comparison |
42
42
  |-----------|----------------|----------------------|------------|
43
- | HSET | 2,660.7 i/s | 2,326.5 i/s | Same-ish (within error) |
44
- | HGETALL | 2,299.9 i/s | 2,886.9 i/s | Same-ish (within error) |
43
+ | HSET | 4,178.2 i/s | 4,149.7 i/s | Same-ish (within error) |
44
+ | HGETALL | 4,147.0 i/s | 4,090.2 i/s | Same-ish (within error) |
45
45
 
46
46
  ### Set Operations
47
47
 
48
48
  | Operation | redis-namespace | redis-client-namespace | Comparison |
49
49
  |-----------|----------------|----------------------|------------|
50
- | SADD | 1,421.5 i/s | 2,536.3 i/s | Same-ish (within error) |
51
- | SMEMBERS | 2,860.3 i/s | 2,945.3 i/s | Same-ish (within error) |
50
+ | SADD | 3,958.2 i/s | 4,048.4 i/s | Same-ish (within error) |
51
+ | SMEMBERS | 4,129.4 i/s | 4,200.5 i/s | Same-ish (within error) |
52
52
 
53
53
  ### Pattern Matching
54
54
 
55
55
  | Operation | redis-namespace | redis-client-namespace | Comparison |
56
56
  |-----------|----------------|----------------------|------------|
57
- | KEYS | 2,137.0 i/s | 2,502.2 i/s | Same-ish (within error) |
57
+ | KEYS | 3,911.4 i/s | 4,049.3 i/s | Same-ish (within error) |
58
58
 
59
59
  ### Complex Operations
60
60
 
61
61
  | Operation | redis-namespace | redis-client-namespace | Comparison |
62
62
  |-----------|----------------|----------------------|------------|
63
- | ZRANGE | 2,908.9 i/s | 2,506.6 i/s | Same-ish (within error) |
63
+ | ZRANGE | 4,215.8 i/s | 4,261.9 i/s | Same-ish (within error) |
64
64
 
65
65
  ### Transactions
66
66
 
67
67
  | Operation | redis-namespace | redis-client-namespace | Comparison |
68
68
  |-----------|----------------|----------------------|------------|
69
- | MULTI/EXEC | 2,039.9 i/s | 1,685.0 i/s | Same-ish (within error) |
69
+ | MULTI/EXEC | 3,941.1 i/s | 4,022.1 i/s | Same-ish (within error) |
70
70
 
71
71
  ## Key Observations
72
72
 
73
- 1. **Performance Parity**: `redis-client-namespace` maintains performance parity with `redis-namespace` across all tested operations.
73
+ 1. **Excellent Performance**: The middleware-based `redis-client-namespace` shows consistently strong performance, often matching or slightly exceeding `redis-namespace` performance across all operations.
74
74
 
75
- 2. **No Significant Overhead**: The new architecture based on `RedisClient::CommandBuilder` doesn't introduce significant overhead compared to the traditional approach.
75
+ 2. **Middleware Architecture Success**: The new middleware pattern successfully maintains high performance while providing cleaner, more maintainable code architecture. The abstraction layer introduces virtually no performance penalty.
76
76
 
77
- 3. **Consistent Performance**: Both libraries show consistent performance characteristics across different types of Redis operations.
77
+ 3. **Significant Performance Improvements**: Compared to previous benchmarks, the middleware implementation shows substantial improvements - throughput has roughly doubled across most operations (from ~2k i/s to ~4k i/s range).
78
78
 
79
- 4. **Production Ready**: The benchmark results indicate that `redis-client-namespace` is suitable for production use as a drop-in replacement for `redis-namespace` when using `redis-rb`.
79
+ 4. **Consistent High Performance**: Both libraries now operate in the 4,000+ i/s range for most operations, demonstrating excellent performance characteristics across different Redis operation types.
80
+
81
+ 5. **Production Ready**: The benchmark results confirm that the middleware-based `redis-client-namespace` is highly suitable for production use, offering both superior architecture and excellent performance.
80
82
 
81
83
  ## Notes
82
84
 
83
85
  - All comparisons marked as "same-ish" indicate that the performance difference falls within the statistical margin of error
84
- - The benchmarks used `benchmark-ips` for accurate measurements
86
+ - The benchmarks used `benchmark-ips` for accurate measurements with proper warm-up periods
85
87
  - Each operation was warmed up before measurement to ensure fair comparison
86
- - LRANGE operations show lower throughput due to the large amount of data being transferred
88
+ - LRANGE operations show lower throughput (~53 i/s) due to the large amount of data being transferred, but this is expected behavior
89
+ - The middleware implementation demonstrates that architectural improvements don't require performance sacrifices
90
+ - Performance improvements may also be attributed to Ruby 3.4.5 optimizations and updated testing environment
@@ -440,30 +440,36 @@ class RedisClient
440
440
 
441
441
  }.freeze
442
442
 
443
- def generate(args, kwargs = nil)
444
- command = @parent_command_builder.generate(args, kwargs)
445
- return command if @namespace.nil? || @namespace.empty? || command.size < 2
443
+ def self.namespaced_command(command, namespace: nil, separator: ":")
444
+ return command if namespace.nil? || namespace.empty? || command.size < 2
446
445
 
447
446
  cmd_name = command[0].to_s.upcase
448
447
  strategy = COMMANDS[cmd_name]
449
448
 
450
449
  # Raise error for unknown commands to maintain compatibility with redis-namespace
451
450
  unless strategy
452
- raise(::RedisClient::Namespace::Error,
453
- "RedisClient::Namespace does not know how to handle '#{cmd_name}'.")
451
+ warn("RedisClient::Namespace does not know how to handle '#{cmd_name}'.")
452
+ return command
454
453
  end
455
454
 
456
- STRATEGIES[strategy].call(command) { |key| rename_key(key) }
455
+ prefix = "#{namespace}#{separator}"
456
+ STRATEGIES[strategy].call(command) { |key| key.start_with?(prefix) ? key : "#{prefix}#{key}" }
457
457
 
458
458
  command
459
459
  end
460
460
 
461
- private
462
-
463
- def rename_key(key)
464
- return key if @namespace.nil? || @namespace.empty?
465
-
466
- "#{@namespace}#{@separator}#{key}"
461
+ def self.trimed_result(command, result, namespace: nil, separator: ":")
462
+ return command if namespace.nil? || namespace.empty? || command.size < 2
463
+
464
+ prefix = "#{namespace}#{separator}"
465
+ case command[0].to_s.upcase
466
+ when "SCAN"
467
+ result[1].map { |r| r.delete_prefix!(prefix) } if result.size > 1
468
+ when "KEYS"
469
+ result.map { |r| r.delete_prefix!(prefix) }
470
+ when "BLPOP", "BRPOP"
471
+ result[0].delete_prefix!(prefix) unless result.nil? || result.empty?
472
+ end
467
473
  end
468
474
  end
469
475
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "command_builder"
4
+
5
+ class RedisClient
6
+ class Namespace
7
+ # Middleware for RedisClient to add namespace support
8
+ #
9
+ # This module implements the RedisClient middleware interface to intercept
10
+ # Redis commands and apply namespace transformations. It automatically prefixes
11
+ # keys with a namespace and removes the prefix from certain command results.
12
+ #
13
+ # @see https://github.com/redis-rb/redis-client/blob/master/README.md#instrumentation-and-middlewares
14
+ #
15
+ # @example Basic usage with RedisClient
16
+ # client = RedisClient.config(
17
+ # middlewares: [RedisClient::Namespace::Middleware],
18
+ # custom: { namespace: "myapp", separator: ":" }
19
+ # ).new_client
20
+ #
21
+ # client.call("SET", "key", "value") # Actually sets "myapp:key"
22
+ # client.call("GET", "key") # Gets "myapp:key" and returns "value"
23
+ #
24
+ # The middleware requires the following custom configuration:
25
+ # - namespace: The namespace prefix to apply (required)
26
+ # - separator: The separator between namespace and key (optional, default: ":")
27
+ module Middleware
28
+ def call(command, redis_config)
29
+ namespace = redis_config.custom[:namespace] or return super
30
+ separator = redis_config.custom[:separator] || ":"
31
+ command = CommandBuilder.namespaced_command(command, namespace: namespace, separator: separator)
32
+ super.tap do |result|
33
+ CommandBuilder.trimed_result(command, result, namespace: namespace, separator: separator)
34
+ end
35
+ end
36
+
37
+ def call_pipelined(commands, redis_config)
38
+ namespace = redis_config.custom[:namespace] or return super
39
+ separator = redis_config.custom[:separator] || ":"
40
+ commands = commands.map do |cmd|
41
+ CommandBuilder.namespaced_command(cmd, namespace: namespace, separator: separator)
42
+ end
43
+ super.tap do |results|
44
+ commands.each_with_index do |command, i|
45
+ CommandBuilder.trimed_result(command, results[i], namespace: namespace, separator: separator)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  class RedisClient
4
4
  class Namespace
5
- VERSION = "0.1.1"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
@@ -3,26 +3,34 @@
3
3
  require "redis-client"
4
4
  require_relative "namespace/version"
5
5
  require_relative "namespace/command_builder"
6
+ require_relative "namespace/middleware"
6
7
 
7
8
  class RedisClient
8
9
  # RedisClient::Namespace provides transparent key namespacing for redis-client.
9
10
  #
10
- # It works by intercepting Redis commands and prefixing keys with a namespace,
11
- # allowing multiple applications or components to share a single Redis instance
12
- # without key collisions.
11
+ # **DEPRECATED**: Using this class as a command_builder is deprecated.
12
+ # Please use RedisClient::Namespace::Middleware instead for full namespace support
13
+ # including automatic removal of namespace prefixes from command results.
13
14
  #
14
- # @example Basic usage
15
- # builder = RedisClient::Namespace.new("myapp")
16
- # client = RedisClient.new(command_builder: builder)
15
+ # The command_builder approach only transforms outgoing commands but cannot
16
+ # process incoming results to remove namespace prefixes from keys returned by
17
+ # commands like KEYS, SCAN, BLPOP, etc.
18
+ #
19
+ # @deprecated Use {RedisClient::Namespace::Middleware} instead
20
+ # @example Recommended middleware approach
21
+ # client = RedisClient.config(
22
+ # middlewares: [RedisClient::Namespace::Middleware],
23
+ # custom: { namespace: "myapp", separator: ":" }
24
+ # ).new_client
17
25
  # client.call("SET", "key", "value") # Actually sets "myapp:key"
26
+ # client.call("KEYS", "*") # Returns ["key"] instead of ["myapp:key"]
18
27
  #
19
- # @example Custom separator
20
- # builder = RedisClient::Namespace.new("myapp", separator: "-")
28
+ # @example Legacy command_builder usage (not recommended)
29
+ # builder = RedisClient::Namespace.new("myapp")
21
30
  # client = RedisClient.new(command_builder: builder)
22
- # client.call("SET", "key", "value") # Actually sets "myapp-key"
31
+ # client.call("SET", "key", "value") # Actually sets "myapp:key"
32
+ # client.call("KEYS", "*") # Returns ["myapp:key"] - namespace not removed
23
33
  class Namespace
24
- include RedisClient::Namespace::CommandBuilder
25
-
26
34
  class Error < StandardError; end
27
35
 
28
36
  attr_reader :namespace, :separator, :parent_command_builder
@@ -33,41 +41,9 @@ class RedisClient
33
41
  @parent_command_builder = parent_command_builder
34
42
  end
35
43
 
36
- # Creates a command builder that conditionally applies namespacing.
37
- #
38
- # If the namespace is nil or empty, returns the parent_command_builder directly,
39
- # effectively disabling namespacing. Otherwise, creates a new Namespace instance.
40
- #
41
- # This is particularly useful for environment-based configuration where you want
42
- # to enable/disable namespacing based on environment variables.
43
- #
44
- # @param namespace [String, nil] The namespace to use. If nil or empty, namespacing is disabled
45
- # @param separator [String] The separator between namespace and key (default: ":")
46
- # @param parent_command_builder [Object] The parent command builder to use (default: RedisClient::CommandBuilder)
47
- # @return [Object] Either a Namespace instance or the parent_command_builder
48
- #
49
- # @example Environment-based namespacing
50
- # # Enable namespacing only when REDIS_NAMESPACE is set
51
- # builder = RedisClient::Namespace.command_builder(ENV.fetch("REDIS_NAMESPACE", ""))
52
- # client = RedisClient.new(command_builder: builder)
53
- #
54
- # # With REDIS_NAMESPACE=myapp: keys will be prefixed with "myapp:"
55
- # # With REDIS_NAMESPACE="" or unset: no namespacing applied
56
- #
57
- # @example Sidekiq configuration
58
- # Sidekiq.configure_server do |config|
59
- # config.redis = {
60
- # url: 'redis://localhost:6379/1',
61
- # command_builder: RedisClient::Namespace.command_builder(ENV.fetch("REDIS_NAMESPACE", ""))
62
- # }
63
- # end
64
- def self.command_builder(namespace = "", separator: ":", parent_command_builder: RedisClient::CommandBuilder)
65
- if namespace.nil? || namespace.empty?
66
- parent_command_builder
67
- else
68
- new(namespace, separator: separator,
69
- parent_command_builder: parent_command_builder)
70
- end
44
+ def generate(args, kwargs = nil)
45
+ CommandBuilder.namespaced_command(@parent_command_builder.generate(args, kwargs), namespace: @namespace,
46
+ separator: @separator)
71
47
  end
72
48
  end
73
49
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-client-namespace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kensaku Araga
@@ -45,6 +45,7 @@ files:
45
45
  - lib/redis-client-namespace.rb
46
46
  - lib/redis_client/namespace.rb
47
47
  - lib/redis_client/namespace/command_builder.rb
48
+ - lib/redis_client/namespace/middleware.rb
48
49
  - lib/redis_client/namespace/version.rb
49
50
  homepage: https://github.com/ken39arg/redis-client-namespace
50
51
  licenses: