flipper 0.4.0 → 0.5.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.
Files changed (59) hide show
  1. data/Guardfile +3 -8
  2. data/README.md +26 -38
  3. data/examples/percentage_of_actors.rb +17 -12
  4. data/examples/percentage_of_random.rb +3 -7
  5. data/lib/flipper.rb +8 -1
  6. data/lib/flipper/adapter.rb +2 -208
  7. data/lib/flipper/adapters/decorator.rb +9 -0
  8. data/lib/flipper/adapters/instrumented.rb +92 -0
  9. data/lib/flipper/adapters/memoizable.rb +88 -0
  10. data/lib/flipper/adapters/memory.rb +89 -7
  11. data/lib/flipper/adapters/operation_logger.rb +31 -45
  12. data/lib/flipper/decorator.rb +6 -0
  13. data/lib/flipper/dsl.rb +29 -2
  14. data/lib/flipper/feature.rb +83 -49
  15. data/lib/flipper/gate.rb +24 -41
  16. data/lib/flipper/gates/actor.rb +24 -24
  17. data/lib/flipper/gates/boolean.rb +28 -15
  18. data/lib/flipper/gates/group.rb +25 -34
  19. data/lib/flipper/gates/percentage_of_actors.rb +21 -13
  20. data/lib/flipper/gates/percentage_of_random.rb +20 -12
  21. data/lib/flipper/instrumentation/log_subscriber.rb +14 -22
  22. data/lib/flipper/middleware/memoizer.rb +23 -0
  23. data/lib/flipper/spec/shared_adapter_specs.rb +141 -92
  24. data/lib/flipper/types/boolean.rb +5 -1
  25. data/lib/flipper/version.rb +1 -1
  26. data/spec/flipper/adapters/instrumented_spec.rb +92 -0
  27. data/spec/flipper/adapters/memoizable_spec.rb +184 -0
  28. data/spec/flipper/adapters/memory_spec.rb +1 -11
  29. data/spec/flipper/adapters/operation_logger_spec.rb +93 -0
  30. data/spec/flipper/dsl_spec.rb +18 -43
  31. data/spec/flipper/feature_spec.rb +25 -9
  32. data/spec/flipper/gate_spec.rb +8 -20
  33. data/spec/flipper/gates/actor_spec.rb +6 -14
  34. data/spec/flipper/gates/boolean_spec.rb +80 -13
  35. data/spec/flipper/gates/group_spec.rb +8 -18
  36. data/spec/flipper/gates/percentage_of_actors_spec.rb +12 -28
  37. data/spec/flipper/gates/percentage_of_random_spec.rb +6 -14
  38. data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -8
  39. data/spec/flipper/instrumentation/metriks_subscriber_spec.rb +3 -6
  40. data/spec/flipper/middleware/{local_cache_spec.rb → memoizer_spec.rb} +25 -55
  41. data/spec/flipper/types/boolean_spec.rb +13 -3
  42. data/spec/flipper_spec.rb +7 -0
  43. data/spec/helper.rb +21 -3
  44. data/spec/integration_spec.rb +115 -116
  45. metadata +17 -27
  46. data/lib/flipper/adapters/memoized.rb +0 -55
  47. data/lib/flipper/key.rb +0 -38
  48. data/lib/flipper/middleware/local_cache.rb +0 -36
  49. data/lib/flipper/toggle.rb +0 -54
  50. data/lib/flipper/toggles/boolean.rb +0 -54
  51. data/lib/flipper/toggles/set.rb +0 -25
  52. data/lib/flipper/toggles/value.rb +0 -25
  53. data/spec/flipper/adapter_spec.rb +0 -463
  54. data/spec/flipper/adapters/memoized_spec.rb +0 -93
  55. data/spec/flipper/key_spec.rb +0 -23
  56. data/spec/flipper/toggle_spec.rb +0 -22
  57. data/spec/flipper/toggles/boolean_spec.rb +0 -40
  58. data/spec/flipper/toggles/set_spec.rb +0 -35
  59. data/spec/flipper/toggles/value_spec.rb +0 -55
@@ -1,33 +1,26 @@
1
1
  require 'helper'
2
2
 
3
3
  describe Flipper::Gate do
4
- let(:adapter) { double('Adapter', :name => 'memory', :read => '22') }
5
- let(:feature) { double('Feature', :name => :search, :adapter => adapter) }
4
+ let(:feature_name) { :stats }
6
5
 
