redis 4.2.5 → 5.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +167 -0
  3. data/README.md +102 -162
  4. data/lib/redis/client.rb +84 -589
  5. data/lib/redis/commands/bitmaps.rb +66 -0
  6. data/lib/redis/commands/cluster.rb +28 -0
  7. data/lib/redis/commands/connection.rb +53 -0
  8. data/lib/redis/commands/geo.rb +84 -0
  9. data/lib/redis/commands/hashes.rb +254 -0
  10. data/lib/redis/commands/hyper_log_log.rb +37 -0
  11. data/lib/redis/commands/keys.rb +437 -0
  12. data/lib/redis/commands/lists.rb +339 -0
  13. data/lib/redis/commands/pubsub.rb +54 -0
  14. data/lib/redis/commands/scripting.rb +114 -0
  15. data/lib/redis/commands/server.rb +188 -0
  16. data/lib/redis/commands/sets.rb +214 -0
  17. data/lib/redis/commands/sorted_sets.rb +884 -0
  18. data/lib/redis/commands/streams.rb +402 -0
  19. data/lib/redis/commands/strings.rb +314 -0
  20. data/lib/redis/commands/transactions.rb +115 -0
  21. data/lib/redis/commands.rb +237 -0
  22. data/lib/redis/distributed.rb +220 -75
  23. data/lib/redis/errors.rb +15 -41
  24. data/lib/redis/hash_ring.rb +26 -26
  25. data/lib/redis/pipeline.rb +66 -120
  26. data/lib/redis/subscribe.rb +23 -15
  27. data/lib/redis/version.rb +1 -1
  28. data/lib/redis.rb +110 -3441
  29. metadata +27 -54
  30. data/lib/redis/cluster/command.rb +0 -81
  31. data/lib/redis/cluster/command_loader.rb +0 -34
  32. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  33. data/lib/redis/cluster/node.rb +0 -107
  34. data/lib/redis/cluster/node_key.rb +0 -31
  35. data/lib/redis/cluster/node_loader.rb +0 -37
  36. data/lib/redis/cluster/option.rb +0 -90
  37. data/lib/redis/cluster/slot.rb +0 -86
  38. data/lib/redis/cluster/slot_loader.rb +0 -49
  39. data/lib/redis/cluster.rb +0 -295
  40. data/lib/redis/connection/command_helper.rb +0 -39
  41. data/lib/redis/connection/hiredis.rb +0 -67
  42. data/lib/redis/connection/registry.rb +0 -13
  43. data/lib/redis/connection/ruby.rb +0 -427
  44. data/lib/redis/connection/synchrony.rb +0 -146
  45. data/lib/redis/connection.rb +0 -11
