redis 4.1.4 → 4.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83f1f7270db68603d63e86ec43e68348cb5ccb2b4e6759642d89898566bdbaf6
4
- data.tar.gz: 45c5bcc92629ec7d85cdc2b913e7922cd5425f2e6691891efc379aeec73026b3
3
+ metadata.gz: d7c23f28cc1a4b66b70d9caf4b890ce521145a51435921e65d5676ce7145c2bf
4
+ data.tar.gz: b88ecef94de49b0419ed1e1152fc05b5a6999a53883235b09b2eef0df3d37d9c
5
5
  SHA512:
6
- metadata.gz: 692dfc5c73c6410492589f38f279976a023f6a2ff13f7b1476806011eb387f41bed784bdeac746de5f4b990b6d22bf297b36dddc7b8e448a842241a389f50796
7
- data.tar.gz: 55a9e305c7563f5dd7d38f50dc7b919967dbb0f6a131ebc5e1569f49f196ab458203b6594394fa9a33ea9e337b741113e781378113783683dd36b87196607b8f
6
+ metadata.gz: 4904a1b642fe757601a0c3bb34cb1d1160ca6bdf269624ae73a9d73599c11ad2d8e52a93ba1396bd76c71f31456211d7332d3c8fa16dd44d2ceb92826cbe86cc
7
+ data.tar.gz: d491bf50bca0c804d1b594ebf2de1d5b5a1d464873fe0d03986464006638028e7dd2b2056ed24919bb70a8c60c9a9fabf0e73663e8065bd474baf52abbf95d98
@@ -1,5 +1,39 @@
1
1
  # Unreleased
2
2
 
3
+ # 4.2.4
4
+
5
+ * Fix bytesize calculations in the ruby connector, and work on a copy of the buffer. Fix #961, #962.
6
+
7
+ # 4.2.3
8
+
9
+ * Use io/wait instead of IO.select in the ruby connector. See #960.
10
+ * Use exception free non blocking IOs in the ruby connector. See #926.
11
+ * Prevent corruption of the client when an interrupt happen during inside a pipeline block. See #945.
12
+
13
+ # 4.2.2
14
+
15
+ * Fix `WATCH` support for `Redis::Distributed`. See #941.
16
+ * Fix handling of empty stream responses. See #905, #929.
17
+
18
+ # 4.2.1
19
+
20
+ * Fix `exists?` returning an actual boolean when called with multiple keys. See #918.
21
+ * Setting `Redis.exists_returns_integer = false` disables warning message about new behaviour. See #920.
22
+
23
+ # 4.2.0
24
+
25
+ * Convert commands to accept keyword arguments rather than option hashes. This both help catching typos, and reduce needless allocations.
26
+ * Deprecate the synchrony driver. It will be removed in 5.0 and hopefully maintained as a separate gem. See #915.
27
+ * Make `Redis#exists` variadic, will return an Integer if called with multiple keys.
28
+ * Add `Redis#exists?` to get a Boolean if any of the keys exists.
29
+ * `Redis#exists` when called with a single key will warn that future versions will return an Integer.
30
+ Set `Redis.exists_returns_integer = true` to opt-in to the new behavior.
31
+ * Support `keepttl` ooption in `set`. See #913.
32
+ * Optimized initialization of Redis::Cluster. See #912.
33
+ * Accept sentinel options even with string key. See #599.
34
+ * Verify TLS connections by default. See #900.
35
+ * Make `Redis#hset` variadic. It now returns an integer, not a boolean. See #910.
36
+
3
37
  # 4.1.4
4
38
 
5
39
  * Alias `Redis#disconnect` as `#close`. See #901.
@@ -9,6 +43,7 @@
9
43
  * Increase buffer size in the ruby connector. See #880.
10
44
  * Fix thread safety of `Redis.queue`. See #878.
11
45
  * Deprecate `Redis::Future#==` as it's likely to be a mistake. See #876.
46
+ * Support `KEEPTTL` option for SET command. See #913.
12
47
 
13
48
  # 4.1.3
14
49
 
data/README.md CHANGED
@@ -3,6 +3,7 @@
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.
@@ -147,8 +151,8 @@ redis.mget('{key}1', '{key}2')
147
151
 
148
152
  ## Storing objects
149
153
 
150
- Redis only stores strings as values. If you want to store an object, you
151
- can use a serialization mechanism such as JSON:
154
+ Redis "string" types can be used to store serialized Ruby objects, for
155
+ example with JSON:
152
156
 
153
157
  ```ruby
154
158
  require "json"
@@ -261,6 +265,7 @@ All timeout values are specified in seconds.
261
265
  When using pub/sub, you can subscribe to a channel using a timeout as well:
262
266
 
263
267
  ```ruby
268
+ redis = Redis.new(reconnect_attempts: 0)
264
269
  redis.subscribe_with_timeout(5, "news") do |on|
265
270
  on.message do |channel, message|
266
271
  # ...
@@ -322,7 +327,7 @@ This library supports natively terminating client side SSL/TLS connections
322
327
  when talking to Redis via a server-side proxy such as [stunnel], [hitch],
323
328
  or [ghostunnel].
324
329
 
325
- To enable SSL support, pass the `:ssl => :true` option when configuring the
330
+ To enable SSL support, pass the `:ssl => true` option when configuring the
326
331
  Redis client, or pass in `:url => "rediss://..."` (like HTTPS for Redis).
327
332
  You will also need to pass in an `:ssl_params => { ... }` hash used to
328
333
  configure the `OpenSSL::SSL::SSLContext` object used for the connection:
@@ -451,7 +456,7 @@ client and evangelized Redis in Rubyland. Thank you, Ezra.
451
456
  ## Contributing
452
457
 
453
458
  [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.
459
+ requests.
455
460
 
456
461
 
457
462
  [inchpages-image]: https://inch-ci.org/github/redis/redis-rb.svg
@@ -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,7 +31,9 @@ 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)
@@ -26,8 +42,10 @@ class Redis
26
42
  # @option options [String] :password Password to authenticate against server
27
43
  # @option options [Integer] :db (0) Database to select after initial connect
