redis-namespace 1.5.1 → 1.8.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,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- MGI3Mjc4NGZmMjkyOTgyNzQ3MzMwNzAwZjkyZTk3MzE2ZjJlYTA0YQ==
5
- data.tar.gz: !binary |-
6
- ZGVlNjU3MDUxNDA3M2I0ZjVlYjQ5ZGJmYmYzOGNiOGJkMjI3NTUwZA==
2
+ SHA256:
3
+ metadata.gz: 07470c91421a4a15227f10daff0378fdd85a7ba7abc30357677cca67ca87305f
4
+ data.tar.gz: 50e2b6ff0b1db22abe4bbfd77e486d9fd71c09122ba3c01bbfbf90710d21353d
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- NWU5MzVmOTNjMDUzOTcwYzUwYzhlZmU1NzAxZTY2M2MzMGY3MjlkYTVhNjNl
10
- OGYyMmE1M2M1ZDY0MWZmZWIwNDA1NDk5M2VlYTcyZDliMTJmZDBhMGFiYTUx
11
- MWMzMjJiYmFhMGE1YjlkMDM2ODY5NzFiNjM1YjQyNDBhMWQzZjc=
12
- data.tar.gz: !binary |-
13
- YjY1MGZkZmEzOWNhNmRhMTEzNjQ0MGNkYTQ4YWZmZjQ1YTAwNzA2MmJkZWJl
14
- OTQ0MWQ3NWNiMzU1YjNkYzYzZGM4MTAyNGE3NTFkMTk0N2UzN2IyZThjY2Zk
15
- NjJhODIyMGY1NzNmNThmN2E2MDUyOWJkNWU1OWZlZjA5MDdmM2I=
6
+ metadata.gz: ec82e340922480f5a48a8bba472babc3385abc84104b56d13f9e94a2654c5a4aaef1d38f1c2a11535a8f5c5ca02aed3ab1c157fb983b7251458f6b8547b08710
7
+ data.tar.gz: 129b38893696d94b57aa04d07fe399e59d76fd6b4269531c7dbee9334604041b9a1fca931ff7a4a8e16dee9b57f9756e26a6cf1b3d9f989139dbcd31f6d5b0ce
data/LICENSE CHANGED
@@ -1,20 +1,21 @@
1
+ MIT License
2
+
1
3
  Copyright (c) 2009 Chris Wanstrath
2
4
 
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
10
11
 
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
13
14
 
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,38 +1,103 @@
1
1
  redis-namespace
2
- ---------------
2
+ ===============
3
+
4
+ Redis::Namespace provides an interface to a namespaced subset of your [redis][] keyspace (e.g., keys with a common beginning), and requires the [redis-rb][] gem.
5
+
6
+ ```ruby
7
+ require 'redis-namespace'
8
+ # => true
9
+
10
+ redis_connection = Redis.new
11
+ # => #<Redis client v3.1.0 for redis://127.0.0.1:6379/0>
12
+ namespaced_redis = Redis::Namespace.new(:ns, redis: redis_connection)
13
+ # => #<Redis::Namespace v1.5.0 with client v3.1.0 for redis://127.0.0.1:6379/0/ns>
14
+
15
+ namespaced_redis.set('foo', 'bar') # redis_connection.set('ns:foo', 'bar')
16
+ # => "OK"
17
+
18
+ # Redis::Namespace automatically prepended our namespace to the key
19
+ # before sending it to our redis client.
20
+
21
+ namespaced_redis.get('foo')
22
+ # => "bar"
23
+ redis_connection.get('foo')
24
+ # => nil
25
+ redis_connection.get('ns:foo')
26
+ # => "bar"
27
+
28
+ namespaced_redis.del('foo')
29
+ # => 1
30
+ namespaced_redis.get('foo')
31
+ # => nil
32
+ redis_connection.get('ns:foo')
33
+ # => nil
34
+ ```
3
35
 
4
- Requires the redis gem.
36
+ Installation
37
+ ============
5
38
 
6
- Namespaces all Redis calls.
39
+ Redis::Namespace is packaged as the redis-namespace gem, and hosted on rubygems.org.
7
40
 
