redis-namespace 1.5.2 → 1.8.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 781a6361b78e31842c9fe934161b83e1acf9d1c1ef50cd015694b78582b461ac
4
+ data.tar.gz: a0856e98fb69b1063c460ef1479dd57a4659741612b398d3fac2d4bb3727e1b0
5
+ SHA512:
6
+ metadata.gz: caf163b05d1f4c5f08a29d4d1f977d666f1a627b582af762f50d883df64f0f2139fe132918edbc6c59d9259f386f7ed7a094da3493579072f25d9c957da7e7eb
7
+ data.tar.gz: 951f20c3aaedd77400f34b161fb642d39ff0a4e8a619506441288f6671043a3bfd3e1f18e13a62fc8e014f79d989246fcb51b351f4d3f1b6f3a2a6ce8f17bd41
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,34 +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
- "disconnect!" => [],
74
70
  "dump" => [ :first ],
75
- "echo" => [],
76
- "exists" => [ :first ],
71
+ "exists" => [ :all ],
72
+ "exists?" => [ :all ],
77
73
  "expire" => [ :first ],
78
74
  "expireat" => [ :first ],
79
75
  "eval" => [ :eval_style ],
80
76
  "evalsha" => [ :eval_style ],
81
- "exec" => [],
82
- "flushall" => [],
83
- "flushdb" => [],
84
77
  "get" => [ :first ],
85
78
  "getbit" => [ :first ],
86
79
  "getrange" => [ :first ],
@@ -103,9 +96,7 @@ class Redis
103
96
  "incr" => [ :first ],
104
97
  "incrby" => [ :first ],
105
98
  "incrbyfloat" => [ :first ],
106
- "info" => [],
107
99
  "keys" => [ :first, :all ],
108
- "lastsave" => [],
109
100
  "lindex" => [ :first ],
110
101
  "linsert" => [ :first ],
111
102
  "llen" => [ :first ],
@@ -124,7 +115,6 @@ class Redis
124
115
  "mget" => [ :all ],
125
116
  "monitor" => [ :monitor ],
126
117
  "move" => [ :first ],
127
- "multi" => [],
128
118
  "mset" => [ :alternate ],
129
119
  "msetnx" => [ :alternate ],
130
120
  "object" => [ :exclude_first ],
@@ -134,14 +124,11 @@ class Redis
134
124
  "pfadd" => [ :first ],
135
125
  "pfcount" => [ :all ],
136
126
  "pfmerge" => [ :all ],
137
- "ping" => [],
138
127
  "psetex" => [ :first ],
139
128
  "psubscribe" => [ :all ],
140
129
  "pttl" => [ :first ],
141
130
  "publish" => [ :first ],
142
131
  "punsubscribe" => [ :all ],
143
- "quit" => [],
144
- "randomkey" => [],
145
132
  "rename" => [ :all ],
146
133
  "renamenx" => [ :all ],
147
134
  "restore" => [ :first ],
@@ -150,24 +137,19 @@ class Redis
150
137
  "rpush" => [ :first ],
151
138
  "rpushx" => [ :first ],
152
139
  "sadd" => [ :first ],
153
- "save" => [],
154
140
  "scard" => [ :first ],
155
141
  "scan" => [ :scan_style, :second ],
156
142
  "scan_each" => [ :scan_style, :all ],
157
- "script" => [],
158
143
  "sdiff" => [ :all ],
159
144
  "sdiffstore" => [ :all ],
160
- "select" => [],
161
145
  "set" => [ :first ],
162
146
  "setbit" => [ :first ],
163
147
  "setex" => [ :first ],
164
148
  "setnx" => [ :first ],
165
149
  "setrange" => [ :first ],
166
- "shutdown" => [],
167
150
  "sinter" => [ :all ],
168
151
  "sinterstore" => [ :all ],
169
152
  "sismember" => [ :first ],
170
- "slaveof" => [],
171
153
  "smembers" => [ :first ],
172
154
  "smove" => [ :exclude_last ],
173
155
  "sort" => [ :sort ],
@@ -182,31 +164,75 @@ class Redis
182
164
  "sunionstore" => [ :all ],
183
165
  "ttl" => [ :first ],
184
166
  "type" => [ :first ],
167
+ "unlink" => [ :all ],
185
168
  "unsubscribe" => [ :all ],
186
- "unwatch" => [ :all ],
187
- "watch" => [ :all ],
188
169
  "zadd" => [ :first ],
189
170
  "zcard" => [ :first ],
190
171
  "zcount" => [ :first ],
191
172
  "zincrby" => [ :first ],
192
173
  "zinterstore" => [ :exclude_options ],
174
+ "zpopmin" => [ :first ],
175
+ "zpopmax" => [ :first ],
193
176
  "zrange" => [ :first ],
194
177
  "zrangebyscore" => [ :first ],
178
+ "zrangebylex" => [ :first ],
195
179
  "zrank" => [ :first ],
196
180
  "zrem" => [ :first ],
197
181
  "zremrangebyrank" => [ :first ],
198
182
  "zremrangebyscore" => [ :first ],
183
+ "zremrangebylex" => [ :first ],
199
184
  "zrevrange" => [ :first ],
200
185
  "zrevrangebyscore" => [ :first ],
186
+ "zrevrangebylex" => [ :first ],
201
187
  "zrevrank" => [ :first ],
202
188
  "zscan" => [ :first ],
203
189
  "zscan_each" => [ :first ],
204
190
  "zscore" => [ :first ],
205
- "zunionstore" => [ :exclude_options ],
206
- "[]" => [ :first ],
207
- "[]=" => [ :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" => [],
208
223
  }
