flipper 0.26.0 → 1.3.6

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.
Files changed (228) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/ci.yml +61 -16
  4. data/.github/workflows/examples.yml +55 -18
  5. data/CLAUDE.md +74 -0
  6. data/Changelog.md +1 -486
  7. data/Gemfile +23 -11
  8. data/README.md +31 -27
  9. data/Rakefile +2 -2
  10. data/benchmark/enabled_ips.rb +10 -0
  11. data/benchmark/enabled_multiple_actors_ips.rb +20 -0
  12. data/benchmark/enabled_profile.rb +20 -0
  13. data/benchmark/instrumentation_ips.rb +21 -0
  14. data/benchmark/typecast_ips.rb +27 -0
  15. data/docs/images/banner.jpg +0 -0
  16. data/docs/images/flipper_cloud.png +0 -0
  17. data/examples/api/basic.ru +3 -4
  18. data/examples/api/custom_memoized.ru +3 -4
  19. data/examples/api/memoized.ru +3 -4
  20. data/examples/cloud/app.ru +12 -0
  21. data/examples/cloud/backoff_policy.rb +13 -0
  22. data/examples/cloud/basic.rb +22 -0
  23. data/examples/cloud/cloud_setup.rb +20 -0
  24. data/examples/cloud/forked.rb +36 -0
  25. data/examples/cloud/import.rb +17 -0
  26. data/examples/cloud/threaded.rb +33 -0
  27. data/examples/dsl.rb +1 -15
  28. data/examples/enabled_for_actor.rb +4 -2
  29. data/examples/expressions.rb +213 -0
  30. data/examples/mirroring.rb +59 -0
  31. data/examples/strict.rb +18 -0
  32. data/exe/flipper +5 -0
  33. data/flipper-cloud.gemspec +19 -0
  34. data/flipper.gemspec +8 -6
  35. data/lib/flipper/actor.rb +6 -3
  36. data/lib/flipper/adapter.rb +33 -7
  37. data/lib/flipper/adapter_builder.rb +44 -0
  38. data/lib/flipper/adapters/actor_limit.rb +28 -0
  39. data/lib/flipper/adapters/cache_base.rb +143 -0
  40. data/lib/flipper/adapters/dual_write.rb +1 -3
  41. data/lib/flipper/adapters/failover.rb +0 -4
  42. data/lib/flipper/adapters/failsafe.rb +0 -4
  43. data/lib/flipper/adapters/http/client.rb +40 -12
  44. data/lib/flipper/adapters/http/error.rb +2 -2
  45. data/lib/flipper/adapters/http.rb +30 -17
  46. data/lib/flipper/adapters/instrumented.rb +25 -6
  47. data/lib/flipper/adapters/memoizable.rb +33 -21
  48. data/lib/flipper/adapters/memory.rb +81 -46
  49. data/lib/flipper/adapters/operation_logger.rb +17 -78
  50. data/lib/flipper/adapters/poll/poller.rb +2 -125
  51. data/lib/flipper/adapters/poll.rb +20 -3
  52. data/lib/flipper/adapters/pstore.rb +17 -11
  53. data/lib/flipper/adapters/read_only.rb +8 -41
  54. data/lib/flipper/adapters/strict.rb +45 -0
  55. data/lib/flipper/adapters/sync/feature_synchronizer.rb +10 -1
  56. data/lib/flipper/adapters/sync.rb +0 -4
  57. data/lib/flipper/adapters/wrapper.rb +54 -0
  58. data/lib/flipper/cli.rb +263 -0
  59. data/lib/flipper/cloud/configuration.rb +266 -0
  60. data/lib/flipper/cloud/dsl.rb +27 -0
  61. data/lib/flipper/cloud/message_verifier.rb +95 -0
  62. data/lib/flipper/cloud/middleware.rb +63 -0
  63. data/lib/flipper/cloud/routes.rb +14 -0
  64. data/lib/flipper/cloud/telemetry/backoff_policy.rb +96 -0
  65. data/lib/flipper/cloud/telemetry/instrumenter.rb +22 -0
  66. data/lib/flipper/cloud/telemetry/metric.rb +39 -0
  67. data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
  68. data/lib/flipper/cloud/telemetry/submitter.rb +100 -0
  69. data/lib/flipper/cloud/telemetry.rb +191 -0
  70. data/lib/flipper/cloud.rb +53 -0
  71. data/lib/flipper/configuration.rb +25 -4
  72. data/lib/flipper/dsl.rb +46 -45
  73. data/lib/flipper/engine.rb +102 -0
  74. data/lib/flipper/errors.rb +3 -3
  75. data/lib/flipper/export.rb +24 -0
  76. data/lib/flipper/exporter.rb +17 -0
  77. data/lib/flipper/exporters/json/export.rb +32 -0
  78. data/lib/flipper/exporters/json/v1.rb +33 -0
  79. data/lib/flipper/expression/builder.rb +73 -0
  80. data/lib/flipper/expression/constant.rb +25 -0
  81. data/lib/flipper/expression.rb +71 -0
  82. data/lib/flipper/expressions/all.rb +9 -0
  83. data/lib/flipper/expressions/any.rb +9 -0
  84. data/lib/flipper/expressions/boolean.rb +9 -0
  85. data/lib/flipper/expressions/comparable.rb +13 -0
  86. data/lib/flipper/expressions/duration.rb +28 -0
  87. data/lib/flipper/expressions/equal.rb +9 -0
  88. data/lib/flipper/expressions/greater_than.rb +9 -0
  89. data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
  90. data/lib/flipper/expressions/less_than.rb +9 -0
  91. data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
  92. data/lib/flipper/expressions/not_equal.rb +9 -0
  93. data/lib/flipper/expressions/now.rb +9 -0
  94. data/lib/flipper/expressions/number.rb +9 -0
  95. data/lib/flipper/expressions/percentage.rb +9 -0
  96. data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
  97. data/lib/flipper/expressions/property.rb +9 -0
  98. data/lib/flipper/expressions/random.rb +9 -0
  99. data/lib/flipper/expressions/string.rb +9 -0
  100. data/lib/flipper/expressions/time.rb +9 -0
  101. data/lib/flipper/feature.rb +94 -26
  102. data/lib/flipper/feature_check_context.rb +10 -6
  103. data/lib/flipper/gate.rb +13 -11
  104. data/lib/flipper/gate_values.rb +5 -18
  105. data/lib/flipper/gates/actor.rb +10 -17
  106. data/lib/flipper/gates/boolean.rb +1 -1
  107. data/lib/flipper/gates/expression.rb +75 -0
  108. data/lib/flipper/gates/group.rb +5 -7
  109. data/lib/flipper/gates/percentage_of_actors.rb +10 -13
  110. data/lib/flipper/gates/percentage_of_time.rb +1 -2
  111. data/lib/flipper/identifier.rb +2 -2
  112. data/lib/flipper/instrumentation/log_subscriber.rb +35 -8
  113. data/lib/flipper/instrumentation/statsd.rb +4 -2
  114. data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
  115. data/lib/flipper/instrumentation/subscriber.rb +8 -5
  116. data/lib/flipper/metadata.rb +8 -1
  117. data/lib/flipper/middleware/memoizer.rb +30 -14
  118. data/lib/flipper/model/active_record.rb +23 -0
  119. data/lib/flipper/poller.rb +118 -0
  120. data/lib/flipper/serializers/gzip.rb +22 -0
  121. data/lib/flipper/serializers/json.rb +17 -0
  122. data/lib/flipper/spec/shared_adapter_specs.rb +105 -63
  123. data/lib/flipper/test/shared_adapter_test.rb +101 -58
  124. data/lib/flipper/test_help.rb +43 -0
  125. data/lib/flipper/typecast.rb +59 -18
  126. data/lib/flipper/types/actor.rb +13 -13
  127. data/lib/flipper/types/group.rb +4 -4
  128. data/lib/flipper/types/percentage.rb +1 -1
  129. data/lib/flipper/version.rb +11 -1
  130. data/lib/flipper.rb +50 -11
  131. data/lib/generators/flipper/setup_generator.rb +68 -0
  132. data/lib/generators/flipper/templates/initializer.rb +45 -0
  133. data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
  134. data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
  135. data/lib/generators/flipper/update_generator.rb +35 -0
  136. data/package-lock.json +41 -0
  137. data/package.json +10 -0
  138. data/spec/fixtures/environment.rb +1 -0
  139. data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
  140. data/spec/flipper/adapter_builder_spec.rb +72 -0
  141. data/spec/flipper/adapter_spec.rb +30 -2
  142. data/spec/flipper/adapters/actor_limit_spec.rb +20 -0
  143. data/spec/flipper/adapters/dual_write_spec.rb +2 -2
  144. data/spec/flipper/adapters/http/client_spec.rb +61 -0
  145. data/spec/flipper/adapters/http_spec.rb +138 -55
  146. data/spec/flipper/adapters/instrumented_spec.rb +29 -11
  147. data/spec/flipper/adapters/memoizable_spec.rb +51 -31
  148. data/spec/flipper/adapters/memory_spec.rb +14 -3
  149. data/spec/flipper/adapters/operation_logger_spec.rb +31 -12
  150. data/spec/flipper/adapters/poll_spec.rb +41 -0
  151. data/spec/flipper/adapters/read_only_spec.rb +32 -17
  152. data/spec/flipper/adapters/strict_spec.rb +64 -0
  153. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +27 -0
  154. data/spec/flipper/cli_spec.rb +166 -0
  155. data/spec/flipper/cloud/configuration_spec.rb +251 -0
  156. data/spec/flipper/cloud/dsl_spec.rb +82 -0
  157. data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
  158. data/spec/flipper/cloud/middleware_spec.rb +289 -0
  159. data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +107 -0
  160. data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
  161. data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
  162. data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
  163. data/spec/flipper/cloud/telemetry_spec.rb +208 -0
  164. data/spec/flipper/cloud_spec.rb +186 -0
  165. data/spec/flipper/configuration_spec.rb +17 -0
  166. data/spec/flipper/dsl_spec.rb +54 -76
  167. data/spec/flipper/engine_spec.rb +374 -0
  168. data/spec/flipper/export_spec.rb +13 -0
  169. data/spec/flipper/exporter_spec.rb +16 -0
  170. data/spec/flipper/exporters/json/export_spec.rb +60 -0
  171. data/spec/flipper/exporters/json/v1_spec.rb +33 -0
  172. data/spec/flipper/expression/builder_spec.rb +248 -0
  173. data/spec/flipper/expression_spec.rb +188 -0
  174. data/spec/flipper/expressions/all_spec.rb +15 -0
  175. data/spec/flipper/expressions/any_spec.rb +15 -0
  176. data/spec/flipper/expressions/boolean_spec.rb +15 -0
  177. data/spec/flipper/expressions/duration_spec.rb +43 -0
  178. data/spec/flipper/expressions/equal_spec.rb +24 -0
  179. data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
  180. data/spec/flipper/expressions/greater_than_spec.rb +28 -0
  181. data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
  182. data/spec/flipper/expressions/less_than_spec.rb +32 -0
  183. data/spec/flipper/expressions/not_equal_spec.rb +15 -0
  184. data/spec/flipper/expressions/now_spec.rb +11 -0
  185. data/spec/flipper/expressions/number_spec.rb +21 -0
  186. data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
  187. data/spec/flipper/expressions/percentage_spec.rb +15 -0
  188. data/spec/flipper/expressions/property_spec.rb +13 -0
  189. data/spec/flipper/expressions/random_spec.rb +9 -0
  190. data/spec/flipper/expressions/string_spec.rb +11 -0
  191. data/spec/flipper/expressions/time_spec.rb +13 -0
  192. data/spec/flipper/feature_check_context_spec.rb +17 -17
  193. data/spec/flipper/feature_spec.rb +453 -39
  194. data/spec/flipper/gate_values_spec.rb +2 -33
  195. data/spec/flipper/gates/boolean_spec.rb +1 -1
  196. data/spec/flipper/gates/expression_spec.rb +108 -0
  197. data/spec/flipper/gates/group_spec.rb +2 -3
  198. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -5
  199. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
  200. data/spec/flipper/identifier_spec.rb +4 -5
  201. data/spec/flipper/instrumentation/log_subscriber_spec.rb +24 -6
  202. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +26 -2
  203. data/spec/flipper/middleware/memoizer_spec.rb +79 -10
  204. data/spec/flipper/model/active_record_spec.rb +72 -0
  205. data/spec/flipper/poller_spec.rb +47 -0
  206. data/spec/flipper/serializers/gzip_spec.rb +13 -0
  207. data/spec/flipper/serializers/json_spec.rb +13 -0
  208. data/spec/flipper/typecast_spec.rb +121 -6
  209. data/spec/flipper/types/actor_spec.rb +63 -46
  210. data/spec/flipper/types/group_spec.rb +2 -2
  211. data/spec/flipper_integration_spec.rb +168 -58
  212. data/spec/flipper_spec.rb +94 -30
  213. data/spec/spec_helper.rb +18 -18
  214. data/spec/support/actor_names.yml +1 -0
  215. data/spec/support/fail_on_output.rb +8 -0
  216. data/spec/support/fake_backoff_policy.rb +15 -0
  217. data/spec/support/skippable.rb +18 -0
  218. data/spec/support/spec_helpers.rb +34 -8
  219. data/test/adapters/actor_limit_test.rb +20 -0
  220. data/test_rails/generators/flipper/setup_generator_test.rb +69 -0
  221. data/test_rails/generators/flipper/update_generator_test.rb +96 -0
  222. data/test_rails/helper.rb +22 -2
  223. data/test_rails/system/test_help_test.rb +52 -0
  224. metadata +203 -20
  225. data/.github/workflows/release.yml +0 -44
  226. data/.tool-versions +0 -1
  227. data/lib/flipper/railtie.rb +0 -47
  228. data/spec/flipper/railtie_spec.rb +0 -109
