redis 3.0.5 → 3.0.6

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.
Files changed (58) hide show
  1. checksums.yaml +8 -8
  2. data/.gitignore +9 -8
  3. data/.travis.yml +10 -8
  4. data/CHANGELOG.md +4 -0
  5. data/Rakefile +21 -10
  6. data/lib/redis.rb +229 -72
  7. data/lib/redis/version.rb +1 -1
  8. data/test/blocking_commands_test.rb +1 -1
  9. data/test/command_map_test.rb +1 -1
  10. data/test/commands_on_hashes_test.rb +1 -1
  11. data/test/commands_on_lists_test.rb +1 -1
  12. data/test/commands_on_sets_test.rb +1 -1
  13. data/test/commands_on_sorted_sets_test.rb +1 -1
  14. data/test/commands_on_strings_test.rb +14 -14
  15. data/test/commands_on_value_types_test.rb +1 -1
  16. data/test/connection_handling_test.rb +1 -1
  17. data/test/distributed_blocking_commands_test.rb +1 -1
  18. data/test/distributed_commands_on_hashes_test.rb +1 -1
  19. data/test/distributed_commands_on_lists_test.rb +1 -1
  20. data/test/distributed_commands_on_sets_test.rb +1 -1
  21. data/test/distributed_commands_on_sorted_sets_test.rb +1 -1
  22. data/test/distributed_commands_on_strings_test.rb +7 -7
  23. data/test/distributed_commands_on_value_types_test.rb +1 -1
  24. data/test/distributed_commands_requiring_clustering_test.rb +14 -14
  25. data/test/distributed_connection_handling_test.rb +1 -1
  26. data/test/distributed_internals_test.rb +1 -1
  27. data/test/distributed_key_tags_test.rb +1 -1
  28. data/test/distributed_persistence_control_commands_test.rb +1 -1
  29. data/test/distributed_publish_subscribe_test.rb +1 -1
  30. data/test/distributed_remote_server_control_commands_test.rb +15 -15
  31. data/test/distributed_scripting_test.rb +57 -57
  32. data/test/distributed_sorting_test.rb +1 -1
  33. data/test/distributed_test.rb +1 -1
  34. data/test/distributed_transactions_test.rb +1 -1
  35. data/test/encoding_test.rb +1 -1
  36. data/test/error_replies_test.rb +1 -1
  37. data/test/helper.rb +10 -1
  38. data/test/helper_test.rb +1 -1
  39. data/test/internals_test.rb +20 -9
  40. data/test/lint/hashes.rb +17 -17
  41. data/test/lint/lists.rb +10 -10
  42. data/test/lint/sets.rb +14 -14
  43. data/test/lint/sorted_sets.rb +30 -30
  44. data/test/lint/strings.rb +45 -45
  45. data/test/lint/value_types.rb +32 -32
  46. data/test/persistence_control_commands_test.rb +1 -1
  47. data/test/pipelining_commands_test.rb +1 -1
  48. data/test/publish_subscribe_test.rb +1 -1
  49. data/test/remote_server_control_commands_test.rb +7 -7
  50. data/test/scanning_test.rb +413 -0
  51. data/test/scripting_test.rb +45 -45
  52. data/test/sorting_test.rb +1 -1
  53. data/test/thread_safety_test.rb +1 -1
  54. data/test/transactions_test.rb +1 -1
  55. data/test/unknown_commands_test.rb +1 -1
  56. data/test/url_param_test.rb +1 -1
  57. metadata +6 -4
  58. data/test/db/.gitignore +0 -1
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- OWE3ZTkzYTU5MjZkMWY5YmQ0YTM0NmQ1NWY2ZTVmZTljNmZmYzE1Yg==
4
+ MWU4NjJlZTMzODJmNjI0MTk5ZWQ4Yzk1OGIxNGUwYmQzNzcwZGM0Zg==
5
5
  data.tar.gz: !binary |-
