simple_feature_flags 1.0.0 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d732d5d2141046aac302cce21de1487ca8c2d3526a64f7ad1845fc964ab8bec
4
- data.tar.gz: 98677ad0f773881b575c7e3e3bb1c28b857544bdb6dbe370f8ad1d9f739848f0
3
+ metadata.gz: 3caaf6b1bc76049e0debeb5a1d5b549bd1853328fb7bf8833469be0c3de666ca
4
+ data.tar.gz: 3e3a2f901936c0f6bad14a0a1efe75b171a1feb64de2e728c8bfadeda777262c
5
5
  SHA512:
6
- metadata.gz: 003561d885bf3e8ea25c6210c1f727453561c761a22bd08607d5b6e4cbfc36182b168b7ffa901d9c4ebc4e71b3499d1a7d4469ec1fd746c8b62ab4dab47a1abc
7
- data.tar.gz: a7bc11c251004a5acfb190f435c993d2c8fd02136581ca74035abbdac174ed3345a6ef7f893048698196c437f22fbd925c14a28917f5bffcd18dc243618b9415
6
+ metadata.gz: dadda81140cd5cc53845d1e0b2396df2258a87a779b9e770e9699e66f81fcb0f8acdd66887cf53d4186f225700e5e76ea5c2e15f10097443c68191201247eb59
7
+ data.tar.gz: 2e52191291340ce433c6e65ebfd71fd6cebbb1e1e77dc9893086fe0b7412f91753a756074a699fdfc2065a93158c892cdea87610a11a8bb953a22a260239e6af
@@ -4,6 +4,7 @@
4
4
  "autorun",
5
5
  "bindir",
6
6
  "concat",
7
+ "featurable",
7
8
  "flushdb",
8
9
  "hget",