@@ -32,6 +32,72 @@ RSpec.describe Flipper::Feature do
32
32
  end
33
33
  end
34
34
 
35
+ describe "#enabled?" do
36
+ context "for an actor" do
37
+ let(:actor) { Flipper::Actor.new("User;1") }
38
+
39
+ it 'returns true if feature is enabled' do
40
+ subject.enable
41
+ expect(subject.enabled?(actor)).to be(true)
42
+ end
43
+
44
+ it 'returns false if feature is disabled' do
45
+ subject.disable
46
+ expect(subject.enabled?(actor)).to be(false)
47
+ end
48
+ end
49
+
50
+ context "for multiple actors" do
51
+ let(:actors) {
52
+ [
53
+ Flipper::Actor.new("User;1"),
54
+ Flipper::Actor.new("User;2"),
55
+ Flipper::Actor.new("User;3"),
56
+ ]
57
+ }
58
+
59
+ it 'returns true if feature is enabled' do
60
+ subject.enable
61
+ expect(subject.enabled?(actors)).to be(true)
62
+ end
63
+
64
+ it 'returns true if feature is enabled for any actor' do
65
+ subject.enable_actor actors.first
66
+ expect(subject.enabled?(actors)).to be(true)
67
+ end
68
+
69
+ it 'returns true if feature is enabled for any actor with multiple arguments' do
70
+ subject.enable_actor actors.last
71
+ expect(subject.enabled?(*actors)).to be(true)
72
+ end
73
+
74
+ it 'returns false if feature is disabled for all actors' do
75
+ subject.disable
76
+ expect(subject.enabled?(actors)).to be(false)
77
+ end
78
+ end
79
+
80
+ context "for an object that implements .nil? == true" do
81
+ let(:actor) { Flipper::Actor.new("User;1") }
82
+
83
+ before do
84
+ def actor.nil?
85
+ true
86
+ end
87
+ end
88
+
89
+ it 'returns true if feature is enabled' do
90
+ subject.enable
91
+ expect(subject.enabled?(actor)).to be(true)
92
+ end
93
+
94
+ it 'returns false if feature is disabled' do
95
+ subject.disable
96
+ expect(subject.enabled?(actor)).to be(false)
97
+ end
98
+ end
99
+ end
100
+
35
101
  describe '#to_s' do
