flipper 1.3.5 → 1.4.1

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +9 -6
  3. data/.github/workflows/examples.yml +5 -4
  4. data/.github/workflows/release.yml +54 -0
  5. data/.superset/config.json +4 -0
  6. data/CLAUDE.md +93 -0
  7. data/README.md +4 -3
  8. data/examples/cloud/poll_interval/README.md +111 -0
  9. data/examples/cloud/poll_interval/client.rb +108 -0
  10. data/examples/cloud/poll_interval/server.rb +98 -0
  11. data/examples/expressions.rb +35 -11
  12. data/lib/flipper/adapter.rb +17 -1
  13. data/lib/flipper/adapters/actor_limit.rb +27 -1
  14. data/lib/flipper/adapters/cache_base.rb +21 -3
  15. data/lib/flipper/adapters/dual_write.rb +6 -2
  16. data/lib/flipper/adapters/failover.rb +9 -3
  17. data/lib/flipper/adapters/failsafe.rb +2 -2
  18. data/lib/flipper/adapters/http/client.rb +15 -4
  19. data/lib/flipper/adapters/http.rb +37 -2
  20. data/lib/flipper/adapters/instrumented.rb +2 -2
  21. data/lib/flipper/adapters/memoizable.rb +3 -3
  22. data/lib/flipper/adapters/memory.rb +1 -1
  23. data/lib/flipper/adapters/pstore.rb +1 -1
  24. data/lib/flipper/adapters/strict.rb +30 -0
  25. data/lib/flipper/adapters/sync/feature_synchronizer.rb +5 -1
  26. data/lib/flipper/adapters/sync/synchronizer.rb +13 -5
  27. data/lib/flipper/adapters/sync.rb +7 -3
  28. data/lib/flipper/cli.rb +51 -0
  29. data/lib/flipper/cloud/configuration.rb +9 -4
  30. data/lib/flipper/cloud/dsl.rb +2 -2
  31. data/lib/flipper/cloud/middleware.rb +1 -1
  32. data/lib/flipper/cloud/migrate.rb +71 -0
  33. data/lib/flipper/cloud/telemetry.rb +1 -1
  34. data/lib/flipper/cloud.rb +1 -0
  35. data/lib/flipper/dsl.rb +1 -1
  36. data/lib/flipper/expressions/feature_enabled.rb +34 -0
  37. data/lib/flipper/expressions/time.rb +8 -1
  38. data/lib/flipper/gates/expression.rb +2 -2
  39. data/lib/flipper/poller.rb +52 -9
  40. data/lib/flipper/version.rb +1 -1
  41. data/lib/flipper.rb +17 -1
  42. data/spec/flipper/adapter_spec.rb +20 -0
  43. data/spec/flipper/adapters/actor_limit_spec.rb +55 -0
  44. data/spec/flipper/adapters/dual_write_spec.rb +13 -0
  45. data/spec/flipper/adapters/failover_spec.rb +12 -0
  46. data/spec/flipper/adapters/http_spec.rb +240 -0
  47. data/spec/flipper/adapters/strict_spec.rb +62 -4
  48. data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +12 -0
  49. data/spec/flipper/adapters/sync/synchronizer_spec.rb +87 -0
  50. data/spec/flipper/adapters/sync_spec.rb +13 -0
  51. data/spec/flipper/cli_spec.rb +51 -0
  52. data/spec/flipper/cloud/configuration_spec.rb +6 -0
  53. data/spec/flipper/cloud/dsl_spec.rb +10 -2
  54. data/spec/flipper/cloud/middleware_spec.rb +34 -16
  55. data/spec/flipper/cloud/migrate_spec.rb +160 -0
  56. data/spec/flipper/cloud/telemetry_spec.rb +1 -1
  57. data/spec/flipper/engine_spec.rb +2 -2
  58. data/spec/flipper/expressions/time_spec.rb +16 -0
  59. data/spec/flipper/gates/expression_spec.rb +82 -0
  60. data/spec/flipper/middleware/memoizer_spec.rb +37 -6
  61. data/spec/flipper/poller_spec.rb +347 -4
  62. data/spec/flipper_integration_spec.rb +133 -0
  63. data/spec/flipper_spec.rb +6 -1
  64. data/spec/spec_helper.rb +7 -0
  65. metadata +18 -112
  66. data/lib/flipper/expressions/duration.rb +0 -28
  67. data/spec/flipper/expressions/duration_spec.rb +0 -43