28
44
  # @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
45
+ # @option options [String] :id ID for the client connection, assigns name to current connection by sending
46
+ # `CLIENT SETNAME`
47
+ # @option options [Hash, Integer] :tcp_keepalive Keepalive values, if Integer `intvl` and `probe` are calculated
48
+ # based on the value, if Hash `time`, `intvl` and `probes` can be specified as a Integer
31
49
  # @option options [Integer] :reconnect_attempts Number of attempts trying to connect
32
50
  # @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not
33
51
  # @option options [Array] :sentinels List of sentinels to contact
@@ -52,7 +70,7 @@ class Redis
52
70
  end
53
71
 
54
72
  # Run code with the client reconnecting
55
- def with_reconnect(val=true, &blk)
73
+ def with_reconnect(val = true, &blk)
56
74
  synchronize do |client|
57
75
  client.with_reconnect(val, &blk)
58
76
  end
@@ -205,7 +223,7 @@ class Redis
205
223
  def config(action, *args)
206
224
  synchronize do |client|
207
225
  client.call([:config, action] + args) do |reply|
208
- if reply.kind_of?(Array) && action == :get
226
+ if reply.is_a?(Array) && action == :get
209
227
  Hashify.call(reply)
210
228
  else
211
229
  reply
@@ -256,7 +274,7 @@ class Redis
256
274
  def flushall(options = nil)
257
275
  synchronize do |client|
258
276
  if options && options[:async]
259
- client.call([:flushall, :async])
277
+ client.call(%i[flushall async])
260
278
  else
261
279
  client.call([:flushall])
262
280
  end
@@ -271,7 +289,7 @@ class Redis
271
289
  def flushdb(options = nil)
272
290
  synchronize do |client|
273
291
  if options && options[:async]
274
- client.call([:flushdb, :async])
292
+ client.call(%i[flushdb async])
275
293
  else
276
294
  client.call([:flushdb])
277
295
  end
@@ -285,7 +303,7 @@ class Redis
285
303
  def info(cmd = nil)
286
304
  synchronize do |client|
287
305
  client.call([:info, cmd].compact) do |reply|
288
- if reply.kind_of?(String)
306
+ if reply.is_a?(String)
289
307
  reply = HashifyInfo.call(reply)
290
308
 
291
309
  if cmd && cmd.to_s == "commandstats"
@@ -358,7 +376,7 @@ class Redis
358
376
  # @param [String] subcommand e.g. `get`, `len`, `reset`
359
377
  # @param [Integer] length maximum number of entries to return
360
378
  # @return [Array<String>, Integer, String] depends on subcommand
361
- def slowlog(subcommand, length=nil)
379
+ def slowlog(subcommand, length = nil)
362
380
  synchronize do |client|
363
381
  args = [:slowlog, subcommand]
364
382
  args << length if length
@@ -383,7 +401,7 @@ class Redis
383
401
  def time
384
402
  synchronize do |client|
385
403
  client.call([:time]) do |reply|
386
- reply.map(&:to_i) if reply
404
+ reply&.map(&:to_i)
387
405
  end
388
406
  end
389
407
  end
@@ -496,9 +514,9 @@ class Redis
496
514
  # - `:replace => Boolean`: if false, raises an error if key already exists
497
515
  # @raise [Redis::CommandError]
498
516
  # @return [String] `"OK"`
499
- def restore(key, ttl, serialized_value, options = {})
517
+ def restore(key, ttl, serialized_value, replace: nil)
500
518
  args = [:restore, key, ttl, serialized_value]
501
- args << 'REPLACE' if options[:replace]
519
+ args << 'REPLACE' if replace
502
520
 
503
521
  synchronize do |client|
504
522
  client.call(args)
@@ -550,13 +568,43 @@ class Redis
550
568
  end
551
569
  end
552
570
 
553
- # Determine if a key exists.
571
+ # Determine how many of the keys exists.
554
572
  #
555
- # @param [String] key
573
+ # @param [String, Array<String>] keys
574
+ # @return [Integer]
575
+ def exists(*keys)
576
+ if !Redis.exists_returns_integer && keys.size == 1
577
+ if Redis.exists_returns_integer.nil?
578
+ message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3. `exists?` returns a boolean, you " \
579
+ "should use it instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = " \
580
+ "true. To disable this message and keep the current (boolean) behaviour of 'exists' you can set " \
581
+ "`Redis.exists_returns_integer = false`, but this option will be removed in 5.0. " \
582
+ "(#{::Kernel.caller(1, 1).first})\n"
583
+
584
+ ::Kernel.warn(message)
585
+ end
586
+
587
+ exists?(*keys)
588
+ else
589
+ _exists(*keys)
590
+ end
591
+ end
592
+
593
+ def _exists(*keys)
594
+ synchronize do |client|
595
+ client.call([:exists, *keys])
596
+ end
597
+ end
598
+
599
+ # Determine if any of the keys exists.
600
+ #
601
+ # @param [String, Array<String>] keys
556
602
  # @return [Boolean]
557
- def exists(key)
603
+ def exists?(*keys)
558
604
  synchronize do |client|
559
- client.call([:exists, key], &Boolify)
605
+ client.call([:exists, *keys]) do |value|
606
+ value > 0
607
+ end
560
608
  end
561
609
  end
562
610
 
@@ -567,7 +615,7 @@ class Redis
567
615
  def keys(pattern = "*")
568
616
  synchronize do |client|
569
617
  client.call([:keys, pattern]) do |reply|
570
- if reply.kind_of?(String)
618
+ if reply.is_a?(String)
571
619
  reply.split(" ")
572
620
  else
573
621
  reply
@@ -663,30 +711,27 @@ class Redis
663
711
  # elements where every element is an array with the result for every
664
712
  # element specified in `:get`
665
713
  # - when `:store` is specified, the number of elements in the stored result
666
- def sort(key, options = {})
667
- args = []
714
+ def sort(key, by: nil, limit: nil, get: nil, order: nil, store: nil)
715
+ args = [:sort, key]
716
+ args << "BY" << by if by
668
717
 