36
102
  it 'returns name as string' do
37
103
  feature = described_class.new(:search, adapter)
@@ -63,7 +129,7 @@ RSpec.describe Flipper::Feature do
63
129
  instance.gates.each do |gate|
64
130
  expect(gate).to be_a(Flipper::Gate)
65
131
  end
66
- expect(instance.gates.size).to be(5)
132
+ expect(instance.gates.size).to be(6)
67
133
  end
68
134
  end
69
135
 
@@ -148,34 +214,33 @@ RSpec.describe Flipper::Feature do
148
214
  end
149
215
 
150
216
  it 'is recorded for enable' do
151
- thing = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
152
- gate = subject.gate_for(thing)
217
+ actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
218
+ subject.gate_for(actor)
153
219
 
154
- subject.enable(thing)
220
+ subject.enable(actor)
155
221
 
156
222
  event = instrumenter.events.last
157
223
  expect(event).not_to be_nil
158
224
  expect(event.name).to eq('feature_operation.flipper')
159
225
  expect(event.payload[:feature_name]).to eq(:search)
160
226
  expect(event.payload[:operation]).to eq(:enable)
161
- expect(event.payload[:thing]).to eq(thing)
227
+ expect(event.payload[:thing]).to eq(actor)
162
228
  expect(event.payload[:result]).not_to be_nil
