flipper 0.20.1 → 0.21.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +57 -0
- data/Changelog.md +66 -0
- data/Gemfile +1 -0
- data/README.md +103 -47
- data/docs/Adapters.md +9 -9
- data/docs/Caveats.md +2 -2
- data/docs/Gates.md +74 -74
- data/docs/Optimization.md +70 -47
- data/docs/http/README.md +12 -11
- data/docs/images/banner.jpg +0 -0
- data/docs/read-only/README.md +8 -5
- data/examples/basic.rb +1 -12
- data/examples/configuring_default.rb +2 -5
- data/examples/dsl.rb +13 -24
- data/examples/enabled_for_actor.rb +8 -15
- data/examples/group.rb +3 -6
- data/examples/group_dynamic_lookup.rb +5 -19
- data/examples/group_with_members.rb +4 -14
- data/examples/importing.rb +1 -1
- data/examples/individual_actor.rb +2 -5
- data/examples/instrumentation.rb +1 -2
- data/examples/memoizing.rb +3 -7
- data/examples/percentage_of_actors.rb +6 -16
- data/examples/percentage_of_actors_enabled_check.rb +7 -10
- data/examples/percentage_of_actors_group.rb +5 -18
- data/examples/percentage_of_time.rb +3 -6
- data/lib/flipper.rb +4 -1
- data/lib/flipper/adapters/http.rb +32 -28
- data/lib/flipper/adapters/memory.rb +20 -94
- data/lib/flipper/adapters/pstore.rb +4 -0
- data/lib/flipper/adapters/sync/interval_synchronizer.rb +1 -1
- data/lib/flipper/configuration.rb +33 -7
- data/lib/flipper/errors.rb +2 -3
- data/lib/flipper/identifier.rb +17 -0
- data/lib/flipper/middleware/memoizer.rb +29 -14
- data/lib/flipper/railtie.rb +38 -0
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/adapters/http_spec.rb +74 -8
- data/spec/flipper/adapters/memory_spec.rb +21 -1
- data/spec/flipper/configuration_spec.rb +20 -2
- data/spec/flipper/identifier_spec.rb +14 -0
- data/spec/flipper/middleware/memoizer_spec.rb +95 -35
- data/spec/flipper/middleware/setup_env_spec.rb +0 -16
- data/spec/flipper/railtie_spec.rb +69 -0
- data/spec/flipper_spec.rb +0 -1
- data/spec/support/spec_helpers.rb +20 -0
- data/test/test_helper.rb +1 -0
- metadata +12 -5
- data/examples/example_setup.rb +0 -8
@@ -19,7 +19,7 @@ module Flipper
|
|
19
19
|
# Public: Initializes a new interval synchronizer.
|
20
20
|
#
|
21
21
|
# synchronizer - The Synchronizer to call when the interval has passed.
|
22
|
-
# interval - The Integer number of
|
22
|
+
# interval - The Integer number of seconds between invocations of
|
23
23
|
# the wrapped synchronizer.
|
24
24
|
def initialize(synchronizer, interval: nil)
|
25
25
|
@synchronizer = synchronizer
|
@@ -1,7 +1,33 @@
|
|
1
1
|
module Flipper
|
2
2
|
class Configuration
|
3
|
-
def initialize
|
4
|
-
@default = -> {
|
3
|
+
def initialize(options = {})
|
4
|
+
@default = -> { Flipper.new(adapter) }
|
5
|
+
@adapter = -> { Flipper::Adapters::Memory.new }
|
6
|
+
end
|
7
|
+
|
8
|
+
# The default adapter to use.
|
9
|
+
#
|
10
|
+
# Pass a block to assign the adapter, and invoke without a block to
|
11
|
+
# return the configured adapter instance.
|
12
|
+
#
|
13
|
+
# Flipper.configure do |config|
|
14
|
+
# config.adapter # => instance of default Memory adapter
|
15
|
+
#
|
16
|
+
# # Configure it to use the ActiveRecord adapter
|
17
|
+
# config.adapter do
|
18
|
+
# require "flipper-active_record"
|
19
|
+
# Flipper::Adapters::ActiveRecord.new
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# config.adapter # => instance of ActiveRecord adapter
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
def adapter(&block)
|
26
|
+
if block_given?
|
27
|
+
@adapter = block
|
28
|
+
else
|
29
|
+
@adapter.call
|
30
|
+
end
|
5
31
|
end
|
6
32
|
|
7
33
|
# Controls the default instance for flipper. When used with a block it
|
@@ -9,15 +35,15 @@ module Flipper
|
|
9
35
|
# without a block, it performs a block invocation and returns the result.
|
10
36
|
#
|
11
37
|
# configuration = Flipper::Configuration.new
|
12
|
-
# configuration.default # =>
|
38
|
+
# configuration.default # => Flipper::DSL instance using Memory adapter
|
13
39
|
#
|
14
|
-
# # sets the default block to generate a new instance using
|
40
|
+
# # sets the default block to generate a new instance using ActiveRecord adapter
|
15
41
|
# configuration.default do
|
16
|
-
# require "flipper
|
17
|
-
# Flipper.new(Flipper::Adapters::
|
42
|
+
# require "flipper-active_record"
|
43
|
+
# Flipper.new(Flipper::Adapters::ActiveRecord.new)
|
18
44
|
# end
|
19
45
|
#
|
20
|
-
# configuration.default # => Flipper::DSL instance using
|
46
|
+
# configuration.default # => Flipper::DSL instance using ActiveRecord adapter
|
21
47
|
#
|
22
48
|
# Returns result of default block invocation if called without block. If
|
23
49
|
# called with block, assigns the default block.
|
data/lib/flipper/errors.rb
CHANGED
@@ -16,9 +16,8 @@ module Flipper
|
|
16
16
|
# use it.
|
17
17
|
class DefaultNotSet < Flipper::Error
|
18
18
|
def initialize(message = nil)
|
19
|
-
|
20
|
-
|
21
|
-
super(message || default)
|
19
|
+
warn "Flipper::DefaultNotSet is deprecated and will be removed in 1.0"
|
20
|
+
super
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Flipper
|
2
|
+
# A default implementation of `#flipper_id` for actors.
|
3
|
+
#
|
4
|
+
# class User < Struct.new(:id)
|
5
|
+
# include Flipper::Identifier
|
6
|
+
# end
|
7
|
+
#
|
8
|
+
# user = User.new(99)
|
9
|
+
# Flipper.enable :thing, user
|
10
|
+
# Flipper.enabled? :thing, user #=> true
|
11
|
+
#
|
12
|
+
module Identifier
|
13
|
+
def flipper_id
|
14
|
+
"#{self.class.name};#{id}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -8,8 +8,7 @@ module Flipper
|
|
8
8
|
#
|
9
9
|
# app - The app this middleware is included in.
|
10
10
|
# opts - The Hash of options.
|
11
|
-
# :
|
12
|
-
# :preload - Array of Symbol feature names to preload.
|
11
|
+
# :preload - Boolean to preload all features or Array of Symbol feature names to preload.
|
13
12
|
#
|
14
13
|
# Examples
|
15
14
|
#
|
@@ -26,6 +25,11 @@ module Flipper
|
|
26
25
|
raise 'Flipper::Middleware::Memoizer no longer initializes with a flipper instance or block. Read more at: https://git.io/vSo31.'
|
27
26
|
end
|
28
27
|
|
28
|
+
if opts[:preload_all]
|
29
|
+
warn "Flipper::Middleware::Memoizer: `preload_all` is deprecated, use `preload: true`"
|
30
|
+
opts[:preload] = true
|
31
|
+
end
|
32
|
+
|
29
33
|
@app = app
|
30
34
|
@opts = opts
|
31
35
|
@env_key = opts.fetch(:env_key, 'flipper')
|
@@ -34,39 +38,50 @@ module Flipper
|
|
34
38
|
def call(env)
|
35
39
|
request = Rack::Request.new(env)
|
36
40
|
|
37
|
-
if
|
38
|
-
@app.call(env)
|
39
|
-
else
|
41
|
+
if memoize?(request)
|
40
42
|
memoized_call(env)
|
43
|
+
else
|
44
|
+
@app.call(env)
|
41
45
|
end
|
42
46
|
end
|
43
47
|
|
44
48
|
private
|
45
49
|
|
46
|
-
def
|
47
|
-
|
50
|
+
def memoize?(request)
|
51
|
+
if @opts[:if]
|
52
|
+
@opts[:if].call(request)
|
53
|
+
elsif @opts[:unless]
|
54
|
+
!@opts[:unless].call(request)
|
55
|
+
else
|
56
|
+
true
|
57
|
+
end
|
48
58
|
end
|
49
59
|
|
50
60
|
def memoized_call(env)
|
51
61
|
reset_on_body_close = false
|
52
62
|
flipper = env.fetch(@env_key) { Flipper }
|
53
|
-
original = flipper.memoizing?
|
54
|
-
flipper.memoize = true
|
55
63
|
|
56
|
-
|
64
|
+
# Already memoizing. This instance does not need to do anything.
|
65
|
+
if flipper.memoizing?
|
66
|
+
warn "Flipper::Middleware::Memoizer appears to be running twice. Read how to resolve this at https://github.com/jnunemaker/flipper/pull/523"
|
67
|
+
return @app.call(env)
|
68
|
+
end
|
69
|
+
|
70
|
+
flipper.memoize = true
|
57
71
|
|
58
|
-
|
59
|
-
|
72
|
+
case @opts[:preload]
|
73
|
+
when true then flipper.preload_all
|
74
|
+
when Array then flipper.preload(@opts[:preload])
|
60
75
|
end
|
61
76
|
|
62
77
|
response = @app.call(env)
|
63
78
|
response[2] = Rack::BodyProxy.new(response[2]) do
|
64
|
-
flipper.memoize =
|
79
|
+
flipper.memoize = false
|
65
80
|
end
|
66
81
|
reset_on_body_close = true
|
67
82
|
response
|
68
83
|
ensure
|
69
|
-
flipper.memoize =
|
84
|
+
flipper.memoize = false if flipper && !reset_on_body_close
|
70
85
|
end
|
71
86
|
end
|
72
87
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Flipper
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
config.before_configuration do
|
4
|
+
config.flipper = ActiveSupport::OrderedOptions.new.update(
|
5
|
+
env_key: "flipper",
|
6
|
+
memoize: true,
|
7
|
+
preload: true,
|
8
|
+
instrumenter: ActiveSupport::Notifications
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer "flipper.default", before: :load_config_initializers do |app|
|
13
|
+
Flipper.configure do |config|
|
14
|
+
config.default do
|
15
|
+
Flipper.new(config.adapter, instrumenter: app.config.flipper.instrumenter)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
initializer "flipper.memoizer", after: :load_config_initializers do |app|
|
21
|
+
config = app.config.flipper
|
22
|
+
|
23
|
+
if config.memoize
|
24
|
+
app.middleware.use Flipper::Middleware::Memoizer, {
|
25
|
+
env_key: config.env_key,
|
26
|
+
preload: config.preload,
|
27
|
+
if: config.memoize.respond_to?(:call) ? config.memoize : nil
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
initializer "flipper.identifier" do
|
33
|
+
ActiveSupport.on_load(:active_record) do
|
34
|
+
ActiveRecord::Base.include Flipper::Identifier
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/flipper/version.rb
CHANGED
@@ -76,9 +76,9 @@ RSpec.describe Flipper::Adapters::Http do
|
|
76
76
|
.to_return(status: 503, body: "", headers: {})
|
77
77
|
|
78
78
|
adapter = described_class.new(url: 'http://app.com/flipper')
|
79
|
-
expect
|
79
|
+
expect {
|
80
80
|
adapter.get(flipper[:feature_panel])
|
81
|
-
|
81
|
+
}.to raise_error(Flipper::Adapters::Http::Error)
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
@@ -88,9 +88,9 @@ RSpec.describe Flipper::Adapters::Http do
|
|
88
88
|
.to_return(status: 503, body: "", headers: {})
|
89
89
|
|
90
90
|
adapter = described_class.new(url: 'http://app.com/flipper')
|
91
|
-
expect
|
91
|
+
expect {
|
92
92
|
adapter.get_multi([flipper[:feature_panel]])
|
93
|
-
|
93
|
+
}.to raise_error(Flipper::Adapters::Http::Error)
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
@@ -100,9 +100,9 @@ RSpec.describe Flipper::Adapters::Http do
|
|
100
100
|
.to_return(status: 503, body: "", headers: {})
|
101
101
|
|
102
102
|
adapter = described_class.new(url: 'http://app.com/flipper')
|
103
|
-
expect
|
103
|
+
expect {
|
104
104
|
adapter.get_all
|
105
|
-
|
105
|
+
}.to raise_error(Flipper::Adapters::Http::Error)
|
106
106
|
end
|
107
107
|
end
|
108
108
|
|
@@ -112,9 +112,75 @@ RSpec.describe Flipper::Adapters::Http do
|
|
112
112
|
.to_return(status: 503, body: "", headers: {})
|
113
113
|
|
114
114
|
adapter = described_class.new(url: 'http://app.com/flipper')
|
115
|
-
expect
|
115
|
+
expect {
|
116
116
|
adapter.features
|
117
|
-
|
117
|
+
}.to raise_error(Flipper::Adapters::Http::Error)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "#add" do
|
122
|
+
it "raises error when not successful" do
|
123
|
+
stub_request(:post, /app.com/)
|
124
|
+
.to_return(status: 503, body: "{}", headers: {})
|
125
|
+
|
126
|
+
adapter = described_class.new(url: 'http://app.com/flipper')
|
127
|
+
expect {
|
128
|
+
adapter.add(Flipper::Feature.new(:search, adapter))
|
129
|
+
}.to raise_error(Flipper::Adapters::Http::Error)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "#remove" do
|
134
|
+
it "raises error when not successful" do
|
135
|
+
stub_request(:delete, /app.com/)
|
136
|
+
.to_return(status: 503, body: "{}", headers: {})
|
137
|
+
|
138
|
+
adapter = described_class.new(url: 'http://app.com/flipper')
|
139
|
+
expect {
|
140
|
+
adapter.remove(Flipper::Feature.new(:search, adapter))
|
141
|
+
}.to raise_error(Flipper::Adapters::Http::Error)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "#clear" do
|
146
|
+
it "raises error when not successful" do
|
147
|
+
stub_request(:delete, /app.com/)
|
148
|
+
.to_return(status: 503, body: "{}", headers: {})
|
149
|
+
|
150
|
+
adapter = described_class.new(url: 'http://app.com/flipper')
|
151
|
+
expect {
|
152
|
+
adapter.clear(Flipper::Feature.new(:search, adapter))
|
153
|
+
}.to raise_error(Flipper::Adapters::Http::Error)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe "#enable" do
|
158
|
+
it "raises error when not successful" do
|
159
|
+
stub_request(:post, /app.com/)
|
160
|
+
.to_return(status: 503, body: "{}", headers: {})
|
161
|
+
|
162
|
+
adapter = described_class.new(url: 'http://app.com/flipper')
|
163
|
+
feature = Flipper::Feature.new(:search, adapter)
|
164
|
+
gate = feature.gate(:boolean)
|
165
|
+
thing = gate.wrap(true)
|
166
|
+
expect {
|
167
|
+
adapter.enable(feature, gate, thing)
|
168
|
+
}.to raise_error(Flipper::Adapters::Http::Error)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "#disable" do
|
173
|
+
it "raises error when not successful" do
|
174
|
+
stub_request(:delete, /app.com/)
|
175
|
+
.to_return(status: 503, body: "{}", headers: {})
|
176
|
+
|
177
|
+
adapter = described_class.new(url: 'http://app.com/flipper')
|
178
|
+
feature = Flipper::Feature.new(:search, adapter)
|
179
|
+
gate = feature.gate(:boolean)
|
180
|
+
thing = gate.wrap(false)
|
181
|
+
expect {
|
182
|
+
adapter.disable(feature, gate, thing)
|
183
|
+
}.to raise_error(Flipper::Adapters::Http::Error)
|
118
184
|
end
|
119
185
|
end
|
120
186
|
|
@@ -2,7 +2,27 @@ require 'helper'
|
|
2
2
|
require 'flipper/spec/shared_adapter_specs'
|
3
3
|
|
4
4
|
RSpec.describe Flipper::Adapters::Memory do
|
5
|
-
|
5
|
+
let(:source) { {} }
|
6
|
+
subject { described_class.new(source) }
|
6
7
|
|
7
8
|
it_should_behave_like 'a flipper adapter'
|
9
|
+
|
10
|
+
it "can initialize from big hash" do
|
11
|
+
flipper = Flipper.new(subject)
|
12
|
+
flipper.enable :subscriptions
|
13
|
+
flipper.disable :search
|
14
|
+
flipper.enable_percentage_of_actors :pro_deal, 20
|
15
|
+
flipper.enable_percentage_of_time :logging, 30
|
16
|
+
flipper.enable_actor :following, Flipper::Actor.new('1')
|
17
|
+
flipper.enable_actor :following, Flipper::Actor.new('3')
|
18
|
+
flipper.enable_group :following, Flipper::Types::Group.new(:staff)
|
19
|
+
|
20
|
+
expect(source).to eq({
|
21
|
+
"subscriptions" => subject.default_config.merge(boolean: "true"),
|
22
|
+
"search" => subject.default_config,
|
23
|
+
"logging" => subject.default_config.merge(:percentage_of_time => "30"),
|
24
|
+
"pro_deal" => subject.default_config.merge(:percentage_of_actors => "20"),
|
25
|
+
"following" => subject.default_config.merge(actors: Set["1", "3"], groups: Set["staff"]),
|
26
|
+
})
|
27
|
+
end
|
8
28
|
end
|
@@ -2,13 +2,31 @@ require 'helper'
|
|
2
2
|
require 'flipper/configuration'
|
3
3
|
|
4
4
|
RSpec.describe Flipper::Configuration do
|
5
|
+
describe '#adapter' do
|
6
|
+
it 'returns instance using Memory adapter' do
|
7
|
+
expect(subject.adapter).to be_a(Flipper::Adapters::Memory)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'can be set' do
|
11
|
+
instance = Flipper::Adapters::Memory.new
|
12
|
+
expect(subject.adapter).not_to be(instance)
|
13
|
+
subject.adapter { instance }
|
14
|
+
expect(subject.adapter).to be(instance)
|
15
|
+
# All adapters are wrapped in Memoizable
|
16
|
+
expect(subject.default.adapter.adapter).to be(instance)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
5
20
|
describe '#default' do
|
6
|
-
it '
|
7
|
-
expect
|
21
|
+
it 'returns instance using Memory adapter' do
|
22
|
+
expect(subject.default).to be_a(Flipper::DSL)
|
23
|
+
# All adapters are wrapped in Memoizable
|
24
|
+
expect(subject.default.adapter.adapter).to be_a(Flipper::Adapters::Memory)
|
8
25
|
end
|
9
26
|
|
10
27
|
it 'can be set default' do
|
11
28
|
instance = Flipper.new(Flipper::Adapters::Memory.new)
|
29
|
+
expect(subject.default).not_to be(instance)
|
12
30
|
subject.default { instance }
|
13
31
|
expect(subject.default).to be(instance)
|
14
32
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'flipper/identifier'
|
3
|
+
|
4
|
+
RSpec.describe Flipper::Identifier do
|
5
|
+
describe '#flipper_id' do
|
6
|
+
class User < Struct.new(:id)
|
7
|
+
include Flipper::Identifier
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'uses class name and id' do
|
11
|
+
expect(User.new(5).flipper_id).to eq('User;5')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -15,10 +15,6 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
15
15
|
let(:flipper) { Flipper.new(adapter) }
|
16
16
|
let(:env) { { 'flipper' => flipper } }
|
17
17
|
|
18
|
-
after do
|
19
|
-
flipper.memoize = nil
|
20
|
-
end
|
21
|
-
|
22
18
|
it 'raises if initialized with app and flipper instance' do
|
23
19
|
expect do
|
24
20
|
described_class.new(app, flipper)
|
@@ -103,14 +99,14 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
103
99
|
end
|
104
100
|
end
|
105
101
|
|
106
|
-
context 'with
|
102
|
+
context 'with preload: true' do
|
107
103
|
let(:app) do
|
108
104
|
# ensure scoped for builder block, annoying...
|
109
105
|
instance = flipper
|
110
106
|
middleware = described_class
|
111
107
|
|
112
108
|
Rack::Builder.new do
|
113
|
-
use middleware,
|
109
|
+
use middleware, preload: true
|
114
110
|
|
115
111
|
map '/' do
|
116
112
|
run ->(_env) { [200, {}, []] }
|
@@ -139,7 +135,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
139
135
|
[200, {}, []]
|
140
136
|
end
|
141
137
|
|
142
|
-
middleware = described_class.new(app,
|
138
|
+
middleware = described_class.new(app, preload: true)
|
143
139
|
middleware.call(env)
|
144
140
|
|
145
141
|
expect(adapter.operations.size).to be(1)
|
@@ -156,7 +152,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
156
152
|
[200, {}, []]
|
157
153
|
end
|
158
154
|
|
159
|
-
middleware = described_class.new(app,
|
155
|
+
middleware = described_class.new(app, preload: true)
|
160
156
|
middleware.call(env)
|
161
157
|
|
162
158
|
expect(adapter.count(:get)).to be(1)
|
@@ -222,6 +218,44 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
222
218
|
end
|
223
219
|
end
|
224
220
|
|
221
|
+
context 'with multiple instances' do
|
222
|
+
let(:app) do
|
223
|
+
# ensure scoped for builder block, annoying...
|
224
|
+
instance = flipper
|
225
|
+
middleware = described_class
|
226
|
+
|
227
|
+
Rack::Builder.new do
|
228
|
+
use middleware, preload: %i(stats)
|
229
|
+
# Second instance should be a noop
|
230
|
+
use middleware, preload: true
|
231
|
+
|
232
|
+
map '/' do
|
233
|
+
run ->(_env) { [200, {}, []] }
|
234
|
+
end
|
235
|
+
|
236
|
+
map '/fail' do
|
237
|
+
run ->(_env) { raise 'FAIL!' }
|
238
|
+
end
|
239
|
+
end.to_app
|
240
|
+
end
|
241
|
+
|
242
|
+
def get(uri, params = {}, env = {}, &block)
|
243
|
+
silence { super(uri, params, env, &block) }
|
244
|
+
end
|
245
|
+
|
246
|
+
include_examples 'flipper middleware'
|
247
|
+
|
248
|
+
it 'does not call preload in second instance' do
|
249
|
+
expect(flipper).not_to receive(:preload_all)
|
250
|
+
|
251
|
+
output = get '/', {}, 'flipper' => flipper
|
252
|
+
|
253
|
+
expect(output).to match(/Flipper::Middleware::Memoizer appears to be running twice/)
|
254
|
+
expect(adapter.count(:get_multi)).to be(1)
|
255
|
+
expect(adapter.last(:get_multi).args).to eq([[flipper[:stats]]])
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
225
259
|
context 'when an app raises an exception' do
|
226
260
|
it 'resets memoize' do
|
227
261
|
begin
|
@@ -259,10 +293,9 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
259
293
|
context 'with Flipper setup in env' do
|
260
294
|
it 'caches getting a feature for duration of request' do
|
261
295
|
Flipper.configure do |config|
|
262
|
-
config.
|
296
|
+
config.adapter do
|
263
297
|
memory = Flipper::Adapters::Memory.new
|
264
|
-
|
265
|
-
Flipper.new(logged_adapter)
|
298
|
+
Flipper::Adapters::OperationLogger.new(memory)
|
266
299
|
end
|
267
300
|
end
|
268
301
|
Flipper.enable(:stats)
|
@@ -308,14 +341,16 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
308
341
|
end
|
309
342
|
end
|
310
343
|
|
311
|
-
context 'with
|
344
|
+
context 'with preload:true' do
|
345
|
+
let(:options) { {preload: true} }
|
346
|
+
|
312
347
|
let(:app) do
|
313
348
|
# ensure scoped for builder block, annoying...
|
314
349
|
middleware = described_class
|
350
|
+
opts = options
|
315
351
|
|
316
352
|
Rack::Builder.new do
|
317
|
-
use middleware,
|
318
|
-
unless: ->(request) { request.path.start_with?("/assets") }
|
353
|
+
use middleware, opts
|
319
354
|
|
320
355
|
map '/' do
|
321
356
|
run ->(_env) { [200, {}, []] }
|
@@ -327,22 +362,57 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
327
362
|
end.to_app
|
328
363
|
end
|
329
364
|
|
330
|
-
|
331
|
-
|
332
|
-
|
365
|
+
|
366
|
+
context 'and unless option' do
|
367
|
+
before do
|
368
|
+
options[:unless] = ->(request) { request.path.start_with?("/assets") }
|
369
|
+
end
|
370
|
+
|
371
|
+
it 'does NOT preload if request matches unless block' do
|
372
|
+
expect(flipper).to receive(:preload_all).never
|
373
|
+
get '/assets/foo.png', {}, 'flipper' => flipper
|
374
|
+
end
|
375
|
+
|
376
|
+
it 'does preload if request does NOT match unless block' do
|
377
|
+
expect(flipper).to receive(:preload_all).once
|
378
|
+
get '/some/other/path', {}, 'flipper' => flipper
|
379
|
+
end
|
333
380
|
end
|
334
381
|
|
335
|
-
|
336
|
-
|
337
|
-
|
382
|
+
context 'and if option' do
|
383
|
+
before do
|
384
|
+
options[:if] = ->(request) { !request.path.start_with?("/assets") }
|
385
|
+
end
|
386
|
+
|
387
|
+
it 'does NOT preload if request does not match if block' do
|
388
|
+
expect(flipper).to receive(:preload_all).never
|
389
|
+
get '/assets/foo.png', {}, 'flipper' => flipper
|
390
|
+
end
|
391
|
+
|
392
|
+
it 'does preload if request matches if block' do
|
393
|
+
expect(flipper).to receive(:preload_all).once
|
394
|
+
get '/some/other/path', {}, 'flipper' => flipper
|
395
|
+
end
|
338
396
|
end
|
339
397
|
end
|
340
398
|
|
341
|
-
context 'with
|
399
|
+
context 'with preload:true and caching adapter' do
|
400
|
+
let(:app) do
|
401
|
+
app = lambda do |_env|
|
402
|
+
flipper[:stats].enabled?
|
403
|
+
flipper[:stats].enabled?
|
404
|
+
flipper[:shiny].enabled?
|
405
|
+
flipper[:shiny].enabled?
|
406
|
+
[200, {}, []]
|
407
|
+
end
|
408
|
+
|
409
|
+
described_class.new(app, preload: true)
|
410
|
+
end
|
411
|
+
|
342
412
|
it 'eagerly caches known features for duration of request' do
|
343
413
|
memory = Flipper::Adapters::Memory.new
|
344
414
|
logged_memory = Flipper::Adapters::OperationLogger.new(memory)
|
345
|
-
cache = ActiveSupport::Cache::
|
415
|
+
cache = ActiveSupport::Cache::MemoryStore.new
|
346
416
|
cache.clear
|
347
417
|
cached = Flipper::Adapters::ActiveSupportCacheStore.new(logged_memory, cache, expires_in: 10)
|
348
418
|
logged_cached = Flipper::Adapters::OperationLogger.new(cached)
|
@@ -355,25 +425,15 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
355
425
|
logged_memory.reset
|
356
426
|
logged_cached.reset
|
357
427
|
|
358
|
-
|
359
|
-
flipper[:stats].enabled?
|
360
|
-
flipper[:stats].enabled?
|
361
|
-
flipper[:shiny].enabled?
|
362
|
-
flipper[:shiny].enabled?
|
363
|
-
[200, {}, []]
|
364
|
-
end
|
365
|
-
|
366
|
-
middleware = described_class.new(app, preload_all: true)
|
367
|
-
|
368
|
-
middleware.call('flipper' => flipper)
|
428
|
+
get '/', {}, 'flipper' => flipper
|
369
429
|
expect(logged_cached.count(:get_all)).to be(1)
|
370
430
|
expect(logged_memory.count(:get_all)).to be(1)
|
371
431
|
|
372
|
-
|
432
|
+
get '/', {}, 'flipper' => flipper
|
373
433
|
expect(logged_cached.count(:get_all)).to be(2)
|
374
434
|
expect(logged_memory.count(:get_all)).to be(1)
|
375
435
|
|
376
|
-
|
436
|
+
get '/', {}, 'flipper' => flipper
|
377
437
|
expect(logged_cached.count(:get_all)).to be(3)
|
378
438
|
expect(logged_memory.count(:get_all)).to be(1)
|
379
439
|
end
|