669
- by = options[:by]
670
- args.concat(["BY", by]) if by
671
-
672
- limit = options[:limit]
673
- args.concat(["LIMIT"] + limit) if limit
718
+ if limit
719
+ args << "LIMIT"
720
+ args.concat(limit)
721
+ end
674
722
 
675
- get = Array(options[:get])
676
- args.concat(["GET"].product(get).flatten) unless get.empty?
723
+ get = Array(get)
724
+ get.each do |item|
725
+ args << "GET" << item
726
+ end
677
727
 
678
- order = options[:order]
679
728
  args.concat(order.split(" ")) if order
680
-
681
- store = options[:store]
682
- args.concat(["STORE", store]) if store
729
+ args << "STORE" << store if store
683
730
 
684
731
  synchronize do |client|
685
- client.call([:sort, key] + args) do |reply|
732
+ client.call(args) do |reply|
686
733
  if get.size > 1 && !store
687
- if reply
688
- reply.each_slice(get.size).to_a
689
- end
734
+ reply.each_slice(get.size).to_a if reply
690
735
  else
691
736
  reply
692
737
  end
@@ -786,27 +831,21 @@ class Redis
786
831
  # - `:px => Integer`: Set the specified expire time, in milliseconds.
787
832
  # - `:nx => true`: Only set the key if it does not already exist.
788
833
  # - `:xx => true`: Only set the key if it already exist.
834
+ # - `:keepttl => true`: Retain the time to live associated with the key.
789
835
  # @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
836
+ def set(key, value, ex: nil, px: nil, nx: nil, xx: nil, keepttl: nil)
837
+ args = [:set, key, value.to_s]
838
+ args << "EX" << ex if ex
839
+ args << "PX" << px if px
840
+ args << "NX" if nx
841
+ args << "XX" if xx
842
+ args << "KEEPTTL" if keepttl
804
843
 
805
844
  synchronize do |client|
806
845
  if nx || xx
807
- client.call([:set, key, value.to_s] + args, &BoolifySet)
846
+ client.call(args, &BoolifySet)
808
847
  else
809
- client.call([:set, key, value.to_s] + args)
848
+ client.call(args)
810
849
  end
811
850
  end
812
851
  end
@@ -888,7 +927,7 @@ class Redis
888
927
  # @see #mapped_msetnx
889
928
  def msetnx(*args)
890
929
  synchronize do |client|
891
- client.call([:msetnx] + args, &Boolify)
930
+ client.call([:msetnx, *args], &Boolify)
892
931
  end
893
932
  end
894
933
 
@@ -928,7 +967,7 @@ class Redis
928
967
  # @see #mapped_mget
929
968
  def mget(*keys, &blk)
930
969
  synchronize do |client|
931
- client.call([:mget] + keys, &blk)
970
+ client.call([:mget, *keys], &blk)
932
971
  end
933
972
  end
934
973
 
@@ -944,7 +983,7 @@ class Redis
944
983
  # @see #mget
945
984
  def mapped_mget(*keys)
946
985
  mget(*keys) do |reply|
947
- if reply.kind_of?(Array)
986
+ if reply.is_a?(Array)
948
987
  Hash[keys.zip(reply)]
949
988
  else
950
989
  reply
@@ -1031,7 +1070,7 @@ class Redis
1031
1070
  # @return [Integer] the length of the string stored in `destkey`
1032
1071
  def bitop(operation, destkey, *keys)
1033
1072
  synchronize do |client|
1034
- client.call([:bitop, operation, destkey] + keys)
1073
+ client.call([:bitop, operation, destkey, *keys])
1035
1074
  end
1036
1075
  end
1037
1076
 
@@ -1043,10 +1082,8 @@ class Redis
1043
1082
  # @param [Integer] stop stop index
1044
1083
  # @return [Integer] the position of the first 1/0 bit.
1045
1084
  # -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
1085
+ def bitpos(key, bit, start = nil, stop = nil)
1086
+ raise(ArgumentError, 'stop parameter specified without start parameter') if stop && !start
1050
1087
 
1051
1088
  synchronize do |client|
1052
1089
  command = [:bitpos, key, bit]
@@ -1240,15 +1277,7 @@ class Redis
1240
1277
  # @return [nil, String]
1241
1278
  # - `nil` when the operation timed out
1242
1279
  # - 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
-
1280
+ def brpoplpush(source, destination, deprecated_timeout = 0, timeout: deprecated_timeout)
1252
1281
  synchronize do |client|
1253
1282
  command = [:brpoplpush, source, destination, timeout]
1254
1283
  timeout += client.timeout if timeout > 0
@@ -1455,7 +1484,7 @@ class Redis
1455
1484
  # @return [Array<String>] members in the difference
1456
1485
  def sdiff(*keys)
1457
1486
  synchronize do |client|
1458
- client.call([:sdiff] + keys)
1487
+ client.call([:sdiff, *keys])
1459
1488
  end
1460
1489
  end
1461
1490
 
@@ -1466,7 +1495,7 @@ class Redis
1466
1495
  # @return [Integer] number of elements in the resulting set
1467
1496
  def sdiffstore(destination, *keys)
1468
1497
  synchronize do |client|
1469
- client.call([:sdiffstore, destination] + keys)
1498
+ client.call([:sdiffstore, destination, *keys])
1470
1499
  end
1471
1500
  end
1472
1501
 
@@ -1476,7 +1505,7 @@ class Redis
1476
1505
  # @return [Array<String>] members in the intersection
1477
1506
  def sinter(*keys)
1478
1507
  synchronize do |client|
1479
- client.call([:sinter] + keys)
1508
+ client.call([:sinter, *keys])
1480
1509
  end
1481
1510
  end
1482
1511
 
@@ -1487,7 +1516,7 @@ class Redis
1487
1516
  # @return [Integer] number of elements in the resulting set
1488
1517
  def sinterstore(destination, *keys)
1489
1518
  synchronize do |client|
1490
- client.call([:sinterstore, destination] + keys)
1519
+ client.call([:sinterstore, destination, *keys])
1491
1520
  end
1492
1521
  end
1493
1522
 
