flipper 0.20.1 → 0.21.0.rc2
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.
- 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
|