163
229
  end
164
230
 
165
231
  it 'always instruments flipper type instance for enable' do
166
- thing = Flipper::Actor.new('1')
167
- gate = subject.gate_for(thing)
232
+ actor = Flipper::Actor.new('1')
233
+ subject.gate_for(actor)
168
234
 
169
- subject.enable(thing)
235
+ subject.enable(actor)
170
236
 
171
237
  event = instrumenter.events.last
172
238
  expect(event).not_to be_nil
173
- expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(thing))
239
+ expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(actor))
174
240
  end
175
241
 
176
242
  it 'is recorded for disable' do
177
243
  thing = Flipper::Types::Boolean.new
178
- gate = subject.gate_for(thing)
179
244
 
180
245
  subject.disable(thing)
181
246
 
@@ -219,15 +284,14 @@ RSpec.describe Flipper::Feature do
219
284
  end
220
285
 
221
286
  it 'always instruments flipper type instance for disable' do
222
- thing = Flipper::Actor.new('1')
223
- gate = subject.gate_for(thing)
287
+ actor = Flipper::Actor.new('1')
224
288
 
225
- subject.disable(thing)
289
+ subject.disable(actor)
226
290
 
227
291
  event = instrumenter.events.last
228
292
  expect(event).not_to be_nil
229
293
  expect(event.payload[:operation]).to eq(:disable)
230
- expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(thing))
294
+ expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(actor))
231
295
  end
232
296
 
233
297
  it 'is recorded for add' do
@@ -275,17 +339,15 @@ RSpec.describe Flipper::Feature do
275
339
  end
276
340
 
277
341
  it 'is recorded for enabled?' do
278
- thing = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
279
- gate = subject.gate_for(thing)
280
-
281
- subject.enabled?(thing)
342
+ actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
343
+ subject.enabled?(actor)
282
344
 
283
345
  event = instrumenter.events.last
284
346
  expect(event).not_to be_nil
285
347
  expect(event.name).to eq('feature_operation.flipper')
286
348
  expect(event.payload[:feature_name]).to eq(:search)
287
349
  expect(event.payload[:operation]).to eq(:enabled?)
288
- expect(event.payload[:thing]).to eq(thing)
350
+ expect(event.payload[:actors]).to eq([actor])
289
351
  expect(event.payload[:result]).to eq(false)
290
352
  end
291
353
 
@@ -293,8 +355,8 @@ RSpec.describe Flipper::Feature do
293
355
  actor = Flipper::Types::Actor.new(user)
294
356
  {
295
357
  nil => nil,
296
- user => actor,
297
- actor => actor,
358
+ user => [actor],
359
+ actor => [actor],
298
360
  }.each do |thing, wrapped_thing|
299
361
  it "always instruments #{thing.inspect} as #{wrapped_thing.class} for enabled?" do
300
362
  subject.enabled?(thing)
@@ -302,7 +364,7 @@ RSpec.describe Flipper::Feature do
302
364
  event = instrumenter.events.last
