simple_feature_flags 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,21 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'yaml'
4
5
 
5
6
  module SimpleFeatureFlags
6
- class RedisStorage
7
- attr_reader :file, :redis, :mandatory_flags
7
+ # Stores feature flags in Redis.
8
+ class RedisStorage < BaseStorage
9
+ sig { override.returns(String) }
10
+ attr_reader :file
8
11
 
12
+ sig { override.returns(T::Array[String]) }
13
+ attr_reader :mandatory_flags
14
+
15
+ sig { returns(T.any(::Redis, ::Redis::Namespace)) }
16
+ attr_reader :redis
17
+
18
+ sig { params(redis: T.any(::Redis, ::Redis::Namespace), file: String).void }
9
19
  def initialize(redis, file)
10
20
  @file = file
11
21
  @redis = redis
@@ -14,46 +24,70 @@ module SimpleFeatureFlags
14
24
  import_flags_from_file
15
25
  end
16
26
 
27
+ # Checks whether the flag is active. Returns `true`, `false`, `:globally` or `:partially`
28
+ sig { override.params(feature: T.any(Symbol, String)).returns(T.any(Symbol, T::Boolean)) }
17
29
  def active(feature)
18
30
  case redis.hget(feature.to_s, 'active')
19
31
  when 'globally'
20
32
  :globally
21
33
  when 'partially'
22
34
  :partially
23
- when 'true'
35
+ when 'true', true
24
36
  true
25
- when 'false'
37
+ else
26
38
  false
27
39
  end
28
40
  end
29
41
 
