simple_feature_flags 0.1.0 → 1.2.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/.vscode/settings.json +6 -0
- data/Gemfile.lock +25 -23
- data/README.md +268 -17
- data/exe/simple_feature_flags +0 -1
- data/lib/example_files/config/initializers/simple_feature_flags.rb +2 -2
- data/lib/example_files/config/simple_feature_flags.yml +3 -3
- data/lib/simple_feature_flags/cli/command/generate.rb +47 -1
- data/lib/simple_feature_flags/cli/options.rb +3 -1
- data/lib/simple_feature_flags/configuration.rb +11 -0
- data/lib/simple_feature_flags/ram_storage.rb +176 -12
- data/lib/simple_feature_flags/redis_storage.rb +179 -17
- data/lib/simple_feature_flags/test_ram_storage.rb +3 -3
- data/lib/simple_feature_flags/version.rb +1 -1
- data/lib/simple_feature_flags.rb +18 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27f8966e824b5b17fd9c76095203e7bb4999d7332e64dcc32c43dcc8784541b2
|
4
|
+
data.tar.gz: 907626c29e851f93aeddd9e1be8d63dac9c85f7fb13ad0424bddbf83fe1e58d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 420763922a049e720412d3dff1a9461c953ab4be1e7d4991b5b0b15f96d22d162c2a169071a31bd528fc3f87dc8f59bfc2c95ae21c6e0751a34b0a7187d8c4e4
|
7
|
+
data.tar.gz: 6403704ba929bfa87268e30676f1b15d520796be973485457ddf55ddccd81f6cbb922c4015decfd8dd95fa009c3305e0030e3881471989784dbabc53d33f9a10
|
data/.vscode/settings.json
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,56 +1,56 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
simple_feature_flags (
|
4
|
+
simple_feature_flags (1.2.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
9
|
ast (2.4.2)
|
10
10
|
backport (1.2.0)
|
11
|
-
benchmark (0.
|
12
|
-
bundler-audit (0.
|
11
|
+
benchmark (0.2.0)
|
12
|
+
bundler-audit (0.9.0.1)
|
13
13
|
bundler (>= 1.2.0, < 3)
|
14
14
|
thor (~> 1.0)
|
15
15
|
byebug (11.1.3)
|
16
|
-
diff-lcs (1.
|
16
|
+
diff-lcs (1.5.0)
|
17
17
|
e2mmap (0.1.0)
|
18
18
|
jaro_winkler (1.5.4)
|
19
19
|
kramdown (2.3.1)
|
20
20
|
rexml
|
21
21
|
kramdown-parser-gfm (1.1.0)
|
22
22
|
kramdown (~> 2.0)
|
23
|
-
mini_portile2 (2.
|
24
|
-
minitest (5.
|
25
|
-
nokogiri (1.
|
26
|
-
mini_portile2 (~> 2.
|
23
|
+
mini_portile2 (2.7.1)
|
24
|
+
minitest (5.15.0)
|
25
|
+
nokogiri (1.13.1)
|
26
|
+
mini_portile2 (~> 2.7.0)
|
27
27
|
racc (~> 1.4)
|
28
|
-
parallel (1.
|
29
|
-
parser (3.0.
|
28
|
+
parallel (1.21.0)
|
29
|
+
parser (3.1.0.0)
|
30
30
|
ast (~> 2.4.1)
|
31
|
-
racc (1.
|
32
|
-
rainbow (3.
|
31
|
+
racc (1.6.0)
|
32
|
+
rainbow (3.1.1)
|
33
33
|
rake (12.3.3)
|
34
|
-
redis (4.
|
34
|
+
redis (4.6.0)
|
35
35
|
redis-namespace (1.8.1)
|
36
36
|
redis (>= 3.0.4)
|
37
|
-
regexp_parser (2.
|
38
|
-
reverse_markdown (2.
|
37
|
+
regexp_parser (2.2.1)
|
38
|
+
reverse_markdown (2.1.1)
|
39
39
|
nokogiri
|
40
40
|
rexml (3.2.5)
|
41
|
-
rubocop (1.
|
41
|
+
rubocop (1.25.1)
|
42
42
|
parallel (~> 1.10)
|
43
|
-
parser (>= 3.
|
43
|
+
parser (>= 3.1.0.0)
|
44
44
|
rainbow (>= 2.2.2, < 4.0)
|
45
45
|
regexp_parser (>= 1.8, < 3.0)
|
46
46
|
rexml
|
47
|
-
rubocop-ast (>= 1.
|
47
|
+
rubocop-ast (>= 1.15.1, < 2.0)
|
48
48
|
ruby-progressbar (~> 1.7)
|
49
49
|
unicode-display_width (>= 1.4.0, < 3.0)
|
50
|
-
rubocop-ast (1.
|
50
|
+
rubocop-ast (1.15.2)
|
51
51
|
parser (>= 3.0.1.1)
|
52
52
|
ruby-progressbar (1.11.0)
|
53
|
-
solargraph (0.
|
53
|
+
solargraph (0.44.3)
|
54
54
|
backport (~> 1.2)
|
55
55
|
benchmark
|
56
56
|
bundler (>= 1.17.2)
|
@@ -65,10 +65,12 @@ GEM
|
|
65
65
|
thor (~> 1.0)
|
66
66
|
tilt (~> 2.0)
|
67
67
|
yard (~> 0.9, >= 0.9.24)
|
68
|
-
thor (1.1
|
68
|
+
thor (1.2.1)
|
69
69
|
tilt (2.0.10)
|
70
|
-
unicode-display_width (2.
|
71
|
-
|
70
|
+
unicode-display_width (2.1.0)
|
71
|
+
webrick (1.7.0)
|
72
|
+
yard (0.9.27)
|
73
|
+
webrick (~> 1.7.0)
|
72
74
|
|
73
75
|
PLATFORMS
|
74
76
|
ruby
|
data/README.md
CHANGED
@@ -66,14 +66,14 @@ This initializer in turn makes use of the generated config file `config/simple_f
|
|
66
66
|
:mandatory:
|
67
67
|
# example flag - it will be created with these properties if there is no such flag in Redis/RAM
|
68
68
|
# - name: example
|
69
|
-
# active: '
|
69
|
+
# active: 'globally' # %w[globally partially false] 'false' is the default value
|
70
70
|
# description: example
|
71
71
|
|
72
72
|
- name: example_flag
|
73
73
|
description: This is an example flag which will be automatically added when you start your app (it will be disabled)
|
74
74
|
|
75
75
|
- name: example_active_flag
|
76
|
-
active: '
|
76
|
+
active: 'globally'
|
77
77
|
description: This is an example flag which will be automatically added when you start your app (it will be enabled)
|
78
78
|
|
79
79
|
# nothing will happen if flag that is to be removed does not exist in Redis/RAM
|
@@ -127,30 +127,186 @@ FEATURE_FLAGS = ::SimpleFeatureFlags::RamStorage.new(config_file)
|
|
127
127
|
|
128
128
|
### Functionality
|
129
129
|
|
130
|
-
####
|
130
|
+
#### Activate a feature
|
131
131
|
|
132
|
-
|
132
|
+
Activates a feature in the global scope
|
133
133
|
|
134
|
-
In case you'd like to add flags programmatically
|
135
134
|
```ruby
|
136
|
-
FEATURE_FLAGS.add(:feature_name, 'Description')
|
137
135
|
FEATURE_FLAGS.active?(:feature_name) #=> false
|
136
|
+
FEATURE_FLAGS.active_globally?(:feature_name) #=> false
|
137
|
+
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
|
138
138
|
|
139
|
-
|
140
|
-
FEATURE_FLAGS.
|
141
|
-
FEATURE_FLAGS.
|
139
|
+
FEATURE_FLAGS.inactive?(:feature_name) #=> true
|
140
|
+
FEATURE_FLAGS.inactive_globally?(:feature_name) #=> true
|
141
|
+
FEATURE_FLAGS.inactive_partially?(:feature_name) #=> true
|
142
|
+
|
143
|
+
FEATURE_FLAGS.activate(:feature_name) # or FEATURE_FLAGS.activate_globally(:feature_name)
|
144
|
+
|
145
|
+
FEATURE_FLAGS.active?(:feature_name) #=> true
|
146
|
+
FEATURE_FLAGS.active_globally?(:feature_name) #=> true
|
147
|
+
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
|
148
|
+
|
149
|
+
FEATURE_FLAGS.inactive?(:feature_name) #=> false
|
150
|
+
FEATURE_FLAGS.inactive_globally?(:feature_name) #=> false
|
151
|
+
FEATURE_FLAGS.inactive_partially?(:feature_name) #=> true
|
142
152
|
```
|
143
153
|
|
144
|
-
####
|
154
|
+
#### Deactivate a feature
|
145
155
|
|
146
|
-
|
156
|
+
Deactivates a feature in the global scope
|
147
157
|
|
148
|
-
In case you'd like to remove flags programmatically
|
149
158
|
```ruby
|
150
|
-
FEATURE_FLAGS.
|
159
|
+
FEATURE_FLAGS.active?(:feature_name) #=> true
|
160
|
+
FEATURE_FLAGS.inactive?(:feature_name) #=> false
|
161
|
+
|
162
|
+
FEATURE_FLAGS.deactivate(:feature_name)
|
163
|
+
|
151
164
|
FEATURE_FLAGS.active?(:feature_name) #=> false
|
165
|
+
FEATURE_FLAGS.inactive?(:feature_name) #=> true
|
166
|
+
```
|
167
|
+
|
168
|
+
#### Activate a feature for a particular record/object
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
FEATURE_FLAGS.active_partially?(:feature_name) #=> true
|
172
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
|
173
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
|
174
|
+
|
175
|
+
FEATURE_FLAGS.inactive_partially?(:feature_name) #=> false
|
176
|
+
FEATURE_FLAGS.inactive_for?(:feature_name, User.first) #=> true
|
177
|
+
FEATURE_FLAGS.inactive_for?(:feature_name, User.last) #=> true
|
178
|
+
|
179
|
+
FEATURE_FLAGS.activate_for(:feature_name, User.first) #=> true
|
180
|
+
|
181
|
+
FEATURE_FLAGS.active_partially?(:feature_name) #=> true
|
182
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
|
183
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
|
184
|
+
|
185
|
+
FEATURE_FLAGS.inactive_partially?(:feature_name) #=> false
|
186
|
+
FEATURE_FLAGS.inactive_for?(:feature_name, User.first) #=> false
|
187
|
+
FEATURE_FLAGS.inactive_for?(:feature_name, User.last) #=> true
|
152
188
|
```
|
153
189
|
|
190
|
+
Note that the flag itself has to be active `partially` for any record/object specific settings to work.
|
191
|
+
When the flag is `deactivated` it is completely turned off globally and for every specific record/object.
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
# The flag is deactivated in the global scope to begin with
|
195
|
+
FEATURE_FLAGS.active?(:feature_name) #=> false
|
196
|
+
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
|
197
|
+
FEATURE_FLAGS.active_globally?(:feature_name) #=> false
|
198
|
+
|
199
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
|
200
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
|
201
|
+
|
202
|
+
# We activate it for the first User
|
203
|
+
FEATURE_FLAGS.activate_for(:feature_name, User.first)
|
204
|
+
|
205
|
+
FEATURE_FLAGS.active?(:feature_name) #=> false
|
206
|
+
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
|
207
|
+
FEATURE_FLAGS.active_globally?(:feature_name) #=> false
|
208
|
+
|
209
|
+
# It is globally `deactivated` though, so the feature stays inactive for all users
|
210
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
|
211
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
|
212
|
+
|
213
|
+
# Once we activate the flag partially, record specific settings will be applied
|
214
|
+
FEATURE_FLAGS.activate_partially(:feature_name)
|
215
|
+
|
216
|
+
FEATURE_FLAGS.active?(:feature_name) #=> true
|
217
|
+
FEATURE_FLAGS.active_partially?(:feature_name) #=> true
|
218
|
+
FEATURE_FLAGS.active_globally?(:feature_name) #=> false
|
219
|
+
|
220
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
|
221
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
|
222
|
+
|
223
|
+
FEATURE_FLAGS.deactivate(:feature_name)
|
224
|
+
|
225
|
+
FEATURE_FLAGS.active?(:feature_name) #=> false
|
226
|
+
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
|
227
|
+
FEATURE_FLAGS.active_globally?(:feature_name) #=> false
|
228
|
+
|
229
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
|
230
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
|
231
|
+
```
|
232
|
+
|
233
|
+
There is a convenience method `activate_for!`, which activates the feature partially and for specific records/objects at the same time
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
# The flag is deactivated in the global scope to begin with
|
237
|
+
FEATURE_FLAGS.active?(:feature_name) #=> false
|
238
|
+
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
|
239
|
+
FEATURE_FLAGS.active_globally?(:feature_name) #=> false
|
240
|
+
|
241
|
+
# We activate it in the global scope and for the first User
|
242
|
+
FEATURE_FLAGS.activate_for!(:feature_name, User.first)
|
243
|
+
|
244
|
+
FEATURE_FLAGS.active?(:feature_name) #=> true
|
245
|
+
FEATURE_FLAGS.active_partially?(:feature_name) #=> true
|
246
|
+
FEATURE_FLAGS.active_globally?(:feature_name) #=> false
|
247
|
+
|
248
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
|
249
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
|
250
|
+
```
|
251
|
+
|
252
|
+
You can also pass an array of objects to activate all of them simultaneously
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
|
256
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.find(2)) #=> false
|
257
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
|
258
|
+
|
259
|
+
FEATURE_FLAGS.activate_for(:feature_name, User.first(2))
|
260
|
+
|
261
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
|
262
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.find(2)) #=> true
|
263
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
|
264
|
+
```
|
265
|
+
|
266
|
+
#### Activate the feature for every record
|
267
|
+
|
268
|
+
```ruby
|
269
|
+
# The flag is active partially
|
270
|
+
FEATURE_FLAGS.active?(:feature_name) #=> true
|
271
|
+
FEATURE_FLAGS.active_partially?(:feature_name) #=> true
|
272
|
+
FEATURE_FLAGS.active_globally?(:feature_name) #=> false
|
273
|
+
|
274
|
+
# It is also enabled for the first user
|
275
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
|
276
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
|
277
|
+
|
278
|
+
# We force it onto every user
|
279
|
+
FEATURE_FLAGS.activate(:feature_name)
|
280
|
+
|
281
|
+
FEATURE_FLAGS.active?(:feature_name) #=> true
|
282
|
+
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
|
283
|
+
FEATURE_FLAGS.active_globally?(:feature_name) #=> true
|
284
|
+
|
285
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
|
286
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> true
|
287
|
+
|
288
|
+
# We can easily return to the previous settings
|
289
|
+
FEATURE_FLAGS.activate_partially(:feature_name)
|
290
|
+
|
291
|
+
FEATURE_FLAGS.active?(:feature_name) #=> true
|
292
|
+
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
|
293
|
+
FEATURE_FLAGS.active_globally?(:feature_name) #=> true
|
294
|
+
|
295
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
|
296
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
|
297
|
+
```
|
298
|
+
|
299
|
+
#### Deactivate a feature for a particular record/object
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
|
303
|
+
|
304
|
+
FEATURE_FLAGS.deactivate_for(:feature_name, User.first)
|
305
|
+
|
306
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
|
307
|
+
```
|
308
|
+
|
309
|
+
|
154
310
|
#### Run a block of code only when the flag is active
|
155
311
|
|
156
312
|
There are two ways of running code only when the feature flag is active
|
@@ -158,18 +314,113 @@ There are two ways of running code only when the feature flag is active
|
|
158
314
|
```ruby
|
159
315
|
number = 1
|
160
316
|
if FEATURE_FLAGS.active?(:feature_name)
|
161
|
-
|
317
|
+
number += 1
|
318
|
+
end
|
319
|
+
|
320
|
+
if FEATURE_FLAGS.inactive?(:feature_name)
|
321
|
+
number += 1
|
162
322
|
end
|
163
323
|
|
164
324
|
# or using a block
|
165
325
|
|
166
|
-
# this code will run only when the :feature_name flag is active
|
167
|
-
FEATURE_FLAGS.
|
168
|
-
|
326
|
+
# this code will run only when the :feature_name flag is active (either partially or globally)
|
327
|
+
FEATURE_FLAGS.when_active(:feature_name) do
|
328
|
+
number += 1
|
329
|
+
end
|
330
|
+
|
331
|
+
# the opposite
|
332
|
+
FEATURE_FLAGS.when_inactive(:feature_name) do
|
333
|
+
number += 1
|
334
|
+
end
|
335
|
+
|
336
|
+
# this code will run only when the :feature_name flag is active globally
|
337
|
+
FEATURE_FLAGS.when_active_globally(:feature_name) do
|
338
|
+
number += 1
|
339
|
+
end
|
340
|
+
|
341
|
+
# the opposite
|
342
|
+
FEATURE_FLAGS.when_inactive_globally(:feature_name) do
|
343
|
+
number += 1
|
344
|
+
end
|
345
|
+
|
346
|
+
# this code will run only when the :feature_name flag is active partially (only for specific records/users)
|
347
|
+
FEATURE_FLAGS.when_active_partially(:feature_name) do
|
348
|
+
number += 1
|
349
|
+
end
|
350
|
+
|
351
|
+
# the opposite
|
352
|
+
FEATURE_FLAGS.when_inactive_partially(:feature_name) do
|
353
|
+
number += 1
|
354
|
+
end
|
355
|
+
|
356
|
+
# this code will run only if the :feature_name flag is active for the first User
|
357
|
+
FEATURE_FLAGS.when_active_for(:feature_name, User.first) do
|
358
|
+
number += 1
|
359
|
+
end
|
360
|
+
|
361
|
+
# the opposite
|
362
|
+
FEATURE_FLAGS.when_inactive_for(:feature_name, User.first) do
|
363
|
+
number += 1
|
169
364
|
end
|
170
365
|
|
171
366
|
# feature flags that don't exist will return false
|
172
367
|
FEATURE_FLAGS.active?(:non_existant) #=> false
|
368
|
+
FEATURE_FLAGS.inactive?(:non_existant) #=> true
|
369
|
+
|
370
|
+
if FEATURE_FLAGS.active_for?(:feature_name, User.first)
|
371
|
+
number += 1
|
372
|
+
end
|
373
|
+
|
374
|
+
if FEATURE_FLAGS.inactive_for?(:feature_name, User.first)
|
375
|
+
number += 1
|
376
|
+
end
|
377
|
+
```
|
378
|
+
|
379
|
+
#### Adding feature flags
|
380
|
+
|
381
|
+
You can add new feature flags programmatically, though we highly encourage you to use the generated `config/simple_feature_flags.yml` file instead. It will make it easier to add and/or remove feature flags automatically on app startup without having to add them manually after merging a branch with new feature flags.
|
382
|
+
|
383
|
+
In case you'd like to add flags programmatically
|
384
|
+
```ruby
|
385
|
+
FEATURE_FLAGS.add(:feature_name, 'Description')
|
386
|
+
|
387
|
+
FEATURE_FLAGS.active?(:feature_name) #=> false
|
388
|
+
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
|
389
|
+
FEATURE_FLAGS.active_globally?(:feature_name) #=> false
|
390
|
+
FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
|
391
|
+
|
392
|
+
# add a new globally active flag
|
393
|
+
FEATURE_FLAGS.add(:active_feature, 'Description', :globally)
|
394
|
+
|
395
|
+
FEATURE_FLAGS.active?(:active_feature) #=> true
|
396
|
+
FEATURE_FLAGS.active_partially?(:active_feature) #=> false
|
397
|
+
FEATURE_FLAGS.active_globally?(:active_feature) #=> true
|
398
|
+
FEATURE_FLAGS.active_for?(:active_feature, User.first) #=> true
|
399
|
+
|
400
|
+
# add a new partially active flag
|
401
|
+
FEATURE_FLAGS.add(:feature_active_partially, 'Description', :partially)
|
402
|
+
|
403
|
+
FEATURE_FLAGS.active?(:feature_active_partially) #=> true
|
404
|
+
FEATURE_FLAGS.active_partially?(:feature_active_partially) #=> true
|
405
|
+
FEATURE_FLAGS.active_globally?(:feature_active_partially) #=> false
|
406
|
+
FEATURE_FLAGS.active_for?(:feature_active_partially, User.first) #=> false
|
407
|
+
```
|
408
|
+
|
409
|
+
#### Removing feature flags
|
410
|
+
|
411
|
+
You can remove feature flags programmatically, though we highly encourage you to use the generated `config/simple_feature_flags.yml` file instead. It will make it easier to add and/or remove feature flags automatically on app startup without having to add them manually after merging a branch with new feature flags.
|
412
|
+
|
413
|
+
In case you'd like to remove flags programmatically
|
414
|
+
```ruby
|
415
|
+
FEATURE_FLAGS.remove(:feature_name)
|
416
|
+
|
417
|
+
FEATURE_FLAGS.active?(:feature_name) #=> false
|
418
|
+
FEATURE_FLAGS.active_partially?(:feature_name) #=> false
|
419
|
+
FEATURE_FLAGS.active_globally?(:feature_name) #=> false
|
420
|
+
|
421
|
+
FEATURE_FLAGS.inactive?(:feature_name) #=> true
|
422
|
+
FEATURE_FLAGS.inactive_partially?(:feature_name) #=> true
|
423
|
+
FEATURE_FLAGS.inactive_globally?(:feature_name) #=> true
|
173
424
|
```
|
174
425
|
|
175
426
|
|
data/exe/simple_feature_flags
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
# Redis has 16 DBs (0 to 15)
|
3
3
|
|
4
4
|
FEATURE_FLAGS = if ::Rails.env.test?
|
5
|
-
# Use
|
6
|
-
::SimpleFeatureFlags::
|
5
|
+
# Use RamStorage in tests to make them faster
|
6
|
+
::SimpleFeatureFlags::RamStorage.new("#{::Rails.root.to_s}/config/simple_feature_flags.yml")
|
7
7
|
else
|
8
8
|
redis = ::Redis.new(host: '127.0.0.1', port: 6379, db: 0)
|
9
9
|
# We recommend using the `redis-namespace` gem to avoid key conflicts with Sidekiq or Resque
|
@@ -3,17 +3,17 @@
|
|
3
3
|
:mandatory:
|
4
4
|
# example flag - it will be created with these properties if there is no such flag in Redis/RAM
|
5
5
|
# - name: example
|
6
|
-
# active: '
|
6
|
+
# active: 'globally' # %w[globally partially false] 'false' is the default value
|
7
7
|
# description: example
|
8
8
|
|
9
9
|
- name: example_flag
|
10
10
|
description: This is an example flag which will be automatically added when you start your app (it will be disabled)
|
11
11
|
|
12
12
|
- name: example_active_flag
|
13
|
-
active: '
|
13
|
+
active: 'globally'
|
14
14
|
description: This is an example flag which will be automatically added when you start your app (it will be enabled)
|
15
15
|
|
16
16
|
# nothing will happen if flag that is to be removed does not exist in Redis/RAM
|
17
17
|
# An array of Feature Flag names that will be removed on app startup
|
18
18
|
:remove:
|
19
|
-
- flag_to_be_removed
|
19
|
+
- flag_to_be_removed
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'fileutils'
|
4
|
-
require 'byebug'
|
5
4
|
|
6
5
|
module SimpleFeatureFlags
|
7
6
|
module Cli
|
@@ -37,6 +36,41 @@ module SimpleFeatureFlags
|
|
37
36
|
puts '----------'
|
38
37
|
puts "- #{::File.join(destination_dir, 'config')}"
|
39
38
|
print_dir_tree(example_config_dir, 1)
|
39
|
+
|
40
|
+
return unless options.ui
|
41
|
+
|
42
|
+
file_gsub(routes_rb, /.routes.draw do/) do |match|
|
43
|
+
"#{match}\n mount #{WEB_UI_CLASS_NAME}.new => '/admin/simple_feature_flags'\n"
|
44
|
+
end
|
45
|
+
|
46
|
+
ui_config_line = <<~CONF
|
47
|
+
#{UI_CLASS_NAME}.configure do |config|
|
48
|
+
config.instance = FEATURE_FLAGS
|
49
|
+
config.featurable_class_names = %w[User]
|
50
|
+
end
|
51
|
+
CONF
|
52
|
+
|
53
|
+
file_append(initializer_file, ui_config_line)
|
54
|
+
file_append(gemfile, %(gem '#{UI_GEM}'))
|
55
|
+
|
56
|
+
puts "\nModified:"
|
57
|
+
puts '----------'
|
58
|
+
puts "* #{routes_rb}"
|
59
|
+
puts "* #{gemfile}"
|
60
|
+
|
61
|
+
puts "\nBundling..."
|
62
|
+
system 'bundle'
|
63
|
+
end
|
64
|
+
|
65
|
+
def file_gsub(file_path, regexp, &block)
|
66
|
+
new_content = File.read(file_path).gsub(regexp, &block)
|
67
|
+
File.open(file_path, 'wb') { |file| file.write(new_content) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def file_append(file_path, line)
|
71
|
+
new_content = File.read(file_path)
|
72
|
+
new_content = "#{new_content}\n#{line}\n"
|
73
|
+
File.open(file_path, 'wb') { |file| file.write(new_content) }
|
40
74
|
end
|
41
75
|
|
42
76
|
def print_dir_tree(dir, embed_level = 0)
|
@@ -54,6 +88,18 @@ module SimpleFeatureFlags
|
|
54
88
|
end
|
55
89
|
end
|
56
90
|
|
91
|
+
def initializer_file
|
92
|
+
::File.join(destination_dir, 'config', 'initializers', 'simple_feature_flags.rb')
|
93
|
+
end
|
94
|
+
|
95
|
+
def gemfile
|
96
|
+
::File.join(destination_dir, 'Gemfile')
|
97
|
+
end
|
98
|
+
|
99
|
+
def routes_rb
|
100
|
+
::File.join(destination_dir, 'config', 'routes.rb')
|
101
|
+
end
|
102
|
+
|
57
103
|
def example_config_dir
|
58
104
|
::File.join(::File.expand_path(__dir__), '..', '..', '..', 'example_files', 'config')
|
59
105
|
end
|
@@ -5,10 +5,11 @@ require 'optparse'
|
|
5
5
|
module SimpleFeatureFlags
|
6
6
|
module Cli
|
7
7
|
class Options
|
8
|
-
attr_reader :opt_parser, :generate, :help, :rails
|
8
|
+
attr_reader :opt_parser, :generate, :help, :rails, :ui
|
9
9
|
|
10
10
|
def initialize(args)
|
11
11
|
@rails = true
|
12
|
+
@ui = false
|
12
13
|
|
13
14
|
@opt_parser = ::OptionParser.new do |opts|
|
14
15
|
opts.banner = 'Usage: simple_feature_flags [options]'
|
@@ -31,6 +32,7 @@ module SimpleFeatureFlags
|
|
31
32
|
opts.separator ''
|
32
33
|
opts.separator 'Modifiers:'
|
33
34
|
|
35
|
+
opts.on('--[no-]ui', '--[no-]web-ui', "Add the #{UI_GEM} gem and mount it in routes") { |u| @ui = u }
|
34
36
|
opts.on('--[no-]rails', 'Use generators suited for Rails apps') { |r| @rails = r }
|
35
37
|
end
|
36
38
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'yaml'
|
4
|
+
|
3
5
|
module SimpleFeatureFlags
|
4
6
|
class RamStorage
|
5
7
|
attr_reader :file, :mandatory_flags, :flags
|
@@ -13,8 +15,59 @@ module SimpleFeatureFlags
|
|
13
15
|
import_flags_from_file
|
14
16
|
end
|
15
17
|
|
16
|
-
def active
|
17
|
-
|
18
|
+
def active(feature)
|
19
|
+
case flags.dig(feature.to_sym, 'active')
|
20
|
+
when 'globally', :globally
|
21
|
+
:globally
|
22
|
+
when 'partially', :partially
|
23
|
+
:partially
|
24
|
+
when 'true', true
|
25
|
+
true
|
26
|
+
when 'false', false
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def active?(feature)
|
32
|
+
return true if active(feature)
|
33
|
+
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
def inactive?(feature)
|
38
|
+
!active?(feature)
|
39
|
+
end
|
40
|
+
|
41
|
+
def active_globally?(feature)
|
42
|
+
ACTIVE_GLOBALLY.include? flags.dig(feature.to_sym, 'active')
|
43
|
+
end
|
44
|
+
|
45
|
+
def inactive_globally?(feature)
|
46
|
+
!active_globally?(feature)
|
47
|
+
end
|
48
|
+
|
49
|
+
def active_partially?(feature)
|
50
|
+
ACTIVE_PARTIALLY.include? flags.dig(feature.to_sym, 'active')
|
51
|
+
end
|
52
|
+
|
53
|
+
def inactive_partially?(feature)
|
54
|
+
!active_partially?(feature)
|
55
|
+
end
|
56
|
+
|
57
|
+
def active_for?(feature, object, object_id_method = CONFIG.default_id_method)
|
58
|
+
return false unless active?(feature)
|
59
|
+
return true if active_globally?(feature)
|
60
|
+
|
61
|
+
active_objects_hash = active_objects(feature)
|
62
|
+
active_ids = active_objects_hash[object.class.to_s]
|
63
|
+
|
64
|
+
return false unless active_ids
|
65
|
+
|
66
|
+
active_ids.include? object.public_send(object_id_method)
|
67
|
+
end
|
68
|
+
|
69
|
+
def inactive_for?(feature, object, object_id_method = CONFIG.default_id_method)
|
70
|
+
!active_for?(feature, object, object_id_method)
|
18
71
|
end
|
19
72
|
|
20
73
|
def exists?(feature)
|
@@ -27,16 +80,101 @@ module SimpleFeatureFlags
|
|
27
80
|
flags.dig(feature.to_sym, 'description')
|
28
81
|
end
|
29
82
|
|
30
|
-
def
|
31
|
-
return unless active?(feature
|
83
|
+
def when_active(feature)
|
84
|
+
return unless active?(feature)
|
85
|
+
|
86
|
+
yield
|
87
|
+
end
|
88
|
+
|
89
|
+
def when_inactive(feature)
|
90
|
+
return unless inactive?(feature)
|
91
|
+
|
92
|
+
yield
|
93
|
+
end
|
94
|
+
|
95
|
+
def when_active_globally(feature)
|
96
|
+
return unless active_globally?(feature)
|
97
|
+
|
98
|
+
yield
|
99
|
+
end
|
100
|
+
|
101
|
+
def when_inactive_globally(feature)
|
102
|
+
return unless inactive_globally?(feature)
|
103
|
+
|
104
|
+
yield
|
105
|
+
end
|
106
|
+
|
107
|
+
def when_active_partially(feature)
|
108
|
+
return unless active_partially?(feature)
|
109
|
+
|
110
|
+
yield
|
111
|
+
end
|
112
|
+
|
113
|
+
def when_inactive_partially(feature)
|
114
|
+
return unless inactive_partially?(feature)
|
115
|
+
|
116
|
+
yield
|
117
|
+
end
|
118
|
+
|
119
|
+
def when_active_for(feature, object, object_id_method = CONFIG.default_id_method)
|
120
|
+
return unless active_for?(feature, object, object_id_method)
|
121
|
+
|
122
|
+
yield
|
123
|
+
end
|
124
|
+
|
125
|
+
def when_inactive_for(feature, object, object_id_method = CONFIG.default_id_method)
|
126
|
+
return unless inactive_for?(feature, object, object_id_method)
|
32
127
|
|
33
|
-
|
128
|
+
yield
|
34
129
|
end
|
35
130
|
|
36
131
|
def activate(feature)
|
37
132
|
return false unless exists?(feature)
|
38
133
|
|
39
|
-
flags[feature.to_sym]['active'] = '
|
134
|
+
flags[feature.to_sym]['active'] = 'globally'
|
135
|
+
|
136
|
+
true
|
137
|
+
end
|
138
|
+
|
139
|
+
alias activate_globally activate
|
140
|
+
|
141
|
+
def activate_partially(feature)
|
142
|
+
return false unless exists?(feature)
|
143
|
+
|
144
|
+
flags[feature.to_sym]['active'] = 'partially'
|
145
|
+
|
146
|
+
true
|
147
|
+
end
|
148
|
+
|
149
|
+
def activate_for(feature, objects, object_id_method = CONFIG.default_id_method)
|
150
|
+
return false unless exists?(feature)
|
151
|
+
|
152
|
+
objects = [objects] unless objects.is_a? ::Array
|
153
|
+
to_activate_hash = objects_to_hash(objects, object_id_method)
|
154
|
+
active_objects_hash = active_objects(feature)
|
155
|
+
|
156
|
+
to_activate_hash.each do |klass, ids|
|
157
|
+
(active_objects_hash[klass] = ids) && next unless active_objects_hash[klass]
|
158
|
+
|
159
|
+
active_objects_hash[klass].concat(ids).uniq!.sort!
|
160
|
+
end
|
161
|
+
|
162
|
+
flags[feature.to_sym]['active_for_objects'] = active_objects_hash
|
163
|
+
|
164
|
+
true
|
165
|
+
end
|
166
|
+
|
167
|
+
def activate_for!(feature, objects, object_id_method = CONFIG.default_id_method)
|
168
|
+
return false unless activate_for(feature, objects, object_id_method)
|
169
|
+
|
170
|
+
activate_partially(feature)
|
171
|
+
end
|
172
|
+
|
173
|
+
def deactivate!(feature)
|
174
|
+
return false unless exists?(feature)
|
175
|
+
|
176
|
+
flags[feature.to_sym]['active'] = 'false'
|
177
|
+
flags[feature.to_sym]['active_for_objects'] = nil
|
40
178
|
|
41
179
|
true
|
42
180
|
end
|
@@ -49,6 +187,29 @@ module SimpleFeatureFlags
|
|
49
187
|
true
|
50
188
|
end
|
51
189
|
|
190
|
+
def active_objects(feature)
|
191
|
+
flags.dig(feature.to_sym, 'active_for_objects') || {}
|
192
|
+
end
|
193
|
+
|
194
|
+
def deactivate_for(feature, objects, object_id_method = CONFIG.default_id_method)
|
195
|
+
return false unless exists?(feature)
|
196
|
+
|
197
|
+
active_objects_hash = active_objects(feature)
|
198
|
+
|
199
|
+
objects_to_deactivate_hash = objects_to_hash(objects, object_id_method)
|
200
|
+
|
201
|
+
objects_to_deactivate_hash.each do |klass, ids_to_remove|
|
202
|
+
active_ids = active_objects_hash[klass]
|
203
|
+
next unless active_ids
|
204
|
+
|
205
|
+
active_ids.reject! { |id| ids_to_remove.include? id }
|
206
|
+
end
|
207
|
+
|
208
|
+
flags[feature.to_sym]['active_for_objects'] = active_objects_hash
|
209
|
+
|
210
|
+
true
|
211
|
+
end
|
212
|
+
|
52
213
|
def get(feature)
|
53
214
|
return unless exists?(feature)
|
54
215
|
|
@@ -61,9 +222,10 @@ module SimpleFeatureFlags
|
|
61
222
|
def add(feature, description, active = 'false')
|
62
223
|
return false if exists?(feature)
|
63
224
|
|
64
|
-
active =
|
65
|
-
|
66
|
-
|
225
|
+
active = if ACTIVE_GLOBALLY.include?(active)
|
226
|
+
'globally'
|
227
|
+
elsif ACTIVE_PARTIALLY.include?(active)
|
228
|
+
'partially'
|
67
229
|
else
|
68
230
|
'false'
|
69
231
|
end
|
@@ -102,13 +264,15 @@ module SimpleFeatureFlags
|
|
102
264
|
|
103
265
|
private
|
104
266
|
|
105
|
-
def
|
106
|
-
|
267
|
+
def objects_to_hash(objects, object_id_method = CONFIG.default_id_method)
|
268
|
+
objects = [objects] unless objects.is_a? ::Array
|
269
|
+
|
270
|
+
objects.group_by { |ob| ob.class.to_s }.transform_values { |arr| arr.map(&object_id_method) }
|
107
271
|
end
|
108
272
|
|
109
273
|
def import_flags_from_file
|
110
274
|
changes = YAML.load_file(file)
|
111
|
-
changes = { mandatory: [], remove: [] } unless changes.is_a? Hash
|
275
|
+
changes = { mandatory: [], remove: [] } unless changes.is_a? ::Hash
|
112
276
|
|
113
277
|
changes[:mandatory].each do |el|
|
114
278
|
mandatory_flags << el['name']
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'yaml'
|
4
|
+
|
3
5
|
module SimpleFeatureFlags
|
4
6
|
class RedisStorage
|
5
7
|
attr_reader :file, :redis, :mandatory_flags
|
@@ -12,8 +14,59 @@ module SimpleFeatureFlags
|
|
12
14
|
import_flags_from_file
|
13
15
|
end
|
14
16
|
|
15
|
-
def active
|
16
|
-
|
17
|
+
def active(feature)
|
18
|
+
case redis.hget(feature.to_s, 'active')
|
19
|
+
when 'globally'
|
20
|
+
:globally
|
21
|
+
when 'partially'
|
22
|
+
:partially
|
23
|
+
when 'true'
|
24
|
+
true
|
25
|
+
when 'false'
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def active?(feature)
|
31
|
+
return true if active(feature)
|
32
|
+
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def inactive?(feature)
|
37
|
+
!active?(feature)
|
38
|
+
end
|
39
|
+
|
40
|
+
def active_globally?(feature)
|
41
|
+
ACTIVE_GLOBALLY.include? redis.hget(feature.to_s, 'active')
|
42
|
+
end
|
43
|
+
|
44
|
+
def inactive_globally?(feature)
|
45
|
+
!active_globally?(feature)
|
46
|
+
end
|
47
|
+
|
48
|
+
def active_partially?(feature)
|
49
|
+
ACTIVE_PARTIALLY.include? redis.hget(feature.to_s, 'active')
|
50
|
+
end
|
51
|
+
|
52
|
+
def inactive_partially?(feature)
|
53
|
+
!active_partially?(feature)
|
54
|
+
end
|
55
|
+
|
56
|
+
def active_for?(feature, object, object_id_method = CONFIG.default_id_method)
|
57
|
+
return false unless active?(feature)
|
58
|
+
return true if active_globally?(feature)
|
59
|
+
|
60
|
+
active_objects_hash = active_objects(feature)
|
61
|
+
active_ids = active_objects_hash[object.class.to_s]
|
62
|
+
|
63
|
+
return false unless active_ids
|
64
|
+
|
65
|
+
active_ids.include? object.public_send(object_id_method)
|
66
|
+
end
|
67
|
+
|
68
|
+
def inactive_for?(feature, object, object_id_method = CONFIG.default_id_method)
|
69
|
+
!active_for?(feature, object, object_id_method)
|
17
70
|
end
|
18
71
|
|
19
72
|
def exists?(feature)
|
@@ -26,16 +79,101 @@ module SimpleFeatureFlags
|
|
26
79
|
redis.hget(feature.to_s, 'description')
|
27
80
|
end
|
28
81
|
|
29
|
-
def
|
82
|
+
def when_active(feature)
|
30
83
|
return unless active?(feature)
|
31
84
|
|
32
|
-
|
85
|
+
yield
|
86
|
+
end
|
87
|
+
|
88
|
+
def when_inactive(feature)
|
89
|
+
return unless inactive?(feature)
|
90
|
+
|
91
|
+
yield
|
92
|
+
end
|
93
|
+
|
94
|
+
def when_active_globally(feature)
|
95
|
+
return unless active_globally?(feature)
|
96
|
+
|
97
|
+
yield
|
98
|
+
end
|
99
|
+
|
100
|
+
def when_inactive_globally(feature)
|
101
|
+
return unless inactive_globally?(feature)
|
102
|
+
|
103
|
+
yield
|
104
|
+
end
|
105
|
+
|
106
|
+
def when_active_partially(feature)
|
107
|
+
return unless active_partially?(feature)
|
108
|
+
|
109
|
+
yield
|
110
|
+
end
|
111
|
+
|
112
|
+
def when_inactive_partially(feature)
|
113
|
+
return unless inactive_partially?(feature)
|
114
|
+
|
115
|
+
yield
|
116
|
+
end
|
117
|
+
|
118
|
+
def when_active_for(feature, object, object_id_method = CONFIG.default_id_method)
|
119
|
+
return unless active_for?(feature, object, object_id_method)
|
120
|
+
|
121
|
+
yield
|
122
|
+
end
|
123
|
+
|
124
|
+
def when_inactive_for(feature, object, object_id_method = CONFIG.default_id_method)
|
125
|
+
return unless inactive_for?(feature, object, object_id_method)
|
126
|
+
|
127
|
+
yield
|
33
128
|
end
|
34
129
|
|
35
130
|
def activate(feature)
|
36
131
|
return false unless exists?(feature)
|
37
132
|
|
38
|
-
redis.hset(feature.to_s, 'active', '
|
133
|
+
redis.hset(feature.to_s, 'active', 'globally')
|
134
|
+
|
135
|
+
true
|
136
|
+
end
|
137
|
+
|
138
|
+
alias activate_globally activate
|
139
|
+
|
140
|
+
def activate_partially(feature)
|
141
|
+
return false unless exists?(feature)
|
142
|
+
|
143
|
+
redis.hset(feature.to_s, 'active', 'partially')
|
144
|
+
|
145
|
+
true
|
146
|
+
end
|
147
|
+
|
148
|
+
def activate_for(feature, objects, object_id_method = CONFIG.default_id_method)
|
149
|
+
return false unless exists?(feature)
|
150
|
+
|
151
|
+
objects = [objects] unless objects.is_a? ::Array
|
152
|
+
to_activate_hash = objects_to_hash(objects, object_id_method)
|
153
|
+
active_objects_hash = active_objects(feature)
|
154
|
+
|
155
|
+
to_activate_hash.each do |klass, ids|
|
156
|
+
(active_objects_hash[klass] = ids) && next unless active_objects_hash[klass]
|
157
|
+
|
158
|
+
active_objects_hash[klass].concat(ids).uniq!.sort!
|
159
|
+
end
|
160
|
+
|
161
|
+
redis.hset(feature.to_s, 'active_for_objects', active_objects_hash.to_json)
|
162
|
+
|
163
|
+
true
|
164
|
+
end
|
165
|
+
|
166
|
+
def activate_for!(feature, objects, object_id_method = CONFIG.default_id_method)
|
167
|
+
return false unless activate_for(feature, objects, object_id_method)
|
168
|
+
|
169
|
+
activate_partially(feature)
|
170
|
+
end
|
171
|
+
|
172
|
+
def deactivate!(feature)
|
173
|
+
return false unless exists?(feature)
|
174
|
+
|
175
|
+
redis.hset(feature.to_s, 'active', 'false')
|
176
|
+
redis.hset(feature.to_s, 'active_for_objects', '')
|
39
177
|
|
40
178
|
true
|
41
179
|
end
|
@@ -48,11 +186,37 @@ module SimpleFeatureFlags
|
|
48
186
|
true
|
49
187
|
end
|
50
188
|
|
189
|
+
def active_objects(feature)
|
190
|
+
::JSON.parse(redis.hget(feature.to_s, 'active_for_objects').to_s)
|
191
|
+
rescue ::JSON::ParserError
|
192
|
+
{}
|
193
|
+
end
|
194
|
+
|
195
|
+
def deactivate_for(feature, objects, object_id_method = CONFIG.default_id_method)
|
196
|
+
return false unless exists?(feature)
|
197
|
+
|
198
|
+
active_objects_hash = active_objects(feature)
|
199
|
+
|
200
|
+
objects_to_deactivate_hash = objects_to_hash(objects, object_id_method)
|
201
|
+
|
202
|
+
objects_to_deactivate_hash.each do |klass, ids_to_remove|
|
203
|
+
active_ids = active_objects_hash[klass]
|
204
|
+
next unless active_ids
|
205
|
+
|
206
|
+
active_ids.reject! { |id| ids_to_remove.include? id }
|
207
|
+
end
|
208
|
+
|
209
|
+
redis.hset(feature.to_s, 'active_for_objects', active_objects_hash.to_json)
|
210
|
+
|
211
|
+
true
|
212
|
+
end
|
213
|
+
|
51
214
|
def get(feature)
|
52
215
|
return unless exists?(feature)
|
53
216
|
|
54
217
|
hash = redis.hgetall(feature.to_s)
|
55
218
|
hash['mandatory'] = mandatory_flags.include?(feature.to_s)
|
219
|
+
hash['active_for_objects'] = ::JSON.parse(hash['active_for_objects']) rescue {}
|
56
220
|
|
57
221
|
hash
|
58
222
|
end
|
@@ -60,9 +224,10 @@ module SimpleFeatureFlags
|
|
60
224
|
def add(feature, description, active = 'false')
|
61
225
|
return false if exists?(feature)
|
62
226
|
|
63
|
-
active =
|
64
|
-
|
65
|
-
|
227
|
+
active = if ACTIVE_GLOBALLY.include?(active)
|
228
|
+
'globally'
|
229
|
+
elsif ACTIVE_PARTIALLY.include?(active)
|
230
|
+
'partially'
|
66
231
|
else
|
67
232
|
'false'
|
68
233
|
end
|
@@ -105,18 +270,15 @@ module SimpleFeatureFlags
|
|
105
270
|
|
106
271
|
private
|
107
272
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
when 'false'
|
113
|
-
false
|
114
|
-
end
|
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) }
|
115
277
|
end
|
116
278
|
|
117
279
|
def import_flags_from_file
|
118
|
-
changes = YAML.load_file(file)
|
119
|
-
changes = { mandatory: [], remove: [] } unless changes.is_a? Hash
|
280
|
+
changes = ::YAML.load_file(file)
|
281
|
+
changes = { mandatory: [], remove: [] } unless changes.is_a? ::Hash
|
120
282
|
|
121
283
|
changes[:mandatory].each do |el|
|
122
284
|
mandatory_flags << el['name']
|
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
module SimpleFeatureFlags
|
4
4
|
class TestRamStorage < RamStorage
|
5
|
-
def active?(feature
|
6
|
-
raise(FlagNotDefinedError, "Feature Flag `#{feature}` is not defined as mandatory in #{file}")
|
5
|
+
def active?(feature)
|
6
|
+
raise(FlagNotDefinedError, "Feature Flag `#{feature}` is not defined as mandatory in #{file}") unless mandatory_flags.include?(feature.to_s)
|
7
7
|
|
8
|
-
|
8
|
+
super
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
data/lib/simple_feature_flags.rb
CHANGED
@@ -1,11 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
Dir[File.expand_path('simple_feature_flags/*.rb', __dir__)].sort.each { |file| require file }
|
6
|
+
|
3
7
|
module SimpleFeatureFlags
|
8
|
+
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'
|
12
|
+
|
13
|
+
ACTIVE_GLOBALLY = ::Set['globally', :globally, 'true', true].freeze
|
14
|
+
ACTIVE_PARTIALLY = ::Set['partially', :partially].freeze
|
15
|
+
|
4
16
|
class NoSuchCommandError < StandardError; end
|
5
17
|
|
6
18
|
class IncorrectWorkingDirectoryError < StandardError; end
|
7
19
|
|
8
20
|
class FlagNotDefinedError < StandardError; end
|
9
|
-
end
|
10
21
|
|
11
|
-
|
22
|
+
CONFIG = Configuration.new
|
23
|
+
|
24
|
+
def self.configure(&block)
|
25
|
+
block.call(CONFIG)
|
26
|
+
end
|
27
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_feature_flags
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Espago
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2022-02-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -169,6 +169,7 @@ files:
|
|
169
169
|
- lib/simple_feature_flags/cli/command/generate.rb
|
170
170
|
- lib/simple_feature_flags/cli/options.rb
|
171
171
|
- lib/simple_feature_flags/cli/runner.rb
|
172
|
+
- lib/simple_feature_flags/configuration.rb
|
172
173
|
- lib/simple_feature_flags/ram_storage.rb
|
173
174
|
- lib/simple_feature_flags/redis_storage.rb
|
174
175
|
- lib/simple_feature_flags/test_ram_storage.rb
|