8
- ``` ruby
9
- r = Redis::Namespace.new(:ns, :redis => @r)
10
- r['foo'] = 1000
41
+ From the command line:
42
+
43
+ $ gem install redis-namespace
44
+
45
+ Or in your Gemfile:
46
+
47
+ ```ruby
48
+ gem 'redis-namespace'
11
49
  ```
12
50
 
13
- This will perform the equivalent of:
51
+ Caveats
52
+ =======
14
53
 
15
- redis-cli set ns:foo 1000
54
+ `Redis::Namespace` provides a namespaced interface to `Redis` by keeping an internal registry of the method signatures in `Redis` provided by the [redis-rb][] gem; we keep track of which arguments need the namespace added, and which return values need the namespace removed.
16
55
 
17
- Useful when you have multiple systems using Redis differently in your app.
56
+ Blind Passthrough
57
+ -----------------
58
+ If your version of this gem doesn't know about a particular command, it can't namespace it. Historically, this has meant that Redis::Namespace blindly passes unknown commands on to the underlying redis connection without modification which can lead to surprising effects.
18
59
 
60
+ As of v1.5.0, blind passthrough has been deprecated, and the functionality will be removed entirely in 2.0.
19
61
 
20
- Installation
21
- ============
62
+ If you come across a command that is not yet supported, please open an issue on the [issue tracker][] or submit a pull-request.
22
63
 
23
- $ gem install redis-namespace
64
+ Administrative Commands
65
+ -----------------------
66
+ The effects of some redis commands cannot be limited to a particular namespace (e.g., `FLUSHALL`, which literally truncates all databases in your redis server, regardless of keyspace). Historically, this has meant that Redis::Namespace intentionally passes administrative commands on to the underlying redis connection without modification, which can lead to surprising effects.
24
67
 
68
+ As of v1.6.0, the direct use of administrative commands has been deprecated, and the functionality will be removed entirely in 2.0; while such commands are often useful for testing or administration, their meaning is inherently hidden when placed behind an interface that implies it will namespace everything.
25
69
 
70
+ The prefered way to send an administrative command is on the redis connection itself, which is publicly exposed as `Redis::Namespace#redis`:
26
71
 
27
- Testing
28
- =======
72
+ ```ruby
73
+ namespaced.redis.flushall()
74
+ # => "OK"
75
+ ```
76
+
77
+ 2.x Planned Breaking Changes
78
+ ============================
29
79
 
30
- $ bundle install
31
- $ rake
80
+ As mentioned above, 2.0 will remove blind passthrough and the administrative command passthrough.
81
+ By default in 1.5+, deprecation warnings are present and enabled;
82
+ they can be silenced by initializing `Redis::Namespace` with `warnings: false` or by setting the `REDIS_NAMESPACE_QUIET` environment variable.
83
+
84
+ Early opt-in
85
+ ------------
86
+
87
+ To enable testing against the 2.x interface before its release, in addition to deprecation warnings, early opt-in to these changes can be enabled by initializing `Redis::Namespace` with `deprecations: true` or by setting the `REDIS_NAMESPACE_DEPRECATIONS` environment variable.
88
+ This should only be done once all warnings have been addressed.
89
+
90
+ Authors
91
+ =======
32
92
 
93
+ While there are many authors who have contributed to this project, the following have done so on an ongoing basis with at least 5 commits:
33
94
 
34
- Author
35
- =====
95
+ - Chris Wanstrath (@defunkt)
96
+ - Ryan Biesemeyer (@yaauie)
97
+ - Steve Klabnik (@steveklabnik)
98
+ - Terence Lee (@hone)
99
+ - Eoin Coffey (@ecoffey)
36
100
 
37
- Chris Wanstrath :: chris@ozmm.org
38
- Terence Lee :: hone02@gmail.com
101
+ [redis]: http://redis.io
102
+ [redis-rb]: https://github.com/redis/redis-rb
103
+ [issue tracker]: https://github.com/resque/redis-namespace/issues
@@ -3,7 +3,7 @@ require 'redis/namespace/version'
3
3
 
4
4
  class Redis
5
5
  class Namespace
6
- # The following table defines how input parameters and result
6
+ # The following tables define how input parameters and result
7
7
  # values should be modified for the namespace.
