redis 4.1.4 → 4.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83f1f7270db68603d63e86ec43e68348cb5ccb2b4e6759642d89898566bdbaf6
4
- data.tar.gz: 45c5bcc92629ec7d85cdc2b913e7922cd5425f2e6691891efc379aeec73026b3
3
+ metadata.gz: 416a2f007042c19453c13361aa4440a507e47fb32c28adc68e7c574c6651f5b4
4
+ data.tar.gz: 1a845f2af649d64f8b274962c9d5d10e6eb5d046474b6e44288676432fe8a98b
5
5
  SHA512:
6
- metadata.gz: 692dfc5c73c6410492589f38f279976a023f6a2ff13f7b1476806011eb387f41bed784bdeac746de5f4b990b6d22bf297b36dddc7b8e448a842241a389f50796
7
- data.tar.gz: 55a9e305c7563f5dd7d38f50dc7b919967dbb0f6a131ebc5e1569f49f196ab458203b6594394fa9a33ea9e337b741113e781378113783683dd36b87196607b8f
6
+ metadata.gz: 3766992242ae284ca474bc8564c6760de88e635a8c3bc3c80da08062d698cc891bf00455b5d98768709ecc766f8ad305fe03cc5806f03fda3ebb93049e0a1cce
7
+ data.tar.gz: f440c984ec58ff091a6a696952239cb04cf145752b485543e5da7215a327b40be4391b3fe6ca67753f84ec43913b9d90ec0b6f812e1696890a7c17cbf3aa3630
data/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # Unreleased
2
2
 
3
+ # 4.3.1
4
+
5
+ * Fix password authentication against redis server 5 and older.
6
+
7
+ # 4.3.0
8
+
9
+ * Add the TYPE argument to scan and scan_each. See #985.
10
+ * Support AUTH command for ACL. See #967.
11
+
12
+ # 4.2.5
13
+
14
+ * Optimize the ruby connector write buffering. See #964.
15
+
16
+ # 4.2.4
17
+
18
+ * Fix bytesize calculations in the ruby connector, and work on a copy of the buffer. Fix #961, #962.
19
+
20
+ # 4.2.3
21
+
22
+ * Use io/wait instead of IO.select in the ruby connector. See #960.
23
+ * Use exception free non blocking IOs in the ruby connector. See #926.
24
+ * Prevent corruption of the client when an interrupt happen during inside a pipeline block. See #945.
25
+
26
+ # 4.2.2
27
+
28
+ * Fix `WATCH` support for `Redis::Distributed`. See #941.
29
+ * Fix handling of empty stream responses. See #905, #929.
30
+
31
+ # 4.2.1
32
+
33
+ * Fix `exists?` returning an actual boolean when called with multiple keys. See #918.
34
+ * Setting `Redis.exists_returns_integer = false` disables warning message about new behaviour. See #920.
35
+
36
+ # 4.2.0
37
+
38
+ * Convert commands to accept keyword arguments rather than option hashes. This both help catching typos, and reduce needless allocations.
39
+ * Deprecate the synchrony driver. It will be removed in 5.0 and hopefully maintained as a separate gem. See #915.
40
+ * Make `Redis#exists` variadic, will return an Integer if called with multiple keys.
41
+ * Add `Redis#exists?` to get a Boolean if any of the keys exists.
42
+ * `Redis#exists` when called with a single key will warn that future versions will return an Integer.
43
+ Set `Redis.exists_returns_integer = true` to opt-in to the new behavior.
44
+ * Support `keepttl` ooption in `set`. See #913.
45
+ * Optimized initialization of Redis::Cluster. See #912.
46
+ * Accept sentinel options even with string key. See #599.
47
+ * Verify TLS connections by default. See #900.
48
+ * Make `Redis#hset` variadic. It now returns an integer, not a boolean. See #910.
49
+
3
50
  # 4.1.4
4
51
 
5
52
  * Alias `Redis#disconnect` as `#close`. See #901.
@@ -9,6 +56,7 @@
9
56
  * Increase buffer size in the ruby connector. See #880.
10
57
  * Fix thread safety of `Redis.queue`. See #878.
11
58
  * Deprecate `Redis::Future#==` as it's likely to be a mistake. See #876.
59
+ * Support `KEEPTTL` option for SET command. See #913.
12
60
 
13
61
  # 4.1.3
14
62
 
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
- # redis-rb [![Build Status][travis-image]][travis-link] [![Inline docs][inchpages-image]][inchpages-link] ![](https://github.com/redis/redis-rb/workflows/Test/badge.svg?branch=master)
1
+ # redis-rb [![Build Status][gh-actions-image]][gh-actions-link] [![Inline docs][inchpages-image]][inchpages-link]
2
2
 
3
3
  A Ruby client that tries to match [Redis][redis-home]' API one-to-one, while still
4
4
  providing an idiomatic interface.
5
5
 
6
+ See [RubyDoc.info][rubydoc] for the API docs of the latest published gem.
6
7
 
7
8
  ## Getting started
8
9
 
@@ -34,6 +35,9 @@ You can also specify connection options as a [`redis://` URL][redis-url]:
34
35
  redis = Redis.new(url: "redis://:p4ssw0rd@10.0.1.1:6380/15")
35
36
  ```
36
37
 
38
+ The client expects passwords with special chracters to be URL-encoded (i.e.
39
+ `CGI.escape(password)`).
40
+
37
41
  By default, the client will try to read the `REDIS_URL` environment variable
38
42
  and use that as URL to connect to. The above statement is therefore equivalent
39
43
  to setting this environment variable and calling `Redis.new` without arguments.
@@ -50,6 +54,12 @@ To connect to a password protected Redis instance, use:
50
54
  redis = Redis.new(password: "mysecret")
51
55
  ```
52
56
 