@@ -1497,7 +1526,7 @@ class Redis
1497
1526
  # @return [Array<String>] members in the union
1498
1527
  def sunion(*keys)
1499
1528
  synchronize do |client|
1500
- client.call([:sunion] + keys)
1529
+ client.call([:sunion, *keys])
1501
1530
  end
1502
1531
  end
1503
1532
 
@@ -1508,7 +1537,7 @@ class Redis
1508
1537
  # @return [Integer] number of elements in the resulting set
1509
1538
  def sunionstore(destination, *keys)
1510
1539
  synchronize do |client|
1511
- client.call([:sunionstore, destination] + keys)
1540
+ client.call([:sunionstore, destination, *keys])
1512
1541
  end
1513
1542
  end
1514
1543
 
@@ -1557,31 +1586,20 @@ class Redis
1557
1586
  # pairs that were **added** to the sorted set.
1558
1587
  # - `Float` when option :incr is specified, holding the score of the member
1559
1588
  # 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
1589
+ def zadd(key, *args, nx: nil, xx: nil, ch: nil, incr: nil)
1590
+ command = [:zadd, key]
1591
+ command << "NX" if nx
1592
+ command << "XX" if xx
1593
+ command << "CH" if ch
1594
+ command << "INCR" if incr
1577
1595
 
1578
1596
  synchronize do |client|
1579
1597
  if args.size == 1 && args[0].is_a?(Array)
1580
1598
  # Variadic: return float if INCR, integer if !INCR
1581
- client.call([:zadd, key] + zadd_options + args[0], &(incr ? Floatify : nil))
1599
+ client.call(command + args[0], &(incr ? Floatify : nil))
1582
1600
  elsif args.size == 2
1583
1601
  # Single pair: return float if INCR, boolean if !INCR
1584
- client.call([:zadd, key] + zadd_options + args, &(incr ? Floatify : Boolify))
1602
+ client.call(command + args, &(incr ? Floatify : Boolify))
1585
1603
  else
1586
1604
  raise ArgumentError, "wrong number of arguments"
1587
1605
  end
@@ -1752,10 +1770,8 @@ class Redis
1752
1770
  # @return [Array<String>, Array<[String, Float]>]
1753
1771
  # - when `:with_scores` is not specified, an array of members
1754
1772
  # - 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]
1773
+ def zrange(key, start, stop, withscores: false, with_scores: withscores)
1774
+ args = [:zrange, key, start, stop]
1759
1775
 
1760
1776
  if with_scores
1761
1777
  args << "WITHSCORES"
@@ -1763,7 +1779,7 @@ class Redis
1763
1779
  end
1764
1780
 
1765
1781
  synchronize do |client|
1766
- client.call([:zrange, key, start, stop] + args, &block)
1782
+ client.call(args, &block)
1767
1783
  end
1768
1784
  end
1769
1785
 
@@ -1778,10 +1794,8 @@ class Redis
1778
1794
  # # => [["b", 64.0], ["a", 32.0]]
1779
1795
  #
1780
1796
  # @see #zrange
1781
- def zrevrange(key, start, stop, options = {})
1782
- args = []
1783
-
1784
- with_scores = options[:with_scores] || options[:withscores]
1797
+ def zrevrange(key, start, stop, withscores: false, with_scores: withscores)
1798
+ args = [:zrevrange, key, start, stop]
1785
1799
 
1786
1800
  if with_scores
1787
1801
  args << "WITHSCORES"
@@ -1789,7 +1803,7 @@ class Redis
1789
1803
  end
1790
1804
 
1791
1805
  synchronize do |client|
1792
- client.call([:zrevrange, key, start, stop] + args, &block)
1806
+ client.call(args, &block)
1793
1807
  end
1794
1808
  end
1795
1809
 
@@ -1880,14 +1894,16 @@ class Redis
1880
1894
  # `count` members
1881
1895
  #
1882
1896
  # @return [Array<String>, Array<[String, Float]>]
1883
- def zrangebylex(key, min, max, options = {})
1884
- args = []
1897
+ def zrangebylex(key, min, max, limit: nil)
1898
+ args = [:zrangebylex, key, min, max]
1885
1899
 
1886
- limit = options[:limit]
1887
- args.concat(["LIMIT"] + limit) if limit
1900
+ if limit
1901
+ args << "LIMIT"
1902
+ args.concat(limit)
1903
+ end
1888
1904
 
1889
1905
  synchronize do |client|
1890
- client.call([:zrangebylex, key, min, max] + args)
1906
+ client.call(args)
1891
1907
  end
1892
1908
  end
1893
1909
 
@@ -1902,14 +1918,16 @@ class Redis
1902
1918
  # # => ["abbygail", "abby"]
1903
1919
  #
1904
1920
  # @see #zrangebylex
1905
- def zrevrangebylex(key, max, min, options = {})
1906
- args = []
1921
+ def zrevrangebylex(key, max, min, limit: nil)
1922
+ args = [:zrevrangebylex, key, max, min]
1907
1923
 
1908
- limit = options[:limit]
1909
- args.concat(["LIMIT"] + limit) if limit
1924
+ if limit
1925
+ args << "LIMIT"
1926
+ args.concat(limit)
1927
+ end
1910
1928
 
1911
1929
  synchronize do |client|
1912
- client.call([:zrevrangebylex, key, max, min] + args)
1930
+ client.call(args)
1913
1931
  end
1914
1932
  end
1915
1933
 
@@ -1940,21 +1958,21 @@ class Redis
1940
1958
  # @return [Array<String>, Array<[String, Float]>]
1941
1959
  # - when `:with_scores` is not specified, an array of members
1942
1960
  # - 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]
1961
+ def zrangebyscore(key, min, max, withscores: false, with_scores: withscores, limit: nil)
1962
+ args = [:zrangebyscore, key, min, max]
1947
1963
 
1948
1964
  if with_scores
1949
1965
  args << "WITHSCORES"
1950
1966
  block = FloatifyPairs
1951
1967
  end
1952
1968
 