6
- OTY0NWU0ZTA3YzdiNTdhOTdiMmQzMzk2NWQ1ZjBkN2M2N2MxMGEzYg==
6
+ ZmQzMjMxMmU2NWRhY2E3NGE1N2I1OWExMjQxMzQ2YmY1MzcxOTM2Mw==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- ZDA2NjI0ZWE0YjM2NDc4MmE0MTZmODIyN2Q5MDgxMDc1NGFiODhiM2M1NGEy
10
- MGI1ODA2NzhmMDQ3OGY5ZGJhNzM3MzI0MjMyZGFkM2FjNDM3YzdlNTlkMjFh
11
- OTM0ZjQyNTc5OGMwOTkwNGQxZWYyY2E3ODk5Mzc0YmY1ODEzOWE=
9
+ NTZjYTMwZjU5ZWMwZDI0ZWM4OTZmZGU3YmMzMGJlZmZjMDUxNjUwMzNhZGMy
10
+ OTZkOWJmNTM5NTQzMWUyZTRjZmMzNWQ5MDY1NDljNzJiNWMzMGE4NGZjMDI0
11
+ YzM4YTE5MGM5MWVmN2YyYTI3YTViMzljMDg2YzQyYTZlY2M0OTE=
12
12
  data.tar.gz: !binary |-