7
6
  subject {
8
- gate = described_class.new(feature)
9
- # implemented in subclass
10
- gate.stub({
11
- :key => :actors,
12
- :description => 'enabled',
13
- })
14
- gate
7
+ described_class.new(feature_name)
15
8
  }
16
9
 
17
10
  describe "#initialize" do
18
- it "sets feature" do
19
- gate = described_class.new(feature)
20
- gate.feature.should be(feature)
11
+ it "sets feature_name" do
12
+ gate = described_class.new(feature_name)
13
+ gate.feature_name.should be(feature_name)
21
14
  end
22
15
 
23
16
  it "defaults instrumenter" do
24
- gate = described_class.new(feature)
17
+ gate = described_class.new(feature_name)
25
18
  gate.instrumenter.should be(Flipper::Instrumenters::Noop)
26
19
  end
27
20
 
28
21
  it "allows overriding instrumenter" do
29
22
  instrumenter = double('Instrumentor')
30
- gate = described_class.new(feature, :instrumenter => instrumenter)
23
+ gate = described_class.new(feature_name, :instrumenter => instrumenter)
31
24
  gate.instrumenter.should be(instrumenter)
32
25
  end
33
26
  end
@@ -36,12 +29,7 @@ describe Flipper::Gate do
36
29
  it "returns easy to read string representation" do
37
30
  string = subject.inspect
38
31
  string.should include('Flipper::Gate')
39
- string.should include('feature=:search')
40
- string.should include('description="enabled"')
41
- string.should include("adapter=#{subject.adapter.name.inspect}")
42
- string.should include('adapter_key=#<Flipper::Key:')
43
- string.should include('toggle_class=Flipper::Toggles::Value')
44
- string.should include('toggle_value="22"')
32
+ string.should include('feature_name=:stats')
45
33
  end
46
34
  end
47
35
  end
@@ -2,18 +2,17 @@ require 'helper'
2
2
  require 'flipper/instrumenters/memory'
3
3
 
4
4
  describe Flipper::Gates::Actor do
5
- let(:adapter) { double('Adapter', :set_members => []) }
6
- let(:feature) { double('Feature', :name => :search, :adapter => adapter) }
7
5
  let(:instrumenter) { Flipper::Instrumenters::Memory.new }
6
+ let(:feature_name) { :search }
8
7
 
9
8
  subject {
10
- described_class.new(feature, :instrumenter => instrumenter)
9
+ described_class.new(feature_name, :instrumenter => instrumenter)
11
10
  }
12
11
 
13
12
  describe "instrumentation" do
14
13
  it "is recorded for open" do
15
14
  thing = Struct.new(:flipper_id).new('22')
16
- subject.open?(thing)
15
+ subject.open?(thing, Set.new)
17
16
 
18
17
  event = instrumenter.events.last
19
18
  event.should_not be_nil
@@ -30,22 +29,15 @@ describe Flipper::Gates::Actor do
30
29
 
31
30
  describe "#description" do
32
31
  context "with actors in set" do
33
- before do
34
- adapter.stub(:set_members => Set['bacon', 'ham'])
35
- end
36
-
37
32
  it "returns text" do
38
- subject.description.should eq("actors (bacon, ham)")
33
+ values = Set['bacon', 'ham']
34
+ subject.description(values).should eq('actors ("bacon", "ham")')
39
35
  end
40
36
  end
41
37
 
42
38
  context "with no actors in set" do
43
- before do
44
- adapter.stub(:set_members => Set.new)
45
- end
46
-
47
39
  it "returns disabled" do
48
- subject.description.should eq('disabled')
40
+ subject.description(Set.new).should eq('disabled')
49
41
  end
50
42
  end
51
43
  end
@@ -2,32 +2,99 @@ require 'helper'
2
2
  require 'flipper/instrumenters/memory'
3
3
 
4
4
  describe Flipper::Gates::Boolean do
5
- let(:adapter) { double('Adapter', :read => nil) }
6
- let(:feature) { double('Feature', :name => :search, :adapter => adapter) }
7
5
  let(:instrumenter) { Flipper::Instrumenters::Memory.new }
6
+ let(:feature_name) { :search }
8
7
 