303
365
  expect(event).not_to be_nil
304
366
  expect(event.payload[:operation]).to eq(:enabled?)
305
- expect(event.payload[:thing]).to eq(wrapped_thing)
367
+ expect(event.payload[:actors]).to eq(wrapped_thing)
306
368
  end
307
369
  end
308
370
  end
@@ -428,10 +490,10 @@ RSpec.describe Flipper::Feature do
428
490
 
429
491
  context 'when one or more groups enabled' do
430
492
  before do
431
- @staff = Flipper.register(:staff) { |_thing| true }
432
- @preview_features = Flipper.register(:preview_features) { |_thing| true }
433
- @not_enabled = Flipper.register(:not_enabled) { |_thing| true }
434
- @disabled = Flipper.register(:disabled) { |_thing| true }
493
+ @staff = Flipper.register(:staff) { |actor| true }
494
+ @preview_features = Flipper.register(:preview_features) { |actor| true }
495
+ @not_enabled = Flipper.register(:not_enabled) { |actor| true }
496
+ @disabled = Flipper.register(:disabled) { |actor| true }
435
497
  subject.enable @staff
436
498
  subject.enable @preview_features
437
499
  subject.disable @disabled
@@ -467,10 +529,10 @@ RSpec.describe Flipper::Feature do
467
529
 
468
530
  context 'when one or more groups enabled' do
469
531
  before do
470
- @staff = Flipper.register(:staff) { |_thing| true }
471
- @preview_features = Flipper.register(:preview_features) { |_thing| true }
472
- @not_enabled = Flipper.register(:not_enabled) { |_thing| true }
473
- @disabled = Flipper.register(:disabled) { |_thing| true }
532
+ @staff = Flipper.register(:staff) { |actor| true }
533
+ @preview_features = Flipper.register(:preview_features) { |actor| true }
534
+ @not_enabled = Flipper.register(:not_enabled) { |actor| true }
535
+ @disabled = Flipper.register(:disabled) { |actor| true }
474
536
  subject.enable @staff
475
537
  subject.enable @preview_features
476
538
  subject.disable @disabled
@@ -494,10 +556,10 @@ RSpec.describe Flipper::Feature do
494
556
 
495
557
  context 'when one or more groups enabled' do
496
558
  before do
497
- @staff = Flipper.register(:staff) { |_thing| true }
498
- @preview_features = Flipper.register(:preview_features) { |_thing| true }
499
- @not_enabled = Flipper.register(:not_enabled) { |_thing| true }
500
- @disabled = Flipper.register(:disabled) { |_thing| true }
559
+ @staff = Flipper.register(:staff) { |actor| true }
560
+ @preview_features = Flipper.register(:preview_features) { |actor| true }
561
+ @not_enabled = Flipper.register(:not_enabled) { |actor| true }
562
+ @disabled = Flipper.register(:disabled) { |actor| true }
501
563
  subject.enable @staff
502
564
  subject.enable @preview_features
503
565
  subject.disable @disabled
@@ -649,6 +711,361 @@ RSpec.describe Flipper::Feature do
649
711
  end
650
712
  end
651
713
 
