flipper 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Flipper
1
+ ![flipper logo](https://raw.github.com/jnunemaker/flipper-ui/master/lib/flipper/ui/public/flipper/images/logo.png)
2
2
 
3
3
  Feature flipping is the act of enabling or disabling features or parts of your application, ideally without re-deploying or changing anything in your code base.
4
4
 
@@ -167,13 +167,49 @@ Randomness is not a good idea for enabling new features in the UI. Most of the t
167
167
 
168
168
  ## Adapters
169
169
 
170
- I plan on supporting [in-memory](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adapters/memory.rb), [Mongo](https://github.com/jnunemaker/flipper-mongo), and [Redis](https://github.com/jnunemaker/flipper-redis) as adapters for flipper. Others are welcome so please let me know if you create one.
170
+ I plan on supporting [in-memory](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adapters/memory.rb), [Mongo](https://github.com/jnunemaker/flipper-mongo), and [Redis](https://github.com/jnunemaker/flipper-redis) as adapters for flipper. Others are welcome, so please let me know if you create one.
171
171
 
172
172
  * [memory adapter](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adapters/memory.rb) - Great for tests.
173
173
  * [mongo adapter](https://github.com/jnunemaker/flipper-mongo)
174
174
  * [redis adapter](https://github.com/jnunemaker/flipper-redis)
175
175
  * [cassanity adapter](https://github.com/jnunemaker/flipper-cassanity)
176
176
 
177
+ The basic API for an adapter is this:
178
+
179
+ * `features` - Get the set of known features.
180
+ * `add(feature)` - Add a feature to the set of known features.
181
+ * `remove(feature)` - Remove a feature from the set of known features.
182
+ * `clear(feature)` - Clear all gate values for a feature.
183
+ * `get(feature)` - Get all gate values for a feature.
184
+ * `enable(feature, gate, thing)` - Enable a gate for a thing.
185
+ * `disable(feature, gate, thing)` - Disable a gate for a thing.
186
+
187
+ If you would like to make your own adapter, there are shared adapter specs that you can use to verify that you have everything working correctly.
188
+
189
+ For example, here is what the in-memory adapter spec looks like:
190
+
191
+ ```ruby
192
+ require 'helper'
193
+ require 'flipper/adapters/memory'
194
+
195
+ # The shared specs are included with the flipper gem so you can use them in
196
+ # separate adapter specific gems.
197
+ require 'flipper/spec/shared_adapter_specs'
198
+
199
+ describe Flipper::Adapters::Memory do
200
+
201
+ # an instance of the new adapter you are trying to create
202
+ subject { described_class.new }
203
+
204
+ # include the shared specs that the subject must pass
205
+ it_should_behave_like 'a flipper adapter'
206
+ end
207
+ ```
208
+
209
+ A good place to start when creating your own adapter is to copy one of the adapters mentioned above and replace the client specific code with whatever client you are attempting to adapt.
210
+
211
+ I would also recommend setting `fail_fast = true` in your RSpec configuration as that will just give you one failure at a time to work through. It is also handy to have the shared adapter spec file open.
212
+
177
213
  ## Optimization
178
214
 
179
215
  One optimization that flipper provides is a memoizing middleware. The memoizing middleware ensures that you only make one adapter call per feature per request.
@@ -183,13 +219,21 @@ This means if you check the same feature over and over, it will only make one mo
183
219
  You can use the middleware from a Rails initializer like so:
184
220
 
185
221
  ```ruby
222
+ # create flipper dsl instance, see above Usage for more details
223
+ flipper = Flipper.new(...)
224
+
186
225
  require 'flipper/middleware/memoizer'
226
+ config.middleware.use Flipper::Middleware::Memoizer, flipper
227
+ ```
187
228
 
188
- # create flipper dsl instance, see above examples for more details
189
- flipper = Flipper.new(...)
229
+ If you set your flipper instance up in an initializer, you can pass a block to the middleware and it will lazily load the instance the first time the middleware is invoked.
230
+
231
+ ```ruby
232
+ # config/initializers/flipper.rb
233
+ $flipper = Flipper.new(...)
190
234
 
191
- # add the middleware
192
- Rails.application.config.middleware.use Flipper::Middleware::Memoizer, flipper
235
+ # config/application.rb
236
+ config.middleware.use Flipper::Middleware::Memoizer, lambda { $flipper }
193
237
  ```
194
238
 
195
239
  **Note**: Be sure that the middlware is high enough up in your stack that all feature checks are wrapped.
@@ -23,9 +23,22 @@ module Flipper
23
23
  @instrumenter = options.fetch(:instrumenter, Flipper::Instrumenters::Noop)
24
24
  end
25
25
 
26
- def get(feature)
26
+ # Public
27
+ def features
27
28
  payload = {
28
- :operation => :get,
29
+ :operation => :features,
30
+ :adapter_name => name,
31
+ }
32
+
33
+ @instrumenter.instrument(InstrumentationName, payload) { |payload|
34
+ payload[:result] = super
35
+ }
36
+ end
37
+
38
+ # Public
39
+ def add(feature)
40
+ payload = {
41
+ :operation => :add,
29
42
  :adapter_name => name,
30
43
  :feature_name => feature.name,
31
44
  }
@@ -35,13 +48,12 @@ module Flipper
35
48
  }
36
49
  end
37
50
 
38
- # Public: Enable feature gate for thing.
39
- def enable(feature, gate, thing)
51
+ # Public
52
+ def remove(feature)
40
53
  payload = {
41
- :operation => :enable,
54
+ :operation => :remove,
42
55
  :adapter_name => name,
43
56
  :feature_name => feature.name,
44
- :gate_name => gate.name,
45
57
  }
46
58
 
47
59
  @instrumenter.instrument(InstrumentationName, payload) { |payload|
@@ -49,13 +61,12 @@ module Flipper
49
61
  }
50
62
  end
51
63
 
52
- # Public: Disable feature gate for thing.
53
- def disable(feature, gate, thing)
64
+ # Public
65
+ def clear(feature)
54
66
  payload = {
55
- :operation => :disable,
67
+ :operation => :clear,
56
68
  :adapter_name => name,
57
69
  :feature_name => feature.name,
58
- :gate_name => gate.name,
59
70
  }
60
71
 
61
72
  @instrumenter.instrument(InstrumentationName, payload) { |payload|
@@ -63,11 +74,12 @@ module Flipper
63
74
  }
64
75
  end
65
76
 
66
- # Public: Returns all the features that the adapter knows of.
67
- def features
77
+ # Public
78
+ def get(feature)
68
79
  payload = {
69
- :operation => :features,
80
+ :operation => :get,
70
81
  :adapter_name => name,
82
+ :feature_name => feature.name,
71
83
  }
72
84
 
73
85
  @instrumenter.instrument(InstrumentationName, payload) { |payload|
@@ -75,12 +87,27 @@ module Flipper
75
87
  }
76
88
  end
77
89
 
78
- # Internal: Adds a known feature to the set of features.
79
- def add(feature)
90
+ # Public
91
+ def enable(feature, gate, thing)
80
92
  payload = {
81
- :operation => :add,
93
+ :operation => :enable,
94
+ :adapter_name => name,
95
+ :feature_name => feature.name,
96
+ :gate_name => gate.name,
97
+ }
98
+
99
+ @instrumenter.instrument(InstrumentationName, payload) { |payload|
100
+ payload[:result] = super
101
+ }
102
+ end
103
+
104
+ # Public
105
+ def disable(feature, gate, thing)
106
+ payload = {
107
+ :operation => :disable,
82
108
  :adapter_name => name,
83
109
  :feature_name => feature.name,
110
+ :gate_name => gate.name,
84
111
  }
85
112
 
86
113
  @instrumenter.instrument(InstrumentationName, payload) { |payload|
@@ -27,43 +27,60 @@ module Flipper
27
27
  end
28
28
 
29
29
  # Public
30
- def get(feature)
30
+ def features
31
31
  if memoizing?
32
- cache.fetch(feature) { cache[feature] = super }
32
+ cache.fetch(FeaturesKey) {
33
+ cache[FeaturesKey] = super
34
+ }
33
35
  else
34
36
  super
35
37
  end
36
38
  end
37
39
 
38
40
  # Public
39
- def enable(feature, gate, thing)
41
+ def add(feature)
40
42
  result = super
41
- cache.delete(feature) if memoizing?
43
+ cache.delete(FeaturesKey) if memoizing?
42
44
  result
43
45
  end
44
46
 
45
47
  # Public
46
- def disable(feature, gate, thing)
48
+ def remove(feature)
49
+ result = super
50
+ if memoizing?
51
+ cache.delete(FeaturesKey)
52
+ cache.delete(feature)
53
+ end
54
+ result
55
+ end
56
+
57
+ # Public
58
+ def clear(feature)
47
59
  result = super
48
60
  cache.delete(feature) if memoizing?
49
61
  result
50
62
  end
51
63
 
52
64
  # Public
53
- def features
65
+ def get(feature)
54
66
  if memoizing?
55
- cache.fetch(FeaturesKey) {
56
- cache[FeaturesKey] = super
57
- }
67
+ cache.fetch(feature) { cache[feature] = super }
58
68
  else
59
69
  super
60
70
  end
61
71
  end
62
72
 
63
73
  # Public
64
- def add(feature)
74
+ def enable(feature, gate, thing)
65
75
  result = super
66
- cache.delete(FeaturesKey) if memoizing?
76
+ cache.delete(feature) if memoizing?
77
+ result
78
+ end
79
+
80
+ # Public
81
+ def disable(feature, gate, thing)
82
+ result = super
83
+ cache.delete(feature) if memoizing?
67
84
  result
68
85
  end
69
86
 
@@ -16,6 +16,32 @@ module Flipper
16
16
  @name = :memory
17
17
  end
18
18
 
19
+ # Public: The set of known features.
20
+ def features
21
+ set_members(FeaturesKey)
22
+ end
23
+
24
+ # Public: Adds a feature to the set of known features.
25
+ def add(feature)
26
+ features.add(feature.name.to_s)
27
+ true
28
+ end
29
+
30
+ # Public: Removes a feature from the set of known features and clears
31
+ # all the values for the feature.
32
+ def remove(feature)
33
+ features.delete(feature.name.to_s)
34
+ clear(feature)
35
+ true
36
+ end
37
+
38
+ # Public: Clears all the gate values for a feature.
39
+ def clear(feature)
40
+ feature.gates.each do |gate|
41
+ delete key(feature, gate)
42
+ end
43
+ end
44
+
19
45
  # Public
20
46
  def get(feature)
21
47
  result = {}
@@ -52,9 +78,7 @@ module Flipper
52
78
  def disable(feature, gate, thing)
53
79
  case gate.data_type
54
80
  when :boolean
55
- feature.gates.each do |gate|
56
- delete key(feature, gate)
57
- end
81
+ clear(feature)
58
82
  when :integer
59
83
  write key(feature, gate), thing.value.to_s
60
84
  when :set
@@ -66,18 +90,7 @@ module Flipper
66
90
  true
67
91
  end
68
92
 
69
- # Public: Adds a feature to the set of known features.
70
- def add(feature)
71
- features.add(feature.name.to_s)
72
-
73
- true
74
- end
75
-
76
- # Public: The set of known features.
77
- def features
78
- set_members(FeaturesKey)
79
- end
80
-
93
+ # Public
81
94
  def inspect
82
95
  attributes = [
83
96
  "name=:memory",
@@ -86,7 +99,7 @@ module Flipper
86
99
  "#<#{self.class.name}:#{object_id} #{attributes.join(', ')}>"
87
100
  end
88
101
 
89
- # private
102
+ # Private
90
103
  def key(feature, gate)
91
104
  "#{feature.key}/#{gate.key}"
92
105
  end
@@ -9,11 +9,13 @@ module Flipper
9
9
  Operation = Struct.new(:type, :args)
10
10
 
11
11
  OperationTypes = [
12
- :get,
12
+ :features,
13
13
  :add,
14
+ :remove,
15
+ :clear,
16
+ :get,
14
17
  :enable,
15
18
  :disable,
16
- :features
17
19
  ]
18
20
 
19
21
  # Internal: An array of the operations that have happened.
@@ -25,6 +27,7 @@ module Flipper
25
27
  @operations = operations || []
26
28
  end
27
29
 
30
+ # Wraps original method with in memory log of operations performed.
28
31
  OperationTypes.each do |type|
29
32
  class_eval <<-EOE
30
33
  def #{type}(*args)
@@ -36,9 +39,7 @@ module Flipper
36
39
 
37
40
  # Public: Count the number of times a certain operation happened.
38
41
  def count(type)
39
- @operations.select { |operation|
40
- operation.type == type
41
- }.size
42
+ @operations.select { |operation| operation.type == type }.size
42
43
  end
43
44
 
44
45
  # Public: Resets the operation log to empty
@@ -59,7 +59,11 @@ module Flipper
59
59
  gate = gate_for(thing)
60
60
  payload[:gate_name] = gate.name
61
61
 
62
- adapter.disable self, gate, gate.wrap(thing)
62
+ if gate.is_a?(Gates::Boolean)
63
+ adapter.clear self
64
+ else
65
+ adapter.disable self, gate, gate.wrap(thing)
66
+ end
63
67
  }
64
68
  end
65
69
 
@@ -3,18 +3,43 @@ require 'rack/body_proxy'
3
3
  module Flipper
4
4
  module Middleware
5
5
  class Memoizer
6
- def initialize(app, flipper)
6
+ # Public: Initializes an instance of the UI middleware.
7
+ #
8
+ # app - The app this middleware is included in.
9
+ # flipper_or_block - The Flipper::DSL instance or a block that yields a
10
+ # Flipper::DSL instance to use for all operations.
11
+ #
12
+ # Examples
13
+ #
14
+ # flipper = Flipper.new(...)
15
+ #
16
+ # # using with a normal flipper instance
17
+ # use Flipper::Middleware::Memoizer, flipper
18
+ #
19
+ # # using with a block that yields a flipper instance
20
+ # use Flipper::Middleware::Memoizer, lambda { Flipper.new(...) }
21
+ #
22
+ def initialize(app, flipper_or_block)
7
23
  @app = app
8
- @flipper = flipper
24
+
25
+ if flipper_or_block.respond_to?(:call)
26
+ @flipper_block = flipper_or_block
27
+ else
28
+ @flipper = flipper_or_block
29
+ end
30
+ end
31
+
32
+ def flipper
33
+ @flipper ||= @flipper_block.call
9
34
  end
10
35
 
11
36
  def call(env)
12
- original = @flipper.adapter.memoizing?
13
- @flipper.adapter.memoize = true
37
+ original = flipper.adapter.memoizing?
38
+ flipper.adapter.memoize = true
14
39
 
15
40
  response = @app.call(env)
16
41
  response[2] = Rack::BodyProxy.new(response[2]) {
17
- @flipper.adapter.memoize = original
42
+ flipper.adapter.memoize = original
18
43
  }
19
44
  response
20
45
  end
@@ -161,7 +161,7 @@ shared_examples_for 'a flipper adapter' do
161
161
  result[:percentage_of_actors].should eq('10')
162
162
  end
163
163
 
164
- it "can add and list known features" do
164
+ it "can add, remove and list known features" do
165
165
  subject.features.should eq(Set.new)
166
166
 
167
167
  subject.add(flipper[:stats]).should be_true
@@ -169,5 +169,53 @@ shared_examples_for 'a flipper adapter' do
169
169
 
170
170
  subject.add(flipper[:search]).should be_true
171
171
  subject.features.should eq(Set['stats', 'search'])
172
+
173
+ subject.remove(flipper[:stats]).should be_true
174
+ subject.features.should eq(Set['search'])
175
+
176
+ subject.remove(flipper[:search]).should be_true
177
+ subject.features.should eq(Set.new)
178
+ end
179
+
180
+ it "clears all the gate values for the feature on remove" do
181
+ actor_22 = actor_class.new('22')
182
+ subject.enable(feature, boolean_gate, flipper.boolean).should be_true
183
+ subject.enable(feature, group_gate, flipper.group(:admins)).should be_true
184
+ subject.enable(feature, actor_gate, flipper.actor(actor_22)).should be_true
185
+ subject.enable(feature, actors_gate, flipper.actors(25)).should be_true
186
+ subject.enable(feature, random_gate, flipper.random(45)).should be_true
187
+
188
+ subject.remove(feature).should be_true
189
+
190
+ subject.get(feature).should eq({
191
+ :boolean => nil,
192
+ :groups => Set.new,
193
+ :actors => Set.new,
194
+ :percentage_of_actors => nil,
195
+ :percentage_of_random => nil,
196
+ })
197
+ end
198
+
199
+ it "can clear all the gate values for a feature" do
200
+ actor_22 = actor_class.new('22')
201
+ subject.enable(feature, boolean_gate, flipper.boolean).should be_true
202
+ subject.enable(feature, group_gate, flipper.group(:admins)).should be_true
203
+ subject.enable(feature, actor_gate, flipper.actor(actor_22)).should be_true
204
+ subject.enable(feature, actors_gate, flipper.actors(25)).should be_true
205
+ subject.enable(feature, random_gate, flipper.random(45)).should be_true
206
+
207
+ subject.clear(feature).should be_true
208
+
209
+ subject.get(feature).should eq({
210
+ :boolean => nil,
211
+ :groups => Set.new,
212
+ :actors => Set.new,
213
+ :percentage_of_actors => nil,
214
+ :percentage_of_random => nil,
215
+ })
216
+ end
217
+
218
+ it "does not complain clearing a feature that does not exist in adapter" do
219
+ subject.clear(flipper[:stats]).should be_true
172
220
  end
173
221
  end
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -77,6 +77,34 @@ describe Flipper::Adapters::Instrumented do
77
77
  end
78
78
  end
79
79
 
80
+ describe "#remove" do
81
+ it "records instrumentation" do
82
+ result = subject.remove(feature)
83
+
84
+ event = instrumenter.events.last
85
+ event.should_not be_nil
86
+ event.name.should eq('adapter_operation.flipper')
87
+ event.payload[:operation].should eq(:remove)
88
+ event.payload[:adapter_name].should eq(:memory)
89
+ event.payload[:feature_name].should eq(:stats)
90
+ event.payload[:result].should be(result)
91
+ end
92
+ end
93
+
94
+ describe "#clear" do
95
+ it "records instrumentation" do
96
+ result = subject.clear(feature)
97
+
98
+ event = instrumenter.events.last
99
+ event.should_not be_nil
100
+ event.name.should eq('adapter_operation.flipper')
101
+ event.payload[:operation].should eq(:clear)
102
+ event.payload[:adapter_name].should eq(:memory)
103
+ event.payload[:feature_name].should eq(:stats)
104
+ event.payload[:result].should be(result)
105
+ end
106
+ end
107
+
80
108
  describe "#features" do
81
109
  it "records instrumentation" do
82
110
  result = subject.features
@@ -164,7 +164,7 @@ describe Flipper::Adapters::Memoizable do
164
164
  subject.memoize = true
165
165
  end
166
166
 
167
- it "unmemoizes features" do
167
+ it "unmemoizes the known features" do
168
168
  cache[features_key] = {:some => 'thing'}
169
169
  subject.add(flipper[:stats])
170
170
  cache.should be_empty
@@ -181,4 +181,61 @@ describe Flipper::Adapters::Memoizable do
181
181
  end
182
182
  end
183
183
  end
184
+
185
+ describe "#remove" do
186
+ context "with memoization enabled" do
187
+ before do
188
+ subject.memoize = true
189
+ end
190
+
191
+ it "unmemoizes the known features" do
192
+ cache[features_key] = {:some => 'thing'}
193
+ subject.remove(flipper[:stats])
194
+ cache.should be_empty
195
+ end
196
+
197
+ it "unmemoizes the feature" do
198
+ feature = flipper[:stats]
199
+ cache[feature] = {:some => 'thing'}
200
+ subject.remove(feature)
201
+ cache[feature].should be_nil
202
+ end
203
+ end
204
+
205
+ context "with memoization disabled" do
206
+ before do
207
+ subject.memoize = false
208
+ end
209
+
210
+ it "returns result" do
211
+ subject.remove(flipper[:stats]).should eq(adapter.remove(flipper[:stats]))
212
+ end
213
+ end
214
+ end
215
+
216
+ describe "#clear" do
217
+ context "with memoization enabled" do
218
+ before do
219
+ subject.memoize = true
220
+ end
221
+
222
+ it "unmemoizes feature" do
223
+ feature = flipper[:stats]
224
+ cache[feature] = {:some => 'thing'}
225
+ subject.clear(feature)
226
+ cache[feature].should be_nil
227
+ end
228
+ end
229
+
230
+ context "with memoization disabled" do
231
+ before do
232
+ subject.memoize = false
233
+ end
234
+
235
+ it "returns result" do
236
+ feature = flipper[:stats]
237
+ subject.clear(feature).should eq(adapter.clear(feature))
238
+ end
239
+ end
240
+ end
184
241
  end
@@ -9,93 +9,123 @@ describe Flipper::Middleware::Memoizer do
9
9
 
10
10
  let(:source) { {} }
11
11
  let(:memory_adapter) { Flipper::Adapters::Memory.new(source) }
12
- let(:adapter) { Flipper::Adapters::OperationLogger.new(memory_adapter) }
13
- let(:flipper) { Flipper.new(adapter) }
14
-
15
- let(:app) {
16
- # ensure scoped for builder block, annoying...
17
- instance = flipper
18
- middleware = described_class
19
-
20
- Rack::Builder.new do
21
- use middleware, instance
22
-
23
- map "/" do
24
- run lambda {|env| [200, {}, []] }
25
- end
26
-
27
- map "/fail" do
28
- run lambda {|env| raise "FAIL!" }
29
- end
30
- end.to_app
12
+ let(:adapter) {
13
+ Flipper::Adapters::OperationLogger.new(memory_adapter)
31
14
  }
15
+ let(:flipper) { Flipper.new(adapter) }
32
16
 
33
17
  after do
34
18
  flipper.adapter.memoize = nil
35
19
  end
36
20
 
37
- it "delegates" do
38
- called = false
39
- app = lambda { |env|
40
- called = true
41
- [200, {}, nil]
42
- }
43
- middleware = described_class.new app, flipper
44
- middleware.call({})
45
- called.should be_true
21
+ shared_examples_for "flipper middleware" do
22
+ it "delegates" do
23
+ called = false
24
+ app = lambda { |env|
25
+ called = true
26
+ [200, {}, nil]
27
+ }
28
+ middleware = described_class.new app, flipper
29
+ middleware.call({})
30
+ called.should be_true
31
+ end
32
+
33
+ it "disables local cache after body close" do
34
+ app = lambda { |env| [200, {}, []] }
35
+ middleware = described_class.new app, flipper
36
+ body = middleware.call({}).last
37
+
38
+ flipper.adapter.memoizing?.should be_true
39
+ body.close
40
+ flipper.adapter.memoizing?.should be_false
41
+ end
42
+
43
+ it "clears local cache after body close" do
44
+ app = lambda { |env| [200, {}, []] }
45
+ middleware = described_class.new app, flipper
46
+ body = middleware.call({}).last
47
+
48
+ flipper.adapter.cache['hello'] = 'world'
49
+ body.close
50
+ flipper.adapter.cache.should be_empty
51
+ end
52
+
53
+ it "clears the local cache with a successful request" do
54
+ flipper.adapter.cache['hello'] = 'world'
55
+ get '/'
56
+ flipper.adapter.cache.should be_empty
57
+ end
58
+
59
+ it "clears the local cache even when the request raises an error" do
60
+ flipper.adapter.cache['hello'] = 'world'
61
+ get '/fail' rescue nil
62
+ flipper.adapter.cache.should be_empty
63
+ end
64
+
65
+ it "caches getting a feature for duration of request" do
66
+ flipper[:stats].enable
67
+
68
+ # clear the log of operations
69
+ adapter.reset
70
+
71
+ app = lambda { |env|
72
+ flipper[:stats].enabled?
73
+ flipper[:stats].enabled?
74
+ flipper[:stats].enabled?
75
+ flipper[:stats].enabled?
76
+ flipper[:stats].enabled?
77
+ flipper[:stats].enabled?
78
+ [200, {}, []]
79
+ }
80
+
81
+ middleware = described_class.new app, flipper
82
+ middleware.call({})
83
+
84
+ adapter.count(:get).should be(1)
85
+ end
46
86
  end
47
87
 
48
- it "disables local cache after body close" do
49
- app = lambda { |env| [200, {}, []] }
50
- middleware = described_class.new app, flipper
51
- body = middleware.call({}).last
88
+ context "with flipper instance" do
89
+ let(:app) {
90
+ # ensure scoped for builder block, annoying...
91
+ instance = flipper
92
+ middleware = described_class
52
93
 
53
- flipper.adapter.memoizing?.should be_true
54
- body.close
55
- flipper.adapter.memoizing?.should be_false
56
- end
94
+ Rack::Builder.new do
95
+ use middleware, instance
57
96
 
58
- it "clears local cache after body close" do
59
- app = lambda { |env| [200, {}, []] }
60
- middleware = described_class.new app, flipper
61
- body = middleware.call({}).last
97
+ map "/" do
98
+ run lambda {|env| [200, {}, []] }
99
+ end
62
100
 
63
- flipper.adapter.cache['hello'] = 'world'
64
- body.close
65
- flipper.adapter.cache.should be_empty
66
- end
101
+ map "/fail" do
102
+ run lambda {|env| raise "FAIL!" }
103
+ end
104
+ end.to_app
105
+ }
67
106
 
68
- it "clears the local cache with a successful request" do
69
- flipper.adapter.cache['hello'] = 'world'
70
- get '/'
71
- flipper.adapter.cache.should be_empty
107
+ include_examples "flipper middleware"
72
108
  end
73
109
 
74
- it "clears the local cache even when the request raises an error" do
75
- flipper.adapter.cache['hello'] = 'world'
76
- get '/fail' rescue nil
77
- flipper.adapter.cache.should be_empty
78
- end
110
+ context "with block that yields flipper instance" do
111
+ let(:app) {
112
+ # ensure scoped for builder block, annoying...
113
+ instance = flipper
114
+ middleware = described_class
79
115
 
80
- it "caches getting a feature for duration of request" do
81
- flipper[:stats].enable
116
+ Rack::Builder.new do
117
+ use middleware, lambda { instance }
82
118
 
83
- # clear the log of operations
84
- adapter.reset
119
+ map "/" do
120
+ run lambda {|env| [200, {}, []] }
121
+ end
85
122
 
86
- app = lambda { |env|
87
- flipper[:stats].enabled?
88
- flipper[:stats].enabled?
89
- flipper[:stats].enabled?
90
- flipper[:stats].enabled?
91
- flipper[:stats].enabled?
92
- flipper[:stats].enabled?
93
- [200, {}, []]
123
+ map "/fail" do
124
+ run lambda {|env| raise "FAIL!" }
125
+ end
126
+ end.to_app
94
127
  }
95
128
 
96
- middleware = described_class.new app, flipper
97
- middleware.call({})
98
-
99
- adapter.count(:get).should be(1)
129
+ include_examples "flipper middleware"
100
130
  end
101
131
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-16 00:00:00.000000000 Z
12
+ date: 2013-02-20 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Feature flipper for any adapter
15
15
  email:
@@ -110,7 +110,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
110
110
  version: '0'
111
111
  segments:
112
112
  - 0
113
- hash: -4560839249202297958
113
+ hash: 592688489057647185
114
114
  required_rubygems_version: !ruby/object:Gem::Requirement
115
115
  none: false
116
116
  requirements:
@@ -119,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
119
  version: '0'
120
120
  segments:
121
121
  - 0
122
- hash: -4560839249202297958
122
+ hash: 592688489057647185
123
123
  requirements: []
124
124
  rubyforge_project:
125
125
  rubygems_version: 1.8.23