9
8
  subject {
10
- described_class.new(feature, :instrumenter => instrumenter)
9
+ described_class.new(feature_name, :instrumenter => instrumenter)
11
10
  }
12
11
 
13
12
  describe "#description" do
14
13
  context "for enabled" do
15
- before do
16
- subject.stub(:enabled? => true)
17
- end
18
-
19
14
  it "returns Enabled" do
20
- subject.description.should eq('Enabled')
15
+ subject.description(true).should eq('Enabled')
21
16
  end
22
17
  end
23
18
 
24
19
  context "for disabled" do
25
- before do
26
- subject.stub(:enabled? => false)
20
+ it "returns Disabled" do
21
+ subject.description(false).should eq('Disabled')
22
+ end
23
+ end
24
+ end
25
+
26
+ describe "#enabled?" do
27
+ context "for true value" do
28
+ it "returns true" do
29
+ subject.enabled?(true).should be_true
27
30
  end
31
+ end
28
32
 
29
- it "returns Disabled" do
30
- subject.description.should eq('Disabled')
33
+ context "for false value" do
34
+ it "returns false" do
35
+ subject.enabled?(false).should be_false
36
+ end
37
+ end
38
+
39
+ context "for nil value" do
40
+ it "returns false" do
41
+ subject.enabled?(nil).should be_false
42
+ end
43
+ end
44
+
45
+ context "for empty string value" do
46
+ it "returns false" do
47
+ subject.enabled?('').should be_false
48
+ end
49
+ end
50
+
51
+ context "for the string true value" do
52
+ it "returns true" do
53
+ subject.enabled?('true').should be_true
54
+ end
55
+ end
56
+
57
+ context "for the string false value" do
58
+ it "returns false" do
59
+ subject.enabled?('false').should be_false
60
+ end
61
+ end
62
+ end
63
+
64
+ describe "#open?" do
65
+ context "for true value" do
66
+ it "returns true" do
67
+ subject.open?(Object.new, true).should be_true
68
+ end
69
+ end
70
+
71
+ context "for false value" do
72
+ it "returns false" do
73
+ subject.open?(Object.new, false).should be_false
74
+ end
75
+ end
76
+
77
+ context "for nil value" do
78
+ it "returns false" do
79
+ subject.open?(Object.new, nil).should be_false
80
+ end
81
+ end
82
+
83
+ context "for string true value" do
84
+ it "returns true" do
85
+ subject.open?(Object.new, 'true').should be_true
86
+ end
87
+ end
88
+
89
+ context "for string false value" do
90
+ it "returns false" do
91
+ subject.open?(Object.new, 'false').should be_false
92
+ end
93
+ end
94
+
95
+ context "for an empty string value" do
96
+ it "returns false" do
97
+ subject.open?(Object.new, '').should be_false
31
98
  end
32
99
  end
33
100
  end
@@ -35,7 +102,7 @@ describe Flipper::Gates::Boolean do
35
102
  describe "instrumentation" do
36
103
  it "is recorded for open" do
37
104
  thing = nil
38
- subject.open?(thing)
105
+ subject.open?(thing, false)
39
106
 
40
107
  event = instrumenter.events.last
41
108
  event.should_not be_nil
@@ -2,18 +2,17 @@ require 'helper'
2
2
  require 'flipper/instrumenters/memory'
3
3
 
4
4
  describe Flipper::Gates::Group do
5
- let(:adapter) { double('Adapter', :set_members => []) }
6
- let(:feature) { double('Feature', :name => :search, :adapter => adapter) }
7
5
  let(:instrumenter) { Flipper::Instrumenters::Memory.new }
6
+ let(:feature_name) { :search }
8
7
 
9
8
  subject {
10
- described_class.new(feature, :instrumenter => instrumenter)
9
+ described_class.new(feature_name, :instrumenter => instrumenter)
11
10
  }
12
11
 
13
12
  describe "instrumentation" do
14
13
  it "is recorded for open" do
15
14
  thing = Struct.new(:flipper_id).new('22')
16
- subject.open?(thing)
15
+ subject.open?(thing, Set.new)
17
16
 
18
17
  event = instrumenter.events.last
19
18
  event.should_not be_nil
@@ -30,22 +29,15 @@ describe Flipper::Gates::Group do
30
29
 
31
30
  describe "#description" do
32
31
  context "with groups in set" do
