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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +57 -0
  3. data/Changelog.md +66 -0
  4. data/Gemfile +1 -0
  5. data/README.md +103 -47
  6. data/docs/Adapters.md +9 -9
  7. data/docs/Caveats.md +2 -2
  8. data/docs/Gates.md +74 -74
  9. data/docs/Optimization.md +70 -47
  10. data/docs/http/README.md +12 -11
  11. data/docs/images/banner.jpg +0 -0
  12. data/docs/read-only/README.md +8 -5
  13. data/examples/basic.rb +1 -12
  14. data/examples/configuring_default.rb +2 -5
  15. data/examples/dsl.rb +13 -24
  16. data/examples/enabled_for_actor.rb +8 -15
  17. data/examples/group.rb +3 -6
  18. data/examples/group_dynamic_lookup.rb +5 -19
  19. data/examples/group_with_members.rb +4 -14
  20. data/examples/importing.rb +1 -1
  21. data/examples/individual_actor.rb +2 -5
  22. data/examples/instrumentation.rb +1 -2
  23. data/examples/memoizing.rb +3 -7
  24. data/examples/percentage_of_actors.rb +6 -16
  25. data/examples/percentage_of_actors_enabled_check.rb +7 -10
  26. data/examples/percentage_of_actors_group.rb +5 -18
  27. data/examples/percentage_of_time.rb +3 -6
  28. data/lib/flipper.rb +4 -1
  29. data/lib/flipper/adapters/http.rb +32 -28
  30. data/lib/flipper/adapters/memory.rb +20 -94
  31. data/lib/flipper/adapters/pstore.rb +4 -0
  32. data/lib/flipper/adapters/sync/interval_synchronizer.rb +1 -1
  33. data/lib/flipper/configuration.rb +33 -7
  34. data/lib/flipper/errors.rb +2 -3
  35. data/lib/flipper/identifier.rb +17 -0
  36. data/lib/flipper/middleware/memoizer.rb +29 -14
  37. data/lib/flipper/railtie.rb +38 -0
  38. data/lib/flipper/version.rb +1 -1
  39. data/spec/flipper/adapters/http_spec.rb +74 -8
  40. data/spec/flipper/adapters/memory_spec.rb +21 -1
  41. data/spec/flipper/configuration_spec.rb +20 -2
  42. data/spec/flipper/identifier_spec.rb +14 -0
  43. data/spec/flipper/middleware/memoizer_spec.rb +95 -35
  44. data/spec/flipper/middleware/setup_env_spec.rb +0 -16
  45. data/spec/flipper/railtie_spec.rb +69 -0
  46. data/spec/flipper_spec.rb +0 -1
  47. data/spec/support/spec_helpers.rb +20 -0
  48. data/test/test_helper.rb +1 -0
  49. metadata +12 -5
  50. data/examples/example_setup.rb +0 -8
@@ -216,3 +216,7 @@ module Flipper
216
216
  end
217
217
  end
218
218
  end
219
+
220
+ Flipper.configure do |config|
221
+ config.adapter { Flipper::Adapters::PStore.new }
222
+ end
@@ -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 milliseconds between invocations 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 = -> { raise DefaultNotSet }
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 # => raises DefaultNotSet error.
38
+ # configuration.default # => Flipper::DSL instance using Memory adapter
13
39
  #
14
- # # sets the default block to generate a new instance using Memory adapter
40
+ # # sets the default block to generate a new instance using ActiveRecord adapter
15
41
  # configuration.default do
16
- # require "flipper/adapters/memory"
17
- # Flipper.new(Flipper::Adapters::Memory.new)
42
+ # require "flipper-active_record"
43
+ # Flipper.new(Flipper::Adapters::ActiveRecord.new)
18
44
  # end
19
45
  #
20
- # configuration.default # => Flipper::DSL instance using Memory adapter
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.
@@ -16,9 +16,8 @@ module Flipper
16
16
  # use it.
17
17
  class DefaultNotSet < Flipper::Error
18
18
  def initialize(message = nil)
19
- default = "Default flipper instance not configured. See " \
20
- "Flipper.configure for how to configure the default instance."
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
- # :preload_all - Boolean of whether or not to preload all features.
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 skip_memoize?(request)
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 skip_memoize?(request)
47
- @opts[:unless] && @opts[:unless].call(request)
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
- flipper.preload_all if @opts[:preload_all]
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
- if (preload = @opts[:preload])
59
- flipper.preload(preload)
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 = original
79
+ flipper.memoize = false
65
80
  end
66
81
  reset_on_body_close = true
67
82
  response
68
83
  ensure
69
- flipper.memoize = original if flipper && !reset_on_body_close
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
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.20.1'.freeze
2
+ VERSION = '0.21.0.rc2'.freeze
3
3
  end
@@ -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 do
79
+ expect {
80
80
  adapter.get(flipper[:feature_panel])
81
- end.to raise_error(Flipper::Adapters::Http::Error)
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 do
91
+ expect {
92
92
  adapter.get_multi([flipper[:feature_panel]])
93
- end.to raise_error(Flipper::Adapters::Http::Error)
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 do
103
+ expect {
104
104
  adapter.get_all
105
- end.to raise_error(Flipper::Adapters::Http::Error)
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 do
115
+ expect {
116
116
  adapter.features
117
- end.to raise_error(Flipper::Adapters::Http::Error)
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
- subject { described_class.new }
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 'raises if default not configured' do
7
- expect { subject.default }.to raise_error(Flipper::DefaultNotSet)
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 preload_all' do
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, preload_all: true
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, preload_all: true)
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, preload_all: true)
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.default do
296
+ config.adapter do
263
297
  memory = Flipper::Adapters::Memory.new
264
- logged_adapter = Flipper::Adapters::OperationLogger.new(memory)
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 preload_all and unless option' do
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, preload_all: true,
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
- it 'does NOT preload_all if request matches unless block' do
331
- expect(flipper).to receive(:preload_all).never
332
- get '/assets/foo.png', {}, 'flipper' => flipper
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
- it 'does preload_all if request does NOT match unless block' do
336
- expect(flipper).to receive(:preload_all).once
337
- get '/some/other/path', {}, 'flipper' => flipper
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 preload_all and caching adapter' do
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::DalliStore.new(ENV['MEMCACHED_URL'])
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
- app = lambda do |_env|
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
- middleware.call('flipper' => flipper)
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
- middleware.call('flipper' => flipper)
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