simple_feature_flags 1.2.0 → 1.4.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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +74 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +15 -58
- data/.ruby-version +1 -1
- data/.vscode/settings.json +5 -1
- data/Gemfile +11 -1
- data/Gemfile.lock +84 -70
- data/Rakefile +5 -5
- data/bin/tapioca +27 -0
- data/bin/test +8 -0
- data/lib/example_files/config/initializers/simple_feature_flags.rb +4 -3
- data/lib/simple_feature_flags/base_storage.rb +332 -0
- data/lib/simple_feature_flags/cli/command/generate.rb +33 -6
- data/lib/simple_feature_flags/cli/command.rb +3 -1
- data/lib/simple_feature_flags/cli/options.rb +19 -3
- data/lib/simple_feature_flags/cli/runner.rb +13 -5
- data/lib/simple_feature_flags/cli.rb +3 -1
- data/lib/simple_feature_flags/configuration.rb +6 -0
- data/lib/simple_feature_flags/ram_storage.rb +292 -80
- data/lib/simple_feature_flags/redis_storage.rb +282 -63
- data/lib/simple_feature_flags/test_ram_storage.rb +7 -1
- data/lib/simple_feature_flags/version.rb +1 -1
- data/lib/simple_feature_flags.rb +23 -6
- data/simple_feature_flags.gemspec +17 -22
- metadata +19 -128
- data/.travis.yml +0 -6
@@ -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
|
-
|
7
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
69
|
-
|
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
|
-
|
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
|
-
|
141
|
+
block.call
|
86
142
|
end
|
87
143
|
|
88
|
-
|
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
|
-
|
155
|
+
block.call
|
92
156
|
end
|
93
157
|
|
94
|
-
|
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
|
-
|
169
|
+
block.call
|
98
170
|
end
|
99
171
|
|
100
|
-
|
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
|
-
|
183
|
+
block.call
|
104
184
|
end
|
105
185
|
|
106
|
-
|
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
|
-
|
197
|
+
block.call
|
110
198
|
end
|
111
199
|
|
112
|
-
|
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
|
-
|
211
|
+
block.call
|
116
212
|
end
|
117
213
|
|
118
|
-
|
119
|
-
|
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
|
-
|
227
|
+
block.call
|
122
228
|
end
|
123
229
|
|
124
|
-
|
125
|
-
|
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
|
-
|
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
|
|
@@ -135,8 +253,30 @@ module SimpleFeatureFlags
|
|
135
253
|
true
|
136
254
|
end
|
137
255
|
|
256
|
+
sig do
|
257
|
+
override
|
258
|
+
.type_parameters(:R)
|
259
|
+
.params(
|
260
|
+
feature: T.any(Symbol, String),
|
261
|
+
block: T.proc.returns(T.type_parameter(:R)),
|
262
|
+
)
|
263
|
+
.returns(T.type_parameter(:R))
|
264
|
+
end
|
265
|
+
def do_activate(feature, &block)
|
266
|
+
feature = feature.to_s
|
267
|
+
prev_value = redis.hget(feature, 'active')
|
268
|
+
activate(feature)
|
269
|
+
block.call
|
270
|
+
ensure
|
271
|
+
redis.hset(feature, 'active', prev_value)
|
272
|
+
end
|
273
|
+
|
274
|
+
alias do_activate_globally do_activate
|
275
|
+
|
138
276
|
alias activate_globally activate
|
139
277
|
|
278
|
+
# Activates the given flag partially. Returns `false` if it does not exist.
|
279
|
+
sig { override.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
|
140
280
|
def activate_partially(feature)
|
141
281
|
return false unless exists?(feature)
|
142
282
|
|
@@ -145,17 +285,43 @@ module SimpleFeatureFlags
|
|
145
285
|
true
|
146
286
|
end
|
147
287
|
|
148
|
-
|
288
|
+
sig do
|
289
|
+
override
|
290
|
+
.type_parameters(:R)
|
291
|
+
.params(
|
292
|
+
feature: T.any(Symbol, String),
|
293
|
+
block: T.proc.returns(T.type_parameter(:R)),
|
294
|
+
)
|
295
|
+
.returns(T.type_parameter(:R))
|
296
|
+
end
|
297
|
+
def do_activate_partially(feature, &block)
|
298
|
+
feature = feature.to_s
|
299
|
+
prev_value = redis.hget(feature, 'active')
|
300
|
+
activate_partially(feature)
|
301
|
+
block.call
|
302
|
+
ensure
|
303
|
+
redis.hset(feature, 'active', prev_value)
|
304
|
+
end
|
305
|
+
|
306
|
+
# Activates the given flag for the given objects. Returns `false` if it does not exist.
|
307
|
+
sig do
|
308
|
+
override
|
309
|
+
.params(
|
310
|
+
feature: T.any(Symbol, String),
|
311
|
+
objects: Object,
|
312
|
+
object_id_method: Symbol,
|
313
|
+
).void
|
314
|
+
end
|
315
|
+
def activate_for(feature, *objects, object_id_method: CONFIG.default_id_method)
|
149
316
|
return false unless exists?(feature)
|
150
317
|
|
151
|
-
|
152
|
-
to_activate_hash = objects_to_hash(objects, object_id_method)
|
318
|
+
to_activate_hash = objects_to_hash(objects, object_id_method: object_id_method)
|
153
319
|
active_objects_hash = active_objects(feature)
|
154
320
|
|
155
321
|
to_activate_hash.each do |klass, ids|
|
156
322
|
(active_objects_hash[klass] = ids) && next unless active_objects_hash[klass]
|
157
323
|
|
158
|
-
active_objects_hash[klass]
|
324
|
+
active_objects_hash[klass]&.concat(ids)&.uniq!&.sort! # rubocop:disable Style/SafeNavigationChainLength
|
159
325
|
end
|
160
326
|
|
161
327
|
redis.hset(feature.to_s, 'active_for_objects', active_objects_hash.to_json)
|
@@ -163,12 +329,26 @@ module SimpleFeatureFlags
|
|
163
329
|
true
|
164
330
|
end
|
165
331
|
|
166
|
-
|
167
|
-
|
332
|
+
# Activates the given flag for the given objects and sets the flag as partially active.
|
333
|
+
# Returns `false` if it does not exist.
|
334
|
+
sig do
|
335
|
+
override
|
336
|
+
.params(
|
337
|
+
feature: T.any(Symbol, String),
|
338
|
+
objects: Object,
|
339
|
+
object_id_method: Symbol,
|
340
|
+
).void
|
341
|
+
end
|
342
|
+
def activate_for!(feature, *objects, object_id_method: CONFIG.default_id_method)
|
343
|
+
return false unless T.unsafe(self).activate_for(feature, *objects, object_id_method: object_id_method)
|
168
344
|
|
169
345
|
activate_partially(feature)
|
170
346
|
end
|
171
347
|
|
348
|
+
# Deactivates the given flag for all objects.
|
349
|
+
# Resets the list of objects that this flag has been turned on for.
|
350
|
+
# Returns `false` if it does not exist.
|
351
|
+
sig { override.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
|
172
352
|
def deactivate!(feature)
|
173
353
|
return false unless exists?(feature)
|
174
354
|
|
@@ -178,6 +358,10 @@ module SimpleFeatureFlags
|
|
178
358
|
true
|
179
359
|
end
|
180
360
|
|
361
|
+
# Deactivates the given flag globally.
|
362
|
+
# Does not reset the list of objects that this flag has been turned on for.
|
363
|
+
# Returns `false` if it does not exist.
|
364
|
+
sig { override.params(feature: T.any(Symbol, String)).returns(T::Boolean) }
|
181
365
|
def deactivate(feature)
|
182
366
|
return false unless exists?(feature)
|
183
367
|
|
@@ -186,18 +370,39 @@ module SimpleFeatureFlags
|
|
186
370
|
true
|
187
371
|
end
|
188
372
|
|
373
|
+
# Returns a hash of Objects that the given flag is turned on for.
|
374
|
+
# The keys are class/model names, values are arrays of IDs of instances/records.
|
375
|
+
#
|
376
|
+
# looks like this:
|
377
|
+
#
|
378
|
+
# { "Page" => [25, 89], "Book" => [152] }
|
379
|
+
#
|
380
|
+
sig do
|
381
|
+
override
|
382
|
+
.params(feature: T.any(Symbol, String))
|
383
|
+
.returns(T::Hash[String, T::Array[Object]])
|
384
|
+
end
|
189
385
|
def active_objects(feature)
|
190
386
|
::JSON.parse(redis.hget(feature.to_s, 'active_for_objects').to_s)
|
191
387
|
rescue ::JSON::ParserError
|
192
388
|
{}
|
193
389
|
end
|
194
390
|
|
195
|
-
|
391
|
+
# Deactivates the given flag for the given objects. Returns `false` if it does not exist.
|
392
|
+
sig do
|
393
|
+
override
|
394
|
+
.params(
|
395
|
+
feature: T.any(Symbol, String),
|
396
|
+
objects: Object,
|
397
|
+
object_id_method: Symbol,
|
398
|
+
).void
|
399
|
+
end
|
400
|
+
def deactivate_for(feature, *objects, object_id_method: CONFIG.default_id_method)
|
196
401
|
return false unless exists?(feature)
|
197
402
|
|
198
403
|
active_objects_hash = active_objects(feature)
|
199
404
|
|
200
|
-
objects_to_deactivate_hash = objects_to_hash(objects, object_id_method)
|
405
|
+
objects_to_deactivate_hash = objects_to_hash(objects, object_id_method: object_id_method)
|
201
406
|
|
202
407
|
objects_to_deactivate_hash.each do |klass, ids_to_remove|
|
203
408
|
active_ids = active_objects_hash[klass]
|
@@ -211,18 +416,38 @@ module SimpleFeatureFlags
|
|
211
416
|
true
|
212
417
|
end
|
213
418
|
|
419
|
+
# Returns the data of the flag in a hash.
|
420
|
+
sig do
|
421
|
+
override
|
422
|
+
.params(
|
423
|
+
feature: T.any(Symbol, String),
|
424
|
+
).returns(T.nilable(T::Hash[String, T.anything]))
|
425
|
+
end
|
214
426
|
def get(feature)
|
215
427
|
return unless exists?(feature)
|
216
428
|
|
217
429
|
hash = redis.hgetall(feature.to_s)
|
218
430
|
hash['mandatory'] = mandatory_flags.include?(feature.to_s)
|
219
|
-
hash['active_for_objects'] =
|
431
|
+
hash['active_for_objects'] = begin
|
432
|
+
::JSON.parse(hash['active_for_objects'])
|
433
|
+
rescue StandardError
|
434
|
+
{}
|
435
|
+
end
|
220
436
|
|
221
437
|
hash
|
222
438
|
end
|
223
439
|
|
224
|
-
|
225
|
-
|
440
|
+
# Adds the given feature flag.
|
441
|
+
sig do
|
442
|
+
override
|
443
|
+
.params(
|
444
|
+
feature: T.any(Symbol, String),
|
445
|
+
description: String,
|
446
|
+
active: T.any(String, Symbol, T::Boolean, NilClass),
|
447
|
+
).returns(T.nilable(T::Hash[String, T.anything]))
|
448
|
+
end
|
449
|
+
def add(feature, description = '', active = 'false')
|
450
|
+
return if exists?(feature)
|
226
451
|
|
227
452
|
active = if ACTIVE_GLOBALLY.include?(active)
|
228
453
|
'globally'
|
@@ -233,17 +458,25 @@ module SimpleFeatureFlags
|
|
233
458
|
end
|
234
459
|
|
235
460
|
hash = {
|
236
|
-
'name'
|
237
|
-
'active'
|
238
|
-
'description' => description
|
461
|
+
'name' => feature.to_s,
|
462
|
+
'active' => active,
|
463
|
+
'description' => description,
|
239
464
|
}
|
240
465
|
|
241
466
|
redis.hset(feature.to_s, hash)
|
242
467
|
hash
|
243
468
|
end
|
244
469
|
|
470
|
+
# Removes the given feature flag.
|
471
|
+
# Returns its data or nil if it does not exist.
|
472
|
+
sig do
|
473
|
+
override
|
474
|
+
.params(
|
475
|
+
feature: T.any(Symbol, String),
|
476
|
+
).returns(T.nilable(T::Hash[String, T.anything]))
|
477
|
+
end
|
245
478
|
def remove(feature)
|
246
|
-
return
|
479
|
+
return unless exists?(feature)
|
247
480
|
|
248
481
|
removed = get(feature)
|
249
482
|
redis.del(feature.to_s)
|
@@ -251,10 +484,14 @@ module SimpleFeatureFlags
|
|
251
484
|
removed
|
252
485
|
end
|
253
486
|
|
487
|
+
# Returns the data of all feature flags.
|
488
|
+
sig do
|
489
|
+
override.returns(T::Array[T::Hash[String, T.anything]])
|
490
|
+
end
|
254
491
|
def all
|
255
492
|
keys = []
|
256
493
|
hashes = []
|
257
|
-
redis.scan_each(match:
|
494
|
+
redis.scan_each(match: '*') do |key|
|
258
495
|
next if keys.include?(key)
|
259
496
|
|
260
497
|
keys << key
|
@@ -264,30 +501,12 @@ module SimpleFeatureFlags
|
|
264
501
|
hashes
|
265
502
|
end
|
266
503
|
|
504
|
+
sig { returns(T.nilable(Redis::Namespace)) }
|
267
505
|
def namespaced_redis
|
268
|
-
redis
|
269
|
-
|
270
|
-
|
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
|
506
|
+
r = redis
|
507
|
+
return unless r.is_a?(Redis::Namespace)
|
282
508
|
|
283
|
-
|
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
|
509
|
+
r
|
291
510
|
end
|
292
511
|
end
|
293
512
|
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
|
-
|
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
|
data/lib/simple_feature_flags.rb
CHANGED
@@ -1,17 +1,28 @@
|
|
1
|
+
# typed: true
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'json'
|
5
|
+
require 'sorbet-runtime'
|
4
6
|
|
5
|
-
Dir[File.expand_path('simple_feature_flags/*.rb', __dir__)].
|
7
|
+
Dir[File.expand_path('simple_feature_flags/*.rb', __dir__)].each { |file| require file }
|
6
8
|
|
9
|
+
# Tha main namespace of the `simple_feature_flags` gem.
|
7
10
|
module SimpleFeatureFlags
|
11
|
+
extend T::Sig
|
12
|
+
|
8
13
|
NOT_PRESENT = ::Object.new.freeze
|
9
14
|
UI_GEM = 'simple_feature_flags-ui'
|
10
15
|
UI_CLASS_NAME = '::SimpleFeatureFlags::Ui'
|
11
16
|
WEB_UI_CLASS_NAME = '::SimpleFeatureFlags::Ui::Web'
|
12
17
|
|
13
|
-
ACTIVE_GLOBALLY =
|
14
|
-
|
18
|
+
ACTIVE_GLOBALLY = T.let(
|
19
|
+
::Set['globally', :globally, 'true', true].freeze,
|
20
|
+
T::Set[T.any(String, Symbol, T::Boolean, NilClass)],
|
21
|
+
)
|
22
|
+
ACTIVE_PARTIALLY = T.let(
|
23
|
+
::Set['partially', :partially].freeze,
|
24
|
+
T::Set[T.any(String, Symbol, T::Boolean, NilClass)],
|
25
|
+
)
|
15
26
|
|
16
27
|
class NoSuchCommandError < StandardError; end
|
17
28
|
|
@@ -19,9 +30,15 @@ module SimpleFeatureFlags
|
|
19
30
|
|
20
31
|
class FlagNotDefinedError < StandardError; end
|
21
32
|
|
22
|
-
CONFIG = Configuration.new
|
33
|
+
CONFIG = T.let(Configuration.new, Configuration)
|
34
|
+
|
35
|
+
class << self
|
36
|
+
extend T::Sig
|
23
37
|
|
24
|
-
|
25
|
-
block
|
38
|
+
sig { params(block: T.proc.params(arg0: Configuration).void).returns(Configuration) }
|
39
|
+
def configure(&block)
|
40
|
+
block.call(CONFIG)
|
41
|
+
CONFIG
|
42
|
+
end
|
26
43
|
end
|
27
44
|
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 =
|
6
|
+
spec.name = 'simple_feature_flags'
|
7
7
|
spec.version = ::SimpleFeatureFlags::VERSION
|
8
|
-
spec.authors = [
|
9
|
-
spec.email = [
|
8
|
+
spec.authors = ['Espago', 'Mateusz Drewniak']
|
9
|
+
spec.email = ['m.drewniak@espago.com']
|
10
10
|
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
13
|
-
|
14
|
-
|
15
|
-
spec.
|
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.2.0'
|
16
18
|
|
17
|
-
spec.metadata[
|
18
|
-
spec.metadata[
|
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 =
|
27
|
+
spec.bindir = 'exe'
|
26
28
|
spec.executables = ['simple_feature_flags']
|
27
|
-
spec.require_paths = [
|
29
|
+
spec.require_paths = ['lib']
|
28
30
|
|
29
|
-
spec.
|
30
|
-
spec.
|
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
|