flipper 0.17.1 → 0.21.0
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 +114 -1
- data/Dockerfile +1 -1
- data/Gemfile +3 -6
- data/README.md +103 -47
- data/Rakefile +1 -4
- data/docs/Adapters.md +9 -9
- data/docs/Caveats.md +2 -2
- data/docs/DockerCompose.md +0 -1
- 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 +35 -0
- 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/flipper.gemspec +3 -4
- data/lib/flipper.rb +7 -3
- data/lib/flipper/adapters/dual_write.rb +67 -0
- data/lib/flipper/adapters/http.rb +32 -28
- data/lib/flipper/adapters/memory.rb +23 -94
- data/lib/flipper/adapters/operation_logger.rb +5 -0
- data/lib/flipper/adapters/pstore.rb +8 -1
- data/lib/flipper/adapters/sync.rb +7 -7
- data/lib/flipper/adapters/sync/interval_synchronizer.rb +1 -1
- data/lib/flipper/adapters/sync/synchronizer.rb +1 -0
- data/lib/flipper/configuration.rb +33 -7
- data/lib/flipper/dsl.rb +8 -0
- data/lib/flipper/errors.rb +2 -3
- data/lib/flipper/feature.rb +2 -2
- data/lib/flipper/identifier.rb +17 -0
- data/lib/flipper/middleware/memoizer.rb +30 -15
- data/lib/flipper/middleware/setup_env.rb +13 -3
- data/lib/flipper/railtie.rb +38 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +15 -0
- data/lib/flipper/test/shared_adapter_test.rb +16 -1
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/adapter_spec.rb +2 -2
- data/spec/flipper/adapters/dual_write_spec.rb +71 -0
- data/spec/flipper/adapters/http_spec.rb +74 -8
- data/spec/flipper/adapters/memory_spec.rb +21 -1
- data/spec/flipper/adapters/operation_logger_spec.rb +9 -0
- data/spec/flipper/adapters/sync_spec.rb +4 -4
- data/spec/flipper/configuration_spec.rb +20 -2
- data/spec/flipper/feature_spec.rb +5 -5
- 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 +23 -3
- data/spec/flipper/railtie_spec.rb +69 -0
- data/spec/{integration_spec.rb → flipper_integration_spec.rb} +0 -0
- data/spec/flipper_spec.rb +26 -0
- data/spec/helper.rb +3 -3
- data/spec/support/descriptions.yml +1 -0
- data/spec/support/spec_helpers.rb +25 -0
- data/test/test_helper.rb +2 -1
- metadata +19 -10
- data/.rubocop.yml +0 -52
- data/.rubocop_todo.yml +0 -562
- 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
|
@@ -15,6 +15,7 @@ module Flipper
|
|
15
15
|
# adapter should be brought in line with.
|
16
16
|
# options - The Hash of options.
|
17
17
|
# :instrumenter - The instrumenter used to instrument.
|
18
|
+
# :raise - Should errors be raised (default: true).
|
18
19
|
def initialize(local, remote, options = {})
|
19
20
|
@local = local
|
20
21
|
@remote = remote
|
@@ -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/adapters/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/adapters/
|
17
|
-
# Flipper.new(Flipper::Adapters::
|
42
|
+
# require "flipper/adapters/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/dsl.rb
CHANGED
@@ -273,5 +273,13 @@ module Flipper
|
|
273
273
|
def import(flipper)
|
274
274
|
adapter.import(flipper.adapter)
|
275
275
|
end
|
276
|
+
|
277
|
+
# Cloud DSL method that does nothing for open source version.
|
278
|
+
def sync
|
279
|
+
end
|
280
|
+
|
281
|
+
# Cloud DSL method that does nothing for open source version.
|
282
|
+
def sync_secret
|
283
|
+
end
|
276
284
|
end
|
277
285
|
end
|
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
|
|
data/lib/flipper/feature.rb
CHANGED
@@ -5,7 +5,7 @@ require 'flipper/feature_check_context'
|
|
5
5
|
require 'flipper/gate_values'
|
6
6
|
|
7
7
|
module Flipper
|
8
|
-
class Feature
|
8
|
+
class Feature
|
9
9
|
# Private: The name of feature instrumentation events.
|
10
10
|
InstrumentationName = "feature_operation.#{InstrumentationNamespace}".freeze
|
11
11
|
|
@@ -205,7 +205,7 @@ module Flipper
|
|
205
205
|
boolean = gate(:boolean)
|
206
206
|
non_boolean_gates = gates - [boolean]
|
207
207
|
|
208
|
-
if values.boolean || values.
|
208
|
+
if values.boolean || values.percentage_of_time == 100
|
209
209
|
:on
|
210
210
|
elsif non_boolean_gates.detect { |gate| gate.enabled?(values[gate.key]) }
|
211
211
|
:conditional
|
@@ -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
|
#
|
@@ -23,7 +22,12 @@ module Flipper
|
|
23
22
|
#
|
24
23
|
def initialize(app, opts = {})
|
25
24
|
if opts.is_a?(Flipper::DSL) || opts.is_a?(Proc)
|
26
|
-
raise 'Flipper::Middleware::Memoizer no longer initializes with a flipper instance or block. Read more at: https://git.io/vSo31.'
|
25
|
+
raise 'Flipper::Middleware::Memoizer no longer initializes with a flipper instance or block. Read more at: https://git.io/vSo31.'
|
26
|
+
end
|
27
|
+
|
28
|
+
if opts[:preload_all]
|
29
|
+
warn "Flipper::Middleware::Memoizer: `preload_all` is deprecated, use `preload: true`"
|
30
|
+
opts[:preload] = true
|
27
31
|
end
|
28
32
|
|
29
33
|
@app = app
|
@@ -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
|
@@ -7,7 +7,8 @@ module Flipper
|
|
7
7
|
#
|
8
8
|
# app - The app this middleware is included in.
|
9
9
|
# flipper_or_block - The Flipper::DSL instance or a block that yields a
|
10
|
-
# Flipper::DSL instance to use for all operations
|
10
|
+
# Flipper::DSL instance to use for all operations
|
11
|
+
# (optional, default: Flipper).
|
11
12
|
#
|
12
13
|
# Examples
|
13
14
|
#
|
@@ -19,18 +20,27 @@ module Flipper
|
|
19
20
|
# # using with a block that yields a flipper instance
|
20
21
|
# use Flipper::Middleware::SetupEnv, lambda { Flipper.new(...) }
|
21
22
|
#
|
22
|
-
|
23
|
+
# # using default configured Flipper instance
|
24
|
+
# Flipper.configure do |config|
|
25
|
+
# config.default { Flipper.new(...) }
|
26
|
+
# end
|
27
|
+
# use Flipper::Middleware::SetupEnv
|
28
|
+
def initialize(app, flipper_or_block = nil, options = {})
|
23
29
|
@app = app
|
24
30
|
@env_key = options.fetch(:env_key, 'flipper')
|
25
31
|
|
26
32
|
if flipper_or_block.respond_to?(:call)
|
27
33
|
@flipper_block = flipper_or_block
|
28
34
|
else
|
29
|
-
@flipper = flipper_or_block
|
35
|
+
@flipper = flipper_or_block || Flipper
|
30
36
|
end
|
31
37
|
end
|
32
38
|
|
33
39
|
def call(env)
|
40
|
+
dup.call!(env)
|
41
|
+
end
|
42
|
+
|
43
|
+
def call!(env)
|
34
44
|
env[@env_key] ||= flipper
|
35
45
|
@app.call(env)
|
36
46
|
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
|
@@ -289,4 +289,19 @@ RSpec.shared_examples_for 'a flipper adapter' do
|
|
289
289
|
expect(subject.enable(feature, boolean_gate, flipper.boolean)).to eq(true)
|
290
290
|
expect(subject.enable(feature, boolean_gate, flipper.boolean)).to eq(true)
|
291
291
|
end
|
292
|
+
|
293
|
+
it 'can get_all features when there are none' do
|
294
|
+
expect(subject.features).to eq(Set.new)
|
295
|
+
expect(subject.get_all).to eq({})
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'clears other gate values on enable' do
|
299
|
+
actor = Flipper::Actor.new('Flipper::Actor;22')
|
300
|
+
subject.enable(feature, actors_gate, flipper.actors(25))
|
301
|
+
subject.enable(feature, time_gate, flipper.time(25))
|
302
|
+
subject.enable(feature, group_gate, flipper.group(:admins))
|
303
|
+
subject.enable(feature, actor_gate, flipper.actor(actor))
|
304
|
+
subject.enable(feature, boolean_gate, flipper.boolean(true))
|
305
|
+
expect(subject.get(feature)).to eq(subject.default_config.merge(boolean: "true"))
|
306
|
+
end
|
292
307
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
# rubocop:disable Metrics/ModuleLength
|
2
1
|
module Flipper
|
3
2
|
module Test
|
4
3
|
module SharedAdapterTests
|
@@ -285,6 +284,22 @@ module Flipper
|
|
285
284
|
assert_equal true, @adapter.enable(@feature, @boolean_gate, @flipper.boolean)
|
286
285
|
assert_equal true, @adapter.enable(@feature, @boolean_gate, @flipper.boolean)
|
287
286
|
end
|
287
|
+
|
288
|
+
def test_can_get_all_features_when_there_are_none
|
289
|
+
expected = {}
|
290
|
+
assert_equal Set.new, @adapter.features
|
291
|
+
assert_equal expected, @adapter.get_all
|
292
|
+
end
|
293
|
+
|
294
|
+
def test_clears_other_gate_values_on_enable
|
295
|
+
actor = Flipper::Actor.new('Flipper::Actor;22')
|
296
|
+
assert_equal true, @adapter.enable(@feature, @actors_gate, @flipper.actors(25))
|
297
|
+
assert_equal true, @adapter.enable(@feature, @time_gate, @flipper.time(25))
|
298
|
+
assert_equal true, @adapter.enable(@feature, @group_gate, @flipper.group(:admins))
|
299
|
+
assert_equal true, @adapter.enable(@feature, @actor_gate, @flipper.actor(actor))
|
300
|
+
assert_equal true, @adapter.enable(@feature, @boolean_gate, @flipper.boolean(true))
|
301
|
+
assert_equal @adapter.default_config.merge(boolean: "true"), @adapter.get(@feature)
|
302
|
+
end
|
288
303
|
end
|
289
304
|
end
|
290
305
|
end
|
data/lib/flipper/version.rb
CHANGED
@@ -16,7 +16,7 @@ RSpec.describe Flipper::Adapter do
|
|
16
16
|
describe '.default_config' do
|
17
17
|
it 'returns default config' do
|
18
18
|
adapter_class = Class.new do
|
19
|
-
include Flipper::Adapter
|
19
|
+
include Flipper::Adapter
|
20
20
|
end
|
21
21
|
expect(adapter_class.default_config).to eq(default_config)
|
22
22
|
end
|
@@ -25,7 +25,7 @@ RSpec.describe Flipper::Adapter do
|
|
25
25
|
describe '#default_config' do
|
26
26
|
it 'returns default config' do
|
27
27
|
adapter_class = Class.new do
|
28
|
-
include Flipper::Adapter
|
28
|
+
include Flipper::Adapter
|
29
29
|
end
|
30
30
|
expect(adapter_class.new.default_config).to eq(default_config)
|
31
31
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'flipper/adapters/dual_write'
|
3
|
+
require 'flipper/adapters/operation_logger'
|
4
|
+
require 'flipper/spec/shared_adapter_specs'
|
5
|
+
require 'active_support/notifications'
|
6
|
+
|
7
|
+
RSpec.describe Flipper::Adapters::DualWrite do
|
8
|
+
let(:local_adapter) do
|
9
|
+
Flipper::Adapters::OperationLogger.new Flipper::Adapters::Memory.new
|
10
|
+
end
|
11
|
+
let(:remote_adapter) do
|
12
|
+
Flipper::Adapters::OperationLogger.new Flipper::Adapters::Memory.new
|
13
|
+
end
|
14
|
+
let(:local) { Flipper.new(local_adapter) }
|
15
|
+
let(:remote) { Flipper.new(remote_adapter) }
|
16
|
+
let(:sync) { Flipper.new(subject) }
|
17
|
+
|
18
|
+
subject do
|
19
|
+
described_class.new(local_adapter, remote_adapter)
|
20
|
+
end
|
21
|
+
|
22
|
+
it_should_behave_like 'a flipper adapter'
|
23
|
+
|
24
|
+
it 'only uses local for #features' do
|
25
|
+
subject.features
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'only uses local for #get' do
|
29
|
+
subject.get sync[:search]
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'only uses local for #get_multi' do
|
33
|
+
subject.get_multi [sync[:search]]
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'only uses local for #get_all' do
|
37
|
+
subject.get_all
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'updates remote and local for #add' do
|
41
|
+
subject.add sync[:search]
|
42
|
+
expect(remote_adapter.count(:add)).to be(1)
|
43
|
+
expect(local_adapter.count(:add)).to be(1)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'updates remote and local for #remove' do
|
47
|
+
subject.remove sync[:search]
|
48
|
+
expect(remote_adapter.count(:remove)).to be(1)
|
49
|
+
expect(local_adapter.count(:remove)).to be(1)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'updates remote and local for #clear' do
|
53
|
+
subject.clear sync[:search]
|
54
|
+
expect(remote_adapter.count(:clear)).to be(1)
|
55
|
+
expect(local_adapter.count(:clear)).to be(1)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'updates remote and local for #enable' do
|
59
|
+
feature = sync[:search]
|
60
|
+
subject.enable feature, feature.gate(:boolean), local.boolean
|
61
|
+
expect(remote_adapter.count(:enable)).to be(1)
|
62
|
+
expect(local_adapter.count(:enable)).to be(1)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'updates remote and local for #disable' do
|
66
|
+
feature = sync[:search]
|
67
|
+
subject.disable feature, feature.gate(:boolean), local.boolean(false)
|
68
|
+
expect(remote_adapter.count(:disable)).to be(1)
|
69
|
+
expect(local_adapter.count(:disable)).to be(1)
|
70
|
+
end
|
71
|
+
end
|