@@ -0,0 +1,314 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Strings
6
+ # Decrement the integer value of a key by one.
7
+ #
8
+ # @example
9
+ # redis.decr("value")
10
+ # # => 4
11
+ #
12
+ # @param [String] key
13
+ # @return [Integer] value after decrementing it
14
+ def decr(key)
15
+ send_command([:decr, key])
16
+ end
17
+
18
+ # Decrement the integer value of a key by the given number.
19
+ #
20
+ # @example
21
+ # redis.decrby("value", 5)
22
+ # # => 0
23
+ #
24
+ # @param [String] key
25
+ # @param [Integer] decrement
26
+ # @return [Integer] value after decrementing it
27
+ def decrby(key, decrement)
28
+ send_command([:decrby, key, Integer(decrement)])
29
+ end
30
+
31
+ # Increment the integer value of a key by one.
32
+ #
33
+ # @example
34
+ # redis.incr("value")
35
+ # # => 6
36
+ #
37
+ # @param [String] key
38
+ # @return [Integer] value after incrementing it
39
+ def incr(key)
40
+ send_command([:incr, key])
41
+ end
42
+
43
+ # Increment the integer value of a key by the given integer number.
44
+ #
45
+ # @example
46
+ # redis.incrby("value", 5)
47
+ # # => 10
48
+ #
49
+ # @param [String] key
50
+ # @param [Integer] increment
51
+ # @return [Integer] value after incrementing it
52
+ def incrby(key, increment)
53
+ send_command([:incrby, key, Integer(increment)])
54
+ end
55
+
56
+ # Increment the numeric value of a key by the given float number.
57
+ #
58
+ # @example
59
+ # redis.incrbyfloat("value", 1.23)
60
+ # # => 1.23
61
+ #
62
+ # @param [String] key
63
+ # @param [Float] increment
64
+ # @return [Float] value after incrementing it
65
+ def incrbyfloat(key, increment)
66
+ send_command([:incrbyfloat, key, Float(increment)], &Floatify)
67
+ end
68
+
69
+ # Set the string value of a key.
70
+ #
71
+ # @param [String] key
72
+ # @param [String] value
73
+ # @param [Hash] options
74
+ # - `:ex => Integer`: Set the specified expire time, in seconds.
75
+ # - `:px => Integer`: Set the specified expire time, in milliseconds.
76
+ # - `:exat => Integer` : Set the specified Unix time at which the key will expire, in seconds.
77
+ # - `:pxat => Integer` : Set the specified Unix time at which the key will expire, in milliseconds.
78
+ # - `:nx => true`: Only set the key if it does not already exist.
79
+ # - `:xx => true`: Only set the key if it already exist.
80
+ # - `:keepttl => true`: Retain the time to live associated with the key.
81
+ # - `:get => true`: Return the old string stored at key, or nil if key did not exist.
82
+ # @return [String, Boolean] `"OK"` or true, false if `:nx => true` or `:xx => true`
83
+ def set(key, value, ex: nil, px: nil, exat: nil, pxat: nil, nx: nil, xx: nil, keepttl: nil, get: nil)
84
+ args = [:set, key, value.to_s]
85
+ args << "EX" << Integer(ex) if ex
86
+ args << "PX" << Integer(px) if px
87
+ args << "EXAT" << Integer(exat) if exat
88
+ args << "PXAT" << Integer(pxat) if pxat
89
+ args << "NX" if nx
90
+ args << "XX" if xx
91
+ args << "KEEPTTL" if keepttl
92
+ args << "GET" if get
93
+
94
+ if nx || xx
95
+ send_command(args, &BoolifySet)
96
+ else
97
+ send_command(args)
98
+ end
99
+ end
100
+
101
+ # Set the time to live in seconds of a key.
102
+ #
103
+ # @param [String] key
104
+ # @param [Integer] ttl
105
+ # @param [String] value
106
+ # @return [String] `"OK"`
107
+ def setex(key, ttl, value)
108
+ send_command([:setex, key, Integer(ttl), value.to_s])
109
+ end
110
+
111
+ # Set the time to live in milliseconds of a key.
112
+ #
113
+ # @param [String] key
114
+ # @param [Integer] ttl
115
+ # @param [String] value
116
+ # @return [String] `"OK"`
117
+ def psetex(key, ttl, value)
118
+ send_command([:psetex, key, Integer(ttl), value.to_s])
119
+ end
120
+
121
+ # Set the value of a key, only if the key does not exist.
122
+ #
123
+ # @param [String] key
124
+ # @param [String] value
125
+ # @return [Boolean] whether the key was set or not
126
+ def setnx(key, value)
127
+ send_command([:setnx, key, value.to_s], &Boolify)
128
+ end
129
+
130
+ # Set one or more values.
131
+ #
132
+ # @example
133
+ # redis.mset("key1", "v1", "key2", "v2")
134
+ # # => "OK"
135
+ #
136
+ # @param [Array<String>] args array of keys and values
137
+ # @return [String] `"OK"`
138
+ #
139
+ # @see #mapped_mset
140
+ def mset(*args)
141
+ send_command([:mset] + args)
142
+ end
143
+
144
+ # Set one or more values.
145
+ #
146
+ # @example
147
+ # redis.mapped_mset({ "f1" => "v1", "f2" => "v2" })
148
+ # # => "OK"
149
+ #
150
+ # @param [Hash] hash keys mapping to values
151
+ # @return [String] `"OK"`
152
+ #
153
+ # @see #mset
154
+ def mapped_mset(hash)
155
+ mset(hash.flatten)
156
+ end
157
+
158
+ # Set one or more values, only if none of the keys exist.
159
+ #
160
+ # @example
161
+ # redis.msetnx("key1", "v1", "key2", "v2")
162
+ # # => true
163
+ #
164
+ # @param [Array<String>] args array of keys and values
165
+ # @return [Boolean] whether or not all values were set
166
+ #
167
+ # @see #mapped_msetnx
168
+ def msetnx(*args)
169
+ send_command([:msetnx, *args], &Boolify)
170
+ end
171
+
172
+ # Set one or more values, only if none of the keys exist.
173
+ #
174
+ # @example
175
+ # redis.mapped_msetnx({ "key1" => "v1", "key2" => "v2" })
176
+ # # => true
177
+ #
178
+ # @param [Hash] hash keys mapping to values
179
+ # @return [Boolean] whether or not all values were set
180
+ #
181
+ # @see #msetnx
182
+ def mapped_msetnx(hash)
183
+ msetnx(hash.flatten)
184
+ end
185
+
186
+ # Get the value of a key.
187
+ #
188
+ # @param [String] key
189
+ # @return [String]
190
+ def get(key)
191
+ send_command([:get, key])
192
+ end
193
+
194
+ # Get the values of all the given keys.
195
+ #
196
+ # @example
197
+ # redis.mget("key1", "key2")
198
+ # # => ["v1", "v2"]
199
+ #
200
+ # @param [Array<String>] keys
201
+ # @return [Array<String>] an array of values for the specified keys
202
+ #
203
+ # @see #mapped_mget
204
+ def mget(*keys, &blk)
205
+ keys.flatten!(1)
206
+ send_command([:mget, *keys], &blk)
207
+ end
208
+
209
+ # Get the values of all the given keys.
210
+ #
211
+ # @example
212
+ # redis.mapped_mget("key1", "key2")
213
+ # # => { "key1" => "v1", "key2" => "v2" }
214
+ #
215
+ # @param [Array<String>] keys array of keys
216
+ # @return [Hash] a hash mapping the specified keys to their values
217
+ #
218
+ # @see #mget
219
+ def mapped_mget(*keys)
220
+ mget(*keys) do |reply|
221
+ if reply.is_a?(Array)
222
+ Hash[keys.zip(reply)]
223
+ else
224
+ reply
225
+ end
226
+ end
227
+ end
228
+
229
+ # Overwrite part of a string at key starting at the specified offset.
230
+ #
231
+ # @param [String] key
232
+ # @param [Integer] offset byte offset
233
+ # @param [String] value
234
+ # @return [Integer] length of the string after it was modified
235
+ def setrange(key, offset, value)
236
+ send_command([:setrange, key, Integer(offset), value.to_s])
237
+ end
238
+
239
+ # Get a substring of the string stored at a key.
240
+ #
241
+ # @param [String] key
242
+ # @param [Integer] start zero-based start offset
243
+ # @param [Integer] stop zero-based end offset. Use -1 for representing
244
+ # the end of the string
245
+ # @return [Integer] `0` or `1`
246
+ def getrange(key, start, stop)
247
+ send_command([:getrange, key, Integer(start), Integer(stop)])
248
+ end
249
+
250
+ # Append a value to a key.
251
+ #
252
+ # @param [String] key
253
+ # @param [String] value value to append
254
+ # @return [Integer] length of the string after appending
255
+ def append(key, value)
256
+ send_command([:append, key, value])
257
+ end
258
+
259
+ # Set the string value of a key and return its old value.
260
+ #
261
+ # @param [String] key
262
+ # @param [String] value value to replace the current value with
263
+ # @return [String] the old value stored in the key, or `nil` if the key
264
+ # did not exist
265
+ def getset(key, value)
266
+ send_command([:getset, key, value.to_s])
267
+ end
268
+
269
+ # Get the value of key and delete the key. This command is similar to GET,
270
+ # except for the fact that it also deletes the key on success.
271
+ #
272
+ # @param [String] key
273
+ # @return [String] the old value stored in the key, or `nil` if the key
274
+ # did not exist
275
+ def getdel(key)
276
+ send_command([:getdel, key])
277
+ end
278
+
279
+ # Get the value of key and optionally set its expiration. GETEX is similar to
280
+ # GET, but is a write command with additional options. When no options are
281
+ # provided, GETEX behaves like GET.
282
+ #
283
+ # @param [String] key
284
+ # @param [Hash] options
285
+ # - `:ex => Integer`: Set the specified expire time, in seconds.
286
+ # - `:px => Integer`: Set the specified expire time, in milliseconds.
287
+ # - `:exat => true`: Set the specified Unix time at which the key will
288
+ # expire, in seconds.
289
+ # - `:pxat => true`: Set the specified Unix time at which the key will
290
+ # expire, in milliseconds.
291
+ # - `:persist => true`: Remove the time to live associated with the key.
292
+ # @return [String] The value of key, or nil when key does not exist.
293
+ def getex(key, ex: nil, px: nil, exat: nil, pxat: nil, persist: false)
294
+ args = [:getex, key]
295
+ args << "EX" << Integer(ex) if ex
296
+ args << "PX" << Integer(px) if px
297
+ args << "EXAT" << Integer(exat) if exat
298
+ args << "PXAT" << Integer(pxat) if pxat
299
+ args << "PERSIST" if persist
300
+
301
+ send_command(args)
302
+ end
303
+
304
+ # Get the length of the value stored in a key.
305
+ #
306
+ # @param [String] key
307
+ # @return [Integer] the length of the value stored in the key, or 0
308
+ # if the key does not exist
309
+ def strlen(key)
310
+ send_command([:strlen, key])
311
+ end
312
+ end
313
+ end
314
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Transactions
6
+ # Mark the start of a transaction block.
7
+ #
8
+ # @example With a block
9
+ # redis.multi do |multi|
10
+ # multi.set("key", "value")
11
+ # multi.incr("counter")
12
+ # end # => ["OK", 6]
13
+ #
14
+ # @yield [multi] the commands that are called inside this block are cached
15
+ # and written to the server upon returning from it
16
+ # @yieldparam [Redis] multi `self`
17
+ #
18
+ # @return [Array<...>]
19
+ # - an array with replies
20
+ #
21
+ # @see #watch
22
+ # @see #unwatch
23
+ def multi
24
+ synchronize do |client|
25
+ client.multi do |raw_transaction|
26
+ yield MultiConnection.new(raw_transaction)
27
+ end
28
+ end
29
+ end
30
+
31
+ # Watch the given keys to determine execution of the MULTI/EXEC block.
32
+ #
33
+ # Using a block is optional, but is necessary for thread-safety.
34
+ #
35
+ # An `#unwatch` is automatically issued if an exception is raised within the
36
+ # block that is a subclass of StandardError and is not a ConnectionError.
37
+ #
38
+ # @example With a block
39
+ # redis.watch("key") do
40
+ # if redis.get("key") == "some value"
41
+ # redis.multi do |multi|
42
+ # multi.set("key", "other value")
43
+ # multi.incr("counter")
44
+ # end
45
+ # else
46
+ # redis.unwatch
47
+ # end
48
+ # end
49
+ # # => ["OK", 6]
50
+ #
51
+ # @example Without a block
52
+ # redis.watch("key")
53
+ # # => "OK"
54
+ #
55
+ # @param [String, Array<String>] keys one or more keys to watch
56
+ # @return [Object] if using a block, returns the return value of the block
57
+ # @return [String] if not using a block, returns `OK`
58
+ #
59
+ # @see #unwatch
60
+ # @see #multi
61
+ def watch(*keys)
62
+ synchronize do |client|
63
+ res = client.call_v([:watch] + keys)
64
+
65
+ if block_given?
66
+ begin
67
+ yield(self)
68
+ rescue ConnectionError
69
+ raise
70
+ rescue StandardError
71
+ unwatch
72
+ raise
73
+ end
74
+ else
75
+ res
76
+ end
77
+ end
78
+ end
79
+
80
+ # Forget about all watched keys.
81
+ #
82
+ # @return [String] `OK`
83
+ #
84
+ # @see #watch
85
+ # @see #multi
86
+ def unwatch
87
+ send_command([:unwatch])
88
+ end
89
+
90
+ # Execute all commands issued after MULTI.
91
+ #
92
+ # Only call this method when `#multi` was called **without** a block.
93
+ #
94
+ # @return [nil, Array<...>]
95
+ # - when commands were not executed, `nil`
96
+ # - when commands were executed, an array with their replies
97
+ #
98
+ # @see #multi
99
+ # @see #discard
100
+ def exec
101
+ send_command([:exec])
102
+ end
103
+
104
+ # Discard all commands issued after MULTI.
105
+ #
106
+ # @return [String] `"OK"`
107
+ #
108
+ # @see #multi
109
+ # @see #exec
110
+ def discard
111
+ send_command([:discard])
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redis/commands/bitmaps"
4
+ require "redis/commands/cluster"
5
+ require "redis/commands/connection"
6
+ require "redis/commands/geo"
7
+ require "redis/commands/hashes"
8
+ require "redis/commands/hyper_log_log"
9
+ require "redis/commands/keys"
10
+ require "redis/commands/lists"
11
+ require "redis/commands/pubsub"
12
+ require "redis/commands/scripting"
13
+ require "redis/commands/server"
14
+ require "redis/commands/sets"
15
+ require "redis/commands/sorted_sets"
16
+ require "redis/commands/streams"
17
+ require "redis/commands/strings"
18
+ require "redis/commands/transactions"
19
+
20
+ class Redis
21
+ module Commands
22
+ include Bitmaps
23
+ include Cluster
24
+ include Connection
25
+ include Geo
26
+ include Hashes
27
+ include HyperLogLog
28
+ include Keys
29
+ include Lists
30
+ include Pubsub
31
+ include Scripting
32
+ include Server
33
+ include Sets
34
+ include SortedSets
35
+ include Streams
36
+ include Strings
37
+ include Transactions
38
+
39
+ # Commands returning 1 for true and 0 for false may be executed in a pipeline
40
+ # where the method call will return nil. Propagate the nil instead of falsely
41
+ # returning false.
42
+ Boolify = lambda { |value|
43
+ value != 0 unless value.nil?
44
+ }
45
+
46
+ BoolifySet = lambda { |value|
47
+ case value
48
+ when "OK"
49
+ true
50
+ when nil
51
+ false
52
+ else
53
+ value
54
+ end
55
+ }
56
+
57
+ Hashify = lambda { |value|
58
+ if value.respond_to?(:each_slice)
59
+ value.each_slice(2).to_h
60
+ else
61
+ value
62
+ end
63
+ }
64
+
65
+ Pairify = lambda { |value|
66
+ if value.respond_to?(:each_slice)
67
+ value.each_slice(2).to_a
68
+ else
69
+ value
70
+ end
71
+ }
72
+
73
+ Floatify = lambda { |value|
74
+ case value
75
+ when "inf"
76
+ Float::INFINITY
77
+ when "-inf"
78
+ -Float::INFINITY
79
+ when String
80
+ Float(value)
81
+ else
82
+ value
83
+ end
84
+ }
85
+
86
+ FloatifyPairs = lambda { |value|
87
+ return value unless value.respond_to?(:each_slice)
88
+
89
+ value.each_slice(2).map do |member, score|
90
+ [member, Floatify.call(score)]
91
+ end
92
+ }
93
+
94
+ HashifyInfo = lambda { |reply|
95
+ lines = reply.split("\r\n").grep_v(/^(#|$)/)
96
+ lines.map! { |line| line.split(':', 2) }
97
+ lines.compact!
98
+ lines.to_h
99
+ }
100
+
101
+ HashifyStreams = lambda { |reply|
102
+ case reply
103
+ when nil
104
+ {}
105
+ else
106
+ reply.map { |key, entries| [key, HashifyStreamEntries.call(entries)] }.to_h
107
+ end
108
+ }
109
+
110
+ EMPTY_STREAM_RESPONSE = [nil].freeze
111
+ private_constant :EMPTY_STREAM_RESPONSE
112
+
113
+ HashifyStreamEntries = lambda { |reply|
114
+ reply.compact.map do |entry_id, values|
115
+ [entry_id, values&.each_slice(2)&.to_h]
116
+ end
117
+ }
118
+
119
+ HashifyStreamAutoclaim = lambda { |reply|
120
+ {
121
+ 'next' => reply[0],
122
+ 'entries' => reply[1].compact.map do |entry, values|
123
+ [entry, values.each_slice(2)&.to_h]
124
+ end
125
+ }
126
+ }
127
+
128
+ HashifyStreamAutoclaimJustId = lambda { |reply|
129
+ {
130
+ 'next' => reply[0],
131
+ 'entries' => reply[1]
132
+ }
133
+ }
134
+
135
+ HashifyStreamPendings = lambda { |reply|
136
+ {
137
+ 'size' => reply[0],
138
+ 'min_entry_id' => reply[1],
139
+ 'max_entry_id' => reply[2],
140
+ 'consumers' => reply[3].nil? ? {} : reply[3].to_h
141
+ }
142
+ }
143
+
144
+ HashifyStreamPendingDetails = lambda { |reply|
145
+ reply.map do |arr|
146
+ {
147
+ 'entry_id' => arr[0],
148
+ 'consumer' => arr[1],
149
+ 'elapsed' => arr[2],
150
+ 'count' => arr[3]
151
+ }
152
+ end
153
+ }
154
+
155
+ HashifyClusterNodeInfo = lambda { |str|
156
+ arr = str.split(' ')
157
+ {
158
+ 'node_id' => arr[0],
159
+ 'ip_port' => arr[1],
160
+ 'flags' => arr[2].split(','),
161
+ 'master_node_id' => arr[3],
162
+ 'ping_sent' => arr[4],
163
+ 'pong_recv' => arr[5],
164
+ 'config_epoch' => arr[6],
165
+ 'link_state' => arr[7],
166
+ 'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
167
+ }
168
+ }
169
+
170
+ HashifyClusterSlots = lambda { |reply|
171
+ reply.map do |arr|
172
+ first_slot, last_slot = arr[0..1]
173
+ master = { 'ip' => arr[2][0], 'port' => arr[2][1], 'node_id' => arr[2][2] }
174
+ replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } }
175
+ {
176
+ 'start_slot' => first_slot,
177
+ 'end_slot' => last_slot,
178
+ 'master' => master,
179
+ 'replicas' => replicas
180
+ }
181
+ end
182
+ }
183
+
184
+ HashifyClusterNodes = lambda { |reply|
185
+ reply.split(/[\r\n]+/).map { |str| HashifyClusterNodeInfo.call(str) }
186
+ }
187
+
188
+ HashifyClusterSlaves = lambda { |reply|
189
+ reply.map { |str| HashifyClusterNodeInfo.call(str) }
190
+ }
191
+
192
+ Noop = ->(reply) { reply }
193
+
194
+ # Sends a command to Redis and returns its reply.
195
+ #
196
+ # Replies are converted to Ruby objects according to the RESP protocol, so
197
+ # you can expect a Ruby array, integer or nil when Redis sends one. Higher
198
+ # level transformations, such as converting an array of pairs into a Ruby
199
+ # hash, are up to consumers.
200
+ #
201
+ # Redis error replies are raised as Ruby exceptions.
202
+ def call(*command)
203
+ send_command(command)
204
+ end
205
+
206
+ # Interact with the sentinel command (masters, master, slaves, failover)
207
+ #
208
+ # @param [String] subcommand e.g. `masters`, `master`, `slaves`
209
+ # @param [Array<String>] args depends on subcommand
210
+ # @return [Array<String>, Hash<String, String>, String] depends on subcommand
211
+ def sentinel(subcommand, *args)
212
+ subcommand = subcommand.to_s.downcase
213
+ send_command([:sentinel, subcommand] + args) do |reply|
214
+ case subcommand
215
+ when "get-master-addr-by-name"
216
+ reply
217
+ else
218
+ if reply.is_a?(Array)
219
+ if reply[0].is_a?(Array)
220
+ reply.map(&Hashify)
221
+ else
222
+ Hashify.call(reply)
223
+ end
224
+ else
225
+ reply
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ private
232
+
233
+ def method_missing(*command) # rubocop:disable Style/MissingRespondToMissing
234
+ send_command(command)
235
+ end
236
+ end
237
+ end