9
10
  "hgetall",
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- simple_feature_flags (1.0.0)
4
+ simple_feature_flags (1.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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: 'true' # 'false' is the default value
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: 'true'
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
@@ -133,10 +133,14 @@ Activates a feature in the global scope
133
133
 
134
134
  ```ruby
135
135
  FEATURE_FLAGS.active?(:feature_name) #=> false
136
+ FEATURE_FLAGS.active_globally?(:feature_name) #=> false
137
+ FEATURE_FLAGS.active_partially?(:feature_name) #=> false
136
138
 
137
- FEATURE_FLAGS.activate(:feature_name)
139
+ FEATURE_FLAGS.activate(:feature_name) # or FEATURE_FLAGS.activate_globally(:feature_name)
138
140
 
139
141
  FEATURE_FLAGS.active?(:feature_name) #=> true
142
+ FEATURE_FLAGS.active_globally?(:feature_name) #=> true
143
+ FEATURE_FLAGS.active_partially?(:feature_name) #=> false
140
144
  ```
141
145
 
142
146
  #### Deactivate a feature
@@ -154,64 +158,75 @@ FEATURE_FLAGS.active?(:feature_name) #=> false
154
158
  #### Activate a feature for a particular record/object
155
159
 
156
160
  ```ruby
161
+ FEATURE_FLAGS.active_partially?(:feature_name) #=> true
162
+ FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
163
+ FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
164
+
157
165
  FEATURE_FLAGS.activate_for(:feature_name, User.first) #=> true
166
+
167
+ FEATURE_FLAGS.active_partially?(:feature_name) #=> true
158
168
  FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
159
169
  FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
160
170
  ```
161
171
 
162
- Note that the flag itself has to be `active` in the global scope for any record/object specific settings to work.
172
+ Note that the flag itself has to be active `partially` for any record/object specific settings to work.
163
173
  When the flag is `deactivated` it is completely turned off globally and for every specific record/object.
164
174
 
165
175
  ```ruby
166
176
  # The flag is deactivated in the global scope to begin with
167
177
  FEATURE_FLAGS.active?(:feature_name) #=> false
178
+ FEATURE_FLAGS.active_partially?(:feature_name) #=> false
179
+ FEATURE_FLAGS.active_globally?(:feature_name) #=> false
180
+
181
+ FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
182
+ FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
168
183
 
169
184
  # We activate it for the first User
170
185
  FEATURE_FLAGS.activate_for(:feature_name, User.first)
171
186
 
172
187
  FEATURE_FLAGS.active?(:feature_name) #=> false
188
+ FEATURE_FLAGS.active_partially?(:feature_name) #=> false
189
+ FEATURE_FLAGS.active_globally?(:feature_name) #=> false
190
+
173
191
  # It is globally `deactivated` though, so the feature stays inactive for all users
174
192
  FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
193
+ FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
175
194
 
176
- # Once we activate the flag in the global scope, record specific settings will be applied
177
- FEATURE_FLAGS.activate(:feature_name)
195
+ # Once we activate the flag partially, record specific settings will be applied
196
+ FEATURE_FLAGS.activate_partially(:feature_name)
178
197
 
179
198
  FEATURE_FLAGS.active?(:feature_name) #=> true
199
+ FEATURE_FLAGS.active_partially?(:feature_name) #=> true
200
+ FEATURE_FLAGS.active_globally?(:feature_name) #=> false
201
+
180
202
  FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
181
203
  FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
182
204
 
183
205
  FEATURE_FLAGS.deactivate(:feature_name)
184
206
 
185
207
  FEATURE_FLAGS.active?(:feature_name) #=> false
208
+ FEATURE_FLAGS.active_partially?(:feature_name) #=> false
209
+ FEATURE_FLAGS.active_globally?(:feature_name) #=> false
210
+
186
211
  FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
187
212
  FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
188
213
  ```
189
214
 
190
- There is a convenience method `activate_for!`, which activates the feature in the global scope and for specific records/objects at the same time
215
+ There is a convenience method `activate_for!`, which activates the feature partially and for specific records/objects at the same time
191
216
 
192
217
  ```ruby
193
218
  # The flag is deactivated in the global scope to begin with
194
219
  FEATURE_FLAGS.active?(:feature_name) #=> false
220
+ FEATURE_FLAGS.active_partially?(:feature_name) #=> false
221
+ FEATURE_FLAGS.active_globally?(:feature_name) #=> false
195
222
 
196
223
  # We activate it in the global scope and for the first User
197
224
  FEATURE_FLAGS.activate_for!(:feature_name, User.first)
198
225
 
199
226
  FEATURE_FLAGS.active?(:feature_name) #=> true
200
- FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
201
- FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
202
- ```
203
-
204
- A feature that is `active` in the global scope is inactive for all specific records, unless it has been activated for them.
205
-
206
- ```ruby
207
- # The flag is active in the global scope to begin with
208
- FEATURE_FLAGS.active?(:feature_name) #=> true
209
- FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> false
210
- FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
211
-
212
- FEATURE_FLAGS.activate_for(:feature_name, User.first)
227
+ FEATURE_FLAGS.active_partially?(:feature_name) #=> true
228
+ FEATURE_FLAGS.active_globally?(:feature_name) #=> false
213
229
 
214
- FEATURE_FLAGS.active?(:feature_name) #=> true
215
230
  FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
216
231
  FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
217
232
  ```
@@ -233,23 +248,32 @@ FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
233
248
  #### Activate the feature for every record
234
249
 
235
250
  ```ruby
236
- # The flag is active in the global scope to begin with
251
+ # The flag is active partially
237
252
  FEATURE_FLAGS.active?(:feature_name) #=> true
253
+ FEATURE_FLAGS.active_partially?(:feature_name) #=> true
254
+ FEATURE_FLAGS.active_globally?(:feature_name) #=> false
255
+
238
256
  # It is also enabled for the first user
239
257
  FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
240
258
  FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
241
259
 
242
260
  # We force it onto every user
243
- FEATURE_FLAGS.activate!(:feature_name)
261
+ FEATURE_FLAGS.activate(:feature_name)
244
262
 
245
263
  FEATURE_FLAGS.active?(:feature_name) #=> true
264
+ FEATURE_FLAGS.active_partially?(:feature_name) #=> false
265
+ FEATURE_FLAGS.active_globally?(:feature_name) #=> true
266
+
246
267
  FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
247
268
  FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> true
248
269
 
249
270
  # We can easily return to the previous settings
250
- FEATURE_FLAGS.activate(:feature_name)
271
+ FEATURE_FLAGS.activate_partially(:feature_name)
251
272
 
252
273
  FEATURE_FLAGS.active?(:feature_name) #=> true
274
+ FEATURE_FLAGS.active_partially?(:feature_name) #=> false
275
+ FEATURE_FLAGS.active_globally?(:feature_name) #=> true
276
+
253
277
  FEATURE_FLAGS.active_for?(:feature_name, User.first) #=> true
254
278
  FEATURE_FLAGS.active_for?(:feature_name, User.last) #=> false
255
279
  ```
@@ -277,22 +301,32 @@ end
277
301
 
278
302
  # or using a block
279
303
 
280
- # this code will run only when the :feature_name flag is active
304
+ # this code will run only when the :feature_name flag is active (either partially or globally)
281
305
  FEATURE_FLAGS.when_active(:feature_name) do
282
306
  number += 1
283
307
  end
284
308
 
285
- # feature flags that don't exist will return false
286
- FEATURE_FLAGS.active?(:non_existant) #=> false
309
+ # this code will run only when the :feature_name flag is active globally
310
+ FEATURE_FLAGS.when_active_globally(:feature_name) do
311
+ number += 1
312
+ end
287
313
 
288
- if FEATURE_FLAGS.active_for?(:feature_name, User.first)
314
+ # this code will run only when the :feature_name flag is active partially (only for specific records/users)
315
+ FEATURE_FLAGS.when_active_partially(:feature_name) do
289
316
  number += 1
290
317
  end
291
318
 
292
- # this code will run only if the :feature_name flag is active for the first User
319
+ # this code will run only if the :feature_name flag is active partially for the first User
293
320
  FEATURE_FLAGS.when_active_for(:feature_name, User.first) do
294
321
  number += 1
295
322
  end
323
+
324
+ # feature flags that don't exist will return false
325
+ FEATURE_FLAGS.active?(:non_existant) #=> false
326
+
327
+ if FEATURE_FLAGS.active_for?(:feature_name, User.first)
328
+ number += 1
329
+ end
296
330
  ```
297
331
 
298
332
  #### Adding feature flags
@@ -302,11 +336,27 @@ You can add new feature flags programmatically, though we highly encourage you t
302
336
  In case you'd like to add flags programmatically
303
337
  ```ruby
304
338
  FEATURE_FLAGS.add(:feature_name, 'Description')
339
+
305
340
  FEATURE_FLAGS.active?(:feature_name) #=> false
341
+ FEATURE_FLAGS.active_partially?(:feature_name) #=> false
342
+ FEATURE_FLAGS.active_globally?(:feature_name) #=> false
343
+ FEATURE_FLAGS.active_for?(:feature_active_partially, User.first) #=> false
344
+
345
+ # add a new globally active flag
346
+ FEATURE_FLAGS.add(:active_feature, 'Description', :globally)
347
+
348
+ FEATURE_FLAGS.active?(:active_feature) #=> true
349
+ FEATURE_FLAGS.active_partially?(:active_feature) #=> false
350
+ FEATURE_FLAGS.active_globally?(:active_feature) #=> true
351
+ FEATURE_FLAGS.active_for?(:active_feature, User.first) #=> true
306
352
 
307
- # add a new active flag
308
- FEATURE_FLAGS.add(:active_feature_name, 'Description', true)
309
- FEATURE_FLAGS.active?(:active_feature_name) #=> true
353
+ # add a new partially active flag
354
+ FEATURE_FLAGS.add(:feature_active_partially, 'Description', :partially)
355
+
356
+ FEATURE_FLAGS.active?(:feature_active_partially) #=> true
357
+ FEATURE_FLAGS.active_partially?(:feature_active_partially) #=> true
358
+ FEATURE_FLAGS.active_globally?(:feature_active_partially) #=> false
359
+ FEATURE_FLAGS.active_for?(:feature_active_partially, User.first) #=> false
310
360
  ```
311
361
 
312
362
  #### Removing feature flags
@@ -316,7 +366,10 @@ You can remove feature flags programmatically, though we highly encourage you to
316
366
  In case you'd like to remove flags programmatically
317
367
  ```ruby
318
368
  FEATURE_FLAGS.remove(:feature_name)
319
- FEATURE_FLAGS.active?(:feature_name) #=> false
369
+
370
+ FEATURE_FLAGS.active?(:feature_active_partially) #=> false
371
+ FEATURE_FLAGS.active_partially?(:feature_active_partially) #=> false
372
+ FEATURE_FLAGS.active_globally?(:feature_active_partially) #=> false
320
373
  ```
321
374
 
322
375
 
@@ -3,7 +3,6 @@
3
3
 
4
4
  $LOAD_PATH.unshift("#{__dir__}/../lib")
5
5
 
6
- require 'byebug'
7
6
  require 'simple_feature_flags'
8
7
 
9
8
  ::SimpleFeatureFlags::Cli::Runner.new.run
@@ -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 TestRamStorage in tests to make them faster
6
- ::SimpleFeatureFlags::TestRamStorage.new("#{::Rails.root.to_s}/config/simple_feature_flags.yml")
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: 'true' # 'false' is the default value
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: 'true'
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
@@ -2,14 +2,26 @@
2
2
 
3
3
  require 'json'
4
4
 
5
+ Dir[File.expand_path('simple_feature_flags/*.rb', __dir__)].sort.each { |file| require file }
6
+
5
7
  module SimpleFeatureFlags
6
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 = ['globally', :globally, 'true', true].freeze
14
+ ACTIVE_PARTIALLY = ['partially', :partially].freeze
7
15
 
8
16
  class NoSuchCommandError < StandardError; end
9
17
 
10
18
  class IncorrectWorkingDirectoryError < StandardError; end
11
19
 
12
20
  class FlagNotDefinedError < StandardError; end
13
- end
14
21
 
15
- Dir[File.expand_path('simple_feature_flags/*.rb', __dir__)].sort.each { |file| require file }
22
+ CONFIG = Configuration.new
23
+
24
+ def self.configure(&block)
25
+ block.call(CONFIG)
26
+ end
27
+ end
@@ -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
 
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleFeatureFlags
4
+ class Configuration
5
+ attr_accessor :default_id_method
6
+
7
+ def initialize
8
+ @default_id_method = :id
9
+ end
10
+ end
11
+ end
@@ -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,15 +15,34 @@ module SimpleFeatureFlags
13
15
  import_flags_from_file
14
16
  end
15
17
 
16
- def active?(feature, _ignore_file = false)
17
- __active__(feature)
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
18
35
  end
19
36
 
20
37
  def active_globally?(feature)
21
- flags.dig(feature.to_sym, 'active') == 'globally'
38
+ ACTIVE_GLOBALLY.include? flags.dig(feature.to_sym, 'active')
22
39
  end
23
40
 
24
- def active_for?(feature, object, object_id_method = :id)
41
+ def active_partially?(feature)
42
+ ACTIVE_PARTIALLY.include? flags.dig(feature.to_sym, 'active')
43
+ end
44
+
45
+ def active_for?(feature, object, object_id_method = CONFIG.default_id_method)
25
46
  return false unless active?(feature)
26
47
  return true if active_globally?(feature)
27
48
 
@@ -43,19 +64,31 @@ module SimpleFeatureFlags
43
64
  flags.dig(feature.to_sym, 'description')
44
65
  end
45
66
 
46
- def when_active(feature, ignore_file = false, &block)
47
- return unless active?(feature, ignore_file)
67
+ def when_active(feature, &block)
68
+ return unless active?(feature)
69
+
70
+ block.call
71
+ end
72
+
73
+ def when_active_globally(feature, &block)
74
+ return unless active_globally?(feature)
75
+
76
+ block.call
77
+ end
78
+
79
+ def when_active_partially(feature, &block)
80
+ return unless active_partially?(feature)
48
81
 
49
82
  block.call
50
83
  end
51
84
 
52
- def when_active_for(feature, object, object_id_method = :id, &block)
85
+ def when_active_for(feature, object, object_id_method = CONFIG.default_id_method, &block)
53
86
  return unless active_for?(feature, object, object_id_method)
54
87
 
55
88
  block.call
56
89
  end
57
90
 
58
- def activate!(feature)
91
+ def activate(feature)
59
92
  return false unless exists?(feature)
60
93
 
61
94
  flags[feature.to_sym]['active'] = 'globally'
@@ -63,15 +96,17 @@ module SimpleFeatureFlags
63
96
  true
64
97
  end
65
98
 
66
- def activate(feature)
99
+ alias activate_globally activate
100
+
101
+ def activate_partially(feature)
67
102
  return false unless exists?(feature)
68
103
 
69
- flags[feature.to_sym]['active'] = 'true'
104
+ flags[feature.to_sym]['active'] = 'partially'
70
105
 
71
106
  true
72
107
  end
73
108
 
74
- def activate_for(feature, objects, object_id_method = :id)
109
+ def activate_for(feature, objects, object_id_method = CONFIG.default_id_method)
75
110
  return false unless exists?(feature)
76
111
 
77
112
  objects = [objects] unless objects.is_a? ::Array
@@ -81,7 +116,7 @@ module SimpleFeatureFlags
81
116
  to_activate_hash.each do |klass, ids|
82
117
  (active_objects_hash[klass] = ids) && next unless active_objects_hash[klass]
83
118
 
84
- active_objects_hash[klass].concat(ids).sort!
119
+ active_objects_hash[klass].concat(ids).uniq!.sort!
85
120
  end
86
121
 
87
122
  flags[feature.to_sym]['active_for_objects'] = active_objects_hash
@@ -89,10 +124,10 @@ module SimpleFeatureFlags
89
124
  true
90
125
  end
91
126
 
92
- def activate_for!(feature, objects, object_id_method = :id)
127
+ def activate_for!(feature, objects, object_id_method = CONFIG.default_id_method)
93
128
  return false unless activate_for(feature, objects, object_id_method)
94
129
 
95
- activate(feature)
130
+ activate_partially(feature)
96
131
  end
97
132
 
98
133
  def deactivate!(feature)
@@ -116,7 +151,7 @@ module SimpleFeatureFlags
116
151
  flags.dig(feature.to_sym, 'active_for_objects') || {}
117
152
  end
118
153
 
119
- def deactivate_for(feature, objects, object_id_method = :id)
154
+ def deactivate_for(feature, objects, object_id_method = CONFIG.default_id_method)
120
155
  return false unless exists?(feature)
121
156
 
122
157
  active_objects_hash = active_objects(feature)
@@ -190,16 +225,12 @@ module SimpleFeatureFlags
190
225
 
191
226
  private
192
227
 
193
- def objects_to_hash(objects, object_id_method = :id)
228
+ def objects_to_hash(objects, object_id_method = CONFIG.default_id_method)
194
229
  objects = [objects] unless objects.is_a? ::Array
195
230
 
196
231
  objects.group_by { |ob| ob.class.to_s }.transform_values { |arr| arr.map(&object_id_method) }
197
232
  end
198
233
 
199
- def __active__(feature)
200
- %w[true globally].include? flags.dig(feature.to_sym, 'active')
201
- end
202
-
203
234
  def import_flags_from_file
204
235
  changes = YAML.load_file(file)
205
236
  changes = { mandatory: [], remove: [] } unless changes.is_a? ::Hash
@@ -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,20 +14,34 @@ module SimpleFeatureFlags
12
14
  import_flags_from_file
13
15
  end
14
16
 
15
- def active?(feature, _ignore_file = false)
16
- __active__(feature)
17
- end
18
-
19
- def active_globally?(feature)
17
+ def active(feature)
20
18
  case redis.hget(feature.to_s, 'active')
21
19
  when 'globally'
20
+ :globally
21
+ when 'partially'
22
+ :partially
23
+ when 'true'
22
24
  true
23
- else
25
+ when 'false'
24
26
  false
25
27
  end
26
28
  end
27
29
 
28
- def active_for?(feature, object, object_id_method = :id)
30
+ def active?(feature)
31
+ return true if active(feature)
32
+
33
+ false
34
+ end
35
+
36
+ def active_globally?(feature)
37
+ ACTIVE_GLOBALLY.include? redis.hget(feature.to_s, 'active')
38
+ end
39
+
40
+ def active_partially?(feature)
41
+ ACTIVE_PARTIALLY.include? redis.hget(feature.to_s, 'active')
42
+ end
43
+
44
+ def active_for?(feature, object, object_id_method = CONFIG.default_id_method)
29
45
  return false unless active?(feature)
30
46
  return true if active_globally?(feature)
31
47
 
@@ -47,19 +63,31 @@ module SimpleFeatureFlags
47
63
  redis.hget(feature.to_s, 'description')
48
64
  end
49
65
 
50
- def when_active(feature, _ignore_file = false, &block)
66
+ def when_active(feature, &block)
51
67
  return unless active?(feature)
52
68
 
53
69
  block.call
54
70
  end
55
71
 
56
- def when_active_for(feature, object, object_id_method = :id, &block)
72
+ def when_active_globally(feature, &block)
73
+ return unless active_globally?(feature)
74
+
75
+ block.call
76
+ end
77
+
78
+ def when_active_partially(feature, &block)
79
+ return unless active_partially?(feature)
80
+
81
+ block.call
82
+ end
83
+
84
+ def when_active_for(feature, object, object_id_method = CONFIG.default_id_method, &block)
57
85
  return unless active_for?(feature, object, object_id_method)
58
86
 
59
87
  block.call
60
88
  end
61
89
 
62
- def activate!(feature)
90
+ def activate(feature)
63
91
  return false unless exists?(feature)
64
92
 
65
93
  redis.hset(feature.to_s, 'active', 'globally')
@@ -67,17 +95,17 @@ module SimpleFeatureFlags
67
95
  true
68
96
  end
69
97
 
70
- alias activate_globally activate!
98
+ alias activate_globally activate
71
99
 
72
- def activate(feature)
100
+ def activate_partially(feature)
73
101
  return false unless exists?(feature)
74
102
 
75
- redis.hset(feature.to_s, 'active', 'true')
103
+ redis.hset(feature.to_s, 'active', 'partially')
76
104
 
77
105
  true
78
106
  end
79
107
 
80
- def activate_for(feature, objects, object_id_method = :id)
108
+ def activate_for(feature, objects, object_id_method = CONFIG.default_id_method)
81
109
  return false unless exists?(feature)
82
110
 
83
111
  objects = [objects] unless objects.is_a? ::Array
@@ -87,7 +115,7 @@ module SimpleFeatureFlags
87
115
  to_activate_hash.each do |klass, ids|
88
116
  (active_objects_hash[klass] = ids) && next unless active_objects_hash[klass]
89
117
 
90
- active_objects_hash[klass].concat(ids).sort!
118
+ active_objects_hash[klass].concat(ids).uniq!.sort!
91
119
  end
92
120
 
93
121
  redis.hset(feature.to_s, 'active_for_objects', active_objects_hash.to_json)
@@ -95,10 +123,10 @@ module SimpleFeatureFlags
95
123
  true
96
124
  end
97
125
 
98
- def activate_for!(feature, objects, object_id_method = :id)
126
+ def activate_for!(feature, objects, object_id_method = CONFIG.default_id_method)
99
127
  return false unless activate_for(feature, objects, object_id_method)
100
128
 
101
- activate(feature)
129
+ activate_partially(feature)
102
130
  end
103
131
 
104
132
  def deactivate!(feature)
@@ -124,7 +152,7 @@ module SimpleFeatureFlags
124
152
  {}
125
153
  end
126
154
 
127
- def deactivate_for(feature, objects, object_id_method = :id)
155
+ def deactivate_for(feature, objects, object_id_method = CONFIG.default_id_method)
128
156
  return false unless exists?(feature)
129
157
 
130
158
  active_objects_hash = active_objects(feature)
@@ -203,23 +231,14 @@ module SimpleFeatureFlags
203
231
 
204
232
  private
205
233
 
206
- def objects_to_hash(objects, object_id_method = :id)
234
+ def objects_to_hash(objects, object_id_method = CONFIG.default_id_method)
207
235
  objects = [objects] unless objects.is_a? ::Array
208
236
 
209
237
  objects.group_by { |ob| ob.class.to_s }.transform_values { |arr| arr.map(&object_id_method) }
210
238
  end
211
239
 
212
- def __active__(feature)
213
- case redis.hget(feature.to_s, 'active')
214
- when 'true', 'globally'
215
- true
216
- when 'false'
217
- false
218
- end
219
- end
220
-
221
240
  def import_flags_from_file
222
- changes = YAML.load_file(file)
241
+ changes = ::YAML.load_file(file)
223
242
  changes = { mandatory: [], remove: [] } unless changes.is_a? ::Hash
224
243
 
225
244
  changes[:mandatory].each do |el|
@@ -2,10 +2,10 @@
2
2
 
3
3
  module SimpleFeatureFlags
4
4
  class TestRamStorage < RamStorage
5
- def active?(feature, ignore_file = false)
6
- raise(FlagNotDefinedError, "Feature Flag `#{feature}` is not defined as mandatory in #{file}") if !ignore_file && !mandatory_flags.include?(feature.to_s)
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
- __active__(feature)
8
+ super
9
9
  end
10
10
  end
11
11
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleFeatureFlags
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  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: 1.0.0
4
+ version: 1.1.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: 2021-08-14 00:00:00.000000000 Z
12
+ date: 2021-08-17 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