redis-namespace 1.5.2 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
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