714
+ describe '#expression' do
715
+ it "returns nil if feature has no expression" do
716
+ expect(subject.expression).to be(nil)
717
+ end
718
+
719
+ it "returns expression if feature has expression" do
720
+ expression = Flipper.property(:plan).eq("basic")
721
+ subject.enable_expression expression
722
+ expect(subject.expression).to eq(expression)
723
+ end
724
+ end
725
+
726
+ describe '#enable_expression/disable_expression' do
727
+ context "with expression instance" do
728
+ it "updates gate values to equal expression or clears expression" do
729
+ expression = Flipper.property(:plan).eq("basic")
730
+ expect(subject.gate_values.expression).to be(nil)
731
+ subject.enable_expression(expression)
732
+ expect(subject.gate_values.expression).to eq(expression.value)
733
+ subject.disable_expression
734
+ expect(subject.gate_values.expression).to be(nil)
735
+ end
736
+ end
737
+
738
+ context "with Hash" do
739
+ it "updates gate values to equal expression or clears expression" do
740
+ expression = Flipper.property(:plan).eq("basic")
741
+ expect(subject.gate_values.expression).to be(nil)
742
+ subject.enable_expression(expression.value)
743
+ expect(subject.gate_values.expression).to eq(expression.value)
744
+ subject.disable_expression
745
+ expect(subject.gate_values.expression).to be(nil)
746
+ end
747
+ end
748
+ end
749
+
750
+ describe "#add_expression" do
751
+ context "when nothing enabled" do
752
+ context "with Expression instance" do
753
+ it "sets expression to Expression" do
754
+ expression = Flipper.property(:plan).eq("basic")
755
+ subject.add_expression(expression)
756
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
757
+ expect(subject.expression).to eq(expression)
758
+ end
759
+ end
760
+
761
+ context "with Any instance" do
762
+ it "sets expression to Any" do
763
+ expression = Flipper.any(Flipper.property(:plan).eq("basic"))
764
+ subject.add_expression(expression)
765
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
766
+ expect(subject.expression).to eq(expression)
767
+ end
768
+ end
769
+
770
+ context "with All instance" do
771
+ it "sets expression to All" do
772
+ expression = Flipper.all(Flipper.property(:plan).eq("basic"))
773
+ subject.add_expression(expression)
774
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
775
+ expect(subject.expression).to eq(expression)
776
+ end
777
+ end
778
+ end
779
+
780
+ context "when Expression enabled" do
781
+ let(:expression) { Flipper.property(:plan).eq("basic") }
782
+
783
+ before do
784
+ subject.enable_expression expression
785
+ end
786
+
787
+ context "with Expression instance" do
788
+ it "changes expression to Any and adds new Expression" do
789
+ new_expression = Flipper.property(:age).gte(21)
790
+ subject.add_expression(new_expression)
791
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
792
+ expect(subject.expression.args).to include(expression)
793
+ expect(subject.expression.args).to include(new_expression)
794
+ end
795
+ end
796
+
797
+ context "with Any instance" do
798
+ it "changes expression to Any and adds new Any" do
799
+ new_expression = Flipper.any(Flipper.property(:age).eq(21))
800
+ subject.add_expression new_expression
801
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
802
+ expect(subject.expression.args).to include(expression)
803
+ expect(subject.expression.args).to include(new_expression)
804
+ end
805
+ end
806
+
807
+ context "with All instance" do
808
+ it "changes expression to Any and adds new All" do
809
+ new_expression = Flipper.all(Flipper.property(:plan).eq("basic"))
810
+ subject.add_expression new_expression
811
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
812
+ expect(subject.expression.args).to include(expression)
813
+ expect(subject.expression.args).to include(new_expression)
814
+ end
815
+ end
816
+ end
817
+
818
+ context "when Any enabled" do
819
+ let(:condition) { Flipper.property(:plan).eq("basic") }
820
+ let(:expression) { Flipper.any(condition) }
821
+
822
+ before do
823
+ subject.enable_expression expression
824
+ end
825
+
826
+ context "with Expression instance" do
827
+ it "adds Expression to Any" do
828
+ new_expression = Flipper.property(:age).gte(21)
829
+ subject.add_expression(new_expression)
830
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
831
+ expect(subject.expression.args).to include(condition)
832
+ expect(subject.expression.args).to include(new_expression)
833
+ end
834
+ end
835
+
836
+ context "with Any instance" do
837
+ it "adds Any to Any" do
838
+ new_expression = Flipper.any(Flipper.property(:age).gte(21))
839
+ subject.add_expression(new_expression)
840
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
841
+ expect(subject.expression.args).to include(condition)
842
+ expect(subject.expression.args).to include(new_expression)
843
+ end
844
+ end
845
+
846
+ context "with All instance" do
847
+ it "adds All to Any" do
848
+ new_expression = Flipper.all(Flipper.property(:age).gte(21))
849
+ subject.add_expression(new_expression)
850
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
851
+ expect(subject.expression.args).to include(condition)
852
+ expect(subject.expression.args).to include(new_expression)
853
+ end
854
+ end
855
+ end
856
+
857
+ context "when All enabled" do
858
+ let(:condition) { Flipper.property(:plan).eq("basic") }
859
+ let(:expression) { Flipper.all(condition) }
860
+
861
+ before do
862
+ subject.enable_expression expression
863
+ end
864
+
865
+ context "with Expression instance" do
866
+ it "adds Expression to All" do
867
+ new_expression = Flipper.property(:age).gte(21)
868
+ subject.add_expression(new_expression)
869
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
870
+ expect(subject.expression.args).to include(condition)
871
+ expect(subject.expression.args).to include(new_expression)
872
+ end
873
+ end
874
+
875
+ context "with Any instance" do
876
+ it "adds Any to All" do
877
+ new_expression = Flipper.any(Flipper.property(:age).gte(21))
878
+ subject.add_expression(new_expression)
879
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
880
+ expect(subject.expression.args).to include(condition)
881
+ expect(subject.expression.args).to include(new_expression)
882
+ end
883
+ end
884
+
885
+ context "with All instance" do
886
+ it "adds All to All" do
887
+ new_expression = Flipper.all(Flipper.property(:age).gte(21))
888
+ subject.add_expression(new_expression)
889
+ expect(subject.expression).to be_instance_of(Flipper::Expression)
890
+ expect(subject.expression.args).to include(condition)
891
+ expect(subject.expression.args).to include(new_expression)
892
+ end
893
+ end
894
+ end
895
+ end
896
+
897
+ describe '#remove_expression' do
898
+ context "when nothing enabled" do
899
+ context "with Expression instance" do
900
+ it "does nothing" do
901
+ expression = Flipper.property(:plan).eq("basic")
902
+ subject.remove_expression(expression)
903
+ expect(subject.expression).to be(nil)
904
+ end
905
+ end
906
+
907
+ context "with Any instance" do
908
+ it "does nothing" do
909
+ expression = Flipper.any(Flipper.property(:plan).eq("basic"))
910
+ subject.remove_expression expression
911
+ expect(subject.expression).to be(nil)
912
+ end
913
+ end
914
+
915
+ context "with All instance" do
916
+ it "does nothing" do
917
+ expression = Flipper.all(Flipper.property(:plan).eq("basic"))
918
+ subject.remove_expression expression
919
+ expect(subject.expression).to be(nil)
920
+ end
921
+ end
922
+ end
923
+
924
+ context "when Expression enabled" do
925
+ let(:expression) { Flipper.property(:plan).eq("basic") }
926
+
927
+ before do
928
+ subject.enable_expression expression
929
+ end
930
+
931
+ context "with Expression instance" do
932
+ it "changes expression to Any and removes Expression if it matches" do
933
+ new_expression = Flipper.property(:plan).eq("basic")
934
+ subject.remove_expression new_expression
935
+ expect(subject.expression).to eq(Flipper.any)
936
+ end
937
+
938
+ it "changes expression to Any if Expression doesn't match" do
939
+ new_expression = Flipper.property(:plan).eq("premium")
940
+ subject.remove_expression new_expression
941
+ expect(subject.expression).to eq(Flipper.any(expression))
942
+ end
943
+ end
944
+
945
+ context "with Any instance" do
946
+ it "changes expression to Any and does nothing" do
947
+ new_expression = Flipper.any(Flipper.property(:plan).eq("basic"))
948
+ subject.remove_expression new_expression
949
+ expect(subject.expression).to eq(Flipper.any(expression))
950
+ end
951
+ end
952
+
953
+ context "with All instance" do
954
+ it "changes expression to Any and does nothing" do
955
+ new_expression = Flipper.all(Flipper.property(:plan).eq("basic"))
956
+ subject.remove_expression new_expression
957
+ expect(subject.expression).to eq(Flipper.any(expression))
958
+ end
959
+ end
960
+ end
961
+
962
+ context "when Any enabled" do
963
+ let(:condition) { Flipper.property(:plan).eq("basic") }
964
+ let(:expression) { Flipper.any condition }
965
+
966
+ before do
967
+ subject.enable_expression expression
968
+ end
969
+
970
+ context "with Expression instance" do
971
+ it "removes Expression if it matches" do
972
+ subject.remove_expression condition
973
+ expect(subject.expression).to eq(Flipper.any)
974
+ end
975
+
976
+ it "does nothing if Expression does not match" do
977
+ subject.remove_expression Flipper.property(:plan).eq("premium")
978
+ expect(subject.expression).to eq(expression)
979
+ end
980
+ end
981
+
982
+ context "with Any instance" do
983
+ it "removes Any if it matches" do
984
+ new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
985
+ subject.add_expression new_expression
986
+ expect(subject.expression.args.size).to be(2)
987
+ subject.remove_expression new_expression
988
+ expect(subject.expression).to eq(expression)
989
+ end
990
+
991
+ it "does nothing if Any does not match" do
992
+ new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
993
+ subject.remove_expression new_expression
994
+ expect(subject.expression).to eq(expression)
995
+ end
996
+ end
997
+
998
+ context "with All instance" do
999
+ it "removes All if it matches" do
1000
+ new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
1001
+ subject.add_expression new_expression
1002
+ expect(subject.expression.args.size).to be(2)
1003
+ subject.remove_expression new_expression
1004
+ expect(subject.expression).to eq(expression)
1005
+ end
1006
+
1007
+ it "does nothing if All does not match" do
1008
+ new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
1009
+ subject.remove_expression new_expression
1010
+ expect(subject.expression).to eq(expression)
1011
+ end
1012
+ end
1013
+ end
1014
+
1015
+ context "when All enabled" do
1016
+ let(:condition) { Flipper.property(:plan).eq("basic") }
1017
+ let(:expression) { Flipper.all condition }
1018
+
1019
+ before do
1020
+ subject.enable_expression expression
1021
+ end
1022
+
1023
+ context "with Expression instance" do
1024
+ it "removes Expression if it matches" do
1025
+ subject.remove_expression condition
1026
+ expect(subject.expression).to eq(Flipper.all)
1027
+ end
1028
+
1029
+ it "does nothing if Expression does not match" do
1030
+ subject.remove_expression Flipper.property(:plan).eq("premium")
1031
+ expect(subject.expression).to eq(expression)
1032
+ end
1033
+ end
1034
+
1035
+ context "with Any instance" do
1036
+ it "removes Any if it matches" do
1037
+ new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
1038
+ subject.add_expression new_expression
1039
+ expect(subject.expression.args.size).to be(2)
1040
+ subject.remove_expression new_expression
1041
+ expect(subject.expression).to eq(expression)
1042
+ end
1043
+
1044
+ it "does nothing if Any does not match" do
1045
+ new_expression = Flipper.any(Flipper.property(:plan).eq("premium"))
1046
+ subject.remove_expression new_expression
1047
+ expect(subject.expression).to eq(expression)
1048
+ end
1049
+ end
1050
+
1051
+ context "with All instance" do
1052
+ it "removes All if it matches" do
1053
+ new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
1054
+ subject.add_expression new_expression
1055
+ expect(subject.expression.args.size).to be(2)
1056
+ subject.remove_expression new_expression
1057
+ expect(subject.expression).to eq(expression)
1058
+ end
1059
+
1060
+ it "does nothing if All does not match" do
1061
+ new_expression = Flipper.all(Flipper.property(:plan).eq("premium"))
1062
+ subject.remove_expression new_expression
1063
+ expect(subject.expression).to eq(expression)
1064
+ end
1065
+ end
1066
+ end
1067
+ end
1068
+
652
1069
  describe '#enable_actor/disable_actor' do