209
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
+
210
236
  # Support 1.8.7 by providing a namespaced reference to Enumerable::Enumerator
211
237
  Enumerator = Enumerable::Enumerator unless defined?(::Enumerator)
212
238
 
@@ -223,6 +249,7 @@ class Redis
223
249
  @deprecations = !!options.fetch(:deprecations) do
224
250
  ENV['REDIS_NAMESPACE_DEPRECATIONS']
225
251
  end
252
+ @has_new_client_method = @redis.respond_to?(:_client)
226
253
  end
227
254
 
228
255
  def deprecations?
@@ -234,7 +261,13 @@ class Redis
234
261
  end
235
262
 
236
263
  def client
237
- @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
238
271
  end
239
272
 
240
273
  # Ruby defines a now deprecated type method so we need to override it here
@@ -247,7 +280,9 @@ class Redis
247
280
 
248
281
  # emulate Ruby 1.9+ and keep respond_to_missing? logic together.
249
282
  def respond_to?(command, include_private=false)
250
- 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
251
286
  end
252
287
 
253
288
  def keys(query = nil)
@@ -275,6 +310,14 @@ class Redis
275
310
  @namespace
276
311
  end
277
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
+
278
321
  def exec
279
322
  call_with_namespace(:exec)
280
323
  end
@@ -282,19 +325,46 @@ class Redis
282
325
  def eval(*args)
283
326
  call_with_namespace(:eval, *args)
284
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
285
356
 
286
357
  def method_missing(command, *args, &block)
287
358
  normalized_command = command.to_s.downcase
288
359
 
289
360
  if COMMANDS.include?(normalized_command)
290
- call_with_namespace(command, *args, &block)
361
+ send(normalized_command, *args, &block)
291
362
  elsif @redis.respond_to?(normalized_command) && !deprecations?
292
363
  # blind passthrough is deprecated and will be removed in 2.0
293
364
  # redis-namespace does not know how to handle this command.
294
365
  # Passing it to @redis as is, where redis-namespace shows
295
366
  # a warning message if @warning is set.
296
367
  if warning?
297
- call_site = caller.reject { |l| l.start_with?(__FILE__) }.first
298
368
  warn("Passing '#{command}' command to redis as is; blind " +
299
369
  "passthrough has been deprecated and will be removed in " +
300
370
  "redis-namespace 2.0 (at #{call_site})")
@@ -304,16 +374,24 @@ class Redis
304
374
  super
305
375
  end
306
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
307
383
 
308
384
  def respond_to_missing?(command, include_all=false)
309
- return true if COMMANDS.include?(command.to_s.downcase)
385
+ normalized_command = command.to_s.downcase
310
386
 
311
- # blind passthrough is deprecated and will be removed in 2.0
312
- if @redis.respond_to?(command, include_all) && !deprecations?
313
- 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
314
394
  end
315
-
316
- defined?(super) && super
317
395
  end
318
396
 
319
397
  def call_with_namespace(command, *args, &block)
@@ -325,10 +403,14 @@ class Redis
325
403
 
326
404
  (before, after) = handling
327
405
 
406
+ # Modify the local *args array in-place, no need to copy it.
407
+ args.map! {|arg| clone_args(arg)}
408
+
328
409
  # Add the namespace to any parameters that are keys.
329
410
  case before
330
411
  when :first
331
412
  args[0] = add_namespace(args[0]) if args[0]
413
+ args[-1] = ruby2_keywords_hash(args[-1]) if args[-1].is_a?(Hash)
332
414
  when :all
333
415
  args = add_namespace(args)
334
416
  when :exclude_first
@@ -341,7 +423,7 @@ class Redis
341
423
  args.push(last) if last
342
424
  when :exclude_options
343
425
  if args.last.is_a?(Hash)
344
- last = args.pop
426
+ last = ruby2_keywords_hash(args.pop)
345
427
  args = add_namespace(args)
346
428
  args.push(last)
347
429
  else
@@ -361,6 +443,7 @@ class Redis
361
443
  args[1][:get].each_index do |i|
362
444
  args[1][:get][i] = add_namespace(args[1][:get][i]) unless args[1][:get][i] == "#"
363
445
  end
446
+ args[1] = ruby2_keywords_hash(args[1])
364
447
  end
365
448
  when :eval_style
366
449
  # redis.eval() and evalsha() can either take the form:
@@ -381,7 +464,7 @@ class Redis
381
464
  when :scan_style
382
465
  options = (args.last.kind_of?(Hash) ? args.pop : {})
383
466
  options[:match] = add_namespace(options.fetch(:match, '*'))
384
- args << options
467
+ args << ruby2_keywords_hash(options)
385
468
 
386
469
  if block
387
470
  original_block = block
@@ -407,9 +490,35 @@ class Redis
407
490
 
408
491
  result
409
492
  end
493
+ ruby2_keywords(:call_with_namespace) if respond_to?(:ruby2_keywords, true)
410
494
 
411
495
  private
412
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
+
413
522
  def namespaced_block(command, &block)
414
523
  redis.send(command) do |r|
415
524
  begin
@@ -426,9 +535,10 @@ class Redis
426
535
 
427
536
  case key
428
537
  when Array
429
- key.map {|k| add_namespace k}
538
+ key.map! {|k| add_namespace k}
430
539
  when Hash
431
- Hash[*key.map {|k, v| [ add_namespace(k), v ]}.flatten]
540
+ key.keys.each {|k| key[add_namespace(k)] = key.delete(k)}
541
+ key
432
542
  else
433
543
  "#{@namespace}:#{key}"
434
544
  end