1953
- limit = options[:limit]
1954
- args.concat(["LIMIT"] + limit) if limit
1969
+ if limit
1970
+ args << "LIMIT"
1971
+ args.concat(limit)
1972
+ end
1955
1973
 
1956
1974
  synchronize do |client|
1957
- client.call([:zrangebyscore, key, min, max] + args, &block)
1975
+ client.call(args, &block)
1958
1976
  end
1959
1977
  end
1960
1978
 
@@ -1972,21 +1990,21 @@ class Redis
1972
1990
  # # => [["b", 64.0], ["a", 32.0]]
1973
1991
  #
1974
1992
  # @see #zrangebyscore
1975
- def zrevrangebyscore(key, max, min, options = {})
1976
- args = []
1977
-
1978
- with_scores = options[:with_scores] || options[:withscores]
1993
+ def zrevrangebyscore(key, max, min, withscores: false, with_scores: withscores, limit: nil)
1994
+ args = [:zrevrangebyscore, key, max, min]
1979
1995
 
1980
1996
  if with_scores
1981
- args << ["WITHSCORES"]
1997
+ args << "WITHSCORES"
1982
1998
  block = FloatifyPairs
1983
1999
  end
1984
2000
 
1985
- limit = options[:limit]
1986
- args.concat(["LIMIT"] + limit) if limit
2001
+ if limit
2002
+ args << "LIMIT"
2003
+ args.concat(limit)
2004
+ end
1987
2005
 
1988
2006
  synchronize do |client|
1989
- client.call([:zrevrangebyscore, key, max, min] + args, &block)
2007
+ client.call(args, &block)
1990
2008
  end
1991
2009
  end
1992
2010
 
@@ -2050,17 +2068,18 @@ class Redis
2050
2068
  # sorted sets
2051
2069
  # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
2052
2070
  # @return [Integer] number of elements in the resulting sorted set
2053
- def zinterstore(destination, keys, options = {})
2054
- args = []
2071
+ def zinterstore(destination, keys, weights: nil, aggregate: nil)
2072
+ args = [:zinterstore, destination, keys.size, *keys]
2055
2073
 
2056
- weights = options[:weights]
2057
- args.concat(["WEIGHTS"] + weights) if weights
2074
+ if weights
2075
+ args << "WEIGHTS"
2076
+ args.concat(weights)
2077
+ end
2058
2078
 
2059
- aggregate = options[:aggregate]
2060
- args.concat(["AGGREGATE", aggregate]) if aggregate
2079
+ args << "AGGREGATE" << aggregate if aggregate
2061
2080
 
2062
2081
  synchronize do |client|
2063
- client.call([:zinterstore, destination, keys.size] + keys + args)
2082
+ client.call(args)
2064
2083
  end
2065
2084
  end
2066
2085
 
@@ -2077,17 +2096,18 @@ class Redis
2077
2096
  # sorted sets
2078
2097
  # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
2079
2098
  # @return [Integer] number of elements in the resulting sorted set
2080
- def zunionstore(destination, keys, options = {})
2081
- args = []
2099
+ def zunionstore(destination, keys, weights: nil, aggregate: nil)
2100
+ args = [:zunionstore, destination, keys.size, *keys]
2082
2101
 
2083
- weights = options[:weights]
2084
- args.concat(["WEIGHTS"] + weights) if weights
2102
+ if weights
2103
+ args << "WEIGHTS"
2104
+ args.concat(weights)
2105
+ end
2085
2106
 
2086
- aggregate = options[:aggregate]
2087
- args.concat(["AGGREGATE", aggregate]) if aggregate
2107
+ args << "AGGREGATE" << aggregate if aggregate
2088
2108
 
2089
2109
  synchronize do |client|
2090
- client.call([:zunionstore, destination, keys.size] + keys + args)
2110
+ client.call(args)
2091
2111
  end
2092
2112
  end
2093
2113
 
@@ -2101,15 +2121,20 @@ class Redis
2101
2121
  end
2102
2122
  end
2103
2123
 
2104
- # Set the string value of a hash field.
2124
+ # Set one or more hash values.
2125
+ #
2126
+ # @example
2127
+ # redis.hset("hash", "f1", "v1", "f2", "v2") # => 2
2128
+ # redis.hset("hash", { "f1" => "v1", "f2" => "v2" }) # => 2
2105
2129
  #
2106
2130
  # @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)
2131
+ # @param [Array<String> | Hash<String, String>] attrs array or hash of fields and values
2132
+ # @return [Integer] The number of fields that were added to the hash
2133
+ def hset(key, *attrs)
2134
+ attrs = attrs.first.flatten if attrs.size == 1 && attrs.first.is_a?(Hash)
2135
+
2111
2136
  synchronize do |client|
2112
- client.call([:hset, key, field, value], &Boolify)
2137
+ client.call([:hset, key, *attrs])
2113
2138
  end
2114
2139
  end
2115
2140
 
@@ -2198,7 +2223,7 @@ class Redis
2198
2223
  # @see #hmget
2199
2224
  def mapped_hmget(key, *fields)
2200
2225
  hmget(key, *fields) do |reply|
2201
- if reply.kind_of?(Array)
2226
+ if reply.is_a?(Array)
2202
2227
  Hash[fields.zip(reply)]
2203
2228
  else
2204
2229
  reply
@@ -2291,20 +2316,21 @@ class Redis
2291
2316
 
2292
2317
  def subscribed?
2293
2318
  synchronize do |client|
2294
- client.kind_of? SubscribedClient
2319
+ client.is_a? SubscribedClient
2295
2320
  end
2296
2321
  end
2297
2322
 
2298
2323
  # Listen for messages published to the given channels.
2299
2324
  def subscribe(*channels, &block)
2300
- synchronize do |client|
2325
+ synchronize do |_client|
2301
2326
  _subscription(:subscribe, 0, channels, block)
2302
2327
  end
2303
2328
  end
2304
2329
 
2305
- # Listen for messages published to the given channels. Throw a timeout error if there is no messages for a timeout period.
2330
+ # Listen for messages published to the given channels. Throw a timeout error
2331
+ # if there is no messages for a timeout period.
2306
2332
  def subscribe_with_timeout(timeout, *channels, &block)
