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
@@ -0,0 +1,184 @@
1
+ require 'helper'
2
+ require 'flipper/adapters/memoizable'
3
+ require 'flipper/adapters/memory'
4
+ require 'flipper/spec/shared_adapter_specs'
5
+
6
+ describe Flipper::Adapters::Memoizable do
7
+ let(:features_key) { described_class::FeaturesKey }
8
+
9
+ let(:adapter) { Flipper::Adapters::Memory.new }
10
+ let(:flipper) { Flipper.new(adapter) }
11
+ let(:cache) { Thread.current[:flipper_memoize_cache] }
12
+
13
+ after do
14
+ described_class.memoize = nil
15
+ end
16
+
17
+ subject { described_class.new(adapter) }
18
+
19
+ it_should_behave_like 'a flipper adapter'
20
+
21
+ describe "#memoize=" do
22
+ it "sets value" do
23
+ subject.memoize = true
24
+ subject.memoizing?.should be_true
25
+
26
+ subject.memoize = false
27
+ subject.memoizing?.should be_false
28
+ end
29
+
30
+ it "clears the local cache" do
31
+ subject.cache['some'] = 'thing'
32
+ subject.memoize = true
33
+ subject.cache.should be_empty
34
+ end
35
+ end
36
+
37
+ describe "#memoizing?" do
38
+ it "returns true if enabled" do
39
+ subject.memoize = true
40
+ subject.memoizing?.should be_true
41
+ end
42
+
43
+ it "returns false if disabled" do
44
+ subject.memoize = false
45
+ subject.memoizing?.should be_false
46
+ end
47
+ end
48
+
49
+ describe "#get" do
50
+ context "with memoization enabled" do
51
+ before do
52
+ subject.memoize = true
53
+ end
54
+
55
+ it "memoizes feature" do
56
+ feature = flipper[:stats]
57
+ result = subject.get(feature)
58
+ cache[feature].should be(result)
59
+ end
60
+ end
61
+
62
+ context "with memoization disabled" do
63
+ before do
64
+ subject.memoize = false
65
+ end
66
+
67
+ it "returns result" do
68
+ feature = flipper[:stats]
69
+ result = subject.get(feature)
70
+ adapter_result = adapter.get(feature)
71
+ result.should eq(adapter_result)
72
+ end
73
+ end
74
+ end
75
+
76
+ describe "#enable" do
77
+ context "with memoization enabled" do
78
+ before do
79
+ subject.memoize = true
80
+ end
81
+
82
+ it "unmemoizes feature" do
83
+ feature = flipper[:stats]
84
+ gate = feature.gate(:boolean)
85
+ cache[feature] = {:some => 'thing'}
86
+ subject.enable(feature, gate, flipper.bool)
87
+ cache[feature].should be_nil
88
+ end
89
+ end
90
+
91
+ context "with memoization disabled" do
92
+ before do
93
+ subject.memoize = false
94
+ end
95
+
96
+ it "returns result" do
97
+ feature = flipper[:stats]
98
+ gate = feature.gate(:boolean)
99
+ result = subject.enable(feature, gate, flipper.bool)
100
+ adapter_result = adapter.enable(feature, gate, flipper.bool)
101
+ result.should eq(adapter_result)
102
+ end
103
+ end
104
+ end
105
+
106
+ describe "#disable" do
107
+ context "with memoization enabled" do
108
+ before do
109
+ subject.memoize = true
110
+ end
111
+
112
+ it "unmemoizes feature" do
113
+ feature = flipper[:stats]
114
+ gate = feature.gate(:boolean)
115
+ cache[feature] = {:some => 'thing'}
116
+ subject.disable(feature, gate, flipper.bool)
117
+ cache[feature].should be_nil
118
+ end
119
+ end
120
+
121
+ context "with memoization disabled" do
122
+ before do
123
+ subject.memoize = false
124
+ end
125
+
126
+ it "returns result" do
127
+ feature = flipper[:stats]
128
+ gate = feature.gate(:boolean)
129
+ result = subject.disable(feature, gate, flipper.bool)
130
+ adapter_result = adapter.disable(feature, gate, flipper.bool)
131
+ result.should eq(adapter_result)
132
+ end
133
+ end
134
+ end
135
+
136
+ describe "#features" do
137
+ context "with memoization enabled" do
138
+ before do
139
+ subject.memoize = true
140
+ end
141
+
142
+ it "memoizes features" do
143
+ flipper[:stats].enable
144
+ flipper[:search].disable
145
+ result = subject.features
146
+ cache[:flipper_features].should be(result)
147
+ end
148
+ end
149
+
150
+ context "with memoization disabled" do
151
+ before do
152
+ subject.memoize = false
153
+ end
154
+
155
+ it "returns result" do
156
+ subject.features.should eq(adapter.features)
157
+ end
158
+ end
159
+ end
160
+
161
+ describe "#add" do
162
+ context "with memoization enabled" do
163
+ before do
164
+ subject.memoize = true
165
+ end
166
+
167
+ it "unmemoizes features" do
168
+ cache[features_key] = {:some => 'thing'}
169
+ subject.add(flipper[:stats])
170
+ cache.should be_empty
171
+ end
172
+ end
173
+
174
+ context "with memoization disabled" do
175
+ before do
176
+ subject.memoize = false
177
+ end
178
+
179
+ it "returns result" do
180
+ subject.add(flipper[:stats]).should eq(adapter.add(flipper[:stats]))
181
+ end
182
+ end
183
+ end
184
+ end
@@ -3,17 +3,7 @@ require 'flipper/adapters/memory'
3
3
  require 'flipper/spec/shared_adapter_specs'