653
1070
  context 'with object that responds to flipper_id' do
654
1071
  it 'updates the gate values to include the actor' do
@@ -677,8 +1094,6 @@ RSpec.describe Flipper::Feature do
677
1094
  describe '#enable_group/disable_group' do
678
1095
  context 'with symbol group name' do
679
1096
  it 'updates the gate values to include the group' do
680
- actor = Flipper::Actor.new(5)
681
- group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
682
1097
  expect(subject.gate_values.groups).to be_empty
683
1098
  subject.enable_group(:five_only)
684
1099
  expect(subject.gate_values.groups).to eq(Set['five_only'])
@@ -689,8 +1104,6 @@ RSpec.describe Flipper::Feature do
689
1104
 
690
1105
  context 'with string group name' do
691
1106
  it 'updates the gate values to include the group' do
692
- actor = Flipper::Actor.new(5)
693
- group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
694
1107
  expect(subject.gate_values.groups).to be_empty
695
1108
  subject.enable_group('five_only')
696
1109
  expect(subject.gate_values.groups).to eq(Set['five_only'])
@@ -701,7 +1114,6 @@ RSpec.describe Flipper::Feature do
701
1114
 
702
1115
  context 'with group instance' do
703
1116
  it 'updates the gate values for the group' do
704
- actor = Flipper::Actor.new(5)
705
1117
  group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