57
+ To connect a Redis instance using [ACL](https://redis.io/topics/acl), use:
58
+
59
+ ```ruby
60
+ redis = Redis.new(username: 'myname', password: 'mysecret')
61
+ ```
62
+
53
63
  The Redis class exports methods that are named identical to the commands
54
64
  they execute. The arguments these methods accept are often identical to
55
65
  the arguments specified on the [Redis website][redis-commands]. For
@@ -147,8 +157,8 @@ redis.mget('{key}1', '{key}2')
147
157
 
148
158
  ## Storing objects
149
159
 
150
- Redis only stores strings as values. If you want to store an object, you
151
- can use a serialization mechanism such as JSON:
160
+ Redis "string" types can be used to store serialized Ruby objects, for
161
+ example with JSON:
152
162
 
153
163
  ```ruby
154
164
  require "json"
@@ -261,6 +271,7 @@ All timeout values are specified in seconds.
261
271
  When using pub/sub, you can subscribe to a channel using a timeout as well:
262
272
 
263
273
  ```ruby
274
+ redis = Redis.new(reconnect_attempts: 0)
264
275
  redis.subscribe_with_timeout(5, "news") do |on|
265
276
  on.message do |channel, message|
266
277
  # ...
@@ -322,7 +333,7 @@ This library supports natively terminating client side SSL/TLS connections
322
333
  when talking to Redis via a server-side proxy such as [stunnel], [hitch],
323
334
  or [ghostunnel].
324
335
 
325
- To enable SSL support, pass the `:ssl => :true` option when configuring the
336
+ To enable SSL support, pass the `:ssl => true` option when configuring the
326
337
  Redis client, or pass in `:url => "rediss://..."` (like HTTPS for Redis).
327
338
  You will also need to pass in an `:ssl_params => { ... }` hash used to
328
339
  configure the `OpenSSL::SSL::SSLContext` object used for the connection:
@@ -435,7 +446,7 @@ redis = Redis.new(:driver => :synchrony)
435
446
  ## Testing
436
447
 
437
448
  This library is tested against recent Ruby and Redis versions.
438
- Check [Travis][travis-link] for the exact versions supported.
449
+ Check [Github Actions][gh-actions-link] for the exact versions supported.
439
450
 
440
451
  ## See Also
441
452
 
@@ -451,15 +462,14 @@ client and evangelized Redis in Rubyland. Thank you, Ezra.
451
462
  ## Contributing
452
463
 
453
464
  [Fork the project](https://github.com/redis/redis-rb) and send pull
454
- requests. You can also ask for help at `#redis-rb` on Freenode.
455
-
456
-
457
- [inchpages-image]: https://inch-ci.org/github/redis/redis-rb.svg
458
- [inchpages-link]: https://inch-ci.org/github/redis/redis-rb
459
- [redis-commands]: https://redis.io/commands
460
- [redis-home]: https://redis.io
461
- [redis-url]: http://www.iana.org/assignments/uri-schemes/prov/redis
462
- [travis-home]: https://travis-ci.org/
463
- [travis-image]: https://secure.travis-ci.org/redis/redis-rb.svg?branch=master
464
- [travis-link]: https://travis-ci.org/redis/redis-rb
465
- [rubydoc]: http://www.rubydoc.info/gems/redis
465
+ requests.
466
+
467
+
468
+ [inchpages-image]: https://inch-ci.org/github/redis/redis-rb.svg
469
+ [inchpages-link]: https://inch-ci.org/github/redis/redis-rb
470
+ [redis-commands]: https://redis.io/commands
471
+ [redis-home]: https://redis.io
472
+ [redis-url]: http://www.iana.org/assignments/uri-schemes/prov/redis
473
+ [gh-actions-image]: https://github.com/redis/redis-rb/workflows/Test/badge.svg
474
+ [gh-actions-link]: https://github.com/redis/redis-rb/actions
475
+ [rubydoc]: http://www.rubydoc.info/gems/redis
data/lib/redis.rb CHANGED
@@ -4,12 +4,26 @@ require "monitor"
4
4
  require_relative "redis/errors"
5
5
 
6
6
  class Redis
7
- def self.current
8
- @current ||= Redis.new
7
+ class << self
8
+ attr_reader :exists_returns_integer
9
+
10
+ def exists_returns_integer=(value)
11
+ unless value
12
+ message = "`Redis#exists(key)` will return an Integer by default in redis-rb 4.3. The option to explicitly " \
13
+ "disable this behaviour via `Redis.exists_returns_integer` will be removed in 5.0. You should use " \
14
+ "`exists?` instead."
15
+
16
+ ::Kernel.warn(message)
17
+ end
18
+
19
+ @exists_returns_integer = value
20
+ end
21
+
22
+ attr_writer :current
9
23
  end
10
24
 
11
- def self.current=(redis)
12
- @current = redis
25
+ def self.current
26
+ @current ||= Redis.new
13
27
  end
14
28
 
15
29
  include MonitorMixin
@@ -17,17 +31,22 @@ class Redis
17
31
  # Create a new client instance
18
32
  #
19
33
  # @param [Hash] options
20
- # @option options [String] :url (value of the environment variable REDIS_URL) a Redis URL, for a TCP connection: `redis://:[password]@[hostname]:[port]/[db]` (password, port and database are optional), for a unix socket connection: `unix://[path to Redis socket]`. This overrides all other options.
34
+ # @option options [String] :url (value of the environment variable REDIS_URL) a Redis URL, for a TCP connection:
35
+ # `redis://:[password]@[hostname]:[port]/[db]` (password, port and database are optional), for a unix socket
36
+ # connection: `unix://[path to Redis socket]`. This overrides all other options.
21
37
  # @option options [String] :host ("127.0.0.1") server hostname
22
38
  # @option options [Integer] :port (6379) server port
23
39
  # @option options [String] :path path to server socket (overrides host and port)
24
40
  # @option options [Float] :timeout (5.0) timeout in seconds
25
41
  # @option options [Float] :connect_timeout (same as timeout) timeout for initial connect in seconds
42
+ # @option options [String] :username Username to authenticate against server
26
43
  # @option options [String] :password Password to authenticate against server
27
44
  # @option options [Integer] :db (0) Database to select after initial connect
28
45
  # @option options [Symbol] :driver Driver to use, currently supported: `:ruby`, `:hiredis`, `:synchrony`
29
- # @option options [String] :id ID for the client connection, assigns name to current connection by sending `CLIENT SETNAME`
30
- # @option options [Hash, Integer] :tcp_keepalive Keepalive values, if Integer `intvl` and `probe` are calculated based on the value, if Hash `time`, `intvl` and `probes` can be specified as a Integer
46
+ # @option options [String] :id ID for the client connection, assigns name to current connection by sending
47
+ # `CLIENT SETNAME`
48
+ # @option options [Hash, Integer] :tcp_keepalive Keepalive values, if Integer `intvl` and `probe` are calculated
49
+ # based on the value, if Hash `time`, `intvl` and `probes` can be specified as a Integer
31
50
  # @option options [Integer] :reconnect_attempts Number of attempts trying to connect
32
51
  # @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not
33
52
  # @option options [Array] :sentinels List of sentinels to contact
@@ -52,7 +71,7 @@ class Redis
52
71
  end
53
72
 
54
73
  # Run code with the client reconnecting
55
- def with_reconnect(val=true, &blk)
74
+ def with_reconnect(val = true, &blk)
56
75
  synchronize do |client|
57
76
  client.with_reconnect(val, &blk)
58
77
  end
@@ -125,12 +144,13 @@ class Redis
125
144
 
126
145
  # Authenticate to the server.
127
146
  #
128
- # @param [String] password must match the password specified in the
129
- # `requirepass` directive in the configuration file
147
+ # @param [Array<String>] args includes both username and password
148
+ # or only password
130
149
  # @return [String] `OK`
131
- def auth(password)
150
+ # @see https://redis.io/commands/auth AUTH command
151
+ def auth(*args)
132
152
  synchronize do |client|
133
- client.call([:auth, password])
153
+ client.call([:auth, *args])
134
154
  end
135
155
  end
136
156
 
@@ -205,7 +225,7 @@ class Redis
205
225
  def config(action, *args)
206
226
  synchronize do |client|
207
227
  client.call([:config, action] + args) do |reply|
208
- if reply.kind_of?(Array) && action == :get
228
+ if reply.is_a?(Array) && action == :get
209
229
  Hashify.call(reply)
210
230
  else
211
231
  reply
@@ -256,7 +276,7 @@ class Redis
256
276
  def flushall(options = nil)
257
277
  synchronize do |client|
258
278
  if options && options[:async]
259
- client.call([:flushall, :async])
279
+ client.call(%i[flushall async])
260
280
  else
261
281
  client.call([:flushall])
262
282
  end
@@ -271,7 +291,7 @@ class Redis
271
291
  def flushdb(options = nil)
272
292
  synchronize do |client|
273
293
  if options && options[:async]
274
- client.call([:flushdb, :async])
294
+ client.call(%i[flushdb async])
275
295
  else
276
296
  client.call([:flushdb])
277
297
  end
@@ -285,7 +305,7 @@ class Redis
285
305
  def info(cmd = nil)
286
306
  synchronize do |client|
287
307
  client.call([:info, cmd].compact) do |reply|
288
- if reply.kind_of?(String)
308
+ if reply.is_a?(String)
289
309
  reply = HashifyInfo.call(reply)
290
310
 
291
311
  if cmd && cmd.to_s == "commandstats"
@@ -358,7 +378,7 @@ class Redis
358
378
  # @param [String] subcommand e.g. `get`, `len`, `reset`
359
379
  # @param [Integer] length maximum number of entries to return
360
380
  # @return [Array<String>, Integer, String] depends on subcommand
361
- def slowlog(subcommand, length=nil)
381
+ def slowlog(subcommand, length = nil)
362
382
  synchronize do |client|
363
383
  args = [:slowlog, subcommand]
364
384
  args << length if length
@@ -383,7 +403,7 @@ class Redis
383
403
  def time
384
404
  synchronize do |client|
385
405
  client.call([:time]) do |reply|
386
- reply.map(&:to_i) if reply
406
+ reply&.map(&:to_i)
387
407
  end
388
408
  end
389
409
  end
@@ -496,9 +516,9 @@ class Redis
496
516
  # - `:replace => Boolean`: if false, raises an error if key already exists
497
517
  # @raise [Redis::CommandError]
498
518
  # @return [String] `"OK"`
499
- def restore(key, ttl, serialized_value, options = {})
519
+ def restore(key, ttl, serialized_value, replace: nil)
500
520
  args = [:restore, key, ttl, serialized_value]
501
- args << 'REPLACE' if options[:replace]
521
+ args << 'REPLACE' if replace
502
522
 
503
523
  synchronize do |client|
504
524
  client.call(args)
@@ -550,13 +570,43 @@ class Redis
550
570
  end
551
571
  end
552
572
 
553
- # Determine if a key exists.
573
+ # Determine how many of the keys exists.
554
574
  #
555
- # @param [String] key
575
+ # @param [String, Array<String>] keys
576
+ # @return [Integer]
577
+ def exists(*keys)
578
+ if !Redis.exists_returns_integer && keys.size == 1
579
+ if Redis.exists_returns_integer.nil?
580
+ message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3. `exists?` returns a boolean, you " \
581
+ "should use it instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = " \
582
+ "true. To disable this message and keep the current (boolean) behaviour of 'exists' you can set " \
583
+ "`Redis.exists_returns_integer = false`, but this option will be removed in 5.0. " \
584
+ "(#{::Kernel.caller(1, 1).first})\n"
585
+
586
+ ::Kernel.warn(message)
587
+ end
588
+
589
+ exists?(*keys)
590
+ else
591
+ _exists(*keys)
592
+ end
593
+ end
594
+
595
+ def _exists(*keys)
596
+ synchronize do |client|
597
+ client.call([:exists, *keys])
598
+ end
599
+ end
600
+
601
+ # Determine if any of the keys exists.
602
+ #
603
+ # @param [String, Array<String>] keys
556
604
  # @return [Boolean]
557
- def exists(key)
605
+ def exists?(*keys)
558
606
  synchronize do |client|
559
- client.call([:exists, key], &Boolify)
607
+ client.call([:exists, *keys]) do |value|
608
+ value > 0
609
+ end
560
610
  end
561
611
  end
562
612
 
@@ -567,7 +617,7 @@ class Redis
567
617
  def keys(pattern = "*")
568
618
  synchronize do |client|
569
619
  client.call([:keys, pattern]) do |reply|
570
- if reply.kind_of?(String)
620
+ if reply.is_a?(String)
571
621
  reply.split(" ")
572
622
  else
573
623
  reply
@@ -663,30 +713,27 @@ class Redis
663
713
  # elements where every element is an array with the result for every
664
714
  # element specified in `:get`
665
715
  # - when `:store` is specified, the number of elements in the stored result
666
- def sort(key, options = {})
667
- args = []
716
+ def sort(key, by: nil, limit: nil, get: nil, order: nil, store: nil)
717
+ args = [:sort, key]
718
+ args << "BY" << by if by
668
719
 
669
- by = options[:by]
670
- args.concat(["BY", by]) if by
671
-
672
- limit = options[:limit]
673
- args.concat(["LIMIT"] + limit) if limit
720
+ if limit
721
+ args << "LIMIT"
722
+ args.concat(limit)
723
+ end
674
724
 
675
- get = Array(options[:get])
676
- args.concat(["GET"].product(get).flatten) unless get.empty?
725
+ get = Array(get)
726
+ get.each do |item|
727
+ args << "GET" << item
728
+ end
677
729
 
678
- order = options[:order]
679
730
  args.concat(order.split(" ")) if order
680
-
681
- store = options[:store]
682
- args.concat(["STORE", store]) if store
731
+ args << "STORE" << store if store
683
732
 
684
733
  synchronize do |client|
685
- client.call([:sort, key] + args) do |reply|
734
+ client.call(args) do |reply|
686
735
  if get.size > 1 && !store
687
- if reply
688
- reply.each_slice(get.size).to_a
689
- end
736
+ reply.each_slice(get.size).to_a if reply
690
737
  else
691
738
  reply
692
739
  end
@@ -786,27 +833,21 @@ class Redis
786
833
  # - `:px => Integer`: Set the specified expire time, in milliseconds.
787
834
  # - `:nx => true`: Only set the key if it does not already exist.
788
835
  # - `:xx => true`: Only set the key if it already exist.
836
+ # - `:keepttl => true`: Retain the time to live associated with the key.
789
837
  # @return [String, Boolean] `"OK"` or true, false if `:nx => true` or `:xx => true`
790
- def set(key, value, options = {})
791
- args = []
792
-
793
- ex = options[:ex]
794
- args.concat(["EX", ex]) if ex
795
-
796
- px = options[:px]
797
- args.concat(["PX", px]) if px
798
-
799
- nx = options[:nx]
800
- args.concat(["NX"]) if nx
801
-
802
- xx = options[:xx]
803
- args.concat(["XX"]) if xx
838
+ def set(key, value, ex: nil, px: nil, nx: nil, xx: nil, keepttl: nil)
839
+ args = [:set, key, value.to_s]
840
+ args << "EX" << ex if ex
841
+ args << "PX" << px if px
842
+ args << "NX" if nx
843
+ args << "XX" if xx
844
+ args << "KEEPTTL" if keepttl
804
845
 
805
846
  synchronize do |client|
806
847
  if nx || xx
807
- client.call([:set, key, value.to_s] + args, &BoolifySet)
848
+ client.call(args, &BoolifySet)
808
849
  else
809
- client.call([:set, key, value.to_s] + args)
850
+ client.call(args)
810
851
  end
811
852
  end
812
853
  end
@@ -888,7 +929,7 @@ class Redis
888
929
  # @see #mapped_msetnx
889
930
  def msetnx(*args)
890
931
  synchronize do |client|
891
- client.call([:msetnx] + args, &Boolify)
932
+ client.call([:msetnx, *args], &Boolify)
892
933
  end
893
934
  end
894
935
 
@@ -928,7 +969,7 @@ class Redis
928
969
  # @see #mapped_mget
929
970
  def mget(*keys, &blk)
930
971
  synchronize do |client|
931
- client.call([:mget] + keys, &blk)
972
+ client.call([:mget, *keys], &blk)
932
973
  end
933
974
  end
934
975
 
@@ -944,7 +985,7 @@ class Redis
944
985
  # @see #mget
945
986
  def mapped_mget(*keys)
946
987
  mget(*keys) do |reply|
947
- if reply.kind_of?(Array)
988
+ if reply.is_a?(Array)
948
989
  Hash[keys.zip(reply)]
949
990
  else
950
991
  reply
@@ -1031,7 +1072,7 @@ class Redis
1031
1072
  # @return [Integer] the length of the string stored in `destkey`
1032
1073
  def bitop(operation, destkey, *keys)
1033
1074
  synchronize do |client|
1034
- client.call([:bitop, operation, destkey] + keys)
1075
+ client.call([:bitop, operation, destkey, *keys])
1035
1076
  end
1036
1077
  end
1037
1078
 
@@ -1043,10 +1084,8 @@ class Redis
1043
1084
  # @param [Integer] stop stop index
1044
1085
  # @return [Integer] the position of the first 1/0 bit.
1045
1086
  # -1 if looking for 1 and it is not found or start and stop are given.
1046
- def bitpos(key, bit, start=nil, stop=nil)
1047
- if stop and not start
1048
- raise(ArgumentError, 'stop parameter specified without start parameter')
1049
- end
1087
+ def bitpos(key, bit, start = nil, stop = nil)
1088
+ raise(ArgumentError, 'stop parameter specified without start parameter') if stop && !start
1050
1089
 
1051
1090
  synchronize do |client|
1052
1091
  command = [:bitpos, key, bit]
@@ -1133,23 +1172,29 @@ class Redis
1133
1172
  end
1134
1173
  end
1135
1174
 
1136
- # Remove and get the first element in a list.
1175
+ # Remove and get the first elements in a list.
1137
1176
  #
1138
1177
  # @param [String] key
1139
- # @return [String]
1140
- def lpop(key)
1178
+ # @param [Integer] count number of elements to remove
1179
+ # @return [String, Array<String>] the values of the first elements
1180
+ def lpop(key, count = nil)
1141
1181
  synchronize do |client|
1142
- client.call([:lpop, key])
1182
+ command = [:lpop, key]
1183
+ command << count if count
1184
+ client.call(command)
1143
1185
  end
1144
1186
  end
1145
1187
 
1146
- # Remove and get the last element in a list.
1188
+ # Remove and get the last elements in a list.
1147
1189
  #
1148
1190
  # @param [String] key
1149
- # @return [String]
1150
- def rpop(key)
1191
+ # @param [Integer] count number of elements to remove
1192
+ # @return [String, Array<String>] the values of the last elements
1193
+ def rpop(key, count = nil)
1151
1194
  synchronize do |client|
1152
- client.call([:rpop, key])
1195
+ command = [:rpop, key]
1196
+ command << count if count
1197
+ client.call(command)
1153
1198
  end
1154
1199
  end
1155
1200
 
@@ -1240,15 +1285,7 @@ class Redis
1240
1285
  # @return [nil, String]
1241
1286
  # - `nil` when the operation timed out
1242
1287
  # - the element was popped and pushed otherwise
1243
- def brpoplpush(source, destination, options = {})
1244
- case options
1245
- when Integer
1246
- # Issue deprecation notice in obnoxious mode...
1247
- options = { :timeout => options }
1248
- end
1249
-
1250
- timeout = options[:timeout] || 0
1251
-
1288
+ def brpoplpush(source, destination, deprecated_timeout = 0, timeout: deprecated_timeout)
1252
1289
  synchronize do |client|
1253
1290
  command = [:brpoplpush, source, destination, timeout]
1254
1291
  timeout += client.timeout if timeout > 0
@@ -1455,7 +1492,7 @@ class Redis
1455
1492
  # @return [Array<String>] members in the difference
1456
1493
  def sdiff(*keys)
1457
1494
  synchronize do |client|
1458
- client.call([:sdiff] + keys)
1495
+ client.call([:sdiff, *keys])
1459
1496
  end
1460
1497
  end
1461
1498
 
@@ -1466,7 +1503,7 @@ class Redis
1466
1503
  # @return [Integer] number of elements in the resulting set
1467
1504
  def sdiffstore(destination, *keys)
1468
1505
  synchronize do |client|
1469
- client.call([:sdiffstore, destination] + keys)
1506
+ client.call([:sdiffstore, destination, *keys])
1470
1507
  end
1471
1508
  end
1472
1509
 
@@ -1476,7 +1513,7 @@ class Redis
1476
1513
  # @return [Array<String>] members in the intersection
1477
1514
  def sinter(*keys)
1478
1515
  synchronize do |client|
1479
- client.call([:sinter] + keys)
1516
+ client.call([:sinter, *keys])
1480
1517
  end
1481
1518
  end
1482
1519
 
@@ -1487,7 +1524,7 @@ class Redis
1487
1524
  # @return [Integer] number of elements in the resulting set
1488
1525
  def sinterstore(destination, *keys)
1489
1526
  synchronize do |client|
1490
- client.call([:sinterstore, destination] + keys)
1527
+ client.call([:sinterstore, destination, *keys])
1491
1528
  end
1492
1529
  end
1493
1530
 
@@ -1497,7 +1534,7 @@ class Redis
1497
1534
  # @return [Array<String>] members in the union
1498
1535
  def sunion(*keys)
1499
1536
  synchronize do |client|
1500
- client.call([:sunion] + keys)
1537
+ client.call([:sunion, *keys])
1501
1538
  end
1502
1539
  end
1503
1540
 
@@ -1508,7 +1545,7 @@ class Redis
1508
1545
  # @return [Integer] number of elements in the resulting set
1509
1546
  def sunionstore(destination, *keys)
1510
1547
  synchronize do |client|
1511
- client.call([:sunionstore, destination] + keys)
1548
+ client.call([:sunionstore, destination, *keys])
1512
1549
  end
1513
1550
  end
1514
1551
 
@@ -1557,31 +1594,20 @@ class Redis
1557
1594
  # pairs that were **added** to the sorted set.
1558
1595
  # - `Float` when option :incr is specified, holding the score of the member
1559
1596
  # after incrementing it.
1560
- def zadd(key, *args) #, options
1561
- zadd_options = []
1562
- if args.last.is_a?(Hash)
1563
- options = args.pop
1564
-
1565
- nx = options[:nx]
1566
- zadd_options << "NX" if nx
1567
-
1568
- xx = options[:xx]
1569
- zadd_options << "XX" if xx
1570
-
1571
- ch = options[:ch]
1572
- zadd_options << "CH" if ch
1573
-
1574
- incr = options[:incr]
1575
- zadd_options << "INCR" if incr
1576
- end
1597
+ def zadd(key, *args, nx: nil, xx: nil, ch: nil, incr: nil)
1598
+ command = [:zadd, key]
1599
+ command << "NX" if nx
1600
+ command << "XX" if xx
1601
+ command << "CH" if ch
1602
+ command << "INCR" if incr
1577
1603
 
1578
1604
  synchronize do |client|
1579
1605
  if args.size == 1 && args[0].is_a?(Array)
1580
1606
  # Variadic: return float if INCR, integer if !INCR
1581
- client.call([:zadd, key] + zadd_options + args[0], &(incr ? Floatify : nil))
1607
+ client.call(command + args[0], &(incr ? Floatify : nil))
1582
1608
  elsif args.size == 2
1583
1609
  # Single pair: return float if INCR, boolean if !INCR
1584
- client.call([:zadd, key] + zadd_options + args, &(incr ? Floatify : Boolify))
1610
+ client.call(command + args, &(incr ? Floatify : Boolify))
1585
1611
  else
1586
1612
  raise ArgumentError, "wrong number of arguments"
1587
1613
  end
@@ -1752,10 +1778,8 @@ class Redis
1752
1778
  # @return [Array<String>, Array<[String, Float]>]
1753
1779
  # - when `:with_scores` is not specified, an array of members
1754
1780
  # - when `:with_scores` is specified, an array with `[member, score]` pairs
1755
- def zrange(key, start, stop, options = {})
1756
- args = []
1757
-
1758
- with_scores = options[:with_scores] || options[:withscores]
1781
+ def zrange(key, start, stop, withscores: false, with_scores: withscores)
1782
+ args = [:zrange, key, start, stop]
1759
1783
 
1760
1784
  if with_scores
1761
1785
  args << "WITHSCORES"
@@ -1763,7 +1787,7 @@ class Redis
1763
1787
  end
1764
1788
 
1765
1789
  synchronize do |client|
1766
- client.call([:zrange, key, start, stop] + args, &block)
1790
+ client.call(args, &block)
1767
1791
  end
1768
1792
  end
1769
1793
 
@@ -1778,10 +1802,8 @@ class Redis
1778
1802
  # # => [["b", 64.0], ["a", 32.0]]
1779
1803
  #
1780
1804
  # @see #zrange
1781
- def zrevrange(key, start, stop, options = {})
1782
- args = []
1783
-
1784
- with_scores = options[:with_scores] || options[:withscores]
1805
+ def zrevrange(key, start, stop, withscores: false, with_scores: withscores)
1806
+ args = [:zrevrange, key, start, stop]
1785
1807
 
1786
1808
  if with_scores
1787
1809
  args << "WITHSCORES"
@@ -1789,7 +1811,7 @@ class Redis
1789
1811
  end
1790
1812
 
1791
1813
  synchronize do |client|
1792
- client.call([:zrevrange, key, start, stop] + args, &block)
1814
+ client.call(args, &block)
1793
1815
  end
1794
1816
  end
1795
1817
 
@@ -1880,14 +1902,16 @@ class Redis
1880
1902
  # `count` members
1881
1903
  #
1882
1904
  # @return [Array<String>, Array<[String, Float]>]
1883
- def zrangebylex(key, min, max, options = {})
1884
- args = []
1905
+ def zrangebylex(key, min, max, limit: nil)
1906
+ args = [:zrangebylex, key, min, max]
1885
1907
 
1886
- limit = options[:limit]
1887
- args.concat(["LIMIT"] + limit) if limit
1908
+ if limit
1909
+ args << "LIMIT"
1910
+ args.concat(limit)
1911
+ end
1888
1912
 
1889
1913
  synchronize do |client|
1890
- client.call([:zrangebylex, key, min, max] + args)
1914
+ client.call(args)
1891
1915
  end
1892
1916
  end
1893
1917
 
@@ -1902,14 +1926,16 @@ class Redis
1902
1926
  # # => ["abbygail", "abby"]
1903
1927
  #
1904
1928
  # @see #zrangebylex
1905
- def zrevrangebylex(key, max, min, options = {})
1906
- args = []
1929
+ def zrevrangebylex(key, max, min, limit: nil)
1930
+ args = [:zrevrangebylex, key, max, min]
1907
1931
 
1908
- limit = options[:limit]
1909
- args.concat(["LIMIT"] + limit) if limit
1932
+ if limit
1933
+ args << "LIMIT"
1934
+ args.concat(limit)
1935
+ end
1910
1936
 
1911
1937
  synchronize do |client|
1912
- client.call([:zrevrangebylex, key, max, min] + args)
1938
+ client.call(args)
1913
1939
  end
1914
1940
  end
1915
1941
 
@@ -1940,21 +1966,21 @@ class Redis
1940
1966
  # @return [Array<String>, Array<[String, Float]>]
1941
1967
  # - when `:with_scores` is not specified, an array of members
1942
1968
  # - when `:with_scores` is specified, an array with `[member, score]` pairs
1943
- def zrangebyscore(key, min, max, options = {})
1944
- args = []
1945
-
1946
- with_scores = options[:with_scores] || options[:withscores]
1969
+ def zrangebyscore(key, min, max, withscores: false, with_scores: withscores, limit: nil)
1970
+ args = [:zrangebyscore, key, min, max]
1947
1971
 
1948
1972
  if with_scores
1949
1973
  args << "WITHSCORES"
1950
1974
  block = FloatifyPairs
1951
1975
  end
1952
1976
 
1953
- limit = options[:limit]
1954
- args.concat(["LIMIT"] + limit) if limit
1977
+ if limit
1978
+ args << "LIMIT"
1979
+ args.concat(limit)
1980
+ end
1955
1981
 
1956
1982
  synchronize do |client|
1957
- client.call([:zrangebyscore, key, min, max] + args, &block)
1983
+ client.call(args, &block)
1958
1984
  end
1959
1985
  end
1960
1986
 
@@ -1972,21 +1998,21 @@ class Redis
1972
1998
  # # => [["b", 64.0], ["a", 32.0]]
1973
1999
  #
1974
2000
  # @see #zrangebyscore
1975
- def zrevrangebyscore(key, max, min, options = {})
1976
- args = []
1977
-
1978
- with_scores = options[:with_scores] || options[:withscores]
2001
+ def zrevrangebyscore(key, max, min, withscores: false, with_scores: withscores, limit: nil)
2002
+ args = [:zrevrangebyscore, key, max, min]
1979
2003
 
1980
2004
  if with_scores
1981
- args << ["WITHSCORES"]
2005
+ args << "WITHSCORES"
1982
2006
  block = FloatifyPairs
1983
2007
  end
1984
2008
 
1985
- limit = options[:limit]
1986
- args.concat(["LIMIT"] + limit) if limit
2009
+ if limit
2010
+ args << "LIMIT"
2011
+ args.concat(limit)
2012
+ end
1987
2013
 
1988
2014
  synchronize do |client|
1989
- client.call([:zrevrangebyscore, key, max, min] + args, &block)
2015
+ client.call(args, &block)
1990
2016
  end
1991
2017
  end
1992
2018
 
@@ -2050,17 +2076,18 @@ class Redis
2050
2076
  # sorted sets
2051
2077
  # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
2052
2078
  # @return [Integer] number of elements in the resulting sorted set
2053
- def zinterstore(destination, keys, options = {})
2054
- args = []
2079
+ def zinterstore(destination, keys, weights: nil, aggregate: nil)
2080
+ args = [:zinterstore, destination, keys.size, *keys]
2055
2081
 
2056
- weights = options[:weights]
2057
- args.concat(["WEIGHTS"] + weights) if weights
2082
+ if weights
2083
+ args << "WEIGHTS"
2084
+ args.concat(weights)
2085
+ end
2058
2086
 
2059
- aggregate = options[:aggregate]
2060
- args.concat(["AGGREGATE", aggregate]) if aggregate
2087
+ args << "AGGREGATE" << aggregate if aggregate
2061
2088
 
2062
2089
  synchronize do |client|
2063
- client.call([:zinterstore, destination, keys.size] + keys + args)
2090
+ client.call(args)
2064
2091
  end
2065
2092
  end
2066
2093
 
@@ -2077,17 +2104,18 @@ class Redis
2077
2104
  # sorted sets
2078
2105
  # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
2079
2106
  # @return [Integer] number of elements in the resulting sorted set
2080
- def zunionstore(destination, keys, options = {})
2081
- args = []
2107
+ def zunionstore(destination, keys, weights: nil, aggregate: nil)
2108
+ args = [:zunionstore, destination, keys.size, *keys]
2082
2109
 
2083
- weights = options[:weights]
2084
- args.concat(["WEIGHTS"] + weights) if weights
2110
+ if weights
2111
+ args << "WEIGHTS"
2112
+ args.concat(weights)
2113
+ end
2085
2114
 
2086
- aggregate = options[:aggregate]
2087
- args.concat(["AGGREGATE", aggregate]) if aggregate
2115
+ args << "AGGREGATE" << aggregate if aggregate
2088
2116
 
2089
2117
  synchronize do |client|
2090
- client.call([:zunionstore, destination, keys.size] + keys + args)
2118
+ client.call(args)
2091
2119
  end
2092
2120
  end
2093
2121
 
@@ -2101,15 +2129,20 @@ class Redis
2101
2129
  end
2102
2130
  end
2103
2131
 
2104
- # Set the string value of a hash field.
2132
+ # Set one or more hash values.
2133
+ #
2134
+ # @example
2135
+ # redis.hset("hash", "f1", "v1", "f2", "v2") # => 2
2136
+ # redis.hset("hash", { "f1" => "v1", "f2" => "v2" }) # => 2
2105
2137
  #
2106
2138
  # @param [String] key
2107
- # @param [String] field
2108
- # @param [String] value
2109
- # @return [Boolean] whether or not the field was **added** to the hash
2110
- def hset(key, field, value)
2139
+ # @param [Array<String> | Hash<String, String>] attrs array or hash of fields and values
2140
+ # @return [Integer] The number of fields that were added to the hash
2141
+ def hset(key, *attrs)
2142
+ attrs = attrs.first.flatten if attrs.size == 1 && attrs.first.is_a?(Hash)
2143
+
2111
2144
  synchronize do |client|
2112
- client.call([:hset, key, field, value], &Boolify)
2145
+ client.call([:hset, key, *attrs])
2113
2146
  end
2114
2147
  end
2115
2148
 
@@ -2198,7 +2231,7 @@ class Redis
2198
2231
  # @see #hmget
2199
2232
  def mapped_hmget(key, *fields)
2200
2233
  hmget(key, *fields) do |reply|
2201
- if reply.kind_of?(Array)
2234
+ if reply.is_a?(Array)
2202
2235
  Hash[fields.zip(reply)]
2203
2236
  else
2204
2237
  reply
@@ -2291,20 +2324,21 @@ class Redis
2291
2324
 
2292
2325
  def subscribed?
2293
2326
  synchronize do |client|
2294
- client.kind_of? SubscribedClient
2327
+ client.is_a? SubscribedClient
2295
2328
  end
2296
2329
  end
2297
2330
 
2298
2331
  # Listen for messages published to the given channels.
2299
2332
  def subscribe(*channels, &block)
2300
- synchronize do |client|
2333
+ synchronize do |_client|
2301
2334
  _subscription(:subscribe, 0, channels, block)
2302
2335
  end
2303
2336
  end
2304
2337
 
2305
- # Listen for messages published to the given channels. Throw a timeout error if there is no messages for a timeout period.
2338
+ # Listen for messages published to the given channels. Throw a timeout error
2339
+ # if there is no messages for a timeout period.
2306
2340
  def subscribe_with_timeout(timeout, *channels, &block)
2307
- synchronize do |client|
2341
+ synchronize do |_client|
2308
2342
  _subscription(:subscribe_with_timeout, timeout, channels, block)
2309
2343
  end
2310
2344
  end
@@ -2312,21 +2346,23 @@ class Redis
2312
2346
  # Stop listening for messages posted to the given channels.
2313
2347
  def unsubscribe(*channels)
2314
2348
  synchronize do |client|
2315
- raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
2349
+ raise "Can't unsubscribe if not subscribed." unless subscribed?
2350
+
2316
2351
  client.unsubscribe(*channels)
2317
2352
  end
2318
2353
  end
2319
2354
 
2320
2355
  # Listen for messages published to channels matching the given patterns.
2321
2356
  def psubscribe(*channels, &block)
2322
- synchronize do |client|
2357
+ synchronize do |_client|
2323
2358
  _subscription(:psubscribe, 0, channels, block)
2324
2359
  end
2325
2360
  end
2326
2361
 
2327
- # Listen for messages published to channels matching the given patterns. Throw a timeout error if there is no messages for a timeout period.
2362
+ # Listen for messages published to channels matching the given patterns.
2363
+ # Throw a timeout error if there is no messages for a timeout period.
2328
2364
  def psubscribe_with_timeout(timeout, *channels, &block)
2329
- synchronize do |client|
2365
+ synchronize do |_client|
2330
2366
  _subscription(:psubscribe_with_timeout, timeout, channels, block)
2331
2367
  end
2332
2368
  end
@@ -2334,7 +2370,8 @@ class Redis
2334
2370
  # Stop listening for messages posted to channels matching the given patterns.
2335
2371
  def punsubscribe(*channels)
2336
2372
  synchronize do |client|
2337
- raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
2373
+ raise "Can't unsubscribe if not subscribed." unless subscribed?
2374
+
2338
2375
  client.punsubscribe(*channels)
2339
2376
  end
2340
2377
  end
@@ -2379,7 +2416,7 @@ class Redis
2379
2416
  # @see #multi
2380
2417
  def watch(*keys)
2381
2418
  synchronize do |client|
2382
- res = client.call([:watch] + keys)
2419
+ res = client.call([:watch, *keys])
2383
2420
 
2384
2421
  if block_given?
2385
2422
  begin
@@ -2409,14 +2446,13 @@ class Redis
2409
2446
  end
2410
2447
 
2411
2448
  def pipelined
2412
- synchronize do |client|
2449
+ synchronize do |prior_client|
2413
2450
  begin
2414
- pipeline = Pipeline.new(@client)
2415
- original, @client = @client, pipeline
2451
+ @client = Pipeline.new(prior_client)
2416
2452
  yield(self)
2417
- original.call_pipeline(@client)
2453
+ prior_client.call_pipeline(@client)
2418
2454
  ensure
2419
- @client = original
2455
+ @client = prior_client
2420
2456
  end
2421
2457
  end
2422
2458
  end
@@ -2452,17 +2488,16 @@ class Redis
2452
2488
  # @see #watch
2453
2489
  # @see #unwatch
2454
2490
  def multi
2455
- synchronize do |client|
2491
+ synchronize do |prior_client|
2456
2492
  if !block_given?
2457
- client.call([:multi])
2493
+ prior_client.call([:multi])
2458
2494
  else
2459
2495
  begin
2460
- pipeline = Pipeline::Multi.new(@client)
2461
- original, @client = @client, pipeline
2496
+ @client = Pipeline::Multi.new(prior_client)
2462
2497
  yield(self)
2463
- original.call_pipeline(pipeline)
2498
+ prior_client.call_pipeline(@client)
2464
2499
  ensure
2465
- @client = original
2500
+ @client = prior_client
2466
2501
  end
2467
2502
  end
2468
2503
  end
@@ -2609,18 +2644,13 @@ class Redis
2609
2644
  _eval(:evalsha, args)
2610
2645
  end
2611
2646
 
2612
- def _scan(command, cursor, args, options = {}, &block)
2647
+ def _scan(command, cursor, args, match: nil, count: nil, type: nil, &block)
2613
2648
  # SSCAN/ZSCAN/HSCAN already prepend the key to +args+.
2614
2649
 
2615
2650
  args << cursor
2616
-
2617
- if match = options[:match]
2618
- args.concat(["MATCH", match])
2619
- end
2620
-
2621
- if count = options[:count]
2622
- args.concat(["COUNT", count])
2623
- end
2651
+ args << "MATCH" << match if match
2652
+ args << "COUNT" << count if count
2653
+ args << "TYPE" << type if type
2624
2654
 
2625
2655
  synchronize do |client|
2626
2656
  client.call([command] + args, &block)
@@ -2635,15 +2665,19 @@ class Redis
2635
2665
  # @example Retrieve a batch of keys matching a pattern
2636
2666
  # redis.scan(4, :match => "key:1?")
2637
2667
  # # => ["92", ["key:13", "key:18"]]
2668
+ # @example Retrieve a batch of keys of a certain type
2669
+ # redis.scan(92, :type => "zset")
2670
+ # # => ["173", ["sortedset:14", "sortedset:78"]]
2638
2671
  #
2639
2672
  # @param [String, Integer] cursor the cursor of the iteration
2640
2673
  # @param [Hash] options
2641
2674
  # - `:match => String`: only return keys matching the pattern
2642
2675
  # - `:count => Integer`: return count keys at most per iteration
2676
+ # - `:type => String`: return keys only of the given type
2643
2677
  #
2644
2678
  # @return [String, Array<String>] the next cursor and all found keys
2645
- def scan(cursor, options={})
2646
- _scan(:scan, cursor, [], options)
2679
+ def scan(cursor, **options)
2680
+ _scan(:scan, cursor, [], **options)
2647
2681
  end
2648
2682
 
2649
2683
  # Scan the keyspace
@@ -2655,17 +2689,23 @@ class Redis
2655
2689
  # redis.scan_each(:match => "key:1?") {|key| puts key}
2656
2690
  # # => key:13
2657
2691
  # # => key:18
2692
+ # @example Execute block for each key of a type
2693
+ # redis.scan_each(:type => "hash") {|key| puts redis.type(key)}
2694
+ # # => "hash"
2695
+ # # => "hash"
2658
2696
  #
2659
2697
  # @param [Hash] options
2660
2698
  # - `:match => String`: only return keys matching the pattern
2661
2699
  # - `:count => Integer`: return count keys at most per iteration
2700
+ # - `:type => String`: return keys only of the given type
2662
2701
  #
2663
2702
  # @return [Enumerator] an enumerator for all found keys
2664
- def scan_each(options={}, &block)
2665
- return to_enum(:scan_each, options) unless block_given?
2703
+ def scan_each(**options, &block)
2704
+ return to_enum(:scan_each, **options) unless block_given?
2705
+
2666
2706
  cursor = 0
2667
2707
  loop do
2668
- cursor, keys = scan(cursor, options)
2708
+ cursor, keys = scan(cursor, **options)
2669
2709
  keys.each(&block)
2670
2710
  break if cursor == "0"
2671
2711
  end
@@ -2682,8 +2722,8 @@ class Redis
2682
2722
  # - `:count => Integer`: return count keys at most per iteration
2683
2723
  #
2684
2724
  # @return [String, Array<[String, String]>] the next cursor and all found keys
2685
- def hscan(key, cursor, options={})
2686
- _scan(:hscan, cursor, [key], options) do |reply|
2725
+ def hscan(key, cursor, **options)
2726
+ _scan(:hscan, cursor, [key], **options) do |reply|
2687
2727
  [reply[0], reply[1].each_slice(2).to_a]
2688
2728
  end
2689
2729
  end
@@ -2699,11 +2739,12 @@ class Redis
2699
2739
  # - `:count => Integer`: return count keys at most per iteration
2700
2740
  #
2701
2741
  # @return [Enumerator] an enumerator for all found keys
2702
- def hscan_each(key, options={}, &block)
2703
- return to_enum(:hscan_each, key, options) unless block_given?
2742
+ def hscan_each(key, **options, &block)
2743
+ return to_enum(:hscan_each, key, **options) unless block_given?
2744
+
2704
2745
  cursor = 0
2705
2746
  loop do
2706
- cursor, values = hscan(key, cursor, options)
2747
+ cursor, values = hscan(key, cursor, **options)
2707
2748
  values.each(&block)
2708
2749
  break if cursor == "0"
2709
2750
  end
@@ -2721,8 +2762,8 @@ class Redis
2721
2762
  #
2722
2763
  # @return [String, Array<[String, Float]>] the next cursor and all found
2723
2764
  # members and scores
2724
- def zscan(key, cursor, options={})
2725
- _scan(:zscan, cursor, [key], options) do |reply|
2765
+ def zscan(key, cursor, **options)
2766
+ _scan(:zscan, cursor, [key], **options) do |reply|
2726
2767
  [reply[0], FloatifyPairs.call(reply[1])]
2727
2768
  end
2728
2769
  end
@@ -2738,11 +2779,12 @@ class Redis
2738
2779
  # - `:count => Integer`: return count keys at most per iteration
2739
2780
  #
2740
2781
  # @return [Enumerator] an enumerator for all found scores and members
2741
- def zscan_each(key, options={}, &block)
2742
- return to_enum(:zscan_each, key, options) unless block_given?
2782
+ def zscan_each(key, **options, &block)
2783
+ return to_enum(:zscan_each, key, **options) unless block_given?
2784
+
2743
2785
  cursor = 0
2744
2786
  loop do
2745
- cursor, values = zscan(key, cursor, options)
2787
+ cursor, values = zscan(key, cursor, **options)
2746
2788
  values.each(&block)
2747
2789
  break if cursor == "0"
2748
2790
  end
@@ -2759,8 +2801,8 @@ class Redis
2759
2801
  # - `:count => Integer`: return count keys at most per iteration
2760
2802
  #
2761
2803
  # @return [String, Array<String>] the next cursor and all found members
2762
- def sscan(key, cursor, options={})
2763
- _scan(:sscan, cursor, [key], options)
2804
+ def sscan(key, cursor, **options)
2805
+ _scan(:sscan, cursor, [key], **options)
2764
2806
  end
2765
2807
 
2766
2808
  # Scan a set
@@ -2774,11 +2816,12 @@ class Redis
2774
2816
  # - `:count => Integer`: return count keys at most per iteration
2775
2817
  #
2776
2818
  # @return [Enumerator] an enumerator for all keys in the set
2777
- def sscan_each(key, options={}, &block)
2778
- return to_enum(:sscan_each, key, options) unless block_given?
2819
+ def sscan_each(key, **options, &block)
2820
+ return to_enum(:sscan_each, key, **options) unless block_given?
2821
+
2779
2822
  cursor = 0
2780
2823
  loop do
2781
- cursor, keys = sscan(key, cursor, options)
2824
+ cursor, keys = sscan(key, cursor, **options)
2782
2825
  keys.each(&block)
2783
2826
  break if cursor == "0"
2784
2827
  end
@@ -2842,12 +2885,12 @@ class Redis
2842
2885
  end
2843
2886
  end
2844
2887
 
2845
-
2846
2888
  # Query a sorted set representing a geospatial index to fetch members matching a
2847
2889
  # given maximum distance from a point
2848
2890
  #
2849
2891
  # @param [Array] args key, longitude, latitude, radius, unit(m|km|ft|mi)
2850
- # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest to the nearest relative to the center
2892
+ # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest
2893
+ # or the farthest to the nearest relative to the center
2851
2894
  # @param [Integer] count limit the results to the first N matching items
2852
2895
  # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
2853
2896
  # @return [Array<String>] may be changed with `options`
@@ -2864,7 +2907,8 @@ class Redis
2864
2907
  # given maximum distance from an already existing member
2865
2908
  #
2866
2909
  # @param [Array] args key, member, radius, unit(m|km|ft|mi)
2867
- # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest to the nearest relative to the center
2910
+ # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest
2911
+ # to the nearest relative to the center
2868
2912
  # @param [Integer] count limit the results to the first N matching items
2869
2913
  # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
2870
2914
  # @return [Array<String>] may be changed with `options`
@@ -2881,7 +2925,8 @@ class Redis
2881
2925
  #
2882
2926
  # @param [String] key
2883
2927
  # @param [String, Array<String>] member one member or array of members
2884
- # @return [Array<Array<String>, nil>] returns array of elements, where each element is either array of longitude and latitude or nil
2928
+ # @return [Array<Array<String>, nil>] returns array of elements, where each
2929
+ # element is either array of longitude and latitude or nil
2885
2930
  def geopos(key, member)
2886
2931
  synchronize do |client|
2887
2932
  client.call([:geopos, key, member])
@@ -2945,10 +2990,14 @@ class Redis
2945
2990
  # @option opts [Boolean] :approximate whether to add `~` modifier of maxlen or not
2946
2991
  #
2947
2992
  # @return [String] the entry id
2948
- def xadd(key, entry, opts = {})
2993
+ def xadd(key, entry, approximate: nil, maxlen: nil, id: '*')
2949
2994
  args = [:xadd, key]
2950
- args.concat(['MAXLEN', (opts[:approximate] ? '~' : nil), opts[:maxlen]].compact) if opts[:maxlen]
2951
- args << (opts[:id] || '*')
2995
+ if maxlen
2996
+ args << "MAXLEN"
2997
+ args << "~" if approximate
2998
+ args << maxlen
2999
+ end
3000
+ args << id
2952
3001
  args.concat(entry.to_a.flatten)
2953
3002
  synchronize { |client| client.call(args) }
2954
3003
  end
@@ -3003,8 +3052,8 @@ class Redis
3003
3052
  # @param count [Integer] the number of entries as limit
3004
3053
  #
3005
3054
  # @return [Array<Array<String, Hash>>] the ids and entries pairs
3006
- def xrange(key, start = '-', _end = '+', count: nil)
3007
- args = [:xrange, key, start, _end]
3055
+ def xrange(key, start = '-', range_end = '+', count: nil)
3056
+ args = [:xrange, key, start, range_end]
3008
3057
  args.concat(['COUNT', count]) if count
3009
3058
  synchronize { |client| client.call(args, &HashifyStreamEntries) }
3010
3059
  end
@@ -3026,8 +3075,8 @@ class Redis
3026
3075
  # @params count [Integer] the number of entries as limit
3027
3076
  #
3028
3077
  # @return [Array<Array<String, Hash>>] the ids and entries pairs
3029
- def xrevrange(key, _end = '+', start = '-', count: nil)
3030
- args = [:xrevrange, key, _end, start]
3078
+ def xrevrange(key, range_end = '+', start = '-', count: nil)
3079
+ args = [:xrevrange, key, range_end, start]
3031
3080
  args.concat(['COUNT', count]) if count
3032
3081
  synchronize { |client| client.call(args, &HashifyStreamEntries) }
3033
3082
  end
@@ -3119,12 +3168,12 @@ class Redis
3119
3168
  # @option opts [Boolean] :noack whether message loss is acceptable or not
3120
3169
  #
3121
3170
  # @return [Hash{String => Hash{String => Hash}}] the entries
3122
- def xreadgroup(group, consumer, keys, ids, opts = {})
3171
+ def xreadgroup(group, consumer, keys, ids, count: nil, block: nil, noack: nil)
3123
3172
  args = [:xreadgroup, 'GROUP', group, consumer]
3124
- args << 'COUNT' << opts[:count] if opts[:count]
3125
- args << 'BLOCK' << opts[:block].to_i if opts[:block]
3126
- args << 'NOACK' if opts[:noack]
3127
- _xread(args, keys, ids, opts[:block])
3173
+ args << 'COUNT' << count if count
3174
+ args << 'BLOCK' << block.to_i if block
3175
+ args << 'NOACK' if noack
3176
+ _xread(args, keys, ids, block)
3128
3177
  end
3129
3178
 
3130
3179
  # Removes one or multiple entries from the pending entries list of a stream consumer group.
@@ -3234,8 +3283,8 @@ class Redis
3234
3283
  when "get-master-addr-by-name"
3235
3284
  reply
3236
3285
  else
3237
- if reply.kind_of?(Array)
3238
- if reply[0].kind_of?(Array)
3286
+ if reply.is_a?(Array)
3287
+ if reply[0].is_a?(Array)
3239
3288
  reply.map(&Hashify)
3240
3289
  else
3241
3290
  Hashify.call(reply)
@@ -3259,12 +3308,17 @@ class Redis
3259
3308
  def cluster(subcommand, *args)
3260
3309
  subcommand = subcommand.to_s.downcase
3261
3310
  block = case subcommand
3262
- when 'slots' then HashifyClusterSlots
3263
- when 'nodes' then HashifyClusterNodes
3264
- when 'slaves' then HashifyClusterSlaves
3265
- when 'info' then HashifyInfo
3266
- else Noop
3267
- end
3311
+ when 'slots'
3312
+ HashifyClusterSlots
3313
+ when 'nodes'
3314
+ HashifyClusterNodes
3315
+ when 'slaves'
3316
+ HashifyClusterSlaves
3317
+ when 'info'
3318
+ HashifyInfo
3319
+ else
3320
+ Noop
3321
+ end
3268
3322
 
3269
3323
  # @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
3270
3324
  block = Noop unless @cluster_mode
@@ -3299,21 +3353,21 @@ class Redis
3299
3353
  return @original_client.connection_info if @cluster_mode
3300
3354
 
3301
3355
  {
3302
- host: @original_client.host,
3303
- port: @original_client.port,
3304
- db: @original_client.db,
3305
- id: @original_client.id,
3356
+ host: @original_client.host,
3357
+ port: @original_client.port,
3358
+ db: @original_client.db,
3359
+ id: @original_client.id,
3306
3360
  location: @original_client.location
3307
3361
  }
3308
3362
  end
3309
3363
 
3310
- def method_missing(command, *args)
3364
+ def method_missing(command, *args) # rubocop:disable Style/MissingRespondToMissing
3311
3365
  synchronize do |client|
3312
3366
  client.call([command] + args)
3313
3367
  end
3314
3368
  end
3315
3369
 
3316
- private
3370
+ private
3317
3371
 
3318
3372
  # Commands returning 1 for true and 0 for false may be executed in a pipeline
3319
3373
  # where the method call will return nil. Propagate the nil instead of falsely
@@ -3385,18 +3439,21 @@ private
3385
3439
  end
3386
3440
  }
3387
3441
 
3442
+ EMPTY_STREAM_RESPONSE = [nil].freeze
3443
+ private_constant :EMPTY_STREAM_RESPONSE
3444
+
3388
3445
  HashifyStreamEntries = lambda { |reply|
3389
- reply.map do |entry_id, values|
3446
+ reply.compact.map do |entry_id, values|
3390
3447
  [entry_id, values.each_slice(2).to_h]
3391
3448
  end
3392
3449
  }
3393
3450
 
3394
3451
  HashifyStreamPendings = lambda { |reply|
3395
3452
  {
3396
- 'size' => reply[0],
3453
+ 'size' => reply[0],
3397
3454
  'min_entry_id' => reply[1],
3398
3455
  'max_entry_id' => reply[2],
3399
- 'consumers' => reply[3].nil? ? {} : reply[3].to_h
3456
+ 'consumers' => reply[3].nil? ? {} : reply[3].to_h
3400
3457
  }
3401
3458
  }
3402
3459
 
@@ -3405,8 +3462,8 @@ private
3405
3462
  {
3406
3463
  'entry_id' => arr[0],
3407
3464
  'consumer' => arr[1],
3408
- 'elapsed' => arr[2],
3409
- 'count' => arr[3]
3465
+ 'elapsed' => arr[2],
3466
+ 'count' => arr[3]
3410
3467
  }
3411
3468
  end
3412
3469
  }