4
4
 
5
5
  describe Flipper::Adapters::Memory do
6
- let(:source) { {} }
7
-
8
- subject { described_class.new(source) }
9
-
10
- def read_key(key)
11
- source[key.to_s]
12
- end
13
-
14
- def write_key(key, value)
15
- source[key.to_s] = value
16
- end
6
+ subject { described_class.new }
17
7
 
18
8
  it_should_behave_like 'a flipper adapter'
19
9
  end
@@ -0,0 +1,93 @@
1
+ require 'helper'
2
+ require 'flipper/adapters/operation_logger'
3
+ require 'flipper/adapters/memory'
4
+ require 'flipper/spec/shared_adapter_specs'
5
+
6
+ describe Flipper::Adapters::OperationLogger do
7
+ let(:operations) { [] }
8
+ let(:adapter) { Flipper::Adapters::Memory.new }
9
+ let(:flipper) { Flipper.new(adapter) }
10
+
11
+ subject { described_class.new(adapter, operations) }
12
+
13
+ it_should_behave_like 'a flipper adapter'
14
+
15
+ describe "#get" do
16
+ before do
17
+ @feature = flipper[:stats]
18
+ @result = subject.get(@feature)
19
+ end
20
+
21
+ it "logs operation" do
22
+ subject.count(:get).should be(1)
23
+ end
24
+
25
+ it "returns result" do
26
+ @result.should eq(adapter.get(@feature))
27
+ end
28
+ end
29
+
30
+ describe "#enable" do
31
+ before do
32
+ @feature = flipper[:stats]
33
+ @gate = @feature.gate(:boolean)
34
+ @thing = flipper.bool
35
+ @result = subject.enable(@feature, @gate, @thing)
36
+ end
37
+
38
+ it "logs operation" do
39
+ subject.count(:enable).should be(1)
40
+ end
41
+
42
+ it "returns result" do
43
+ @result.should eq(adapter.enable(@feature, @gate, @thing))
44
+ end
45
+ end
46
+
47
+ describe "#disable" do
48
+ before do
49
+ @feature = flipper[:stats]
50
+ @gate = @feature.gate(:boolean)
51
+ @thing = flipper.bool
52
+ @result = subject.disable(@feature, @gate, @thing)
53
+ end
54
+
55
+ it "logs operation" do
56
+ subject.count(:disable).should be(1)
57
+ end
58
+
59
+ it "returns result" do
60
+ @result.should eq(adapter.disable(@feature, @gate, @thing))
61
+ end
62
+ end
63
+
64
+ describe "#features" do
65
+ before do
66
+ flipper[:stats].enable
67
+ @result = subject.features
68
+ end
69
+
70
+ it "logs operation" do
71
+ subject.count(:features).should be(1)
72
+ end
73
+
74
+ it "returns result" do
75
+ @result.should eq(adapter.features)
76
+ end
77
+ end
78
+
79
+ describe "#add" do
80
+ before do
81
+ @feature = flipper[:stats]
82
+ @result = subject.add(@feature)
83
+ end
84
+
85
+ it "logs operation" do
86
+ subject.count(:add).should be(1)
87
+ end
88
+
89
+ it "returns result" do
90
+ @result.should eq(adapter.add(@feature))
91
+ end
92
+ end
93
+ end
@@ -8,13 +8,10 @@ describe Flipper::DSL do
8
8
  let(:source) { {} }
9
9
  let(:adapter) { Flipper::Adapters::Memory.new(source) }
10
10
 
11
- let(:admins_feature) { Flipper::Feature.new(:admins, adapter) }
12
-
13
11
  describe "#initialize" do
14
- it "wraps adapter" do
12
+ it "sets adapter" do
15
13
  dsl = described_class.new(adapter)
16
- dsl.adapter.should be_instance_of(Flipper::Adapter)
17
- dsl.adapter.adapter.should eq(adapter)
14
+ dsl.adapter.should_not be_nil
18
15
  end
19
16
 
20
17
  it "defaults instrumenter to noop" do
@@ -37,58 +34,36 @@ describe Flipper::DSL do
37
34
  end
38
35
  end
39
36
 