706
1118
  expect(subject.gate_values.groups).to be_empty
707
1119
  subject.enable_group(group)
@@ -801,12 +1213,14 @@ RSpec.describe Flipper::Feature do
801
1213
  :actor,
802
1214
  :boolean,
803
1215
  :group,
1216
+ :expression,
804
1217
  ])
805
1218
 
806
1219
  expect(subject.disabled_gate_names.to_set).to eq(Set[
807
1220
  :actor,
808
1221
  :boolean,
809
1222
  :group,
1223
+ :expression,
810
1224
  ])
811
1225
  end
812
1226
  end
@@ -80,13 +80,13 @@ RSpec.describe Flipper::GateValues do
80
80
  it 'raises argument error for percentage of time value that cannot be converted to an integer' do
81
81
  expect do
82
82
  described_class.new(percentage_of_time: ['asdf'])
83
- end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to an integer))
83
+ end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to a number))
84
84
  end
85
85
 
86
86
  it 'raises argument error for percentage of actors value that cannot be converted to an int' do
87
87
  expect do
88
88
  described_class.new(percentage_of_actors: ['asdf'])
89
- end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to an integer))
89
+ end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to a number))
90
90
  end
91
91
 
92
92
  it 'raises argument error for actors value that cannot be converted to a set' do
@@ -100,35 +100,4 @@ RSpec.describe Flipper::GateValues do
100
100
  described_class.new(groups: 'asdf')
101
101
  end.to raise_error(ArgumentError, %("asdf" cannot be converted to a set))
102
102
  end
103
-
104
- describe '#[]' do
105
- it 'can read the boolean value' do
106
- expect(described_class.new(boolean: true)[:boolean]).to be(true)
107
- expect(described_class.new(boolean: true)['boolean']).to be(true)
108
- end
109
-
110
- it 'can read the actors value' do
111
- expect(described_class.new(actors: Set[1, 2])[:actors]).to eq(Set[1, 2])
112
- expect(described_class.new(actors: Set[1, 2])['actors']).to eq(Set[1, 2])
113
- end
114
-
115
- it 'can read the groups value' do
116
- expect(described_class.new(groups: Set[:admins])[:groups]).to eq(Set[:admins])
117
- expect(described_class.new(groups: Set[:admins])['groups']).to eq(Set[:admins])
118
- end
119
-
120
- it 'can read the percentage of time value' do
121
- expect(described_class.new(percentage_of_time: 15)[:percentage_of_time]).to eq(15)
122
- expect(described_class.new(percentage_of_time: 15)['percentage_of_time']).to eq(15)
123
- end
124
-
125
- it 'can read the percentage of actors value' do
126
- expect(described_class.new(percentage_of_actors: 15)[:percentage_of_actors]).to eq(15)
127
- expect(described_class.new(percentage_of_actors: 15)['percentage_of_actors']).to eq(15)
128
- end
129
-
130
- it 'returns nil for value that is not present' do
131
- expect(described_class.new({})['not legit']).to be(nil)
132
- end
133
- end
134
103
  end
@@ -9,7 +9,7 @@ RSpec.describe Flipper::Gates::Boolean do
9
9
  Flipper::FeatureCheckContext.new(
10
10
  feature_name: feature_name,
11
11
  values: Flipper::GateValues.new(boolean: bool),
12
- thing: Flipper::Types::Actor.new(Flipper::Actor.new(1))
12
+ actors: [Flipper::Types::Actor.new(Flipper::Actor.new('1'))]
13
13
  )
14
14
  end
15
15