13
- MGYyMzA5MDg1YTU1ZTcyYjVkMDljZjU4OGFhMDc1MTg3NzhhMjkzZDQyZjk5
14
- MTVmZjJhNmRiZDc0NjE3ODgzMTE5NTMyZGM3YjM0ZjMzMzkyYjA3MjNmYWYx
15
- MzZjNTk2NzRiOGMzYmYyODZkYzZjZjE5YWU0OTc2MWUwMTAyODk=
13
+ Mzk0NmVkMmEwYjc1MzIzOTU4MDEwN2QwNmRhZThkMzA2NzIyOTU5YjlmMzMz
14
+ Y2NkZjUyZTE5YmI2MjFiZTA4YjNkOWMzZWEwYWRkMjZlNDgzNTc3NDViOTRl
15
+ ZDMyZDlhYThhZjMxOGM5YzE1NjNkZDQzNjAwODEyNjM3ZmQ3OWE=
data/.gitignore CHANGED
@@ -1,10 +1,11 @@
1
- nohup.out
2
- redis/*
3
- rdsrv
4
- pkg/*
5
- coverage/*
6
- .idea
7
1
  *.rdb
8
2
  *.swp
9
- .yardoc
10
- doc/
3
+ /.idea
4
+ /.yardoc
5
+ /coverage/*
6
+ /doc/
7
+ /nohup.out
8
+ /pkg/*
9
+ /rdsrv
10
+ /redis/*
11
+ /test/db
@@ -16,30 +16,32 @@ gemfile:
16
16
  - .travis/Gemfile
17
17
 
18
18
  env:
19
- - conn=ruby
20
- - conn=hiredis
21
- - conn=synchrony
19
+ - conn=ruby REDIS_BRANCH=2.6
20
+ - conn=hiredis REDIS_BRANCH=2.6
21
+ - conn=synchrony REDIS_BRANCH=2.6
22
+ - conn=ruby REDIS_BRANCH=2.8
23
+ - conn=ruby REDIS_BRANCH=unstable
22
24
 
23
25
  matrix:
24
26
  exclude:
25
27
  # hiredis
26
28
  - rvm: jruby-18mode
27
29
  gemfile: .travis/Gemfile
28
- env: conn=hiredis
30
+ env: conn=hiredis REDIS_BRANCH=2.6
29
31
  - rvm: jruby-19mode
30
32
  gemfile: .travis/Gemfile
31
- env: conn=hiredis
33
+ env: conn=hiredis REDIS_BRANCH=2.6
32
34
 
33
35
  # synchrony
34
36
  - rvm: 1.8.7
35
37
  gemfile: .travis/Gemfile
36
- env: conn=synchrony
38
+ env: conn=synchrony REDIS_BRANCH=2.6
37
39
  - rvm: jruby-18mode
38
40
  gemfile: .travis/Gemfile
39
- env: conn=synchrony
41
+ env: conn=synchrony REDIS_BRANCH=2.6
40
42
  - rvm: jruby-19mode
41
43
  gemfile: .travis/Gemfile
42
- env: conn=synchrony
44
+ env: conn=synchrony REDIS_BRANCH=2.6
43
45
 
44
46
  notifications:
45
47
  irc:
@@ -1,3 +1,7 @@
1
+ # 3.0.6
2
+
3
+ * Added support for `SCAN` and variants.
4
+
1
5
  # 3.0.5
2
6
 
3
7
  * Fix calling #select from a pipeline (#309).
data/Rakefile CHANGED
@@ -2,12 +2,15 @@ require 'rubygems'
2
2
  require 'rubygems/package_task'
3
3
  require 'rake/testtask'
4
4
 
5
+ ENV["REDIS_BRANCH"] ||= "unstable"
6
+
5
7
  $:.unshift File.join(File.dirname(__FILE__), 'lib')
6
8
  require 'redis/version'
7
9
 
8
10
  REDIS_DIR = File.expand_path(File.join("..", "test"), __FILE__)
9
11
  REDIS_CNF = File.join(REDIS_DIR, "test.conf")
10
12
  REDIS_PID = File.join(REDIS_DIR, "db", "redis.pid")
13
+ BINARY = "tmp/redis-#{ENV["REDIS_BRANCH"]}/src/redis-server"
11
14
 
12
15
  task :default => :run
13
16
 
@@ -15,7 +18,9 @@ desc "Run tests and manage server start/stop"
15
18
  task :run => [:start, :test, :stop]
16
19
 
17
20
  desc "Start the Redis server"
18
- task :start do
21
+ task :start => BINARY do
22
+ sh "#{BINARY} --version"
23
+
19
24
  redis_running = \
20
25
  begin
21
26
  File.exists?(REDIS_PID) && Process.kill(0, File.read(REDIS_PID).to_i)
@@ -25,14 +30,8 @@ task :start do
25
30
  end
26
31
 
27
32
  unless redis_running
28
- unless system("which redis-server")
29
- STDERR.puts "redis-server not in PATH"
30
- exit 1
31
- end
32
-
33
- unless system("redis-server #{REDIS_CNF}")
34
- STDERR.puts "could not start redis-server"
35
- exit 1
33
+ unless system("#{BINARY} #{REDIS_CNF}")
34
+ abort "could not start redis-server"
36
35
  end
37
36
  end
38
37
  end
@@ -45,9 +44,21 @@ task :stop do
45
44
  end
46
45
  end
47
46
 
47
+ file BINARY do
48
+ branch = ENV.fetch("REDIS_BRANCH")
49
+
50
+ sh <<-SH
51
+ mkdir -p tmp;
52
+ cd tmp;
53
+ wget https://github.com/antirez/redis/archive/#{branch}.tar.gz -O #{branch}.tar.gz;
54
+ tar xf #{branch}.tar.gz;
55
+ cd redis-#{branch};
56
+ make
57
+ SH
58
+ end
59
+
48
60
  Rake::TestTask.new do |t|
49
61
  t.options = "-v"
50
- t.libs << "test"
51
62
  t.test_files = FileList["test/*_test.rb"]
52
63
  end
53
64
 
@@ -135,7 +135,7 @@ class Redis
135
135
  synchronize do |client|
136
136
  client.call([:config, action] + args) do |reply|
137
137
  if reply.kind_of?(Array) && action == :get
138
- Hash[reply.each_slice(2).to_a]
138
+ Hash[_pairify(reply)]
139
139
  else
140
140
  reply
141
141
  end
@@ -639,9 +639,7 @@ class Redis
639
639
  # @return [Float] value after incrementing it
640
640
  def incrbyfloat(key, increment)
641
641
  synchronize do |client|
642
- client.call([:incrbyfloat, key, increment]) do |reply|
643
- _floatify(reply) if reply
644
- end
642
+ client.call([:incrbyfloat, key, increment], &_floatify)
645
643
  end
646
644
  end
647
645
 
@@ -1417,9 +1415,7 @@ class Redis
1417
1415
  # @return [Float] score of the member after incrementing it
1418
1416
  def zincrby(key, increment, member)
1419
1417
  synchronize do |client|
1420
- client.call([:zincrby, key, increment, member]) do |reply|
1421
- _floatify(reply) if reply
1422
- end
1418
+ client.call([:zincrby, key, increment, member], &_floatify)
1423
1419
  end
1424
1420
  end
1425
1421
 
@@ -1465,9 +1461,7 @@ class Redis
1465
1461
  # @return [Float] score of the member
1466
1462
  def zscore(key, member)
1467
1463
  synchronize do |client|
1468
- client.call([:zscore, key, member]) do |reply|
1469
- _floatify(reply) if reply
1470
- end
1464
+ client.call([:zscore, key, member], &_floatify)
1471
1465
  end
1472
1466
  end
1473
1467
 
@@ -1493,20 +1487,14 @@ class Redis
1493
1487
  args = []
1494
1488
 
1495
1489
  with_scores = options[:with_scores] || options[:withscores]
1496
- args << "WITHSCORES" if with_scores
1490
+
1491
+ if with_scores
1492
+ args << "WITHSCORES"
1493
+ block = _floatify_pairs
1494
+ end
1497
1495
 
1498
1496
  synchronize do |client|
1499
- client.call([:zrange, key, start, stop] + args) do |reply|
1500
- if with_scores
1501
- if reply
1502
- reply.each_slice(2).map do |member, score|
1503
- [member, _floatify(score)]
1504
- end
1505
- end
1506
- else
1507
- reply
1508
- end
1509
- end
1497
+ client.call([:zrange, key, start, stop] + args, &block)
1510
1498
  end
1511
1499
  end
1512
1500
 
@@ -1525,20 +1513,14 @@ class Redis
1525
1513
  args = []
1526
1514
 
1527
1515
  with_scores = options[:with_scores] || options[:withscores]
1528
- args << "WITHSCORES" if with_scores
1516
+
1517
+ if with_scores
1518
+ args << "WITHSCORES"
1519
+ block = _floatify_pairs
1520
+ end
1529
1521
 
1530
1522
  synchronize do |client|
1531
- client.call([:zrevrange, key, start, stop] + args) do |reply|
1532
- if with_scores
1533
- if reply
1534
- reply.each_slice(2).map do |member, score|
1535
- [member, _floatify(score)]
1536
- end
1537
- end
1538
- else
1539
- reply
1540
- end
1541
- end
1523
+ client.call([:zrevrange, key, start, stop] + args, &block)
1542
1524
  end
1543
1525
  end
1544
1526
 
@@ -1615,23 +1597,17 @@ class Redis
1615
1597
  args = []
1616
1598
 
1617
1599
  with_scores = options[:with_scores] || options[:withscores]
1618
- args.concat(["WITHSCORES"]) if with_scores
1600
+
1601
+ if with_scores
1602
+ args << "WITHSCORES"
1603
+ block = _floatify_pairs
1604
+ end
1619
1605
 
1620
1606
  limit = options[:limit]
1621
1607
  args.concat(["LIMIT"] + limit) if limit
1622
1608
 
1623
1609
  synchronize do |client|
1624
- client.call([:zrangebyscore, key, min, max] + args) do |reply|
1625
- if with_scores
1626
- if reply
1627
- reply.each_slice(2).map do |member, score|
1628
- [member, _floatify(score)]
1629
- end
1630
- end
1631
- else
1632
- reply
1633
- end
1634
- end
1610
+ client.call([:zrangebyscore, key, min, max] + args, &block)
1635
1611
  end
1636
1612
  end
1637
1613
 
@@ -1653,23 +1629,17 @@ class Redis
1653
1629
  args = []
1654
1630
 
1655
1631
  with_scores = options[:with_scores] || options[:withscores]
1656
- args.concat(["WITHSCORES"]) if with_scores
1632
+
1633
+ if with_scores
1634
+ args << ["WITHSCORES"]
1635
+ block = _floatify_pairs
1636
+ end
1657
1637
 
1658
1638
  limit = options[:limit]
1659
1639
  args.concat(["LIMIT"] + limit) if limit
1660
1640
 
1661
1641
  synchronize do |client|
1662
- client.call([:zrevrangebyscore, key, max, min] + args) do |reply|
1663
- if with_scores
1664
- if reply
1665
- reply.each_slice(2).map do |member, score|
1666
- [member, _floatify(score)]
1667
- end
1668
- end
1669
- else
1670
- reply
1671
- end
1672
- end
1642
+ client.call([:zrevrangebyscore, key, max, min] + args, &block)
1673
1643
  end
1674
1644
  end
1675
1645
 
@@ -1931,9 +1901,7 @@ class Redis
1931
1901
  # @return [Float] value of the field after incrementing it
1932
1902
  def hincrbyfloat(key, field, increment)
1933
1903
  synchronize do |client|
1934
- client.call([:hincrbyfloat, key, field, increment]) do |reply|
1935
- _floatify(reply) if reply
1936
- end
1904
+ client.call([:hincrbyfloat, key, field, increment], &_floatify)
1937
1905
  end
1938
1906
  end
1939
1907
 
@@ -2271,18 +2239,189 @@ class Redis
2271
2239
  _eval(:evalsha, args)
2272
2240
  end
2273
2241
 
2274
- def id
2242
+ def _scan(command, cursor, args, options = {}, &block)
2243
+ # SSCAN/ZSCAN/HSCAN already prepend the key to +args+.
2244
+
2245
+ args << cursor
2246
+
2247
+ if match = options[:match]
2248
+ args.concat(["MATCH", match])
2249
+ end
2250
+
2251
+ if count = options[:count]
2252
+ args.concat(["COUNT", count])
2253
+ end
2254
+
2275
2255
  synchronize do |client|
2276
- client.id
2256
+ client.call([command] + args, &block)
2277
2257
  end
2278
2258
  end
2279
2259
 
2280
- def inspect
2281
- synchronize do |client|
2282
- "#<Redis client v#{Redis::VERSION} for #{@original_client.id}>"
2260
+ # Scan the keyspace
2261
+ #
2262
+ # @example Retrieve the first batch of keys
2263
+ # redis.scan(0)
2264
+ # # => ["4", ["key:21", "key:47", "key:42"]]
2265
+ # @example Retrieve a batch of keys matching a pattern
2266
+ # redis.scan(4, :match => "key:1?")
2267
+ # # => ["92", ["key:13", "key:18"]]
2268
+ #
2269
+ # @param [String, Integer] cursor: the cursor of the iteration
2270
+ # @param [Hash] options
2271
+ # - `:match => String`: only return keys matching the pattern
2272
+ # - `:count => Integer`: return count keys at most per iteration
2273
+ #
2274
+ # @return [String, Array<String>] the next cursor and all found keys
2275
+ def scan(cursor, options={})
2276
+ _scan(:scan, cursor, [], options)
2277
+ end
2278
+
2279
+ # Scan the keyspace
2280
+ #
2281
+ # @example Retrieve all of the keys (with possible duplicates)
2282
+ # redis.scan_each.to_a
2283
+ # # => ["key:21", "key:47", "key:42"]
2284
+ # @example Execute block for each key matching a pattern
2285
+ # redis.scan_each(:match => "key:1?") {|key| puts key}
2286
+ # # => key:13
2287
+ # # => key:18
2288
+ #
2289
+ # @param [Hash] options
2290
+ # - `:match => String`: only return keys matching the pattern
2291
+ # - `:count => Integer`: return count keys at most per iteration
2292
+ #
2293
+ # @return [Enumerator] an enumerator for all found keys
2294
+ def scan_each(options={}, &block)
2295
+ return to_enum(:scan_each, options) unless block_given?
2296
+ cursor = 0
2297
+ loop do
2298
+ cursor, keys = scan(cursor, options)
2299
+ keys.each(&block)
2300
+ break if cursor == "0"
2301
+ end
2302
+ end
2303
+
2304
+ # Scan a hash
2305
+ #
2306
+ # @example Retrieve the first batch of key/value pairs in a hash
2307
+ # redis.hscan("hash", 0)
2308
+ #
2309
+ # @param [String, Integer] cursor: the cursor of the iteration
2310
+ # @param [Hash] options
2311
+ # - `:match => String`: only return keys matching the pattern
2312
+ # - `:count => Integer`: return count keys at most per iteration
2313
+ #
2314
+ # @return [String, Array<[String, String]>] the next cursor and all found keys
2315
+ def hscan(key, cursor, options={})
2316
+ _scan(:hscan, cursor, [key], options) do |reply|
2317
+ [reply[0], _pairify(reply[1])]
2318
+ end
2319
+ end
2320
+
2321
+ # Scan a hash
2322
+ #
2323
+ # @example Retrieve all of the key/value pairs in a hash
2324
+ # redis.hscan_each("hash").to_a
2325
+ # # => [["key70", "70"], ["key80", "80"]]
2326
+ #
2327
+ # @param [Hash] options
2328
+ # - `:match => String`: only return keys matching the pattern
2329
+ # - `:count => Integer`: return count keys at most per iteration
2330
+ #
2331
+ # @return [Enumerator] an enumerator for all found keys
2332
+ def hscan_each(key, options={}, &block)
2333
+ return to_enum(:hscan_each, key, options) unless block_given?
2334
+ cursor = 0
2335
+ loop do
2336
+ cursor, values = hscan(key, cursor, options)
2337
+ values.each(&block)
2338
+ break if cursor == "0"
2339
+ end
2340
+ end
2341
+
2342
+ # Scan a sorted set
2343
+ #
2344
+ # @example Retrieve the first batch of key/value pairs in a hash
2345
+ # redis.zscan("zset", 0)
2346
+ #
2347
+ # @param [String, Integer] cursor: the cursor of the iteration
2348
+ # @param [Hash] options
2349
+ # - `:match => String`: only return keys matching the pattern
2350
+ # - `:count => Integer`: return count keys at most per iteration
2351
+ #
2352
+ # @return [String, Array<[String, Float]>] the next cursor and all found
2353
+ # members and scores
2354
+ def zscan(key, cursor, options={})
2355
+ _scan(:zscan, cursor, [key], options) do |reply|
2356
+ [reply[0], _floatify_pairs.call(reply[1])]
2357
+ end
2358
+ end
2359
+
2360
+ # Scan a sorted set
2361
+ #
2362
+ # @example Retrieve all of the members/scores in a sorted set
2363
+ # redis.zscan_each("zset").to_a
2364
+ # # => [["key70", "70"], ["key80", "80"]]
2365
+ #
2366
+ # @param [Hash] options
2367
+ # - `:match => String`: only return keys matching the pattern
2368
+ # - `:count => Integer`: return count keys at most per iteration
2369
+ #
2370
+ # @return [Enumerator] an enumerator for all found scores and members
2371
+ def zscan_each(key, options={}, &block)
2372
+ return to_enum(:zscan_each, key, options) unless block_given?
2373
+ cursor = 0
2374
+ loop do
2375
+ cursor, values = zscan(key, cursor, options)
2376
+ values.each(&block)
2377
+ break if cursor == "0"
2378
+ end
2379
+ end
2380
+
2381
+ # Scan a set
2382
+ #
2383
+ # @example Retrieve the first batch of keys in a set
2384
+ # redis.sscan("set", 0)
2385
+ #
2386
+ # @param [String, Integer] cursor: the cursor of the iteration
2387
+ # @param [Hash] options
2388
+ # - `:match => String`: only return keys matching the pattern
2389
+ # - `:count => Integer`: return count keys at most per iteration
2390
+ #
2391
+ # @return [String, Array<String>] the next cursor and all found members
2392
+ def sscan(key, cursor, options={})
2393
+ _scan(:sscan, cursor, [key], options)
2394
+ end
2395
+
2396
+ # Scan a set
2397
+ #
2398
+ # @example Retrieve all of the keys in a set
2399
+ # redis.sscan("set").to_a
2400
+ # # => ["key1", "key2", "key3"]
2401
+ #
2402
+ # @param [Hash] options
2403
+ # - `:match => String`: only return keys matching the pattern
2404
+ # - `:count => Integer`: return count keys at most per iteration
2405
+ #
2406
+ # @return [Enumerator] an enumerator for all keys in the set
2407
+ def sscan_each(key, options={}, &block)
2408
+ return to_enum(:sscan_each, key, options) unless block_given?
2409
+ cursor = 0
2410
+ loop do
2411
+ cursor, keys = sscan(key, cursor, options)
2412
+ keys.each(&block)
2413
+ break if cursor == "0"
2283
2414
  end
2284
2415
  end
2285
2416
 
2417
+ def id
2418
+ @original_client.id
2419
+ end
2420
+
2421
+ def inspect
2422
+ "#<Redis client v#{Redis::VERSION} for #{id}>"
2423
+ end
2424
+
2286
2425
  def method_missing(command, *args)
2287
2426
  synchronize do |client|
2288
2427
  client.call([command] + args)
@@ -2320,12 +2459,30 @@ private
2320
2459
  }
2321
2460
  end
2322
2461
 
2323
- def _floatify(str)
2324
- if (inf = str.match(/^(-)?inf/i))
2325
- (inf[1] ? -1.0 : 1.0) / 0.0
2326
- else
2327
- Float str
2328
- end
2462
+ def _floatify
2463
+ lambda { |str|
2464
+ return unless str
2465
+
2466
+ if (inf = str.match(/^(-)?inf/i))
2467
+ (inf[1] ? -1.0 : 1.0) / 0.0
2468
+ else
2469
+ Float(str)
2470
+ end
2471
+ }
2472
+ end
2473
+
2474
+ def _floatify_pairs
2475
+ lambda { |array|
2476
+ return unless array
2477
+
2478
+ array.each_slice(2).map do |member, score|
2479
+ [member, _floatify.call(score)]
2480
+ end
2481
+ }
2482
+ end
2483
+
2484
+ def _pairify(array)
2485
+ array.each_slice(2).to_a
2329
2486
  end
2330
2487
 
2331
2488
  def _subscription(method, channels, block)