@@ -3414,15 +3471,15 @@ private
3414
3471
  HashifyClusterNodeInfo = lambda { |str|
3415
3472
  arr = str.split(' ')
3416
3473
  {
3417
- 'node_id' => arr[0],
3418
- 'ip_port' => arr[1],
3419
- 'flags' => arr[2].split(','),
3474
+ 'node_id' => arr[0],
3475
+ 'ip_port' => arr[1],
3476
+ 'flags' => arr[2].split(','),
3420
3477
  'master_node_id' => arr[3],
3421
- 'ping_sent' => arr[4],
3422
- 'pong_recv' => arr[5],
3423
- 'config_epoch' => arr[6],
3424
- 'link_state' => arr[7],
3425
- 'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
3478
+ 'ping_sent' => arr[4],
3479
+ 'pong_recv' => arr[5],
3480
+ 'config_epoch' => arr[6],
3481
+ 'link_state' => arr[7],
3482
+ 'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
3426
3483
  }
3427
3484
  }
3428
3485
 
@@ -3433,9 +3490,9 @@ private
3433
3490
  replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } }
3434
3491
  {
3435
3492
  'start_slot' => first_slot,
3436
- 'end_slot' => last_slot,
3437
- 'master' => master,
3438
- 'replicas' => replicas
3493
+ 'end_slot' => last_slot,
3494
+ 'master' => master,
3495
+ 'replicas' => replicas
3439
3496
  }
3440
3497
  end
3441
3498
  }