@@ -600,6 +600,139 @@ RSpec.describe Flipper do
600
600
  end
601
601
  end
602
602
 
603
+ context "for FeatureEnabled" do
604
+ before do
605
+ @original_instance = Flipper.instance
606
+ Flipper.instance = flipper
607
+ end
608
+
609
+ after do
610
+ Flipper.instance = @original_instance
611
+ Thread.current[:flipper_evaluating_features] = nil
612
+ end
613
+
614
+ it "returns true when referenced feature is enabled" do
615
+ flipper.enable(:search_beta)
616
+ feature.enable Flipper.feature_enabled(:search_beta)
617
+
618
+ expect(feature.enabled?).to be(true)
619
+ end
620
+
621
+ it "returns false when referenced feature is disabled" do
622
+ feature.enable Flipper.feature_enabled(:search_beta)
623
+
624
+ expect(feature.enabled?).to be(false)
625
+ end
626
+
627
+ it "passes actor through to referenced feature" do
628
+ flipper.enable_actor(:search_beta, pitt)
629
+ feature.enable Flipper.feature_enabled(:search_beta)
630
+
631
+ expect(feature.enabled?(pitt)).to be(true)
632
+ expect(feature.enabled?(clooney)).to be(false)
633
+ end
634
+
635
+ it "returns false on circular dependency" do
636
+ feature.enable Flipper.feature_enabled(:other)
637
+ flipper[:other].enable Flipper.feature_enabled(:search)
638
+
639
+ expect(feature.enabled?).to be(false)
640
+ end
641
+
642
+ it "returns false on self-reference" do
643
+ feature.enable Flipper.feature_enabled(:search)
644
+
645
+ expect(feature.enabled?).to be(false)
646
+ end
647
+
648
+ it "cleans up thread-local state after evaluation" do
649
+ flipper.enable(:search_beta)
650
+ feature.enable Flipper.feature_enabled(:search_beta)
651
+
652
+ feature.enabled?
653
+ evaluating = Thread.current[:flipper_evaluating_features]
654
+ expect(evaluating.nil? || evaluating.empty?).to be(true)
655
+ end
656
+
657
+ it "cleans up thread-local state after self-reference" do
658
+ feature.enable Flipper.feature_enabled(:search)
659
+
660
+ feature.enabled?
661
+ evaluating = Thread.current[:flipper_evaluating_features]
662
+ expect(evaluating.nil? || evaluating.empty?).to be(true)
663
+ end
664
+
665
+ it "cleans up thread-local state after circular dependency" do
666
+ feature.enable Flipper.feature_enabled(:other)
667
+ flipper[:other].enable Flipper.feature_enabled(:search)
668
+
669
+ feature.enabled?
670
+ evaluating = Thread.current[:flipper_evaluating_features]
671
+ expect(evaluating.nil? || evaluating.empty?).to be(true)
672
+ end
673
+ end
674
+
675
+ context "for FeatureDisabled" do
676
+ before do
677
+ @original_instance = Flipper.instance
678
+ Flipper.instance = flipper
679
+ end
680
+
681
+ after do
682
+ Flipper.instance = @original_instance
683
+ Thread.current[:flipper_evaluating_features] = nil
684
+ end
685
+
686
+ it "returns true when referenced feature is disabled" do
687
+ feature.enable Flipper.feature_disabled(:old_checkout)
688
+
689
+ expect(feature.enabled?).to be(true)
690
+ end
691
+
692
+ it "returns false when referenced feature is enabled" do
693
+ flipper.enable(:old_checkout)
694
+ feature.enable Flipper.feature_disabled(:old_checkout)
695
+
696
+ expect(feature.enabled?).to be(false)
697
+ end
698
+ end
699
+
700
+ context "for FeatureEnabled composed with other expressions" do
701
+ before do
702
+ @original_instance = Flipper.instance
703
+ Flipper.instance = flipper
704
+ end
705
+
706
+ after do
707
+ Flipper.instance = @original_instance
708
+ Thread.current[:flipper_evaluating_features] = nil
709
+ end
710
+
711
+ it "works with Any" do
712
+ feature.enable Flipper.any(
713
+ Flipper.feature_enabled(:beta_program),
714
+ Flipper.property(:plan).eq("basic")
715
+ )
716
+
717
+ expect(feature.enabled?(basic_plan_actor)).to be(true)
718
+ expect(feature.enabled?(premium_plan_actor)).to be(false)
719
+
720
+ flipper.enable(:beta_program)
721
+ expect(feature.enabled?(premium_plan_actor)).to be(true)
722
+ end
723
+
724
+ it "works with All" do
725
+ flipper.enable(:basic_search)
726
+ feature.enable Flipper.all(
727
+ Flipper.feature_enabled(:basic_search),
728
+ Flipper.property(:plan).eq("basic")
729
+ )
730
+
731
+ expect(feature.enabled?(basic_plan_actor)).to be(true)
732
+ expect(feature.enabled?(premium_plan_actor)).to be(false)
733
+ end
734
+ end
735
+
603
736
  context "for All" do
