flipper 0.10.1 → 0.10.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +9 -0
- data/docs/Adapters.md +1 -0
- data/docs/Optimization.md +15 -0
- data/lib/flipper/adapter.rb +11 -1
- data/lib/flipper/adapters/memoizable.rb +44 -12
- data/lib/flipper/adapters/operation_logger.rb +12 -0
- data/lib/flipper/dsl.rb +11 -0
- data/lib/flipper/middleware/memoizer.rb +20 -1
- data/lib/flipper/spec/shared_adapter_specs.rb +27 -14
- data/lib/flipper/test/shared_adapter_test.rb +26 -30
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/adapters/memoizable_spec.rb +41 -9
- data/spec/flipper/dsl_spec.rb +33 -0
- data/spec/flipper/middleware/memoizer_spec.rb +132 -0
- data/spec/helper.rb +1 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: babc8ef94b2e4998e345e538272ce930a3fc8919
|
4
|
+
data.tar.gz: 0fa3de8f4ab472e65bcf203fdaece22a44e481a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b12d5d3debc25773f9ffe0f07d1f58d13b1eaec384603d7cdf1b5d073a25c98d4c61364f94021d5ad017e3071810d1f91a4e69b865004246c8b3b53ff87cf96
|
7
|
+
data.tar.gz: 1633e3e20e4ecb43673c438d1a45e54e35c438ac33f86e25337766b2ca0732f1052cc5dd48af17e2cf33315e5e15756b8a040861a243db93a73fe93d61e4dec0
|
data/Changelog.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## 0.10.2
|
2
|
+
|
3
|
+
* Add Adapter#get_multi to allow for efficient loading of more than one feature at a time (https://github.com/jnunemaker/flipper/pull/198)
|
4
|
+
* Add DSL#preload for efficiently loading several features at once using get_mutli (https://github.com/jnunemaker/flipper/pull/198)
|
5
|
+
* Add :preload and :preload_all options to memoizer as a way of efficiently loading several features for a request in one network call instead of N where N is the number of features checked (https://github.com/jnunemaker/flipper/pull/198)
|
6
|
+
* Strip whitespace out of feature/actor/group values posted by UI (https://github.com/jnunemaker/flipper/pull/205)
|
7
|
+
* Fix bug with dalli adapter where deleting a feature using the UI or API was not clearing the cache in the dalli adapter which meant the feature would continue to use whatever cached enabled state was present until the TTL was hit (1cd96f6)
|
8
|
+
* Change cache keys for dalli adapter. Backwards compatible in that it will just repopulate new keys on first check with this version, but old keys are not expired, so if you used the default ttl of 0, you'll have to expire them on your own. The primary reason for the change was safer namespacing of the cache keys to avoid collisions.
|
9
|
+
|
1
10
|
## 0.10.1
|
2
11
|
|
3
12
|
* Add docker compose support for contributing
|
data/docs/Adapters.md
CHANGED
@@ -29,6 +29,7 @@ The basic API for an adapter is this:
|
|
29
29
|
* `get(feature)` - Get all gate values for a feature.
|
30
30
|
* `enable(feature, gate, thing)` - Enable a gate for a thing.
|
31
31
|
* `disable(feature, gate, thing)` - Disable a gate for a thing.
|
32
|
+
* `get_multi(features)` - Get all gate values for several features at once. Implementation is optional. If none provided, default implementation performs N+1 `get` calls where N is the number of elements in the features parameter.
|
32
33
|
|
33
34
|
If you would like to make your own adapter, there are shared adapter specs (RSpec) and tests (MiniTest) that you can use to verify that you have everything working correctly.
|
34
35
|
|
data/docs/Optimization.md
CHANGED
@@ -34,6 +34,21 @@ config.middleware.use Flipper::Middleware::Memoizer, lambda {
|
|
34
34
|
|
35
35
|
**Note**: Be sure that the middleware is high enough up in your stack that all feature checks are wrapped.
|
36
36
|
|
37
|
+
### Options
|
38
|
+
|
39
|
+
The Memoizer middleware also supports a few options. Use either `preload` or `preload_all`, not both.
|
40
|
+
|
41
|
+
* **`:preload`** - An `Array` of feature names (`Symbol`) to preload for every request. Useful if you have features that are used on every endpoint. `preload` uses `Adapter#get_multi` to attempt to load the features in one network call instead of N+1 network calls.
|
42
|
+
```ruby
|
43
|
+
config.middleware.use Flipper::Middleware::Memoizer, flipper,
|
44
|
+
preload: [:stats, :search, :some_feature]
|
45
|
+
```
|
46
|
+
* **`:preload_all`** - A Boolean value (default: false) of whether or not all features should be preloaded. Using this results in a `preload` call with the result of `Adapter#features`. Any subsequent feature checks will be memoized and perform no network calls. I wouldn't recommend using this unless you have few features (< 30?) and nearly all of them are used on every request.
|
47
|
+
```ruby
|
48
|
+
config.middleware.use Flipper::Middleware::Memoizer, flipper,
|
49
|
+
preload_all: true
|
50
|
+
```
|
51
|
+
|
37
52
|
## Cache Adapters
|
38
53
|
|
39
54
|
Cache adapters allow you to cache adapter calls for longer than a single request and should be used alongside the memoization middleware to add another caching layer.
|
data/lib/flipper/adapter.rb
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
module Flipper
|
2
|
+
# Adding a module include so we have some hooks for stuff down the road
|
2
3
|
module Adapter
|
3
|
-
#
|
4
|
+
# Public: Get multiple features in one call. Defaults to one get per
|
5
|
+
# feature. Feel free to override per adapter to make this more efficient and
|
6
|
+
# reduce network calls.
|
7
|
+
def get_multi(features)
|
8
|
+
result = {}
|
9
|
+
features.each do |feature|
|
10
|
+
result[feature.key] = get(feature)
|
11
|
+
end
|
12
|
+
result
|
13
|
+
end
|
4
14
|
end
|
5
15
|
end
|
@@ -31,9 +31,7 @@ module Flipper
|
|
31
31
|
# Public
|
32
32
|
def features
|
33
33
|
if memoizing?
|
34
|
-
cache.fetch(FeaturesKey) {
|
35
|
-
cache[FeaturesKey] = @adapter.features
|
36
|
-
}
|
34
|
+
cache.fetch(FeaturesKey) { cache[FeaturesKey] = @adapter.features }
|
37
35
|
else
|
38
36
|
@adapter.features
|
39
37
|
end
|
@@ -42,47 +40,67 @@ module Flipper
|
|
42
40
|
# Public
|
43
41
|
def add(feature)
|
44
42
|
result = @adapter.add(feature)
|
45
|
-
|
43
|
+
expire_features_set
|
46
44
|
result
|
47
45
|
end
|
48
46
|
|
49
47
|
# Public
|
50
48
|
def remove(feature)
|
51
49
|
result = @adapter.remove(feature)
|
52
|
-
|
53
|
-
|
54
|
-
cache.delete(feature)
|
55
|
-
end
|
50
|
+
expire_features_set
|
51
|
+
expire_feature(feature)
|
56
52
|
result
|
57
53
|
end
|
58
54
|
|
59
55
|
# Public
|
60
56
|
def clear(feature)
|
61
57
|
result = @adapter.clear(feature)
|
62
|
-
|
58
|
+
expire_feature(feature)
|
63
59
|
result
|
64
60
|
end
|
65
61
|
|
66
62
|
# Public
|
67
63
|
def get(feature)
|
68
64
|
if memoizing?
|
69
|
-
cache.fetch(feature) { cache[feature] = @adapter.get(feature) }
|
65
|
+
cache.fetch(feature.key) { cache[feature.key] = @adapter.get(feature) }
|
70
66
|
else
|
71
67
|
@adapter.get(feature)
|
72
68
|
end
|
73
69
|
end
|
74
70
|
|
71
|
+
# Public
|
72
|
+
def get_multi(features)
|
73
|
+
if memoizing?
|
74
|
+
uncached_features = features.reject { |feature| cache[feature.key] }
|
75
|
+
|
76
|
+
if uncached_features.any?
|
77
|
+
response = @adapter.get_multi(uncached_features)
|
78
|
+
response.each do |key, hash|
|
79
|
+
cache[key] = hash
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
result = {}
|
84
|
+
features.each do |feature|
|
85
|
+
result[feature.key] = cache[feature.key]
|
86
|
+
end
|
87
|
+
result
|
88
|
+
else
|
89
|
+
@adapter.get_multi(features)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
75
93
|
# Public
|
76
94
|
def enable(feature, gate, thing)
|
77
95
|
result = @adapter.enable(feature, gate, thing)
|
78
|
-
|
96
|
+
expire_feature(feature)
|
79
97
|
result
|
80
98
|
end
|
81
99
|
|
82
100
|
# Public
|
83
101
|
def disable(feature, gate, thing)
|
84
102
|
result = @adapter.disable(feature, gate, thing)
|
85
|
-
|
103
|
+
expire_feature(feature)
|
86
104
|
result
|
87
105
|
end
|
88
106
|
|
@@ -98,6 +116,20 @@ module Flipper
|
|
98
116
|
def memoizing?
|
99
117
|
!!@memoize
|
100
118
|
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def expire_feature(feature)
|
123
|
+
if memoizing?
|
124
|
+
cache.delete(feature.key)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def expire_features_set
|
129
|
+
if memoizing?
|
130
|
+
cache.delete(FeaturesKey)
|
131
|
+
end
|
132
|
+
end
|
101
133
|
end
|
102
134
|
end
|
103
135
|
end
|
@@ -16,6 +16,7 @@ module Flipper
|
|
16
16
|
:remove,
|
17
17
|
:clear,
|
18
18
|
:get,
|
19
|
+
:get_multi,
|
19
20
|
:enable,
|
20
21
|
:disable,
|
21
22
|
]
|
@@ -65,6 +66,12 @@ module Flipper
|
|
65
66
|
@adapter.get(feature)
|
66
67
|
end
|
67
68
|
|
69
|
+
# Public
|
70
|
+
def get_multi(features)
|
71
|
+
@operations << Operation.new(:get_multi, [features])
|
72
|
+
@adapter.get_multi(features)
|
73
|
+
end
|
74
|
+
|
68
75
|
# Public
|
69
76
|
def enable(feature, gate, thing)
|
70
77
|
@operations << Operation.new(:enable, [feature, gate, thing])
|
@@ -82,6 +89,11 @@ module Flipper
|
|
82
89
|
@operations.select { |operation| operation.type == type }.size
|
83
90
|
end
|
84
91
|
|
92
|
+
# Public: Get the last operation of a certain type.
|
93
|
+
def last(type)
|
94
|
+
@operations.reverse.find { |operation| operation.type == type }
|
95
|
+
end
|
96
|
+
|
85
97
|
# Public: Resets the operation log to empty
|
86
98
|
def reset
|
87
99
|
@operations.clear
|
data/lib/flipper/dsl.rb
CHANGED
@@ -163,6 +163,17 @@ module Flipper
|
|
163
163
|
})
|
164
164
|
end
|
165
165
|
|
166
|
+
# Public: Preload the features with the given names.
|
167
|
+
#
|
168
|
+
# names - An Array of String or Symbol names of the features.
|
169
|
+
#
|
170
|
+
# Returns an Array of Flipper::Feature.
|
171
|
+
def preload(names)
|
172
|
+
features = names.map { |name| feature(name) }
|
173
|
+
@adapter.get_multi(features)
|
174
|
+
features
|
175
|
+
end
|
176
|
+
|
166
177
|
# Public: Shortcut access to a feature instance by name.
|
167
178
|
#
|
168
179
|
# name - The String or Symbol name of the feature.
|
@@ -18,8 +18,15 @@ module Flipper
|
|
18
18
|
# # using with a block that yields a flipper instance
|
19
19
|
# use Flipper::Middleware::Memoizer, lambda { Flipper.new(...) }
|
20
20
|
#
|
21
|
-
|
21
|
+
# # using with preload_all features
|
22
|
+
# use Flipper::Middleware::Memoizer, flipper, preload_all: true
|
23
|
+
#
|
24
|
+
# # using with preload specific features
|
25
|
+
# use Flipper::Middleware::Memoizer, flipper, preload: [:stats, :search, :some_feature]
|
26
|
+
#
|
27
|
+
def initialize(app, flipper_or_block, opts = {})
|
22
28
|
@app = app
|
29
|
+
@opts = opts
|
23
30
|
|
24
31
|
if flipper_or_block.respond_to?(:call)
|
25
32
|
@flipper_block = flipper_or_block
|
@@ -36,11 +43,23 @@ module Flipper
|
|
36
43
|
original = flipper.adapter.memoizing?
|
37
44
|
flipper.adapter.memoize = true
|
38
45
|
|
46
|
+
if @opts[:preload_all]
|
47
|
+
names = flipper.features.map(&:name)
|
48
|
+
flipper.preload(names)
|
49
|
+
end
|
50
|
+
|
51
|
+
if @opts[:preload]
|
52
|
+
flipper.preload(@opts[:preload])
|
53
|
+
end
|
54
|
+
|
39
55
|
response = @app.call(env)
|
40
56
|
response[2] = Rack::BodyProxy.new(response[2]) {
|
41
57
|
flipper.adapter.memoize = original
|
42
58
|
}
|
43
59
|
response
|
60
|
+
rescue
|
61
|
+
flipper.adapter.memoize = original
|
62
|
+
raise
|
44
63
|
end
|
45
64
|
end
|
46
65
|
end
|
@@ -12,6 +12,16 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
12
12
|
let(:actors_gate) { feature.gate(:percentage_of_actors) }
|
13
13
|
let(:time_gate) { feature.gate(:percentage_of_time) }
|
14
14
|
|
15
|
+
let(:default_config) {
|
16
|
+
{
|
17
|
+
:boolean => nil,
|
18
|
+
:groups => Set.new,
|
19
|
+
:actors => Set.new,
|
20
|
+
:percentage_of_actors => nil,
|
21
|
+
:percentage_of_time => nil,
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
15
25
|
before do
|
16
26
|
Flipper.register(:admins) { |actor|
|
17
27
|
actor.respond_to?(:admin?) && actor.admin?
|
@@ -36,13 +46,7 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
36
46
|
end
|
37
47
|
|
38
48
|
it "returns correct default values for the gates if none are enabled" do
|
39
|
-
expect(subject.get(feature)).to eq(
|
40
|
-
:boolean => nil,
|
41
|
-
:groups => Set.new,
|
42
|
-
:actors => Set.new,
|
43
|
-
:percentage_of_actors => nil,
|
44
|
-
:percentage_of_time => nil,
|
45
|
-
})
|
49
|
+
expect(subject.get(feature)).to eq(default_config)
|
46
50
|
end
|
47
51
|
|
48
52
|
it "can enable, disable and get value for boolean gate" do
|
@@ -241,16 +245,25 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
241
245
|
|
242
246
|
expect(subject.clear(feature)).to eq(true)
|
243
247
|
expect(subject.features).to include(feature.key)
|
244
|
-
expect(subject.get(feature)).to eq(
|
245
|
-
:boolean => nil,
|
246
|
-
:groups => Set.new,
|
247
|
-
:actors => Set.new,
|
248
|
-
:percentage_of_actors => nil,
|
249
|
-
:percentage_of_time => nil,
|
250
|
-
})
|
248
|
+
expect(subject.get(feature)).to eq(default_config)
|
251
249
|
end
|
252
250
|
|
253
251
|
it "does not complain clearing a feature that does not exist in adapter" do
|
254
252
|
expect(subject.clear(flipper[:stats])).to eq(true)
|
255
253
|
end
|
254
|
+
|
255
|
+
it "can get multiple features" do
|
256
|
+
expect(subject.add(flipper[:stats])).to eq(true)
|
257
|
+
expect(subject.enable(flipper[:stats], boolean_gate, flipper.boolean)).to eq(true)
|
258
|
+
|
259
|
+
expect(subject.add(flipper[:search])).to eq(true)
|
260
|
+
|
261
|
+
result = subject.get_multi([flipper[:stats], flipper[:search], flipper[:other]])
|
262
|
+
expect(result).to be_instance_of(Hash)
|
263
|
+
|
264
|
+
stats, search, other = result.values
|
265
|
+
expect(stats).to eq(default_config.merge(boolean: "true"))
|
266
|
+
expect(search).to eq(default_config)
|
267
|
+
expect(other).to eq(default_config)
|
268
|
+
end
|
256
269
|
end
|
@@ -12,6 +12,14 @@ module Flipper
|
|
12
12
|
@actors_gate = @feature.gate(:percentage_of_actors)
|
13
13
|
@time_gate = @feature.gate(:percentage_of_time)
|
14
14
|
|
15
|
+
@default_config = {
|
16
|
+
:boolean => nil,
|
17
|
+
:groups => Set.new,
|
18
|
+
:actors => Set.new,
|
19
|
+
:percentage_of_actors => nil,
|
20
|
+
:percentage_of_time => nil,
|
21
|
+
}
|
22
|
+
|
15
23
|
Flipper.register(:admins) do |actor|
|
16
24
|
actor.respond_to?(:admin?) && actor.admin?
|
17
25
|
end
|
@@ -36,14 +44,7 @@ module Flipper
|
|
36
44
|
end
|
37
45
|
|
38
46
|
def test_returns_correct_default_values_for_gates_if_none_are_enabled
|
39
|
-
|
40
|
-
:boolean => nil,
|
41
|
-
:groups => Set.new,
|
42
|
-
:actors => Set.new,
|
43
|
-
:percentage_of_actors => nil,
|
44
|
-
:percentage_of_time => nil,
|
45
|
-
}
|
46
|
-
assert_equal expected, @adapter.get(@feature)
|
47
|
+
assert_equal @default_config, @adapter.get(@feature)
|
47
48
|
end
|
48
49
|
|
49
50
|
def test_can_enable_disable_and_get_value_for_boolean_gate
|
@@ -61,14 +62,7 @@ module Flipper
|
|
61
62
|
assert_equal true, @adapter.enable(@feature, @actors_gate, @flipper.actors(25))
|
62
63
|
assert_equal true, @adapter.enable(@feature, @time_gate, @flipper.time(45))
|
63
64
|
assert_equal true, @adapter.disable(@feature, @boolean_gate, @flipper.boolean(false))
|
64
|
-
|
65
|
-
:boolean => nil,
|
66
|
-
:groups => Set.new,
|
67
|
-
:actors => Set.new,
|
68
|
-
:percentage_of_actors => nil,
|
69
|
-
:percentage_of_time => nil,
|
70
|
-
}
|
71
|
-
assert_equal expected, @adapter.get(@feature)
|
65
|
+
assert_equal @default_config, @adapter.get(@feature)
|
72
66
|
end
|
73
67
|
|
74
68
|
def test_can_enable_disable_get_value_for_group_gate
|
@@ -214,13 +208,7 @@ module Flipper
|
|
214
208
|
|
215
209
|
assert_equal true, @adapter.remove(@feature)
|
216
210
|
|
217
|
-
assert_equal @adapter.get(@feature)
|
218
|
-
:boolean => nil,
|
219
|
-
:groups => Set.new,
|
220
|
-
:actors => Set.new,
|
221
|
-
:percentage_of_actors => nil,
|
222
|
-
:percentage_of_time => nil,
|
223
|
-
}
|
211
|
+
assert_equal @default_config, @adapter.get(@feature)
|
224
212
|
end
|
225
213
|
|
226
214
|
def test_can_clear_all_the_gate_values_for_a_feature
|
@@ -236,18 +224,26 @@ module Flipper
|
|
236
224
|
|
237
225
|
assert_equal true, @adapter.clear(@feature)
|
238
226
|
assert_includes @adapter.features, @feature.key
|
239
|
-
assert_equal @adapter.get(@feature)
|
240
|
-
:boolean => nil,
|
241
|
-
:groups => Set.new,
|
242
|
-
:actors => Set.new,
|
243
|
-
:percentage_of_actors => nil,
|
244
|
-
:percentage_of_time => nil,
|
245
|
-
}
|
227
|
+
assert_equal @default_config, @adapter.get(@feature)
|
246
228
|
end
|
247
229
|
|
248
230
|
def test_does_not_complain_clearing_a_feature_that_does_not_exist_in_adapter
|
249
231
|
assert_equal true, @adapter.clear(@flipper[:stats])
|
250
232
|
end
|
233
|
+
|
234
|
+
def test_can_get_multiple_features
|
235
|
+
assert @adapter.add(@flipper[:stats])
|
236
|
+
assert @adapter.enable(@flipper[:stats], @boolean_gate, @flipper.boolean)
|
237
|
+
assert @adapter.add(@flipper[:search])
|
238
|
+
|
239
|
+
result = @adapter.get_multi([@flipper[:stats], @flipper[:search], @flipper[:other]])
|
240
|
+
assert_instance_of Hash, result
|
241
|
+
|
242
|
+
stats, search, other = result.values
|
243
|
+
assert_equal @default_config.merge(boolean: "true"), stats
|
244
|
+
assert_equal @default_config, search
|
245
|
+
assert_equal @default_config, other
|
246
|
+
end
|
251
247
|
end
|
252
248
|
end
|
253
249
|
end
|
data/lib/flipper/version.rb
CHANGED
@@ -66,7 +66,7 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
66
66
|
it "memoizes feature" do
|
67
67
|
feature = flipper[:stats]
|
68
68
|
result = subject.get(feature)
|
69
|
-
expect(cache[feature]).to be(result)
|
69
|
+
expect(cache[feature.key]).to be(result)
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
@@ -84,6 +84,38 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
|
+
describe "#get_multi" do
|
88
|
+
context "with memoization enabled" do
|
89
|
+
before do
|
90
|
+
subject.memoize = true
|
91
|
+
end
|
92
|
+
|
93
|
+
it "memoizes feature" do
|
94
|
+
names = %i{stats shiny}
|
95
|
+
features = names.map { |name| flipper[name] }
|
96
|
+
results = subject.get_multi(features)
|
97
|
+
features.each do |feature|
|
98
|
+
expect(cache[feature.key]).to_not be(nil)
|
99
|
+
expect(cache[feature.key]).to be(results[feature.key])
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context "with memoization disabled" do
|
105
|
+
before do
|
106
|
+
subject.memoize = false
|
107
|
+
end
|
108
|
+
|
109
|
+
it "returns result" do
|
110
|
+
names = %i{stats shiny}
|
111
|
+
features = names.map { |name| flipper[name] }
|
112
|
+
result = subject.get_multi(features)
|
113
|
+
adapter_result = adapter.get_multi(features)
|
114
|
+
expect(result).to eq(adapter_result)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
87
119
|
describe "#enable" do
|
88
120
|
context "with memoization enabled" do
|
89
121
|
before do
|
@@ -93,9 +125,9 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
93
125
|
it "unmemoizes feature" do
|
94
126
|
feature = flipper[:stats]
|
95
127
|
gate = feature.gate(:boolean)
|
96
|
-
cache[feature] = {:some => 'thing'}
|
128
|
+
cache[feature.key] = {:some => 'thing'}
|
97
129
|
subject.enable(feature, gate, flipper.bool)
|
98
|
-
expect(cache[feature]).to be_nil
|
130
|
+
expect(cache[feature.key]).to be_nil
|
99
131
|
end
|
100
132
|
end
|
101
133
|
|
@@ -123,9 +155,9 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
123
155
|
it "unmemoizes feature" do
|
124
156
|
feature = flipper[:stats]
|
125
157
|
gate = feature.gate(:boolean)
|
126
|
-
cache[feature] = {:some => 'thing'}
|
158
|
+
cache[feature.key] = {:some => 'thing'}
|
127
159
|
subject.disable(feature, gate, flipper.bool)
|
128
|
-
expect(cache[feature]).to be_nil
|
160
|
+
expect(cache[feature.key]).to be_nil
|
129
161
|
end
|
130
162
|
end
|
131
163
|
|
@@ -207,9 +239,9 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
207
239
|
|
208
240
|
it "unmemoizes the feature" do
|
209
241
|
feature = flipper[:stats]
|
210
|
-
cache[feature] = {:some => 'thing'}
|
242
|
+
cache[feature.key] = {:some => 'thing'}
|
211
243
|
subject.remove(feature)
|
212
|
-
expect(cache[feature]).to be_nil
|
244
|
+
expect(cache[feature.key]).to be_nil
|
213
245
|
end
|
214
246
|
end
|
215
247
|
|
@@ -232,9 +264,9 @@ RSpec.describe Flipper::Adapters::Memoizable do
|
|
232
264
|
|
233
265
|
it "unmemoizes feature" do
|
234
266
|
feature = flipper[:stats]
|
235
|
-
cache[feature] = {:some => 'thing'}
|
267
|
+
cache[feature.key] = {:some => 'thing'}
|
236
268
|
subject.clear(feature)
|
237
|
-
expect(cache[feature]).to be_nil
|
269
|
+
expect(cache[feature.key]).to be_nil
|
238
270
|
end
|
239
271
|
end
|
240
272
|
|
data/spec/flipper/dsl_spec.rb
CHANGED
@@ -37,6 +37,39 @@ RSpec.describe Flipper::DSL do
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
describe "#preload" do
|
41
|
+
let(:instrumenter) { double('Instrumentor', :instrument => nil) }
|
42
|
+
let(:dsl) { Flipper::DSL.new(adapter, :instrumenter => instrumenter) }
|
43
|
+
let(:names) { %i{stats shiny} }
|
44
|
+
let(:features) { dsl.preload(names) }
|
45
|
+
|
46
|
+
it "returns array of features" do
|
47
|
+
expect(features).to all be_instance_of(Flipper::Feature)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "sets names" do
|
51
|
+
expect(features.map(&:name)).to eq(names)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "sets adapter" do
|
55
|
+
features.each do |feature|
|
56
|
+
expect(feature.adapter.name).to eq(dsl.adapter.name)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it "sets instrumenter" do
|
61
|
+
features.each do |feature|
|
62
|
+
expect(feature.instrumenter).to eq(dsl.instrumenter)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it "memoizes the feature" do
|
67
|
+
features.each do |feature|
|
68
|
+
expect(dsl.feature(feature.name)).to equal(feature)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
40
73
|
describe "#[]" do
|
41
74
|
it_should_behave_like "a DSL feature" do
|
42
75
|
let(:method_name) { :[] }
|
@@ -106,6 +106,138 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
106
106
|
include_examples "flipper middleware"
|
107
107
|
end
|
108
108
|
|
109
|
+
context "with preload_all" do
|
110
|
+
let(:app) {
|
111
|
+
# ensure scoped for builder block, annoying...
|
112
|
+
instance = flipper
|
113
|
+
middleware = described_class
|
114
|
+
|
115
|
+
Rack::Builder.new do
|
116
|
+
use middleware, instance, preload_all: true
|
117
|
+
|
118
|
+
map "/" do
|
119
|
+
run lambda {|env| [200, {}, []] }
|
120
|
+
end
|
121
|
+
|
122
|
+
map "/fail" do
|
123
|
+
run lambda {|env| raise "FAIL!" }
|
124
|
+
end
|
125
|
+
end.to_app
|
126
|
+
}
|
127
|
+
|
128
|
+
include_examples "flipper middleware"
|
129
|
+
|
130
|
+
it "eagerly caches known features for duration of request" do
|
131
|
+
flipper[:stats].enable
|
132
|
+
flipper[:shiny].enable
|
133
|
+
|
134
|
+
# clear the log of operations
|
135
|
+
adapter.reset
|
136
|
+
|
137
|
+
app = lambda { |env|
|
138
|
+
flipper[:stats].enabled?
|
139
|
+
flipper[:stats].enabled?
|
140
|
+
flipper[:shiny].enabled?
|
141
|
+
flipper[:shiny].enabled?
|
142
|
+
[200, {}, []]
|
143
|
+
}
|
144
|
+
|
145
|
+
middleware = described_class.new app, flipper, preload_all: true
|
146
|
+
middleware.call({})
|
147
|
+
|
148
|
+
expect(adapter.count(:features)).to be(1)
|
149
|
+
expect(adapter.count(:get_multi)).to be(1)
|
150
|
+
expect(adapter.last(:get_multi).args).to eq([[flipper[:stats], flipper[:shiny]]])
|
151
|
+
end
|
152
|
+
|
153
|
+
it "caches unknown features for duration of request" do
|
154
|
+
# clear the log of operations
|
155
|
+
adapter.reset
|
156
|
+
|
157
|
+
app = lambda { |env|
|
158
|
+
flipper[:other].enabled?
|
159
|
+
flipper[:other].enabled?
|
160
|
+
[200, {}, []]
|
161
|
+
}
|
162
|
+
|
163
|
+
middleware = described_class.new app, flipper, preload_all: true
|
164
|
+
middleware.call({})
|
165
|
+
|
166
|
+
expect(adapter.count(:get)).to be(1)
|
167
|
+
expect(adapter.last(:get).args).to eq([flipper[:other]])
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context "with preload specific" do
|
172
|
+
let(:app) {
|
173
|
+
# ensure scoped for builder block, annoying...
|
174
|
+
instance = flipper
|
175
|
+
middleware = described_class
|
176
|
+
|
177
|
+
Rack::Builder.new do
|
178
|
+
use middleware, instance, preload: %i{stats}
|
179
|
+
|
180
|
+
map "/" do
|
181
|
+
run lambda {|env| [200, {}, []] }
|
182
|
+
end
|
183
|
+
|
184
|
+
map "/fail" do
|
185
|
+
run lambda {|env| raise "FAIL!" }
|
186
|
+
end
|
187
|
+
end.to_app
|
188
|
+
}
|
189
|
+
|
190
|
+
include_examples "flipper middleware"
|
191
|
+
|
192
|
+
it "eagerly caches specified features for duration of request" do
|
193
|
+
# clear the log of operations
|
194
|
+
adapter.reset
|
195
|
+
|
196
|
+
app = lambda { |env|
|
197
|
+
flipper[:stats].enabled?
|
198
|
+
flipper[:stats].enabled?
|
199
|
+
flipper[:shiny].enabled?
|
200
|
+
flipper[:shiny].enabled?
|
201
|
+
[200, {}, []]
|
202
|
+
}
|
203
|
+
|
204
|
+
middleware = described_class.new app, flipper, preload: %i{stats}
|
205
|
+
middleware.call({})
|
206
|
+
|
207
|
+
expect(adapter.count(:get_multi)).to be(1)
|
208
|
+
expect(adapter.last(:get_multi).args).to eq([[flipper[:stats]]])
|
209
|
+
end
|
210
|
+
|
211
|
+
it "caches unknown features for duration of request" do
|
212
|
+
# clear the log of operations
|
213
|
+
adapter.reset
|
214
|
+
|
215
|
+
app = lambda { |env|
|
216
|
+
flipper[:other].enabled?
|
217
|
+
flipper[:other].enabled?
|
218
|
+
[200, {}, []]
|
219
|
+
}
|
220
|
+
|
221
|
+
middleware = described_class.new app, flipper, preload: %i{stats}
|
222
|
+
middleware.call({})
|
223
|
+
|
224
|
+
expect(adapter.count(:get)).to be(1)
|
225
|
+
expect(adapter.last(:get).args).to eq([flipper[:other]])
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
context "when an app raises an exception" do
|
230
|
+
it "resets memoize" do
|
231
|
+
begin
|
232
|
+
app = lambda { |env| raise }
|
233
|
+
middleware = described_class.new app, flipper
|
234
|
+
middleware.call({})
|
235
|
+
rescue RuntimeError
|
236
|
+
expect(flipper.adapter.memoizing?).to be(false)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
109
241
|
context "with block that yields flipper instance" do
|
110
242
|
let(:app) {
|
111
243
|
# ensure scoped for builder block, annoying...
|
data/spec/helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flipper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.10.
|
4
|
+
version: 0.10.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Nunemaker
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-12-08 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Feature flipper is the act of enabling/disabling features in your application,
|
14
14
|
ideally without re-deploying or changing anything in your code base. Flipper makes
|