redis-namespace 1.5.1 → 1.8.0

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