simple_feature_flags 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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