33
- before do
34
- adapter.stub(:set_members => Set['bacon', 'ham'])
35
- end
36
-
37
32
  it "returns text" do
38
- subject.description.should eq("groups (bacon, ham)")
33
+ values = Set['bacon', 'ham']
34
+ subject.description(values).should eq('groups (:bacon, :ham)')
39
35
  end
40
36
  end
41
37
 
42
38
  context "with no groups in set" do
43
- before do
44
- adapter.stub(:set_members => Set.new)
45
- end
46
-
47
39
  it "returns disabled" do
48
- subject.description.should eq('disabled')
40
+ subject.description(Set.new).should eq('disabled')
49
41
  end
50
42
  end
51
43
  end
@@ -54,24 +46,22 @@ describe Flipper::Gates::Group do
54
46
  context "with a group in adapter, but not registered" do
55
47
  before do
56
48
  Flipper.register(:staff) { |thing| true }
57
- adapter.stub(:set_members => Set[:newbs, :staff])
58
49
  end
59
50
 
60
51
  it "ignores group" do
61
52
  thing = Struct.new(:flipper_id).new('5')
62
- subject.open?(thing).should be_true
53
+ subject.open?(thing, Set[:newbs, :staff]).should be_true
63
54
  end
64
55
  end
65
56
 
66
57
  context "thing that does not respond to method in group block" do
67
58
  before do
68
59
  Flipper.register(:stinkers) { |thing| thing.stinker? }
69
- adapter.stub(:set_members => Set[:stinkers])
70
60
  end
71
61
 
72
62
  it "raises error" do
73
63
  expect {
74
- subject.open?(Object.new)
64
+ subject.open?(Object.new, Set[:stinkers])
75
65
  }.to raise_error(NoMethodError)
76
66
  end
77
67
  end
@@ -2,18 +2,17 @@ require 'helper'
2
2
  require 'flipper/instrumenters/memory'
3
3
 
4
4
  describe Flipper::Gates::PercentageOfActors do
5
- let(:adapter) { double('Adapter', :read => nil) }
6
- let(:feature) { double('Feature', :name => :search, :adapter => adapter) }
7
5
  let(:instrumenter) { Flipper::Instrumenters::Memory.new }
6
+ let(:feature_name) { :search }
8
7
 
9
8
  subject {
10
- described_class.new(feature, :instrumenter => instrumenter)
9
+ described_class.new(feature_name, :instrumenter => instrumenter)
11
10
  }
12
11
 
13
12
  describe "instrumentation" do
14
13
  it "is recorded for open" do
15
14
  thing = Struct.new(:flipper_id).new('22')
16
- subject.open?(thing)
15
+ subject.open?(thing, 0)
17
16
 
18
17
  event = instrumenter.events.last
19
18
  event.should_not be_nil
@@ -30,22 +29,15 @@ describe Flipper::Gates::PercentageOfActors do
30
29
 
31
30
  describe "#description" do
32
31
  context "when enabled" do
33
- before do
34
- adapter.stub(:read => 22)
35
- end
36
-
37
32
  it "returns text" do
38
- subject.description.should eq('22% of actors')
33
+ subject.description(22).should eq('22% of actors')
39
34
  end
40
35
  end
41
36
 
42
37
  context "when disabled" do
43
- before do
44
- adapter.stub(:read => nil)
45
- end
46
-
47
38
  it "returns disabled" do
48
- subject.description.should eq('disabled')
39
+ subject.description(nil).should eq('disabled')
40
+ subject.description(0).should eq('disabled')
49
41
  end
50
42
  end
51
43
  end
@@ -53,29 +45,21 @@ describe Flipper::Gates::PercentageOfActors do
53
45
  describe "#open?" do
54
46
  context "when compared against two features" do
55
47
  let(:percentage) { 0.05 }
48
+ let(:percentage_as_integer) { percentage * 100 }
56
49
  let(:number_of_actors) { 100 }
57
50
 
58
51
  let(:actors) {
59
- (1..number_of_actors).map { |n|
60
- Struct.new(:flipper_id).new(n)
61
- }
52
+ (1..number_of_actors).map { |n| Struct.new(:flipper_id).new(n) }
62
53
  }
63
54
 
64
55
  let(:feature_one_enabled_actors) do
