simple_feature_flags 1.2.0 → 1.3.0

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.
@@ -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