40
- describe "#enabled?" do
41
- before do
42
- subject.stub(:feature => admins_feature)
43
- end
44
-
45
- it "passes arguments to feature enabled check and returns result" do
46
- admins_feature.should_receive(:enabled?).with(:foo).and_return(true)
47
- subject.should_receive(:feature).with(:stats).and_return(admins_feature)
48
- subject.enabled?(:stats, :foo).should be_true
49
- end
50
- end
51
-
52
- describe "#enable" do
53
- before do
54
- subject.stub(:feature => admins_feature)
55
- end
56
-
57
- it "calls enable for feature with arguments" do
58
- admins_feature.should_receive(:enable).with(:foo)
59
- subject.should_receive(:feature).with(:stats).and_return(admins_feature)
60
- subject.enable :stats, :foo
61
- end
62
- end
63
-
64
- describe "#disable" do
65
- before do
66
- subject.stub(:feature => admins_feature)
67
- end
68
-
69
- it "calls disable for feature with arguments" do
70
- admins_feature.should_receive(:disable).with(:foo)
71
- subject.should_receive(:feature).with(:stats).and_return(admins_feature)
72
- subject.disable :stats, :foo
73
- end
74
- end
75
-
76
37
  describe "#feature" do
77
38
  it_should_behave_like "a DSL feature" do
39
+ let(:method_name) { :feature }
78
40
  let(:instrumenter) { double('Instrumentor', :instrument => nil) }
79
- let(:feature) { dsl.feature(:stats) }
41
+ let(:feature) { dsl.send(method_name, :stats) }
80
42
  let(:dsl) { Flipper::DSL.new(adapter, :instrumenter => instrumenter) }
81
43
  end
82
44
  end
83
45
 
84
46
  describe "#[]" do
85
47
  it_should_behave_like "a DSL feature" do
48
+ let(:method_name) { :[] }
86
49
  let(:instrumenter) { double('Instrumentor', :instrument => nil) }
87
- let(:feature) { dsl[:stats] }
50
+ let(:feature) { dsl.send(method_name, :stats) }
88
51
  let(:dsl) { Flipper::DSL.new(adapter, :instrumenter => instrumenter) }
89
52
  end
90
53
  end
91
54
 
55
+ describe "#boolean" do
56
+ it_should_behave_like "a DSL boolean method" do
57
+ let(:method_name) { :boolean }
58
+ end
59
+ end
60
+
61
+ describe "#bool" do
62
+ it_should_behave_like "a DSL boolean method" do
63
+ let(:method_name) { :bool }
64
+ end
65
+ end
66
+
92
67
  describe "#group" do
93
68
  context "for registered group" do
94
69
  before do
@@ -17,7 +17,7 @@ describe Flipper::Feature do
17
17
 
18
18
  it "sets adapter" do
19
19
  feature = described_class.new(:search, adapter)
20
- feature.adapter.should eq(Flipper::Adapter.wrap(adapter))
20
+ feature.adapter.should eq(adapter)
21
21
  end
22
22
 
23
23
  it "defaults instrumenter" do
@@ -34,13 +34,6 @@ describe Flipper::Feature do
34
34
  })
35
35
  feature.instrumenter.should be(instrumenter)
36
36
  end
37
-
38
- it "passes overridden instrumenter to adapter wrapping" do
39
- feature = described_class.new(:search, adapter, {
40
- :instrumenter => instrumenter,
41
- })
42
- feature.adapter.instrumenter.should be(instrumenter)
43
- end
44
37
  end
45
38
  end
46
39
 
@@ -67,12 +60,35 @@ describe Flipper::Feature do
67
60
  end
68
61
  end
69
62
 
63
+ describe "#gate" do
64
+ context "with symbol name" do
65
+ it "returns gate by name" do
66
+ boolean_gate = subject.gates.detect { |gate| gate.name == :boolean }
67
+ subject.gate(:boolean).should eq(boolean_gate)
68
+ end
69
+ end
70
+
71
+ context "with string name" do
72
+ it "returns gate by name" do
73
+ boolean_gate = subject.gates.detect { |gate| gate.name == :boolean }
74
+ subject.gate('boolean').should eq(boolean_gate)
75
+ end
76
+ end
77
+
78
+ context "with name that does not exist" do
79
+ it "returns nil" do
80
+ subject.gate(:poo).should be_nil
81
+ end
82
+ end
83
+ end
84
+
70
85
  describe "#inspect" do
71
86
  it "returns easy to read string representation" do
72
87
  string = subject.inspect
73
88
  string.should include('Flipper::Feature')
74
89
  string.should include('name=:search')
75
90
  string.should include('state=:off')
91
+ string.should include('description="Disabled"')
76
92
  string.should include("adapter=#{subject.adapter.name.inspect}")
77
93
  end
78
94
  end
@@ -191,7 +207,7 @@ describe Flipper::Feature do
191
207
  end
192
208
 
193
209
  it "returns text" do
194
- subject.description.should eq('Enabled for actors (5), 5% of the time')
210
+ subject.description.should eq('Enabled for actors ("5"), 5% of the time')
195
211
  end
196
212
  end
197
213
  end