65
- feature_one = double('Feature', :name => :name_one, :adapter => adapter)
66
- gate = described_class.new(feature_one)
67
- actors.select { |actor| gate.open? actor }
56
+ gate = described_class.new(:name_one)
57
+ actors.select { |actor| gate.open? actor, percentage_as_integer }
68
58
  end
69
59
 
70
60
  let(:feature_two_enabled_actors) do
71
- feature_two = double('Feature', :name => :name_two, :adapter => adapter)
72
- gate = described_class.new(feature_two)
73
- actors.select { |actor| gate.open? actor }
74
- end
75
-
76
- before do
77
- percentage_as_integer = percentage * 100
78
- adapter.stub(:read => percentage_as_integer)
61
+ gate = described_class.new(:name_two)
62
+ actors.select { |actor| gate.open? actor, percentage_as_integer }
79
63
  end
80
64
 
81
65
  it "does not enable both features for same set of actors" do
@@ -2,18 +2,17 @@ require 'helper'
2
2
  require 'flipper/instrumenters/memory'
3
3
 
4
4
  describe Flipper::Gates::PercentageOfRandom do
5
- let(:adapter) { double('Adapter', :read => 5) }
6
- let(:feature) { double('Feature', :name => :search, :adapter => adapter) }
7
5
  let(:instrumenter) { Flipper::Instrumenters::Memory.new }
6
+ let(:feature_name) { :search }
8
7
 
9
8
  subject {
10
- described_class.new(feature, :instrumenter => instrumenter)
9
+ described_class.new(feature_name, :instrumenter => instrumenter)
11
10
  }
12
11
 
13
12
  describe "instrumentation" do
14
13
  it "is recorded for open" do
15
14
  thing = Struct.new(:flipper_id).new('22')
16
- subject.open?(thing)
15
+ subject.open?(thing, 0)
17
16
 
18
17
  event = instrumenter.events.last
19
18
  event.should_not be_nil
@@ -32,22 +31,15 @@ describe Flipper::Gates::PercentageOfRandom do
32
31
 
33
32
  describe "#description" do
34
33
  context "when enabled" do
35
- before do
36
- adapter.stub(:read => 22)
37
- end
38
-
39
34
  it "returns text" do
40
- subject.description.should eq('22% of the time')
35
+ subject.description(22).should eq('22% of the time')
41
36
  end
42
37
  end
43
38
 
44
39
  context "when disabled" do
45
- before do
46
- adapter.stub(:read => nil)
47
- end
48
-
49
40
  it "returns disabled" do
50
- subject.description.should eq('disabled')
41
+ subject.description(nil).should eq('disabled')
42
+ subject.description(0).should eq('disabled')
51
43
  end
52
44
  end
53
45
  end
@@ -37,8 +37,9 @@ describe Flipper::Instrumentation::LogSubscriber do
37
37
  end
38
38
 
39
39
  it "logs adapter calls" do
40
- adapter_line = find_line('Flipper feature(search) adapter(memory) read("search/boolean")')
41
- adapter_line.should include('[ result=nil ]')
40
+ adapter_line = find_line('Flipper feature(search) adapter(memory) get')
41
+ adapter_line.should include('[ result={')
42
+ adapter_line.should include('} ]')
42
43
  end
43
44
 
44
45
  it "logs gate calls" do
@@ -80,14 +81,20 @@ describe Flipper::Instrumentation::LogSubscriber do
80
81
  end
81
82
 
82
83
  it "logs adapter value" do
83
- adapter_line = find_line('Flipper feature(search) adapter(memory) set_add("search/actors")')
84
- adapter_line.should include("value=#{user.flipper_id.to_s.inspect}")
84
+ adapter_line = find_line('Flipper feature(search) adapter(memory) enable')
85
+ adapter_line.should include("[ result=")
86
+ end
87
+ end
88
+
89
+ context "getting all the features from the adapter" do
90
+ before do
91
+ clear_logs
92
+ flipper.features
85
93
  end
86
94
 
87
- it "logs adapter calls not related to a specific feature" do
88
- adapter_line = find_line('Flipper adapter(memory) set_add("features")')
89
- log.should_not include('Could not log')
90
- log.should_not include('NoMethodError: undefined method')
95
+ it "logs adapter calls" do
96
+ adapter_line = find_line('Flipper adapter(memory) features')
97
+ adapter_line.should include('[ result=')
91
98
  end
92
99
  end
93
100