604
737
  it "works" do
605
738
  true_actor = Flipper::Actor.new("User;1", {
data/spec/flipper_spec.rb CHANGED
@@ -218,6 +218,11 @@ RSpec.describe Flipper do
218
218
  expect(described_class.adapter).to eq(described_class.instance.adapter)
219
219
  end
220
220
 
221
+ it 'delegates adapter_stack to instance' do
222
+ expect(described_class.adapter_stack).to eq(described_class.instance.adapter_stack)
223
+ expect(described_class.adapter_stack).to eq("memoizable -> memory")
224
+ end
225
+
221
226
  it 'delegates memoize= to instance' do
222
227
  expect(described_class.adapter.memoizing?).to be(false)
223
228
  described_class.memoize = true
@@ -254,7 +259,7 @@ RSpec.describe Flipper do
254
259
  end
255
260
  described_class.sync
256
261
  expect(described_class.sync_secret).to eq("tasty")
257
- expect(stub).to have_been_requested
262
+ expect(stub).to have_been_made.at_least_once
258
263
  end
259
264
  end
260
265
 
data/spec/spec_helper.rb CHANGED
@@ -39,6 +39,13 @@ RSpec.configure do |config|
39
39
  Flipper.configuration = nil
40
40
  end
41
41
 
42
+ config.after(:example) do
43
+ # Stop any pollers started during the test BEFORE WebMock clears stubs
44
+ # in its own after hook (RSpec runs after hooks in reverse registration
45
+ # order, so ours runs first since webmock/rspec was required before this).
46
+ Flipper::Poller.reset if defined?(Flipper::Poller)
47
+ end
48
+
42
49
  config.disable_monkey_patching!
43
50
 
44
51
  config.filter_run focus: true
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.5
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-03-25 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: concurrent-ruby
@@ -23,6 +24,7 @@ dependencies:
23
24
  - - "<"
24
25
  - !ruby/object:Gem::Version
25
26
  version: '2'
27
+ description:
26
28
  email: support@flippercloud.io
27
29
  executables:
28
30
  - flipper
@@ -34,7 +36,10 @@ files:
34
36
  - ".github/dependabot.yml"
35
37
  - ".github/workflows/ci.yml"
36
38
  - ".github/workflows/examples.yml"
39
+ - ".github/workflows/release.yml"
37
40
  - ".rspec"
41
+ - ".superset/config.json"
42
+ - CLAUDE.md
38
43
  - CODE_OF_CONDUCT.md
39
44
  - Changelog.md
40
45
  - Dockerfile
@@ -62,6 +67,9 @@ files:
62
67
  - examples/cloud/cloud_setup.rb
63
68
  - examples/cloud/forked.rb
64
69
  - examples/cloud/import.rb
70
+ - examples/cloud/poll_interval/README.md
71
+ - examples/cloud/poll_interval/client.rb
72
+ - examples/cloud/poll_interval/server.rb
65
73
  - examples/cloud/threaded.rb
66
74
  - examples/configuring_default.rb
67
75
  - examples/dsl.rb
@@ -116,6 +124,7 @@ files:
116
124
  - lib/flipper/cloud/dsl.rb
117
125
  - lib/flipper/cloud/message_verifier.rb
118
126
  - lib/flipper/cloud/middleware.rb
127
+ - lib/flipper/cloud/migrate.rb
119
128
  - lib/flipper/cloud/routes.rb
120
129
  - lib/flipper/cloud/telemetry.rb
121
130
  - lib/flipper/cloud/telemetry/backoff_policy.rb
@@ -138,8 +147,8 @@ files:
138
147
  - lib/flipper/expressions/any.rb
139
148
  - lib/flipper/expressions/boolean.rb
140
149
  - lib/flipper/expressions/comparable.rb
141
- - lib/flipper/expressions/duration.rb
142
150
  - lib/flipper/expressions/equal.rb
151
+ - lib/flipper/expressions/feature_enabled.rb
143
152
  - lib/flipper/expressions/greater_than.rb
144
153
  - lib/flipper/expressions/greater_than_or_equal_to.rb
145
154
  - lib/flipper/expressions/less_than.rb
@@ -226,6 +235,7 @@ files:
226
235
  - spec/flipper/cloud/dsl_spec.rb
227
236
  - spec/flipper/cloud/message_verifier_spec.rb
228
237
  - spec/flipper/cloud/middleware_spec.rb
238
+ - spec/flipper/cloud/migrate_spec.rb
229
239
  - spec/flipper/cloud/telemetry/backoff_policy_spec.rb
230
240
  - spec/flipper/cloud/telemetry/metric_spec.rb
231
241
  - spec/flipper/cloud/telemetry/metric_storage_spec.rb
@@ -244,7 +254,6 @@ files:
244
254
  - spec/flipper/expressions/all_spec.rb
245
255
  - spec/flipper/expressions/any_spec.rb
246
256
  - spec/flipper/expressions/boolean_spec.rb
247
- - spec/flipper/expressions/duration_spec.rb
248
257
  - spec/flipper/expressions/equal_spec.rb
249
258
  - spec/flipper/expressions/greater_than_or_equal_to_spec.rb
250
259
  - spec/flipper/expressions/greater_than_spec.rb
@@ -314,8 +323,9 @@ metadata:
314
323
  homepage_uri: https://www.flippercloud.io
315
324
  source_code_uri: https://github.com/flippercloud/flipper
316
325
  bug_tracker_uri: https://github.com/flippercloud/flipper/issues
317
- changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.3.5
326
+ changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.4.1
318
327
  funding_uri: https://github.com/sponsors/flippercloud
328
+ post_install_message:
319
329
  rdoc_options: []
320
330
  require_paths:
321
331
  - lib
@@ -330,112 +340,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
330
340
  - !ruby/object:Gem::Version
331
341
  version: '0'
332
342
  requirements: []
333
- rubygems_version: 3.6.9
343
+ rubygems_version: 3.5.22
344
+ signing_key:
334
345
  specification_version: 4
335
346
  summary: Beautiful, performant feature flags for Ruby and Rails.
336
- test_files:
337
- - spec/fixtures/environment.rb
338
- - spec/fixtures/feature.json
339
- - spec/fixtures/flipper_pstore_1679087600.json
340
- - spec/flipper/actor_spec.rb
341
- - spec/flipper/adapter_builder_spec.rb
342
- - spec/flipper/adapter_spec.rb
343
- - spec/flipper/adapters/actor_limit_spec.rb
344
- - spec/flipper/adapters/dual_write_spec.rb
345
- - spec/flipper/adapters/failover_spec.rb
346
- - spec/flipper/adapters/failsafe_spec.rb
347
- - spec/flipper/adapters/http/client_spec.rb
348
- - spec/flipper/adapters/http_spec.rb
349
- - spec/flipper/adapters/instrumented_spec.rb
350
- - spec/flipper/adapters/memoizable_spec.rb
351
- - spec/flipper/adapters/memory_spec.rb
352
- - spec/flipper/adapters/operation_logger_spec.rb
353
- - spec/flipper/adapters/poll_spec.rb
354
- - spec/flipper/adapters/pstore_spec.rb
355
- - spec/flipper/adapters/read_only_spec.rb
356
- - spec/flipper/adapters/strict_spec.rb
357
- - spec/flipper/adapters/sync/feature_synchronizer_spec.rb
358
- - spec/flipper/adapters/sync/interval_synchronizer_spec.rb
359
- - spec/flipper/adapters/sync/synchronizer_spec.rb
360
- - spec/flipper/adapters/sync_spec.rb
361
- - spec/flipper/cli_spec.rb
362
- - spec/flipper/cloud/configuration_spec.rb
363
- - spec/flipper/cloud/dsl_spec.rb
364
- - spec/flipper/cloud/message_verifier_spec.rb
365
- - spec/flipper/cloud/middleware_spec.rb
366
- - spec/flipper/cloud/telemetry/backoff_policy_spec.rb
367
- - spec/flipper/cloud/telemetry/metric_spec.rb
368
- - spec/flipper/cloud/telemetry/metric_storage_spec.rb
369
- - spec/flipper/cloud/telemetry/submitter_spec.rb
370
- - spec/flipper/cloud/telemetry_spec.rb
371
- - spec/flipper/cloud_spec.rb
372
- - spec/flipper/configuration_spec.rb
373
- - spec/flipper/dsl_spec.rb
374
- - spec/flipper/engine_spec.rb
375
- - spec/flipper/export_spec.rb
376
- - spec/flipper/exporter_spec.rb
377
- - spec/flipper/exporters/json/export_spec.rb
378
- - spec/flipper/exporters/json/v1_spec.rb
379
- - spec/flipper/expression/builder_spec.rb
380
- - spec/flipper/expression_spec.rb
381
- - spec/flipper/expressions/all_spec.rb
382
- - spec/flipper/expressions/any_spec.rb
383
- - spec/flipper/expressions/boolean_spec.rb
384
- - spec/flipper/expressions/duration_spec.rb
385
- - spec/flipper/expressions/equal_spec.rb
386
- - spec/flipper/expressions/greater_than_or_equal_to_spec.rb
387
- - spec/flipper/expressions/greater_than_spec.rb
388
- - spec/flipper/expressions/less_than_or_equal_to_spec.rb
389
- - spec/flipper/expressions/less_than_spec.rb
390
- - spec/flipper/expressions/not_equal_spec.rb
391
- - spec/flipper/expressions/now_spec.rb
392
- - spec/flipper/expressions/number_spec.rb
393
- - spec/flipper/expressions/percentage_of_actors_spec.rb
394
- - spec/flipper/expressions/percentage_spec.rb
395
- - spec/flipper/expressions/property_spec.rb
396
- - spec/flipper/expressions/random_spec.rb
397
- - spec/flipper/expressions/string_spec.rb
398
- - spec/flipper/expressions/time_spec.rb
399
- - spec/flipper/feature_check_context_spec.rb
400
- - spec/flipper/feature_spec.rb
401
- - spec/flipper/gate_spec.rb
402
- - spec/flipper/gate_values_spec.rb
403
- - spec/flipper/gates/actor_spec.rb
404
- - spec/flipper/gates/boolean_spec.rb
405
- - spec/flipper/gates/expression_spec.rb
406
- - spec/flipper/gates/group_spec.rb
407
- - spec/flipper/gates/percentage_of_actors_spec.rb
408
- - spec/flipper/gates/percentage_of_time_spec.rb
409
- - spec/flipper/identifier_spec.rb
410
- - spec/flipper/instrumentation/log_subscriber_spec.rb
411
- - spec/flipper/instrumentation/statsd_subscriber_spec.rb
412
- - spec/flipper/instrumenters/memory_spec.rb
413
- - spec/flipper/instrumenters/noop_spec.rb
414
- - spec/flipper/middleware/memoizer_spec.rb
415
- - spec/flipper/middleware/setup_env_spec.rb
416
- - spec/flipper/model/active_record_spec.rb
417
- - spec/flipper/poller_spec.rb
418
- - spec/flipper/registry_spec.rb
419
- - spec/flipper/serializers/gzip_spec.rb
420
- - spec/flipper/serializers/json_spec.rb
421
- - spec/flipper/typecast_spec.rb
422
- - spec/flipper/types/actor_spec.rb
423
- - spec/flipper/types/boolean_spec.rb
424
- - spec/flipper/types/group_spec.rb
425
- - spec/flipper/types/percentage_of_actors_spec.rb
426
- - spec/flipper/types/percentage_of_time_spec.rb
427
- - spec/flipper/types/percentage_spec.rb
428
- - spec/flipper_integration_spec.rb
429
- - spec/flipper_spec.rb
430
- - spec/spec_helper.rb
431
- - spec/support/actor_names.yml
432
- - spec/support/descriptions.yml
433
- - spec/support/fail_on_output.rb
434
- - spec/support/fake_backoff_policy.rb
435
- - spec/support/fake_udp_socket.rb
436
- - spec/support/skippable.rb
437
- - spec/support/spec_helpers.rb
438
- - test/adapters/actor_limit_test.rb
439
- - test/adapters/memory_test.rb
440
- - test/adapters/pstore_test.rb
441
- - test/test_helper.rb
347
+ test_files: []
@@ -1,28 +0,0 @@
1
- module Flipper
2
- module Expressions
3
- class Duration
4
- SECONDS_PER = {
5
- "second" => 1,
6
- "minute" => 60,
7
- "hour" => 3600,
8
- "day" => 86400,
9
- "week" => 604_800,
10
- "month" => 2_629_746, # 1/12 of a gregorian year
11
- "year" => 31_556_952 # length of a gregorian year (365.2425 days)
12
- }.freeze
13
-
14
- def self.call(scalar, unit = 'second')
15
- unit = unit.to_s.downcase.chomp("s")
16
-
17
- unless scalar.is_a?(Numeric)
18
- raise ArgumentError.new("Duration value must be a number but was #{scalar.inspect}")
19
- end
20
- unless SECONDS_PER[unit]
21
- raise ArgumentError.new("Duration unit #{unit.inspect} must be one of: #{SECONDS_PER.keys.join(', ')}")
22
- end
23
-
24
- scalar * SECONDS_PER[unit]
25
- end
26
- end
27
- end
28
- end
@@ -1,43 +0,0 @@
1
- RSpec.describe Flipper::Expressions::Duration do
2
- describe "#call" do
3
- it "raises error with invalid value" do
4
- expect { described_class.call(false, 'minute') }.to raise_error(ArgumentError)
5
- end
6
-
7
- it "raises error with invalid unit" do
8
- expect { described_class.call(4, 'score') }.to raise_error(ArgumentError)
9
- end
10
-
11
- it 'defaults unit to seconds' do
12
- expect(described_class.call(10)).to eq(10)
13
- end
14
-
15
- it "evaluates seconds" do
16
- expect(described_class.call(10, 'seconds')).to eq(10)
17
- end
18
-
19
- it "evaluates minutes" do
20
- expect(described_class.call(2, 'minutes')).to eq(120)
21
- end
22
-
23
- it "evaluates hours" do
24
- expect(described_class.call(2, 'hours')).to eq(7200)
25
- end
26
-
27
- it "evaluates days" do
28
- expect(described_class.call(2, 'days')).to eq(172_800)
29
- end
30
-
31
- it "evaluates weeks" do
32
- expect(described_class.call(2, 'weeks')).to eq(1_209_600)
33
- end
34
-
35
- it "evaluates months" do
36
- expect(described_class.call(2, 'months')).to eq(5_259_492)
37
- end
38
-
39
- it "evaluates years" do
40
- expect(described_class.call(2, 'years')).to eq(63_113_904)
41
- end
42
- end
43
- end