8
8
  #
9
9
  # COMMANDS is a hash. Each key is the name of a command and each
@@ -53,33 +53,27 @@ class Redis
53
53
  # :all
54
54
  # Add the namespace to all elements returned, e.g.
55
55
  # key1 key2 => namespace:key1 namespace:key2
56
- COMMANDS = {
57
- "append" => [:first],
58
- "auth" => [],
59
- "bgrewriteaof" => [],
60
- "bgsave" => [],
56
+ NAMESPACED_COMMANDS = {
57
+ "append" => [ :first ],
61
58
  "bitcount" => [ :first ],
62
59
  "bitop" => [ :exclude_first ],
60
+ "bitpos" => [ :first ],
63
61
  "blpop" => [ :exclude_last, :first ],
64
62
  "brpop" => [ :exclude_last, :first ],
65
63
  "brpoplpush" => [ :exclude_last ],
66
- "config" => [],
67
- "dbsize" => [],
64
+ "bzpopmin" => [ :first ],
65
+ "bzpopmax" => [ :first ],
68
66
  "debug" => [ :exclude_first ],
69
67
  "decr" => [ :first ],
70
68
  "decrby" => [ :first ],
71
69
  "del" => [ :all ],
72
- "discard" => [],
73
70
  "dump" => [ :first ],
74
- "echo" => [],
75
- "exists" => [ :first ],
71
+ "exists" => [ :all ],
72
+ "exists?" => [ :all ],
76
73
  "expire" => [ :first ],
77
74
  "expireat" => [ :first ],
78
75
  "eval" => [ :eval_style ],
79
76
  "evalsha" => [ :eval_style ],
80
- "exec" => [],
81
- "flushall" => [],
82
- "flushdb" => [],
83
77
  "get" => [ :first ],
84
78
  "getbit" => [ :first ],
85
79
  "getrange" => [ :first ],
@@ -102,9 +96,7 @@ class Redis
102
96
  "incr" => [ :first ],
103
97
  "incrby" => [ :first ],
104
98
  "incrbyfloat" => [ :first ],
105
- "info" => [],
106
99
  "keys" => [ :first, :all ],
107
- "lastsave" => [],
108
100
  "lindex" => [ :first ],
109
101
  "linsert" => [ :first ],
110
102
  "llen" => [ :first ],
@@ -123,7 +115,6 @@ class Redis
123
115
  "mget" => [ :all ],
124
116
  "monitor" => [ :monitor ],
125
117
  "move" => [ :first ],
126
- "multi" => [],
127
118
  "mset" => [ :alternate ],
128
119
  "msetnx" => [ :alternate ],
129
120
  "object" => [ :exclude_first ],
@@ -133,14 +124,11 @@ class Redis
133
124
  "pfadd" => [ :first ],
134
125
  "pfcount" => [ :all ],
135
126
  "pfmerge" => [ :all ],
136
- "ping" => [],
137
127
  "psetex" => [ :first ],
138
128
  "psubscribe" => [ :all ],
139
129
  "pttl" => [ :first ],
140
130
  "publish" => [ :first ],
141
131
  "punsubscribe" => [ :all ],
142
- "quit" => [],
143
- "randomkey" => [],
144
132
  "rename" => [ :all ],
145
133
  "renamenx" => [ :all ],
146
134
  "restore" => [ :first ],
@@ -149,24 +137,19 @@ class Redis
149
137
  "rpush" => [ :first ],
150
138
  "rpushx" => [ :first ],
151
139
  "sadd" => [ :first ],
152
- "save" => [],
153
140
  "scard" => [ :first ],
154
141
  "scan" => [ :scan_style, :second ],
155
142
  "scan_each" => [ :scan_style, :all ],
156
- "script" => [],
157
143
  "sdiff" => [ :all ],
158
144
  "sdiffstore" => [ :all ],
159
- "select" => [],
160
145
  "set" => [ :first ],
161
146
  "setbit" => [ :first ],
162
147
  "setex" => [ :first ],
163
148
  "setnx" => [ :first ],
164
149
  "setrange" => [ :first ],
165
- "shutdown" => [],
166
150
  "sinter" => [ :all ],
167
151
  "sinterstore" => [ :all ],
168
152
  "sismember" => [ :first ],
169
- "slaveof" => [],
170
153
  "smembers" => [ :first ],
171
154
  "smove" => [ :exclude_last ],
172
155
  "sort" => [ :sort ],
@@ -181,31 +164,75 @@ class Redis
181
164
  "sunionstore" => [ :all ],
182
165
  "ttl" => [ :first ],
183
166
  "type" => [ :first ],
167
+ "unlink" => [ :all ],
184
168
  "unsubscribe" => [ :all ],
185
- "unwatch" => [ :all ],
186
- "watch" => [ :all ],
187
169
  "zadd" => [ :first ],
188
170
  "zcard" => [ :first ],
189
171
  "zcount" => [ :first ],
190
172
  "zincrby" => [ :first ],
191
173
  "zinterstore" => [ :exclude_options ],
174
+ "zpopmin" => [ :first ],
175
+ "zpopmax" => [ :first ],
192
176
  "zrange" => [ :first ],
193
177
  "zrangebyscore" => [ :first ],
178
+ "zrangebylex" => [ :first ],
194
179
  "zrank" => [ :first ],
195
180
  "zrem" => [ :first ],
196
181
  "zremrangebyrank" => [ :first ],
197
182
  "zremrangebyscore" => [ :first ],
183
+ "zremrangebylex" => [ :first ],
198
184
  "zrevrange" => [ :first ],
199
185
  "zrevrangebyscore" => [ :first ],
186
+ "zrevrangebylex" => [ :first ],
200
187
  "zrevrank" => [ :first ],
201
188
  "zscan" => [ :first ],
202
189
  "zscan_each" => [ :first ],
203
190
  "zscore" => [ :first ],
204
- "zunionstore" => [ :exclude_options ],
205
- "[]" => [ :first ],
206
- "[]=" => [ :first ]
191
+ "zunionstore" => [ :exclude_options ]
192
+ }
193
+ TRANSACTION_COMMANDS = {
194
+ "discard" => [],
195
+ "exec" => [],
196
+ "multi" => [],
197
+ "unwatch" => [ :all ],
198
+ "watch" => [ :all ],
199
+ }
200
+ HELPER_COMMANDS = {
201
+ "auth" => [],
202
+ "disconnect!" => [],
203
+ "echo" => [],
204
+ "ping" => [],
205
+ "time" => [],
206
+ }
207
+ ADMINISTRATIVE_COMMANDS = {
208
+ "bgrewriteaof" => [],
209
+ "bgsave" => [],
210
+ "config" => [],
211
+ "dbsize" => [],
212
+ "flushall" => [],
213
+ "flushdb" => [],
214
+ "info" => [],
215
+ "lastsave" => [],
216
+ "quit" => [],
217
+ "randomkey" => [],
218
+ "save" => [],
219
+ "script" => [],
220
+ "select" => [],
221
+ "shutdown" => [],
222
+ "slaveof" => [],
207
223
  }
208
224
 
225
+ DEPRECATED_COMMANDS = [
226
+ ADMINISTRATIVE_COMMANDS
227
+ ].compact.reduce(:merge)
228
+
229
+ COMMANDS = [
230
+ NAMESPACED_COMMANDS,
231
+ TRANSACTION_COMMANDS,
232
+ HELPER_COMMANDS,
233
+ ADMINISTRATIVE_COMMANDS,
234
+ ].compact.reduce(:merge)
235
+
209
236
  # Support 1.8.7 by providing a namespaced reference to Enumerable::Enumerator
210
237
  Enumerator = Enumerable::Enumerator unless defined?(::Enumerator)
211
238
 
@@ -222,6 +249,7 @@ class Redis
222
249
  @deprecations = !!options.fetch(:deprecations) do
223
250
  ENV['REDIS_NAMESPACE_DEPRECATIONS']
224
251
  end
252
+ @has_new_client_method = @redis.respond_to?(:_client)
225
253
  end
226
254
 
227
255
  def deprecations?
@@ -233,7 +261,13 @@ class Redis
233
261
  end
234
262
 
235
263
  def client
236
- @redis.client
264
+ warn("The client method is deprecated as of redis-rb 4.0.0, please use the new _client" +
265
+ "method instead. Support for the old method will be removed in redis-namespace 2.0.") if @has_new_client_method && deprecations?
266
+ _client
267
+ end
268
+
269
+ def _client
270
+ @has_new_client_method ? @redis._client : @redis.client # for redis-4.0.0
237
271
  end
238
272
 
239
273
  # Ruby defines a now deprecated type method so we need to override it here
@@ -246,7 +280,9 @@ class Redis
246
280
 
247
281
  # emulate Ruby 1.9+ and keep respond_to_missing? logic together.
248
282
  def respond_to?(command, include_private=false)
249
- super or respond_to_missing?(command, include_private)
283
+ return !deprecations? if DEPRECATED_COMMANDS.include?(command.to_s.downcase)
284
+
285
+ respond_to_missing?(command, include_private) or super
250
286
  end
251
287
 
252
288
  def keys(query = nil)
@@ -274,6 +310,14 @@ class Redis
274
310
  @namespace
275
311
  end
276
312
 
313
+ def full_namespace
314
+ redis.is_a?(Namespace) ? "#{redis.full_namespace}:#{namespace}" : namespace.to_s
315
+ end
316
+
317
+ def connection
318
+ @redis.connection.tap { |info| info[:namespace] = @namespace }
319
+ end
320
+
277
321
  def exec
278
322
  call_with_namespace(:exec)
279
323
  end
@@ -281,19 +325,46 @@ class Redis
281
325
  def eval(*args)
282
326
  call_with_namespace(:eval, *args)
283
327
  end
328
+ ruby2_keywords(:eval) if respond_to?(:ruby2_keywords, true)
329
+
330
+ ADMINISTRATIVE_COMMANDS.keys.each do |command|
331
+ define_method(command) do |*args, &block|
332
+ raise NoMethodError if deprecations?
333
+
334
+ if warning?
335
+ warn("Passing '#{command}' command to redis as is; " +
336
+ "administrative commands cannot be effectively namespaced " +
337
+ "and should be called on the redis connection directly; " +
338
+ "passthrough has been deprecated and will be removed in " +
339
+ "redis-namespace 2.0 (at #{call_site})"
340
+ )
341
+ end
342
+ call_with_namespace(command, *args, &block)
343
+ end
344
+ ruby2_keywords(command) if respond_to?(:ruby2_keywords, true)
345
+ end
346
+
347
+ COMMANDS.keys.each do |command|
348
+ next if ADMINISTRATIVE_COMMANDS.include?(command)
349
+ next if method_defined?(command)
350
+
351
+ define_method(command) do |*args, &block|
352
+ call_with_namespace(command, *args, &block)
353
+ end
354
+ ruby2_keywords(command) if respond_to?(:ruby2_keywords, true)
355
+ end
284
356
 
285
357
  def method_missing(command, *args, &block)
286
358
  normalized_command = command.to_s.downcase
287
359
 
288
360
  if COMMANDS.include?(normalized_command)
289
- call_with_namespace(command, *args, &block)
361
+ send(normalized_command, *args, &block)
290
362
  elsif @redis.respond_to?(normalized_command) && !deprecations?
291
363
  # blind passthrough is deprecated and will be removed in 2.0
292
364
  # redis-namespace does not know how to handle this command.
293
365
  # Passing it to @redis as is, where redis-namespace shows
294
366
  # a warning message if @warning is set.
295
367
  if warning?
296
- call_site = caller.reject { |l| l.start_with?(__FILE__) }.first
297
368
  warn("Passing '#{command}' command to redis as is; blind " +
298
369
  "passthrough has been deprecated and will be removed in " +
299
370
  "redis-namespace 2.0 (at #{call_site})")
@@ -303,16 +374,24 @@ class Redis
303
374
  super
304
375
  end
305
376
  end
377
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
378
+
379
+ def inspect
380
+ "<#{self.class.name} v#{VERSION} with client v#{Redis::VERSION} "\
381
+ "for #{@redis.id}/#{full_namespace}>"
382
+ end
306
383
 
307
384
  def respond_to_missing?(command, include_all=false)
308
- return true if COMMANDS.include?(command.to_s.downcase)
385
+ normalized_command = command.to_s.downcase
309
386
 
310
- # blind passthrough is deprecated and will be removed in 2.0
311
- if @redis.respond_to?(command, include_all) && !deprecations?
312
- return true
387
+ case
388
+ when COMMANDS.include?(normalized_command)
389
+ true
390
+ when !deprecations? && redis.respond_to?(command, include_all)
391
+ true
392
+ else
393
+ defined?(super) && super
313
394
  end
314
-
315
- defined?(super) && super
316
395
  end
317
396
 
318
397
  def call_with_namespace(command, *args, &block)
@@ -324,10 +403,14 @@ class Redis
324
403
 
325
404
  (before, after) = handling
326
405
 
406
+ # Modify the local *args array in-place, no need to copy it.
407
+ args.map! {|arg| clone_args(arg)}
408
+
327
409
  # Add the namespace to any parameters that are keys.
328
410
  case before
329
411
  when :first
330
412
  args[0] = add_namespace(args[0]) if args[0]
413
+ args[-1] = ruby2_keywords_hash(args[-1]) if args[-1].is_a?(Hash)
331
414
  when :all
332
415
  args = add_namespace(args)
333
416
  when :exclude_first
@@ -340,7 +423,7 @@ class Redis
340
423
  args.push(last) if last
341
424
  when :exclude_options
342
425
  if args.last.is_a?(Hash)
343
- last = args.pop
426
+ last = ruby2_keywords_hash(args.pop)
344
427
  args = add_namespace(args)
345
428
  args.push(last)
346
429
  else
@@ -360,6 +443,7 @@ class Redis
360
443
  args[1][:get].each_index do |i|
361
444
  args[1][:get][i] = add_namespace(args[1][:get][i]) unless args[1][:get][i] == "#"
362
445
  end
446
+ args[1] = ruby2_keywords_hash(args[1])
363
447
  end
364
448
  when :eval_style
365
449
  # redis.eval() and evalsha() can either take the form:
@@ -380,7 +464,7 @@ class Redis
380
464
  when :scan_style
381
465
  options = (args.last.kind_of?(Hash) ? args.pop : {})
382
466
  options[:match] = add_namespace(options.fetch(:match, '*'))
383
- args << options
467
+ args << ruby2_keywords_hash(options)
384
468
 
385
469
  if block
386
470
  original_block = block
@@ -406,9 +490,35 @@ class Redis
406
490
 
407
491
  result
408
492
  end
493
+ ruby2_keywords(:call_with_namespace) if respond_to?(:ruby2_keywords, true)
409
494
 
410
495
  private
411
496
 
497
+ if Hash.respond_to?(:ruby2_keywords_hash)
498
+ def ruby2_keywords_hash(kwargs)
499
+ Hash.ruby2_keywords_hash(kwargs)
500
+ end
501
+ else
502
+ def ruby2_keywords_hash(kwargs)
503
+ kwargs
504
+ end
505
+ end
506
+
507
+ # Avoid modifying the caller's (pass-by-reference) arguments.
508
+ def clone_args(arg)
509
+ if arg.is_a?(Array)
510
+ arg.map {|sub_arg| clone_args(sub_arg)}
511
+ elsif arg.is_a?(Hash)
512
+ Hash[arg.map {|k, v| [clone_args(k), clone_args(v)]}]
513
+ else
514
+ arg # Some objects (e.g. symbol) can't be dup'd.
515
+ end
516
+ end
517
+
518
+ def call_site
519
+ caller.reject { |l| l.start_with?(__FILE__) }.first
520
+ end
521
+
412
522
  def namespaced_block(command, &block)
413
523
  redis.send(command) do |r|
414
524
  begin
@@ -425,9 +535,10 @@ class Redis
425
535
 
426
536
  case key
427
537
  when Array
428
- key.map {|k| add_namespace k}
538
+ key.map! {|k| add_namespace k}
429
539
  when Hash
430
- Hash[*key.map {|k, v| [ add_namespace(k), v ]}.flatten]
540
+ key.keys.each {|k| key[add_namespace(k)] = key.delete(k)}
541
+ key
431
542
  else
432
543
  "#{@namespace}:#{key}"
433
544
  end