42
+ # Checks whether the flag is active.
43
+ sig { override.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
30
44
  def active?(feature)
31
45
  return true if active(feature)
32
46
 
33
47
  false
34
48
  end
35
49
 
50
+ # Checks whether the flag is inactive.
51
+ sig { override.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
36
52
  def inactive?(feature)
37
53
  !active?(feature)
38
54
  end
39
55
 
56
+ # Checks whether the flag is active globally, for every object.
57
+ sig { override.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
40
58
  def active_globally?(feature)
41
59
  ACTIVE_GLOBALLY.include? redis.hget(feature.to_s, 'active')
42
60
  end
43
61
 
62
+ # Checks whether the flag is inactive globally, for every object.
63
+ sig { override.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
44
64
  def inactive_globally?(feature)
45
65
  !active_globally?(feature)
46
66
  end
47
67
 
68
+ # Checks whether the flag is active partially, only for certain objects.
69
+ sig { override.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
48
70
  def active_partially?(feature)
49
71
  ACTIVE_PARTIALLY.include? redis.hget(feature.to_s, 'active')
50
72
  end
51
73
 
74
+ # Checks whether the flag is inactive partially, only for certain objects.
75
+ sig { override.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
52
76
  def inactive_partially?(feature)
53
77
  !active_partially?(feature)
54
78
  end
55
79
 
56
- def active_for?(feature, object, object_id_method = CONFIG.default_id_method)
80
+ # Checks whether the flag is active for the given object.
81
+ sig do
82
+ override
83
+ .params(
84
+ feature: T.any(Symbol, String),
85
+ object: Object,
86
+ object_id_method: Symbol,
87
+ )
88
+ .returns(T::Boolean)
89
+ end
90
+ def active_for?(feature, object, object_id_method: CONFIG.default_id_method)
57
91
  return false unless active?(feature)
58
92
  return true if active_globally?(feature)
59
93
 
@@ -65,68 +99,152 @@ module SimpleFeatureFlags
65
99
  active_ids.include? object.public_send(object_id_method)
66
100
  end
67
101
 
68
- def inactive_for?(feature, object, object_id_method = CONFIG.default_id_method)
69
- !active_for?(feature, object, object_id_method)
102
+ # Checks whether the flag is inactive for the given object.
103
+ sig do
104
+ override
105
+ .params(
106
+ feature: T.any(Symbol, String),
107
+ object: Object,
108
+ object_id_method: Symbol,
109
+ )
110
+ .returns(T::Boolean)
111
+ end
112
+ def inactive_for?(feature, object, object_id_method: CONFIG.default_id_method)
113
+ !active_for?(feature, object, object_id_method: object_id_method)
70
114
  end
71
115
 
116
+ # Checks whether the flag exists.
117
+ sig { override.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
72
118
  def exists?(feature)
73
119
  return false if [nil, ''].include? redis.hget(feature.to_s, 'name')
74
120
 
75
121
  true
76
122
  end
77
123
 
124
+ # Returns the description of the flag if it exists.
125
+ sig { override.params(feature: T.any(Symbol, String)).returns(T.nilable(String)) }
78
126
  def description(feature)
79
127
  redis.hget(feature.to_s, 'description')
80
128
  end
81
129
 
82
- def when_active(feature)
130
+ # Calls the given block if the flag is active.
131
+ sig do
132
+ override
133
+ .params(
134
+ feature: T.any(Symbol, String),
135
+ block: T.proc.void,
136
+ ).void
137
+ end
138
+ def when_active(feature, &block)
83
139
  return unless active?(feature)
84
140
 
85
- yield
141
+ block.call
86
142
  end
87
143
 
88
- def when_inactive(feature)
144
+ # Calls the given block if the flag is inactive.
145
+ sig do
146
+ override
147
+ .params(
148
+ feature: T.any(Symbol, String),
149
+ block: T.proc.void,
150
+ ).void
151
+ end
152
+ def when_inactive(feature, &block)
89
153
  return unless inactive?(feature)
90
154
 
91
- yield
155
+ block.call
92
156
  end
93
157
 
94
- def when_active_globally(feature)
158
+ # Calls the given block if the flag is active globally.
159
+ sig do
160
+ override
161
+ .params(
162
+ feature: T.any(Symbol, String),
163
+ block: T.proc.void,
164
+ ).void
165
+ end
166
+ def when_active_globally(feature, &block)
95
167
  return unless active_globally?(feature)
96
168
 
97
- yield
169
+ block.call
98
170
  end
99
171
 
100
- def when_inactive_globally(feature)
172
+ # Calls the given block if the flag is inactive globally.
173
+ sig do
174
+ override
175
+ .params(
176
+ feature: T.any(Symbol, String),
177
+ block: T.proc.void,
178
+ ).void
179
+ end
180
+ def when_inactive_globally(feature, &block)
101
181
  return unless inactive_globally?(feature)
102
182
 
103
- yield
183
+ block.call
104
184
  end
105
185
 
106
- def when_active_partially(feature)
186
+ # Calls the given block if the flag is active partially.
187
+ sig do
188
+ override
189
+ .params(
190
+ feature: T.any(Symbol, String),
191
+ block: T.proc.void,
192
+ ).void
193
+ end
194
+ def when_active_partially(feature, &block)
107
195
  return unless active_partially?(feature)
108
196
 
109
- yield
197
+ block.call
110
198
  end
111
199
 
112
- def when_inactive_partially(feature)
200
+ # Calls the given block if the flag is inactive partially.
201
+ sig do
202
+ override
203
+ .params(
204
+ feature: T.any(Symbol, String),
205
+ block: T.proc.void,
206
+ ).void
207
+ end
208
+ def when_inactive_partially(feature, &block)
113
209
  return unless inactive_partially?(feature)
114
210
 
115
- yield
211
+ block.call
116
212
  end
117
213
 
118
- def when_active_for(feature, object, object_id_method = CONFIG.default_id_method)
119
- return unless active_for?(feature, object, object_id_method)
214
+ # Calls the given block if the flag is active for the given object.
215
+ sig do
216
+ override
217
+ .params(
218
+ feature: T.any(Symbol, String),
219
+ object: Object,
220
+ object_id_method: Symbol,
221
+ block: T.proc.void,
222
+ ).void
223
+ end
224
+ def when_active_for(feature, object, object_id_method: CONFIG.default_id_method, &block)
225
+ return unless active_for?(feature, object, object_id_method: object_id_method)
120
226
 
121
- yield
227
+ block.call
122
228
  end
123
229
 
124
- def when_inactive_for(feature, object, object_id_method = CONFIG.default_id_method)
125
- return unless inactive_for?(feature, object, object_id_method)
230
+ # Calls the given block if the flag is inactive for the given object.
231
+ sig do
232
+ override
233
+ .params(
234
+ feature: T.any(Symbol, String),
235
+ object: Object,
236
+ object_id_method: Symbol,
237
+ block: T.proc.void,
238
+ ).void
239
+ end
240
+ def when_inactive_for(feature, object, object_id_method: CONFIG.default_id_method, &block)
241
+ return unless inactive_for?(feature, object, object_id_method: object_id_method)
126
242
 
127
- yield
243
+ block.call
128
244
  end
129
245
 
246
+ # Activates the given flag. Returns `false` if it does not exist.
247
+ sig { override.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
130
248
  def activate(feature)
131
249
  return false unless exists?(feature)
132
250
 
@@ -137,6 +255,8 @@ module SimpleFeatureFlags
137
255
 
138
256
  alias activate_globally activate
139
257
 
258
+ # Activates the given flag partially. Returns `false` if it does not exist.
259
+ sig { override.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
140
260
  def activate_partially(feature)
141
261
  return false unless exists?(feature)
142
262
 
@@ -145,17 +265,25 @@ module SimpleFeatureFlags
145
265
  true
146
266
  end
147
267
 
148
- def activate_for(feature, objects, object_id_method = CONFIG.default_id_method)
268
+ # Activates the given flag for the given objects. Returns `false` if it does not exist.
269
+ sig do
270
+ override
271
+ .params(
272
+ feature: T.any(Symbol, String),
273
+ objects: Object,
274
+ object_id_method: Symbol,
275
+ ).void
276
+ end
277
+ def activate_for(feature, *objects, object_id_method: CONFIG.default_id_method)
149
278
  return false unless exists?(feature)
150
279
 
151
- objects = [objects] unless objects.is_a? ::Array
152
- to_activate_hash = objects_to_hash(objects, object_id_method)
280
+ to_activate_hash = objects_to_hash(objects, object_id_method: object_id_method)
153
281
  active_objects_hash = active_objects(feature)
154
282
 
155
283
  to_activate_hash.each do |klass, ids|
156
284
  (active_objects_hash[klass] = ids) && next unless active_objects_hash[klass]
157
285
 
158
- active_objects_hash[klass].concat(ids).uniq!.sort!
286
+ active_objects_hash[klass]&.concat(ids)&.uniq!&.sort! # rubocop:disable Style/SafeNavigationChainLength
159
287
  end
160
288
 
161
289
  redis.hset(feature.to_s, 'active_for_objects', active_objects_hash.to_json)
@@ -163,12 +291,26 @@ module SimpleFeatureFlags
163
291
  true
164
292
  end
165
293
 
166
- def activate_for!(feature, objects, object_id_method = CONFIG.default_id_method)
167
- return false unless activate_for(feature, objects, object_id_method)
294
+ # Activates the given flag for the given objects and sets the flag as partially active.
295
+ # Returns `false` if it does not exist.
296
+ sig do
297
+ override
298
+ .params(
299
+ feature: T.any(Symbol, String),
300
+ objects: Object,
301
+ object_id_method: Symbol,
302
+ ).void
303
+ end
304
+ def activate_for!(feature, *objects, object_id_method: CONFIG.default_id_method)
305
+ return false unless T.unsafe(self).activate_for(feature, *objects, object_id_method: object_id_method)
168
306
 
169
307
  activate_partially(feature)
170
308
  end
171
309
 
310
+ # Deactivates the given flag for all objects.
311
+ # Resets the list of objects that this flag has been turned on for.
312
+ # Returns `false` if it does not exist.
313
+ sig { override.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
172
314
  def deactivate!(feature)
173
315
  return false unless exists?(feature)
174
316
 
@@ -178,6 +320,10 @@ module SimpleFeatureFlags
178
320
  true
179
321
  end
180
322
 
323
+ # Deactivates the given flag globally.
324
+ # Does not reset the list of objects that this flag has been turned on for.
325
+ # Returns `false` if it does not exist.
326
+ sig { override.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
181
327
  def deactivate(feature)
182
328
  return false unless exists?(feature)
183
329
 
@@ -186,18 +332,39 @@ module SimpleFeatureFlags
186
332
  true
187
333
  end
188
334
 
335
+ # Returns a hash of Objects that the given flag is turned on for.
336
+ # The keys are class/model names, values are arrays of IDs of instances/records.
337
+ #
338
+ # looks like this:
339
+ #
340
+ # { "Page" => [25, 89], "Book" => [152] }
341
+ #
342
+ sig do
343
+ override
344
+ .params(feature: T.any(Symbol, String))
345
+ .returns(T::Hash[String, T::Array[Object]])
346
+ end
189
347
  def active_objects(feature)
190
348
  ::JSON.parse(redis.hget(feature.to_s, 'active_for_objects').to_s)
191
349
  rescue ::JSON::ParserError
192
350
  {}
193
351
  end
194
352
 
195
- def deactivate_for(feature, objects, object_id_method = CONFIG.default_id_method)
353
+ # Deactivates the given flag for the given objects. Returns `false` if it does not exist.
354
+ sig do
355
+ override
356
+ .params(
357
+ feature: T.any(Symbol, String),
358
+ objects: Object,
359
+ object_id_method: Symbol,
360
+ ).void
361
+ end
362
+ def deactivate_for(feature, *objects, object_id_method: CONFIG.default_id_method)
196
363
  return false unless exists?(feature)
197
364
 
198
365
  active_objects_hash = active_objects(feature)
199
366
 
200
- objects_to_deactivate_hash = objects_to_hash(objects, object_id_method)
367
+ objects_to_deactivate_hash = objects_to_hash(objects, object_id_method: object_id_method)
201
368
 
202
369
  objects_to_deactivate_hash.each do |klass, ids_to_remove|
203
370
  active_ids = active_objects_hash[klass]
@@ -211,18 +378,38 @@ module SimpleFeatureFlags
211
378
  true
212
379
  end
213
380
 
381
+ # Returns the data of the flag in a hash.
382
+ sig do
383
+ override
384
+ .params(
385
+ feature: T.any(Symbol, String),
386
+ ).returns(T.nilable(T::Hash[String, T.anything]))
387
+ end
214
388
  def get(feature)
215
389
  return unless exists?(feature)
216
390
 
217
391
  hash = redis.hgetall(feature.to_s)
218
392
  hash['mandatory'] = mandatory_flags.include?(feature.to_s)
219
- hash['active_for_objects'] = ::JSON.parse(hash['active_for_objects']) rescue {}
393
+ hash['active_for_objects'] = begin
394
+ ::JSON.parse(hash['active_for_objects'])
395
+ rescue StandardError
396
+ {}
397
+ end
220
398
 
221
399
  hash
222
400
  end
223
401
 
402
+ # Adds the given feature flag.
403
+ sig do
404
+ override
405
+ .params(
406
+ feature: T.any(Symbol, String),
407
+ description: String,
408
+ active: T.any(String, Symbol, T::Boolean, NilClass),
409
+ ).returns(T.nilable(T::Hash[String, T.anything]))
410
+ end
224
411
  def add(feature, description, active = 'false')
225
- return false if exists?(feature)
412
+ return if exists?(feature)
226
413
 
227
414
  active = if ACTIVE_GLOBALLY.include?(active)
228
415
  'globally'
@@ -233,17 +420,25 @@ module SimpleFeatureFlags
233
420
  end
234
421
 
235
422
  hash = {
236
- 'name' => feature.to_s,
237
- 'active' => active,
238
- 'description' => description
423
+ 'name' => feature.to_s,
424
+ 'active' => active,
425
+ 'description' => description,
239
426
  }
240
427
 
241
428
  redis.hset(feature.to_s, hash)
242
429
  hash
243
430
  end
244
431
 
432
+ # Removes the given feature flag.
433
+ # Returns its data or nil if it does not exist.
434
+ sig do
435
+ override
436
+ .params(
437
+ feature: T.any(Symbol, String),
438
+ ).returns(T.nilable(T::Hash[String, T.anything]))
439
+ end
245
440
  def remove(feature)
246
- return false unless exists?(feature)
441
+ return unless exists?(feature)
247
442
 
248
443
  removed = get(feature)
249
444
  redis.del(feature.to_s)
@@ -251,10 +446,14 @@ module SimpleFeatureFlags
251
446
  removed
252
447
  end
253
448
 
449
+ # Returns the data of all feature flags.
450
+ sig do
451
+ override.returns(T::Array[T::Hash[String, T.anything]])
452
+ end
254
453
  def all
255
454
  keys = []
256
455
  hashes = []
257
- redis.scan_each(match: "*") do |key|
456
+ redis.scan_each(match: '*') do |key|
258
457
  next if keys.include?(key)
259
458
 
260
459
  keys << key
@@ -264,30 +463,12 @@ module SimpleFeatureFlags
264
463
  hashes
265
464
  end
266
465
 
466
+ sig { returns(T.nilable(Redis::Namespace)) }
267
467
  def namespaced_redis
268
- redis
269
- end
468
+ r = redis
469
+ return unless r.is_a?(Redis::Namespace)
270
470
 
271
- private
272
-
273
- def objects_to_hash(objects, object_id_method = CONFIG.default_id_method)
274
- objects = [objects] unless objects.is_a? ::Array
275
-
276
- objects.group_by { |ob| ob.class.to_s }.transform_values { |arr| arr.map(&object_id_method) }
277
- end
278
-
279
- def import_flags_from_file
280
- changes = ::YAML.load_file(file)
281
- changes = { mandatory: [], remove: [] } unless changes.is_a? ::Hash
282
-
283
- changes[:mandatory].each do |el|
284
- mandatory_flags << el['name']
285
- add(el['name'], el['description'], el['active'])
286
- end
287
-
288
- changes[:remove].each do |el|
289
- remove(el)
290
- end
471
+ r
291
472
  end
292
473
  end
293
474
  end
@@ -1,9 +1,15 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module SimpleFeatureFlags
5
+ # Used in tests
4
6
  class TestRamStorage < RamStorage
7
+ sig { override.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
5
8
  def active?(feature)
6
- raise(FlagNotDefinedError, "Feature Flag `#{feature}` is not defined as mandatory in #{file}") unless mandatory_flags.include?(feature.to_s)
9
+ unless mandatory_flags.include?(feature.to_s)
10
+ raise(FlagNotDefinedError,
11
+ "Feature Flag `#{feature}` is not defined as mandatory in #{file}",)
12
+ end
7
13
 
8
14
  super
9
15
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleFeatureFlags
4
- VERSION = "1.2.0"
4
+ VERSION = '1.3.0'
5
5
  end
@@ -1,17 +1,24 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'json'
5
+ require 'set'
6
+ require 'sorbet-runtime'
4
7
 
5
- Dir[File.expand_path('simple_feature_flags/*.rb', __dir__)].sort.each { |file| require file }
8
+ Dir[File.expand_path('simple_feature_flags/*.rb', __dir__)].each { |file| require file }
6
9
 
10
+ # Tha main namespace of the `simple_feature_flags` gem.
7
11
  module SimpleFeatureFlags
12
+ extend T::Sig
13
+
8
14
  NOT_PRESENT = ::Object.new.freeze
9
- UI_GEM = 'simple_feature_flags-ui'
10
- UI_CLASS_NAME = '::SimpleFeatureFlags::Ui'
11
- WEB_UI_CLASS_NAME = '::SimpleFeatureFlags::Ui::Web'
15
+ UI_GEM = T.let('simple_feature_flags-ui', String)
16
+ UI_CLASS_NAME = T.let('::SimpleFeatureFlags::Ui', String)
17
+ WEB_UI_CLASS_NAME = T.let('::SimpleFeatureFlags::Ui::Web', String)
12
18
 
13
- ACTIVE_GLOBALLY = ::Set['globally', :globally, 'true', true].freeze
14
- ACTIVE_PARTIALLY = ::Set['partially', :partially].freeze
19
+ ACTIVE_GLOBALLY = T.let(::Set['globally', :globally, 'true', true].freeze,
20
+ T::Set[T.any(String, Symbol, T::Boolean, NilClass)],)
21
+ ACTIVE_PARTIALLY = T.let(::Set['partially', :partially].freeze, T::Set[T.any(String, Symbol, T::Boolean, NilClass)])
15
22
 
16
23
  class NoSuchCommandError < StandardError; end
17
24
 
@@ -19,9 +26,15 @@ module SimpleFeatureFlags
19
26
 
20
27
  class FlagNotDefinedError < StandardError; end
21
28
 
22
- CONFIG = Configuration.new
29
+ CONFIG = T.let(Configuration.new, Configuration)
30
+
31
+ class << self
32
+ extend T::Sig
23
33
 
24
- def self.configure(&block)
25
- block.call(CONFIG)
34
+ sig { params(block: T.proc.params(arg0: Configuration).void).returns(Configuration) }
35
+ def configure(&block)
36
+ block.call(CONFIG)
37
+ CONFIG
38
+ end
26
39
  end
27
40
  end
@@ -3,36 +3,31 @@
3
3
  require_relative 'lib/simple_feature_flags/version'
4
4
 
5
5
  ::Gem::Specification.new do |spec|
6
- spec.name = "simple_feature_flags"
6
+ spec.name = 'simple_feature_flags'
7
7
  spec.version = ::SimpleFeatureFlags::VERSION
8
- spec.authors = ["Espago", "Mateusz Drewniak"]
9
- spec.email = ["m.drewniak@espago.com"]
8
+ spec.authors = ['Espago', 'Mateusz Drewniak']
9
+ spec.email = ['m.drewniak@espago.com']
10
10
 
11
- spec.summary = "Simple feature flag functionality for your Ruby/Rails/Sinatra app!"
12
- spec.description = "A simple Ruby gem which lets you dynamically enable/disable parts of your code using Redis or your server's RAM!"
13
- spec.homepage = "https://github.com/espago/simple_feature_flags"
14
- spec.license = "MIT"
15
- spec.required_ruby_version = ::Gem::Requirement.new(">= 2.5.0")
11
+ spec.summary = 'Simple feature flag functionality for your Ruby/Rails/Sinatra app!'
12
+ spec.description = <<~DESC
13
+ A simple Ruby gem which lets you dynamically enable/disable parts of your code using Redis or your server's RAM!
14
+ DESC
15
+ spec.homepage = 'https://github.com/espago/simple_feature_flags'
16
+ spec.license = 'MIT'
17
+ spec.required_ruby_version = '>= 3.1.0'
16
18
 
17
- spec.metadata["homepage_uri"] = spec.homepage
18
- spec.metadata["source_code_uri"] = "https://github.com/espago/simple_feature_flags"
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = 'https://github.com/espago/simple_feature_flags'
19
21
 
20
22
  # Specify which files should be added to the gem when it is released.
21
23
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
24
  spec.files = ::Dir.chdir(::File.expand_path(__dir__)) do
23
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|sorbet)/}) }
24
26
  end
25
- spec.bindir = "exe"
27
+ spec.bindir = 'exe'
26
28
  spec.executables = ['simple_feature_flags']
27
- spec.require_paths = ["lib"]
29
+ spec.require_paths = ['lib']
28
30
 
29
- spec.add_development_dependency 'bundler'
30
- spec.add_development_dependency 'bundler-audit'
31
- spec.add_development_dependency 'byebug'
32
- spec.add_development_dependency 'minitest', '~> 5.0'
33
- spec.add_development_dependency 'rake', '~> 12.0'
34
- spec.add_development_dependency 'redis'
35
- spec.add_development_dependency 'redis-namespace'
36
- spec.add_development_dependency 'rubocop'
37
- spec.add_development_dependency 'solargraph'
31
+ spec.add_dependency 'sorbet-runtime', '> 0.5'
32
+ spec.metadata['rubygems_mfa_required'] = 'true'
38
33
  end