2307
- synchronize do |client|
2333
+ synchronize do |_client|
2308
2334
  _subscription(:subscribe_with_timeout, timeout, channels, block)
2309
2335
  end
2310
2336
  end
@@ -2312,21 +2338,23 @@ class Redis
2312
2338
  # Stop listening for messages posted to the given channels.
2313
2339
  def unsubscribe(*channels)
2314
2340
  synchronize do |client|
2315
- raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
2341
+ raise "Can't unsubscribe if not subscribed." unless subscribed?
2342
+
2316
2343
  client.unsubscribe(*channels)
2317
2344
  end
2318
2345
  end
2319
2346
 
2320
2347
  # Listen for messages published to channels matching the given patterns.
2321
2348
  def psubscribe(*channels, &block)
2322
- synchronize do |client|
2349
+ synchronize do |_client|
2323
2350
  _subscription(:psubscribe, 0, channels, block)
2324
2351
  end
2325
2352
  end
2326
2353
 
2327
- # Listen for messages published to channels matching the given patterns. Throw a timeout error if there is no messages for a timeout period.
2354
+ # Listen for messages published to channels matching the given patterns.
2355
+ # Throw a timeout error if there is no messages for a timeout period.
2328
2356
  def psubscribe_with_timeout(timeout, *channels, &block)
2329
- synchronize do |client|
2357
+ synchronize do |_client|
2330
2358
  _subscription(:psubscribe_with_timeout, timeout, channels, block)
2331
2359
  end
2332
2360
  end
@@ -2334,7 +2362,8 @@ class Redis
2334
2362
  # Stop listening for messages posted to channels matching the given patterns.
2335
2363
  def punsubscribe(*channels)
2336
2364
  synchronize do |client|
2337
- raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed?
2365
+ raise "Can't unsubscribe if not subscribed." unless subscribed?
2366
+
2338
2367
  client.punsubscribe(*channels)
2339
2368
  end
2340
2369
  end
@@ -2379,7 +2408,7 @@ class Redis
2379
2408
  # @see #multi
2380
2409
  def watch(*keys)
2381
2410
  synchronize do |client|
2382
- res = client.call([:watch] + keys)
2411
+ res = client.call([:watch, *keys])
2383
2412
 
2384
2413
  if block_given?
2385
2414
  begin
@@ -2409,14 +2438,13 @@ class Redis
2409
2438
  end
2410
2439
 
2411
2440
  def pipelined
2412
- synchronize do |client|
2441
+ synchronize do |prior_client|
2413
2442
  begin
2414
- pipeline = Pipeline.new(@client)
2415
- original, @client = @client, pipeline
2443
+ @client = Pipeline.new(prior_client)
2416
2444
  yield(self)
2417
- original.call_pipeline(@client)
2445
+ prior_client.call_pipeline(@client)
2418
2446
  ensure
2419
- @client = original
2447
+ @client = prior_client
2420
2448
  end
2421
2449
  end
2422
2450
  end
@@ -2452,17 +2480,16 @@ class Redis
2452
2480
  # @see #watch
2453
2481
  # @see #unwatch
2454
2482
  def multi
2455
- synchronize do |client|
2483
+ synchronize do |prior_client|
2456
2484
  if !block_given?
2457
- client.call([:multi])
2485
+ prior_client.call([:multi])
2458
2486
  else
2459
2487
  begin
2460
- pipeline = Pipeline::Multi.new(@client)
2461
- original, @client = @client, pipeline
2488
+ @client = Pipeline::Multi.new(prior_client)
2462
2489
  yield(self)
2463
- original.call_pipeline(pipeline)
2490
+ prior_client.call_pipeline(@client)
2464
2491
  ensure
2465
- @client = original
2492
+ @client = prior_client
2466
2493
  end
2467
2494
  end
2468
2495
  end
@@ -2609,18 +2636,12 @@ class Redis
2609
2636
  _eval(:evalsha, args)
2610
2637
  end
2611
2638
 
2612
- def _scan(command, cursor, args, options = {}, &block)
2639
+ def _scan(command, cursor, args, match: nil, count: nil, &block)
2613
2640
  # SSCAN/ZSCAN/HSCAN already prepend the key to +args+.
2614
2641
 
2615
2642
  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
2643
+ args << "MATCH" << match if match
2644
+ args << "COUNT" << count if count
2624
2645
 
2625
2646
  synchronize do |client|
2626
2647
  client.call([command] + args, &block)
@@ -2642,8 +2663,8 @@ class Redis
2642
2663
  # - `:count => Integer`: return count keys at most per iteration
2643
2664
  #
2644
2665
  # @return [String, Array<String>] the next cursor and all found keys
2645
- def scan(cursor, options={})
2646
- _scan(:scan, cursor, [], options)
2666
+ def scan(cursor, **options)
2667
+ _scan(:scan, cursor, [], **options)
2647
2668
  end
2648
2669
 
2649
2670
  # Scan the keyspace
@@ -2661,11 +2682,12 @@ class Redis
2661
2682
  # - `:count => Integer`: return count keys at most per iteration
2662
2683
  #
2663
2684
  # @return [Enumerator] an enumerator for all found keys
2664
- def scan_each(options={}, &block)
2665
- return to_enum(:scan_each, options) unless block_given?
2685
+ def scan_each(**options, &block)
2686
+ return to_enum(:scan_each, **options) unless block_given?
2687
+
2666
2688
  cursor = 0
2667
2689
  loop do
2668
- cursor, keys = scan(cursor, options)
2690
+ cursor, keys = scan(cursor, **options)
2669
2691
  keys.each(&block)
2670
2692
  break if cursor == "0"
2671
2693
  end
@@ -2682,8 +2704,8 @@ class Redis
2682
2704
  # - `:count => Integer`: return count keys at most per iteration
2683
2705
  #
2684
2706
  # @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|
2707
+ def hscan(key, cursor, **options)
2708
+ _scan(:hscan, cursor, [key], **options) do |reply|
2687
2709
  [reply[0], reply[1].each_slice(2).to_a]
2688
2710
  end
2689
2711
  end
@@ -2699,11 +2721,12 @@ class Redis
2699
2721
  # - `:count => Integer`: return count keys at most per iteration
2700
2722
  #
2701
2723
  # @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?
2724
+ def hscan_each(key, **options, &block)
2725
+ return to_enum(:hscan_each, key, **options) unless block_given?
2726
+
2704
2727
  cursor = 0
2705
2728
  loop do
2706
- cursor, values = hscan(key, cursor, options)
2729
+ cursor, values = hscan(key, cursor, **options)
2707
2730
  values.each(&block)
2708
2731
  break if cursor == "0"
2709
2732
  end
@@ -2721,8 +2744,8 @@ class Redis
2721
2744
  #
2722
2745
  # @return [String, Array<[String, Float]>] the next cursor and all found
2723
2746
  # members and scores
2724
- def zscan(key, cursor, options={})
2725
- _scan(:zscan, cursor, [key], options) do |reply|
2747
+ def zscan(key, cursor, **options)
2748
+ _scan(:zscan, cursor, [key], **options) do |reply|
2726
2749
  [reply[0], FloatifyPairs.call(reply[1])]
2727
2750
  end
2728
2751
  end
@@ -2738,11 +2761,12 @@ class Redis
2738
2761
  # - `:count => Integer`: return count keys at most per iteration
2739
2762
  #
2740
2763
  # @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?
2764
+ def zscan_each(key, **options, &block)
2765
+ return to_enum(:zscan_each, key, **options) unless block_given?
2766
+
2743
2767
  cursor = 0
2744
2768
  loop do
2745
- cursor, values = zscan(key, cursor, options)
2769
+ cursor, values = zscan(key, cursor, **options)
2746
2770
  values.each(&block)
2747
2771
  break if cursor == "0"
2748
2772
  end
@@ -2759,8 +2783,8 @@ class Redis
2759
2783
  # - `:count => Integer`: return count keys at most per iteration
2760
2784
  #
2761
2785
  # @return [String, Array<String>] the next cursor and all found members
2762
- def sscan(key, cursor, options={})
2763
- _scan(:sscan, cursor, [key], options)
2786
+ def sscan(key, cursor, **options)
2787
+ _scan(:sscan, cursor, [key], **options)
2764
2788
  end
2765
2789
 
2766
2790
  # Scan a set
@@ -2774,11 +2798,12 @@ class Redis
2774
2798
  # - `:count => Integer`: return count keys at most per iteration
2775
2799
  #
2776
2800
  # @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?
2801
+ def sscan_each(key, **options, &block)
2802
+ return to_enum(:sscan_each, key, **options) unless block_given?
2803
+
2779
2804
  cursor = 0
2780
2805
  loop do
2781
- cursor, keys = sscan(key, cursor, options)
2806
+ cursor, keys = sscan(key, cursor, **options)
2782
2807
  keys.each(&block)
2783
2808
  break if cursor == "0"
2784
2809
  end
@@ -2842,12 +2867,12 @@ class Redis
2842
2867
  end
2843
2868
  end
2844
2869
 
2845
-
2846
2870
  # Query a sorted set representing a geospatial index to fetch members matching a
2847
2871
  # given maximum distance from a point
2848
2872
  #
2849
2873
  # @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
2874
+ # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest
2875
+ # or the farthest to the nearest relative to the center
2851
2876
  # @param [Integer] count limit the results to the first N matching items
2852
2877
  # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
2853
2878
  # @return [Array<String>] may be changed with `options`
@@ -2864,7 +2889,8 @@ class Redis
2864
2889
  # given maximum distance from an already existing member
2865
2890
  #
2866
2891
  # @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
2892
+ # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest
2893
+ # to the nearest relative to the center
2868
2894
  # @param [Integer] count limit the results to the first N matching items
2869
2895
  # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
2870
2896
  # @return [Array<String>] may be changed with `options`
@@ -2881,7 +2907,8 @@ class Redis
2881
2907
  #
2882
2908
  # @param [String] key
2883
2909
  # @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
2910
+ # @return [Array<Array<String>, nil>] returns array of elements, where each
2911
+ # element is either array of longitude and latitude or nil
2885
2912
  def geopos(key, member)
2886
2913
  synchronize do |client|
2887
2914
  client.call([:geopos, key, member])
@@ -2945,10 +2972,14 @@ class Redis
2945
2972
  # @option opts [Boolean] :approximate whether to add `~` modifier of maxlen or not
2946
2973
  #
2947
2974
  # @return [String] the entry id
2948
- def xadd(key, entry, opts = {})
2975
+ def xadd(key, entry, approximate: nil, maxlen: nil, id: '*')
2949
2976
  args = [:xadd, key]
2950
- args.concat(['MAXLEN', (opts[:approximate] ? '~' : nil), opts[:maxlen]].compact) if opts[:maxlen]
2951
- args << (opts[:id] || '*')
2977
+ if maxlen
2978
+ args << "MAXLEN"
2979
+ args << "~" if approximate
2980
+ args << maxlen
2981
+ end
2982
+ args << id
2952
2983
  args.concat(entry.to_a.flatten)
2953
2984
  synchronize { |client| client.call(args) }
2954
2985
  end
@@ -3003,8 +3034,8 @@ class Redis
3003
3034
  # @param count [Integer] the number of entries as limit
3004
3035
  #
3005
3036
  # @return [Array<Array<String, Hash>>] the ids and entries pairs
3006
- def xrange(key, start = '-', _end = '+', count: nil)
3007
- args = [:xrange, key, start, _end]
3037
+ def xrange(key, start = '-', range_end = '+', count: nil)
3038
+ args = [:xrange, key, start, range_end]
3008
3039
  args.concat(['COUNT', count]) if count
3009
3040
  synchronize { |client| client.call(args, &HashifyStreamEntries) }
3010
3041
  end
@@ -3026,8 +3057,8 @@ class Redis
3026
3057
  # @params count [Integer] the number of entries as limit
3027
3058
  #
3028
3059
  # @return [Array<Array<String, Hash>>] the ids and entries pairs
3029
- def xrevrange(key, _end = '+', start = '-', count: nil)
3030
- args = [:xrevrange, key, _end, start]
3060
+ def xrevrange(key, range_end = '+', start = '-', count: nil)
3061
+ args = [:xrevrange, key, range_end, start]
3031
3062
  args.concat(['COUNT', count]) if count
3032
3063
  synchronize { |client| client.call(args, &HashifyStreamEntries) }
3033
3064
  end
@@ -3119,12 +3150,12 @@ class Redis
3119
3150
  # @option opts [Boolean] :noack whether message loss is acceptable or not
3120
3151
  #
3121
3152
  # @return [Hash{String => Hash{String => Hash}}] the entries
3122
- def xreadgroup(group, consumer, keys, ids, opts = {})
3153
+ def xreadgroup(group, consumer, keys, ids, count: nil, block: nil, noack: nil)
3123
3154
  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])
3155
+ args << 'COUNT' << count if count
3156
+ args << 'BLOCK' << block.to_i if block
3157
+ args << 'NOACK' if noack
3158
+ _xread(args, keys, ids, block)
3128
3159
  end
3129
3160
 
3130
3161
  # Removes one or multiple entries from the pending entries list of a stream consumer group.
@@ -3234,8 +3265,8 @@ class Redis
3234
3265
  when "get-master-addr-by-name"
3235
3266
  reply
3236
3267
  else
3237
- if reply.kind_of?(Array)
3238
- if reply[0].kind_of?(Array)
3268
+ if reply.is_a?(Array)
3269
+ if reply[0].is_a?(Array)
3239
3270
  reply.map(&Hashify)
3240
3271
  else
3241
3272
  Hashify.call(reply)
@@ -3259,12 +3290,17 @@ class Redis
3259
3290
  def cluster(subcommand, *args)
3260
3291
  subcommand = subcommand.to_s.downcase
3261
3292
  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
3293
+ when 'slots'
3294
+ HashifyClusterSlots
3295
+ when 'nodes'
3296
+ HashifyClusterNodes
3297
+ when 'slaves'
3298
+ HashifyClusterSlaves
3299
+ when 'info'
3300
+ HashifyInfo
3301
+ else
3302
+ Noop
3303
+ end
3268
3304
 
3269
3305
  # @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
3270
3306
  block = Noop unless @cluster_mode
@@ -3299,21 +3335,21 @@ class Redis
3299
3335
  return @original_client.connection_info if @cluster_mode
3300
3336
 
3301
3337
  {
3302
- host: @original_client.host,
3303
- port: @original_client.port,
3304
- db: @original_client.db,
3305
- id: @original_client.id,
3338
+ host: @original_client.host,
3339
+ port: @original_client.port,
3340
+ db: @original_client.db,
3341
+ id: @original_client.id,
3306
3342
  location: @original_client.location
3307
3343
  }
3308
3344
  end
3309
3345
 
3310
- def method_missing(command, *args)
3346
+ def method_missing(command, *args) # rubocop:disable Style/MissingRespondToMissing
3311
3347
  synchronize do |client|
3312
3348
  client.call([command] + args)
3313
3349
  end
3314
3350
  end
3315
3351
 
3316
- private
3352
+ private
3317
3353
 
3318
3354
  # Commands returning 1 for true and 0 for false may be executed in a pipeline
3319
3355
  # where the method call will return nil. Propagate the nil instead of falsely
@@ -3385,18 +3421,21 @@ private
3385
3421
  end
3386
3422
  }
3387
3423
 
3424
+ EMPTY_STREAM_RESPONSE = [nil].freeze
3425
+ private_constant :EMPTY_STREAM_RESPONSE
3426
+
3388
3427
  HashifyStreamEntries = lambda { |reply|
3389
- reply.map do |entry_id, values|
3428
+ reply.compact.map do |entry_id, values|
3390
3429
  [entry_id, values.each_slice(2).to_h]
3391
3430
  end
3392
3431
  }
3393
3432
 
3394
3433
  HashifyStreamPendings = lambda { |reply|
3395
3434
  {
3396
- 'size' => reply[0],
3435
+ 'size' => reply[0],
3397
3436
  'min_entry_id' => reply[1],
3398
3437
  'max_entry_id' => reply[2],
3399
- 'consumers' => reply[3].nil? ? {} : reply[3].to_h
3438
+ 'consumers' => reply[3].nil? ? {} : reply[3].to_h
3400
3439
  }
3401
3440
  }
3402
3441
 
@@ -3405,8 +3444,8 @@ private
3405
3444
  {
3406
3445
  'entry_id' => arr[0],
3407
3446
  'consumer' => arr[1],
3408
- 'elapsed' => arr[2],
3409
- 'count' => arr[3]
3447
+ 'elapsed' => arr[2],
3448
+ 'count' => arr[3]
3410
3449
  }
3411
3450
  end
3412
3451
  }
@@ -3414,15 +3453,15 @@ private
3414
3453
  HashifyClusterNodeInfo = lambda { |str|
3415
3454
  arr = str.split(' ')
3416
3455
  {
3417
- 'node_id' => arr[0],
3418
- 'ip_port' => arr[1],
3419
- 'flags' => arr[2].split(','),
3456
+ 'node_id' => arr[0],
3457
+ 'ip_port' => arr[1],
3458
+ 'flags' => arr[2].split(','),
3420
3459
  '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('-'))
3460
+ 'ping_sent' => arr[4],
3461
+ 'pong_recv' => arr[5],
3462
+ 'config_epoch' => arr[6],
3463
+ 'link_state' => arr[7],
3464
+ 'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
3426
3465
  }
3427
3466
  }
3428
3467
 
@@ -3433,9 +3472,9 @@ private
3433
3472
  replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } }
3434
3473
  {
3435
3474
  'start_slot' => first_slot,
3436
- 'end_slot' => last_slot,
3437
- 'master' => master,
3438
- 'replicas' => replicas
3475
+ 'end_slot' => last_slot,
3476
+ 'master' => master,
3477
+ 'replicas' => replicas
3439
3478
  }
3440
3479
  end
3441
3480
  }