flipper 0.11.0.beta1 → 0.11.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1dba40ce57cd34d4be2608ce8725c2b314eb5345
4
- data.tar.gz: 929230ec45235cfb08634d63ab0bdfe704197b45
3
+ metadata.gz: 7ff62d3ba23318ccb0c071a989e83364739c731a
4
+ data.tar.gz: ecefd31c44930c4d6a3dc70c55175b3d3d1ba44f
5
5
  SHA512:
6
- metadata.gz: 3618c5c38a0901f219e719de7b04230ad8ca4534673f271b31c055fe1d88bb7545638b5bdb90c3fdec6e842819678e4137c2f32eb83eb77826381396eb75d95e
7
- data.tar.gz: 98e939ae62ee4ac6bd7f19eab5be8c0c58281525db190a852a0b36643e8108c16feb5df94a4ca943ab4517bccec6c7a20d80ea5389fa7bb595bb7d7fdb394cf9
6
+ metadata.gz: d6cf59627197fb719492b2438b931c51645c2aa8860943b89845a01f4ef8f1696080aec6b53965ea1e918aaa647325c23d1b3731dd85ae5b787f426333c43bb3
7
+ data.tar.gz: 73b5f76153abc395ef61069e4dc1338335d9a2c21932d853891435a7f9529f6fdf322e402562515db0af62cd95c0f8ab8e937432bb6e0636304e75917ffd7121
@@ -1,12 +1,18 @@
1
1
  ## 0.11
2
2
 
3
- * Use primary keys with sequel adapter (https://github.com/jnunemaker/flipper/pull/210).
3
+ ### Backwards Compatibility Breaks
4
+
5
+ * * Set flipper from env for API and UI (https://github.com/jnunemaker/flipper/pull/223 and https://github.com/jnunemaker/flipper/pull/229). It is documented, but now the memoizing middleware requires that the SetupEnv middleware is used first.
6
+ * Drop support for Ruby 2.0 as it is end of lined (https://github.com/jnunemaker/flipper/commit/c2c81ed89938155ce91acb5173ac38580f630e3d).
7
+ * Allow unregistered groups (https://github.com/jnunemaker/flipper/pull/244). Only break in compatiblity is that previously unregistered groups could not be enabled and now they can be.
8
+
9
+ ### Additions/Changes
10
+
11
+ * Use primary keys with sequel adapter (https://github.com/jnunemaker/flipper/pull/210). Should be backwards compatible, but if you want it to work this way you will need to migrate your database to the new schema.
4
12
  * Add redis cache adapter (https://github.com/jnunemaker/flipper/pull/211).
5
13
  * Finish API and HTTP adapter that speaks to API.
6
- * Set flipper from env for API and UI (https://github.com/jnunemaker/flipper/pull/223 and https://github.com/jnunemaker/flipper/pull/229).
7
- * Add flipper cloud adapter (https://github.com/jnunemaker/flipper/pull/249).
8
- * Allow unregistered groups (https://github.com/jnunemaker/flipper/pull/244).
9
- * Drop support for Ruby 2.0 as it is end of lined (https://github.com/jnunemaker/flipper/commit/c2c81ed89938155ce91acb5173ac38580f630e3d).
14
+ * Add flipper cloud adapter (https://github.com/jnunemaker/flipper/pull/249). Nothing to see here yet, but good stuff soon. ;)
15
+ * Add importing (https://github.com/jnunemaker/flipper/pull/251).
10
16
 
11
17
  ## 0.10.2
12
18
 
@@ -91,3 +91,33 @@ end
91
91
  A good place to start when creating your own adapter is to copy one of the adapters mentioned above and replace the client specific code with whatever client you are attempting to adapt.
92
92
 
93
93
  I would also recommend setting `fail_fast = true` in your RSpec configuration as that will just give you one failure at a time to work through. It is also handy to have the shared adapter spec file open.
94
+
95
+ ## Swapping Adapters
96
+
97
+ If you find yourself using one adapter and would like to swap to another, you can do that! Flipper adapters support importing another adapter's data. This will wipe the adapter you are wanting to swap to, if it isn't already clean, so please be careful.
98
+
99
+ ```ruby
100
+ # Say you are using redis...
101
+ redis_adapter = Flipper::Adapters::Redis.new(Redis.new)
102
+ redis_flipper = Flipper.new(redis_adapter)
103
+
104
+ # And redis has some stuff enabled...
105
+ redis_flipper.enable(:search)
106
+ redis_flipper.enable_percentage_of_time(:verbose_logging, 5)
107
+ redis_flipper.enable_percentage_of_actors(:new_feature, 5)
108
+ redis_flipper.enable_actor(:issues, Flipper::Actor.new('1'))
109
+ redis_flipper.enable_actor(:issues, Flipper::Actor.new('2'))
110
+ redis_flipper.enable_group(:request_tracing, :staff)
111
+
112
+ # And you would like to switch to active record...
113
+ ar_adapter = Flipper::Adapters::ActiveRecord.new
114
+ ar_flipper = Flipper.new(ar_adapter)
115
+
116
+ # NOTE: This wipes active record clean and copies features/gates from redis into active record.
117
+ ar_flipper.import(redis_flipper)
118
+
119
+ # active record is now identical to redis.
120
+ ar_flipper.features.each do |feature|
121
+ pp feature: feature.key, values: feature.gate_values
122
+ end
123
+ ```
@@ -0,0 +1,37 @@
1
+ require File.expand_path('../example_setup', __FILE__)
2
+
3
+ require 'flipper'
4
+ require 'flipper/adapters/redis'
5
+ require 'flipper/adapters/active_record'
6
+
7
+ # Active Record boiler plate, feel free to ignore.
8
+ ActiveRecord::Base.establish_connection({
9
+ adapter: 'sqlite3',
10
+ database: ':memory:',
11
+ })
12
+ require 'generators/flipper/templates/migration'
13
+ CreateFlipperTables.up
14
+
15
+ # Say you are using redis...
16
+ redis_adapter = Flipper::Adapters::Redis.new(Redis.new)
17
+ redis_flipper = Flipper.new(redis_adapter)
18
+
19
+ # And redis has some stuff enabled...
20
+ redis_flipper.enable(:search)
21
+ redis_flipper.enable_percentage_of_time(:verbose_logging, 5)
22
+ redis_flipper.enable_percentage_of_actors(:new_feature, 5)
23
+ redis_flipper.enable_actor(:issues, Flipper::Actor.new('1'))
24
+ redis_flipper.enable_actor(:issues, Flipper::Actor.new('2'))
25
+ redis_flipper.enable_group(:request_tracing, :staff)
26
+
27
+ # And you would like to switch to active record...
28
+ ar_adapter = Flipper::Adapters::ActiveRecord.new
29
+ ar_flipper = Flipper.new(ar_adapter)
30
+
31
+ # NOTE: This wipes active record clean and copies features/gates from redis into active record.
32
+ ar_flipper.import(redis_flipper)
33
+
34
+ # active record is now identical to redis.
35
+ ar_flipper.features.each do |feature|
36
+ pp feature: feature.key, values: feature.gate_values
37
+ end
@@ -80,6 +80,7 @@ module Flipper
80
80
  end
81
81
  end
82
82
 
83
+ require 'flipper/actor'
83
84
  require 'flipper/adapter'
84
85
  require 'flipper/dsl'
85
86
  require 'flipper/errors'
@@ -0,0 +1,16 @@
1
+ # Simple class for turning a flipper_id into an actor that can be based
2
+ # to Flipper::Feature#enabled?.
3
+ module Flipper
4
+ class Actor
5
+ attr_reader :flipper_id
6
+
7
+ def initialize(flipper_id)
8
+ @flipper_id = flipper_id
9
+ end
10
+
11
+ def eql?(other)
12
+ self.class.eql?(other.class) && @flipper_id == other.flipper_id
13
+ end
14
+ alias_method :==, :eql?
15
+ end
16
+ end
@@ -1,6 +1,23 @@
1
1
  module Flipper
2
2
  # Adding a module include so we have some hooks for stuff down the road
3
3
  module Adapter
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ # Public: Default config for a feature's gate values.
10
+ def default_config
11
+ {
12
+ boolean: nil,
13
+ groups: Set.new,
14
+ actors: Set.new,
15
+ percentage_of_actors: nil,
16
+ percentage_of_time: nil,
17
+ }
18
+ end
19
+ end
20
+
4
21
  # Public: Get multiple features in one call. Defaults to one get per
5
22
  # feature. Feel free to override per adapter to make this more efficient and
6
23
  # reduce network calls.
@@ -12,14 +29,55 @@ module Flipper
12
29
  result
13
30
  end
14
31
 
32
+ # Public: Wipe features and gate values and then import features and gate
33
+ # values from provided adapter.
34
+ #
35
+ # Returns nothing.
36
+ def import(source_adapter)
37
+ wipe
38
+ copy_features_and_gates(source_adapter)
39
+ nil
40
+ end
41
+
42
+ # Public: Default config for a feature's gate values.
15
43
  def 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
- }
44
+ self.class.default_config
45
+ end
46
+
47
+ private
48
+
49
+ # Private: Copy source adapter features and gate values into self.
50
+ def copy_features_and_gates(source_adapter)
51
+ source_adapter.features.each do |key|
52
+ source_feature = Flipper::Feature.new(key, source_adapter)
53
+ destination_feature = Flipper::Feature.new(key, self)
54
+
55
+ case source_feature.state
56
+ when :on
57
+ destination_feature.enable
58
+ when :conditional
59
+ source_feature.groups_value.each do |value|
60
+ destination_feature.enable_group(value)
61
+ end
62
+
63
+ source_feature.actors_value.each do |value|
64
+ destination_feature.enable_actor(Flipper::Actor.new(value))
65
+ end
66
+
67
+ destination_feature.enable_percentage_of_actors(source_feature.percentage_of_actors_value)
68
+ destination_feature.enable_percentage_of_time(source_feature.percentage_of_time_value)
69
+ when :off
70
+ destination_feature.add
71
+ end
72
+ end
73
+ end
74
+
75
+ # Private: Completely wipe adapter features and gate values.
76
+ def wipe
77
+ features.each do |key|
78
+ feature = Flipper::Feature.new(key, self)
79
+ remove(feature)
80
+ end
23
81
  end
24
82
  end
25
83
  end
@@ -139,6 +139,15 @@ module Flipper
139
139
  feature(name).disable_percentage_of_actors
140
140
  end
141
141
 
142
+ # Public: Add a feature.
143
+ #
144
+ # name - The String or Symbol name of the feature.
145
+ #
146
+ # Returns result of add.
147
+ def add(name)
148
+ feature(name).add
149
+ end
150
+
142
151
  # Public: Remove a feature.
143
152
  #
144
153
  # name - The String or Symbol name of the feature.
@@ -241,5 +250,9 @@ module Flipper
241
250
  def features
242
251
  adapter.features.map { |name| feature(name) }.to_set
243
252
  end
253
+
254
+ def import(flipper)
255
+ adapter.import(flipper.adapter)
256
+ end
244
257
  end
245
258
  end
@@ -69,6 +69,13 @@ module Flipper
69
69
  end
70
70
  end
71
71
 
72
+ # Public: Adds this feature.
73
+ #
74
+ # Returns the result of Adapter#add.
75
+ def add
76
+ instrument(:add) { adapter.add(self) }
77
+ end
78
+
72
79
  # Public: Removes this feature.
73
80
  #
74
81
  # Returns the result of Adapter#remove.
@@ -8,6 +8,9 @@ module Flipper
8
8
  # using the Flipper::Middleware::SetupEnv middleware.
9
9
  #
10
10
  # app - The app this middleware is included in.
11
+ # opts - The Hash of options.
12
+ # :preload_all - Boolean of whether or not to preload all features.
13
+ # :preload - Array of Symbol feature names to preload.
11
14
  #
12
15
  # Examples
13
16
  #
@@ -20,6 +23,10 @@ module Flipper
20
23
  # use Flipper::Middleware::Memoizer, preload: [:stats, :search, :some_feature]
21
24
  #
22
25
  def initialize(app, opts = {})
26
+ if opts.is_a?(Flipper::DSL) || opts.is_a?(Proc)
27
+ raise 'Flipper::Middleware::Memoizer no longer initializes with a flipper instance or block. Read more at: https://git.io/vSo31.' # rubocop:disable LineLength
28
+ end
29
+
23
30
  @app = app
24
31
  @opts = opts
25
32
  end
@@ -1,8 +1,6 @@
1
1
  # Requires the following methods:
2
2
  # * subject - The instance of the adapter
3
3
  RSpec.shared_examples_for 'a flipper adapter' do
4
- let(:actor_class) { Struct.new(:flipper_id) }
5
-
6
4
  let(:flipper) { Flipper.new(subject) }
7
5
  let(:feature) { flipper[:stats] }
8
6
 
@@ -52,7 +50,7 @@ RSpec.shared_examples_for 'a flipper adapter' do
52
50
  end
53
51
 
54
52
  it 'fully disables all enabled things when boolean gate disabled' do
55
- actor22 = actor_class.new('22')
53
+ actor22 = Flipper::Actor.new('22')
56
54
  expect(subject.enable(feature, boolean_gate, flipper.boolean)).to eq(true)
57
55
  expect(subject.enable(feature, group_gate, flipper.group(:admins))).to eq(true)
58
56
  expect(subject.enable(feature, actor_gate, flipper.actor(actor22))).to eq(true)
@@ -81,8 +79,8 @@ RSpec.shared_examples_for 'a flipper adapter' do
81
79
  end
82
80
 
83
81
  it 'can enable, disable and get value for actor gate' do
84
- actor22 = actor_class.new('22')
85
- actor_asdf = actor_class.new('asdf')
82
+ actor22 = Flipper::Actor.new('22')
83
+ actor_asdf = Flipper::Actor.new('asdf')
86
84
 
87
85
  expect(subject.enable(feature, actor_gate, flipper.actor(actor22))).to eq(true)
88
86
  expect(subject.enable(feature, actor_gate, flipper.actor(actor_asdf))).to eq(true)
@@ -158,7 +156,7 @@ RSpec.shared_examples_for 'a flipper adapter' do
158
156
  end
159
157
 
160
158
  it 'converts the actor value to a string' do
161
- expect(subject.enable(feature, actor_gate, flipper.actor(actor_class.new(22)))).to eq(true)
159
+ expect(subject.enable(feature, actor_gate, flipper.actor(Flipper::Actor.new(22)))).to eq(true)
162
160
  result = subject.get(feature)
163
161
  expect(result[:actors]).to eq(Set['22'])
164
162
  end
@@ -198,7 +196,7 @@ RSpec.shared_examples_for 'a flipper adapter' do
198
196
  end
199
197
 
200
198
  it 'clears all the gate values for the feature on remove' do
201
- actor22 = actor_class.new('22')
199
+ actor22 = Flipper::Actor.new('22')
202
200
  expect(subject.enable(feature, boolean_gate, flipper.boolean)).to eq(true)
203
201
  expect(subject.enable(feature, group_gate, flipper.group(:admins))).to eq(true)
204
202
  expect(subject.enable(feature, actor_gate, flipper.actor(actor22))).to eq(true)
@@ -211,7 +209,7 @@ RSpec.shared_examples_for 'a flipper adapter' do
211
209
  end
212
210
 
213
211
  it 'can clear all the gate values for a feature' do
214
- actor22 = actor_class.new('22')
212
+ actor22 = Flipper::Actor.new('22')
215
213
  subject.add(feature)
216
214
  expect(subject.features).to include(feature.key)
217
215
 
@@ -5,7 +5,6 @@ module Flipper
5
5
  def setup
6
6
  super
7
7
  @flipper = Flipper.new(@adapter)
8
- @actor_class = Struct.new(:flipper_id)
9
8
  @feature = @flipper[:stats]
10
9
  @boolean_gate = @feature.gate(:boolean)
11
10
  @group_gate = @feature.gate(:group)
@@ -48,7 +47,7 @@ module Flipper
48
47
  end
49
48
 
50
49
  def test_fully_disables_all_enabled_things_when_boolean_gate_disabled
51
- actor22 = @actor_class.new('22')
50
+ actor22 = Flipper::Actor.new('22')
52
51
  assert_equal true, @adapter.enable(@feature, @boolean_gate, @flipper.boolean)
53
52
  assert_equal true, @adapter.enable(@feature, @group_gate, @flipper.group(:admins))
54
53
  assert_equal true, @adapter.enable(@feature, @actor_gate, @flipper.actor(actor22))
@@ -75,8 +74,8 @@ module Flipper
75
74
  end
76
75
 
77
76
  def test_can_enable_disable_and_get_value_for_an_actor_gate
78
- actor22 = @actor_class.new('22')
79
- actor_asdf = @actor_class.new('asdf')
77
+ actor22 = Flipper::Actor.new('22')
78
+ actor_asdf = Flipper::Actor.new('asdf')
80
79
 
81
80
  assert_equal true, @adapter.enable(@feature, @actor_gate, @flipper.actor(actor22))
82
81
  assert_equal true, @adapter.enable(@feature, @actor_gate, @flipper.actor(actor_asdf))
@@ -153,7 +152,7 @@ module Flipper
153
152
 
154
153
  def test_converts_the_actor_value_to_a_string
155
154
  assert_equal true,
156
- @adapter.enable(@feature, @actor_gate, @flipper.actor(@actor_class.new(22)))
155
+ @adapter.enable(@feature, @actor_gate, @flipper.actor(Flipper::Actor.new(22)))
157
156
  result = @adapter.get(@feature)
158
157
  assert_equal Set['22'], result[:actors]
159
158
  end
@@ -193,7 +192,7 @@ module Flipper
193
192
  end
194
193
 
195
194
  def test_clears_all_the_gate_values_for_the_feature_on_remove
196
- actor22 = @actor_class.new('22')
195
+ actor22 = Flipper::Actor.new('22')
197
196
  assert_equal true, @adapter.enable(@feature, @boolean_gate, @flipper.boolean)
198
197
  assert_equal true, @adapter.enable(@feature, @group_gate, @flipper.group(:admins))
199
198
  assert_equal true, @adapter.enable(@feature, @actor_gate, @flipper.actor(actor22))
@@ -206,7 +205,7 @@ module Flipper
206
205
  end
207
206
 
208
207
  def test_can_clear_all_the_gate_values_for_a_feature
209
- actor22 = @actor_class.new('22')
208
+ actor22 = Flipper::Actor.new('22')
210
209
  @adapter.add(@feature)
211
210
  assert_includes @adapter.features, @feature.key
212
211
 
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.11.0.beta1'.freeze
2
+ VERSION = '0.11.0.beta3'.freeze
3
3
  end
@@ -0,0 +1,48 @@
1
+ require 'helper'
2
+
3
+ RSpec.describe Flipper::Actor do
4
+ it 'initializes with and knows flipper_id' do
5
+ actor = described_class.new("User;235")
6
+ expect(actor.flipper_id).to eq("User;235")
7
+ end
8
+
9
+ describe '#eql?' do
10
+ it 'returns true if same class and flipper_id' do
11
+ actor1 = described_class.new("User;235")
12
+ actor2 = described_class.new("User;235")
13
+ expect(actor1.eql?(actor2)).to be(true)
14
+ end
15
+
16
+ it 'returns false if same class but different flipper_id' do
17
+ actor1 = described_class.new("User;235")
18
+ actor2 = described_class.new("User;1")
19
+ expect(actor1.eql?(actor2)).to be(false)
20
+ end
21
+
22
+ it 'returns false for different class' do
23
+ actor1 = described_class.new("User;235")
24
+ actor2 = Struct.new(:flipper_id).new("User;235")
25
+ expect(actor1.eql?(actor2)).to be(false)
26
+ end
27
+ end
28
+
29
+ describe '#==' do
30
+ it 'returns true if same class and flipper_id' do
31
+ actor1 = described_class.new("User;235")
32
+ actor2 = described_class.new("User;235")
33
+ expect(actor1.==(actor2)).to be(true)
34
+ end
35
+
36
+ it 'returns false if same class but different flipper_id' do
37
+ actor1 = described_class.new("User;235")
38
+ actor2 = described_class.new("User;1")
39
+ expect(actor1.==(actor2)).to be(false)
40
+ end
41
+
42
+ it 'returns false for different class' do
43
+ actor1 = described_class.new("User;235")
44
+ actor2 = Struct.new(:flipper_id).new("User;235")
45
+ expect(actor1.==(actor2)).to be(false)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,114 @@
1
+ require 'helper'
2
+ require 'flipper/adapters/memory'
3
+
4
+ RSpec.describe Flipper::Adapter do
5
+ let(:source_flipper) { build_flipper }
6
+ let(:destination_flipper) { build_flipper }
7
+ let(:default_config) do
8
+ {
9
+ boolean: nil,
10
+ groups: Set.new,
11
+ actors: Set.new,
12
+ percentage_of_actors: nil,
13
+ percentage_of_time: nil,
14
+ }
15
+ end
16
+
17
+ describe '.default_config' do
18
+ it 'returns default config' do
19
+ adapter_class = Class.new do
20
+ include described_class
21
+ end
22
+ expect(adapter_class.default_config).to eq(default_config)
23
+ end
24
+ end
25
+
26
+ describe '#default_config' do
27
+ it 'returns default config' do
28
+ adapter_class = Class.new do
29
+ include described_class
30
+ end
31
+ expect(adapter_class.new.default_config).to eq(default_config)
32
+ end
33
+ end
34
+
35
+ describe '#import' do
36
+ it 'returns nothing' do
37
+ result = destination_flipper.import(source_flipper)
38
+ expect(result).to be(nil)
39
+ end
40
+
41
+ it 'can import from one adapter to another' do
42
+ source_flipper.enable(:search)
43
+ destination_flipper.import(source_flipper)
44
+ expect(destination_flipper[:search].boolean_value).to eq(true)
45
+ expect(destination_flipper.features.map(&:key).sort).to eq(%w(search))
46
+ end
47
+
48
+ it 'can import features that have been added but their state is off' do
49
+ source_flipper.add(:search)
50
+ destination_flipper.import(source_flipper)
51
+ expect(destination_flipper.features.map(&:key)).to eq(["search"])
52
+ end
53
+
54
+ it 'can import multiple features' do
55
+ source_flipper.enable(:yep)
56
+ source_flipper.enable_group(:preview_features, :developers)
57
+ source_flipper.enable_group(:preview_features, :marketers)
58
+ source_flipper.enable_group(:preview_features, :company)
59
+ source_flipper.enable_group(:preview_features, :early_access)
60
+ source_flipper.enable_actor(:preview_features, Flipper::Actor.new('1'))
61
+ source_flipper.enable_actor(:preview_features, Flipper::Actor.new('2'))
62
+ source_flipper.enable_actor(:preview_features, Flipper::Actor.new('3'))
63
+ source_flipper.enable_percentage_of_actors(:issues_next, 25)
64
+ source_flipper.enable_percentage_of_time(:verbose_logging, 5)
65
+
66
+ destination_flipper.import(source_flipper)
67
+
68
+ feature = destination_flipper[:preview_features]
69
+ expect(feature.boolean_value).to be(false)
70
+ expect(feature.actors_value).to eq(Set['1', '2', '3'])
71
+ expected_groups = Set['developers', 'marketers', 'company', 'early_access']
72
+ expect(feature.groups_value).to eq(expected_groups)
73
+ expect(feature.percentage_of_actors_value).to be(0)
74
+ expect(feature.percentage_of_time_value).to be(0)
75
+
76
+ feature = destination_flipper[:issues_next]
77
+ expect(feature.boolean_value).to eq(false)
78
+ expect(feature.actors_value).to eq(Set.new)
79
+ expect(feature.groups_value).to eq(Set.new)
80
+ expect(feature.percentage_of_actors_value).to be(25)
81
+ expect(feature.percentage_of_time_value).to be(0)
82
+
83
+ feature = destination_flipper[:verbose_logging]
84
+ expect(feature.boolean_value).to eq(false)
85
+ expect(feature.actors_value).to eq(Set.new)
86
+ expect(feature.groups_value).to eq(Set.new)
87
+ expect(feature.percentage_of_actors_value).to be(0)
88
+ expect(feature.percentage_of_time_value).to be(5)
89
+ end
90
+
91
+ it 'wipes existing enablements for adapter' do
92
+ destination_flipper.enable(:stats)
93
+ destination_flipper.enable_percentage_of_time(:verbose_logging, 5)
94
+ source_flipper.enable_percentage_of_time(:stats, 5)
95
+ source_flipper.enable_percentage_of_actors(:verbose_logging, 25)
96
+
97
+ destination_flipper.import(source_flipper)
98
+
99
+ feature = destination_flipper[:stats]
100
+ expect(feature.boolean_value).to be(false)
101
+ expect(feature.percentage_of_time_value).to be(5)
102
+
103
+ feature = destination_flipper[:verbose_logging]
104
+ expect(feature.percentage_of_time_value).to be(0)
105
+ expect(feature.percentage_of_actors_value).to be(25)
106
+ end
107
+
108
+ it 'wipes existing features for adapter' do
109
+ destination_flipper.add(:stats)
110
+ destination_flipper.import(source_flipper)
111
+ expect(destination_flipper.features.map(&:key)).to eq([])
112
+ end
113
+ end
114
+ end
@@ -60,7 +60,7 @@ RSpec.describe Flipper::Adapters::Http do
60
60
  headers = {
61
61
  'Accept' => 'application/json',
62
62
  'Content-Type' => 'application/json',
63
- 'User-Agent' => 'Flipper HTTP Adapter v0.10.2',
63
+ 'User-Agent' => "Flipper HTTP Adapter v#{Flipper::VERSION}",
64
64
  }
65
65
  stub_request(:get, "http://app.com/flipper/features/feature_panel")
66
66
  .with(headers: headers)
@@ -2,8 +2,6 @@ require 'helper'
2
2
  require 'flipper/adapters/read_only'
3
3
 
4
4
  RSpec.describe Flipper::Adapters::ReadOnly do
5
- let(:actor_class) { Struct.new(:flipper_id) }
6
-
7
5
  let(:adapter) { Flipper::Adapters::Memory.new }
8
6
  let(:flipper) { Flipper.new(subject) }
9
7
  let(:feature) { flipper[:stats] }
@@ -44,7 +42,7 @@ RSpec.describe Flipper::Adapters::ReadOnly do
44
42
  end
45
43
 
46
44
  it 'can get feature' do
47
- actor22 = actor_class.new('22')
45
+ actor22 = Flipper::Actor.new('22')
48
46
  adapter.enable(feature, boolean_gate, flipper.boolean)
49
47
  adapter.enable(feature, group_gate, flipper.group(:admins))
50
48
  adapter.enable(feature, actor_gate, flipper.actor(actor22))
@@ -107,7 +107,7 @@ RSpec.describe Flipper::DSL do
107
107
  describe '#actor' do
108
108
  context 'for a thing' do
109
109
  it 'returns actor instance' do
110
- thing = Struct.new(:flipper_id).new(33)
110
+ thing = Flipper::Actor.new(33)
111
111
  actor = subject.actor(thing)
112
112
  expect(actor).to be_instance_of(Flipper::Types::Actor)
113
113
  expect(actor.value).to eq('33')
@@ -204,7 +204,7 @@ RSpec.describe Flipper::DSL do
204
204
 
205
205
  describe '#enable_actor/disable_actor' do
206
206
  it 'enables and disables the feature for actor' do
207
- actor = Struct.new(:flipper_id).new(5)
207
+ actor = Flipper::Actor.new(5)
208
208
 
209
209
  expect(subject[:stats].actors_value).to be_empty
210
210
  subject.enable_actor(:stats, actor)
@@ -217,7 +217,7 @@ RSpec.describe Flipper::DSL do
217
217
 
218
218
  describe '#enable_group/disable_group' do
219
219
  it 'enables and disables the feature for group' do
220
- actor = Struct.new(:flipper_id).new(5)
220
+ actor = Flipper::Actor.new(5)
221
221
  group = Flipper.register(:fives) { |actor| actor.flipper_id == 5 }
222
222
 
223
223
  expect(subject[:stats].groups_value).to be_empty
@@ -251,13 +251,28 @@ RSpec.describe Flipper::DSL do
251
251
  end
252
252
  end
253
253
 
254
+ describe '#add' do
255
+ it 'adds the feature' do
256
+ expect(subject.features).to eq(Set.new)
257
+ subject.add(:stats)
258
+ expect(subject.features).to eq(Set[subject[:stats]])
259
+ end
260
+ end
261
+
254
262
  describe '#remove' do
255
263
  it 'removes the feature' do
256
- subject.enable(:stats)
257
-
258
- expect { subject.remove(:stats) }.to change { subject.enabled?(:stats) }.to(false)
264
+ subject.adapter.add(subject[:stats])
265
+ expect(subject.features).to eq(Set[subject[:stats]])
266
+ subject.remove(:stats)
267
+ expect(subject.features).to eq(Set.new)
268
+ end
269
+ end
259
270
 
260
- expect(subject.features).to be_empty
271
+ describe '#import' do
272
+ it 'delegates to adapter' do
273
+ destination_flipper = build_flipper
274
+ expect(subject.adapter).to receive(:import).with(destination_flipper.adapter)
275
+ subject.import(destination_flipper)
261
276
  end
262
277
  end
263
278
  end
@@ -3,7 +3,7 @@ require 'helper'
3
3
  RSpec.describe Flipper::FeatureCheckContext do
4
4
  let(:feature_name) { :new_profiles }
5
5
  let(:values) { Flipper::GateValues.new({}) }
6
- let(:thing) { Struct.new(:flipper_id).new('5') }
6
+ let(:thing) { Flipper::Actor.new('5') }
7
7
  let(:options) do
8
8
  {
9
9
  feature_name: feature_name,
@@ -89,6 +89,23 @@ RSpec.describe Flipper::Feature do
89
89
  end
90
90
  end
91
91
 
92
+ describe '#add' do
93
+ it 'adds feature to adapter' do
94
+ expect(adapter.features).to eq(Set.new)
95
+ subject.add
96
+ expect(adapter.features).to eq(Set[subject.key])
97
+ end
98
+ end
99
+
100
+ describe '#remove' do
101
+ it 'removes feature from adapter' do
102
+ adapter.add(subject)
103
+ expect(adapter.features).to eq(Set[subject.key])
104
+ subject.remove
105
+ expect(adapter.features).to eq(Set.new)
106
+ end
107
+ end
108
+
92
109
  describe '#inspect' do
93
110
  it 'returns easy to read string representation' do
94
111
  string = subject.inspect
@@ -113,7 +130,7 @@ RSpec.describe Flipper::Feature do
113
130
  end
114
131
 
115
132
  it 'is recorded for enable' do
116
- thing = Flipper::Types::Actor.new(Struct.new(:flipper_id).new('1'))
133
+ thing = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
117
134
  gate = subject.gate_for(thing)
118
135
 
119
136
  subject.enable(thing)
@@ -128,7 +145,7 @@ RSpec.describe Flipper::Feature do
128
145
  end
129
146
 
130
147
  it 'always instruments flipper type instance for enable' do
131
- thing = Struct.new(:flipper_id).new('1')
148
+ thing = Flipper::Actor.new('1')
132
149
  gate = subject.gate_for(thing)
133
150
 
134
151
  subject.enable(thing)
@@ -153,7 +170,7 @@ RSpec.describe Flipper::Feature do
153
170
  expect(event.payload[:result]).not_to be_nil
154
171
  end
155
172
 
156
- user = Struct.new(:flipper_id).new('1')
173
+ user = Flipper::Actor.new('1')
157
174
  actor = Flipper::Types::Actor.new(user)
158
175
  boolean_true = Flipper::Types::Boolean.new(true)
159
176
  boolean_false = Flipper::Types::Boolean.new(false)
@@ -184,7 +201,7 @@ RSpec.describe Flipper::Feature do
184
201
  end
185
202
 
186
203
  it 'always instruments flipper type instance for disable' do
187
- thing = Struct.new(:flipper_id).new('1')
204
+ thing = Flipper::Actor.new('1')
188
205
  gate = subject.gate_for(thing)
189
206
 
190
207
  subject.disable(thing)
@@ -195,6 +212,17 @@ RSpec.describe Flipper::Feature do
195
212
  expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(thing))
196
213
  end
197
214
 
215
+ it 'is recorded for add' do
216
+ subject.add
217
+
218
+ event = instrumenter.events.last
219
+ expect(event).not_to be_nil
220
+ expect(event.name).to eq('feature_operation.flipper')
221
+ expect(event.payload[:feature_name]).to eq(:search)
222
+ expect(event.payload[:operation]).to eq(:add)
223
+ expect(event.payload[:result]).not_to be_nil
224
+ end
225
+
198
226
  it 'is recorded for remove' do
199
227
  subject.remove
200
228
 
@@ -207,7 +235,7 @@ RSpec.describe Flipper::Feature do
207
235
  end
208
236
 
209
237
  it 'is recorded for enabled?' do
210
- thing = Flipper::Types::Actor.new(Struct.new(:flipper_id).new('1'))
238
+ thing = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
211
239
  gate = subject.gate_for(thing)
212
240
 
213
241
  subject.enabled?(thing)
@@ -221,7 +249,7 @@ RSpec.describe Flipper::Feature do
221
249
  expect(event.payload[:result]).to eq(false)
222
250
  end
223
251
 
224
- user = Struct.new(:flipper_id).new('1')
252
+ user = Flipper::Actor.new('1')
225
253
  actor = Flipper::Types::Actor.new(user)
226
254
  {
227
255
  nil => nil,
@@ -461,8 +489,8 @@ RSpec.describe Flipper::Feature do
461
489
 
462
490
  context 'when one or more actors are enabled' do
463
491
  before do
464
- subject.enable Flipper::Types::Actor.new(Struct.new(:flipper_id).new('User:5'))
465
- subject.enable Flipper::Types::Actor.new(Struct.new(:flipper_id).new('User:22'))
492
+ subject.enable Flipper::Types::Actor.new(Flipper::Actor.new('User:5'))
493
+ subject.enable Flipper::Types::Actor.new(Flipper::Actor.new('User:22'))
466
494
  end
467
495
 
468
496
  it 'returns set of actor ids' do
@@ -565,7 +593,7 @@ RSpec.describe Flipper::Feature do
565
593
  context 'with gate values set in adapter' do
566
594
  before do
567
595
  subject.enable Flipper::Types::Boolean.new(true)
568
- subject.enable Flipper::Types::Actor.new(Struct.new(:flipper_id).new(5))
596
+ subject.enable Flipper::Types::Actor.new(Flipper::Actor.new(5))
569
597
  subject.enable Flipper::Types::Group.new(:admins)
570
598
  subject.enable Flipper::Types::PercentageOfTime.new(50)
571
599
  subject.enable Flipper::Types::PercentageOfActors.new(25)
@@ -584,7 +612,7 @@ RSpec.describe Flipper::Feature do
584
612
  describe '#enable_actor/disable_actor' do
585
613
  context 'with object that responds to flipper_id' do
586
614
  it 'updates the gate values to include the actor' do
587
- actor = Struct.new(:flipper_id).new(5)
615
+ actor = Flipper::Actor.new(5)
588
616
  expect(subject.gate_values.actors).to be_empty
589
617
  subject.enable_actor(actor)
590
618
  expect(subject.gate_values.actors).to eq(Set['5'])
@@ -595,7 +623,7 @@ RSpec.describe Flipper::Feature do
595
623
 
596
624
  context 'with actor instance' do
597
625
  it 'updates the gate values to include the actor' do
598
- actor = Struct.new(:flipper_id).new(5)
626
+ actor = Flipper::Actor.new(5)
599
627
  instance = Flipper::Types::Actor.new(actor)
600
628
  expect(subject.gate_values.actors).to be_empty
601
629
  subject.enable_actor(instance)
@@ -609,7 +637,7 @@ RSpec.describe Flipper::Feature do
609
637
  describe '#enable_group/disable_group' do
610
638
  context 'with symbol group name' do
611
639
  it 'updates the gate values to include the group' do
612
- actor = Struct.new(:flipper_id).new(5)
640
+ actor = Flipper::Actor.new(5)
613
641
  group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
614
642
  expect(subject.gate_values.groups).to be_empty
615
643
  subject.enable_group(:five_only)
@@ -621,7 +649,7 @@ RSpec.describe Flipper::Feature do
621
649
 
622
650
  context 'with string group name' do
623
651
  it 'updates the gate values to include the group' do
624
- actor = Struct.new(:flipper_id).new(5)
652
+ actor = Flipper::Actor.new(5)
625
653
  group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
626
654
  expect(subject.gate_values.groups).to be_empty
627
655
  subject.enable_group('five_only')
@@ -633,7 +661,7 @@ RSpec.describe Flipper::Feature do
633
661
 
634
662
  context 'with group instance' do
635
663
  it 'updates the gate values for the group' do
636
- actor = Struct.new(:flipper_id).new(5)
664
+ actor = Flipper::Actor.new(5)
637
665
  group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
638
666
  expect(subject.gate_values.groups).to be_empty
639
667
  subject.enable_group(group)
@@ -11,7 +11,7 @@ RSpec.describe Flipper::Gates::Boolean do
11
11
  Flipper::FeatureCheckContext.new(
12
12
  feature_name: feature_name,
13
13
  values: Flipper::GateValues.new(boolean: bool),
14
- thing: Flipper::Types::Actor.new(Struct.new(:flipper_id).new(1))
14
+ thing: Flipper::Types::Actor.new(Flipper::Actor.new(1))
15
15
  )
16
16
  end
17
17
 
@@ -11,7 +11,7 @@ RSpec.describe Flipper::Gates::Group do
11
11
  Flipper::FeatureCheckContext.new(
12
12
  feature_name: feature_name,
13
13
  values: Flipper::GateValues.new(groups: set),
14
- thing: Flipper::Types::Actor.new(Struct.new(:flipper_id).new('5'))
14
+ thing: Flipper::Types::Actor.new(Flipper::Actor.new('5'))
15
15
  )
16
16
  end
17
17
 
@@ -22,7 +22,7 @@ RSpec.describe Flipper::Gates::Group do
22
22
  end
23
23
 
24
24
  it 'ignores group' do
25
- thing = Struct.new(:flipper_id).new('5')
25
+ thing = Flipper::Actor.new('5')
26
26
  expect(subject.open?(context(Set[:newbs, :staff]))).to be(true)
27
27
  end
28
28
  end
@@ -11,7 +11,7 @@ RSpec.describe Flipper::Gates::PercentageOfActors do
11
11
  Flipper::FeatureCheckContext.new(
12
12
  feature_name: feature,
13
13
  values: Flipper::GateValues.new(percentage_of_actors: integer),
14
- thing: thing || Flipper::Types::Actor.new(Struct.new(:flipper_id).new(1))
14
+ thing: thing || Flipper::Types::Actor.new(Flipper::Actor.new(1))
15
15
  )
16
16
  end
17
17
 
@@ -22,7 +22,7 @@ RSpec.describe Flipper::Gates::PercentageOfActors do
22
22
  let(:number_of_actors) { 100 }
23
23
 
24
24
  let(:actors) do
25
- (1..number_of_actors).map { |n| Struct.new(:flipper_id).new(n) }
25
+ (1..number_of_actors).map { |n| Flipper::Actor.new(n) }
26
26
  end
27
27
 
28
28
  let(:feature_one_enabled_actors) do
@@ -49,7 +49,7 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
49
49
  end
50
50
 
51
51
  context 'feature enabled checks with a thing' do
52
- let(:user) { Flipper::Types::Actor.new(Struct.new(:flipper_id).new('1')) }
52
+ let(:user) { Flipper::Types::Actor.new(Flipper::Actor.new('1')) }
53
53
 
54
54
  before do
55
55
  clear_logs
@@ -63,7 +63,7 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do
63
63
  end
64
64
 
65
65
  context 'changing feature enabled state' do
66
- let(:user) { Flipper::Types::Actor.new(Struct.new(:flipper_id).new('1')) }
66
+ let(:user) { Flipper::Types::Actor.new(Flipper::Actor.new('1')) }
67
67
 
68
68
  before do
69
69
  clear_logs
@@ -11,7 +11,7 @@ RSpec.describe Flipper::Instrumentation::MetriksSubscriber do
11
11
  Flipper.new(adapter, instrumenter: ActiveSupport::Notifications)
12
12
  end
13
13
 
14
- let(:user) { user = Struct.new(:flipper_id).new('1') }
14
+ let(:user) { user = Flipper::Actor.new('1') }
15
15
 
16
16
  before do
17
17
  Metriks::Registry.default.clear
@@ -15,7 +15,7 @@ RSpec.describe Flipper::Instrumentation::StatsdSubscriber do
15
15
  Flipper.new(adapter, instrumenter: ActiveSupport::Notifications)
16
16
  end
17
17
 
18
- let(:user) { user = Struct.new(:flipper_id).new('1') }
18
+ let(:user) { user = Flipper::Actor.new('1') }
19
19
 
20
20
  before do
21
21
  described_class.client = statsd_client
@@ -18,6 +18,19 @@ RSpec.describe Flipper::Middleware::Memoizer do
18
18
  flipper.adapter.memoize = nil
19
19
  end
20
20
 
21
+ it 'raises if initialized with app and flipper instance' do
22
+ expect do
23
+ described_class.new(app, flipper)
24
+ end.to raise_error(/no longer initializes with a flipper/)
25
+ end
26
+
27
+ it 'raises if initialized with app and block' do
28
+ block = -> { flipper }
29
+ expect do
30
+ described_class.new(app, block)
31
+ end.to raise_error(/no longer initializes with a flipper/)
32
+ end
33
+
21
34
  RSpec.shared_examples_for 'flipper middleware' do
22
35
  it 'delegates' do
23
36
  called = false
@@ -69,7 +69,7 @@ RSpec.describe Flipper::Types::Group do
69
69
  context = Flipper::FeatureCheckContext.new(
70
70
  feature_name: :my_feature,
71
71
  values: Flipper::GateValues.new({}),
72
- thing: Flipper::Types::Actor.new(Struct.new(:flipper_id).new(1))
72
+ thing: Flipper::Types::Actor.new(Flipper::Actor.new(1))
73
73
  )
74
74
  group = Flipper.register(:group_with_context) { |actor| actor }
75
75
  yielded_actor = group.match?(admin_actor, context)
@@ -80,7 +80,7 @@ RSpec.describe Flipper::Types::Group do
80
80
  context = Flipper::FeatureCheckContext.new(
81
81
  feature_name: :my_feature,
82
82
  values: Flipper::GateValues.new({}),
83
- thing: Flipper::Types::Actor.new(Struct.new(:flipper_id).new(1))
83
+ thing: Flipper::Types::Actor.new(Flipper::Actor.new(1))
84
84
  )
85
85
  group = Flipper.register(:group_with_context) { |actor, context| [actor, context] }
86
86
  yielded_actor, yielded_context = group.match?(admin_actor, context)
@@ -6,9 +6,6 @@ RSpec.describe Flipper do
6
6
  let(:adapter) { Flipper::Adapters::Memory.new }
7
7
  let(:flipper) { described_class.new(adapter) }
8
8
  let(:feature) { flipper[:search] }
9
-
10
- let(:actor_class) { Struct.new(:flipper_id) }
11
-
12
9
  let(:admin_group) { flipper.group(:admins) }
13
10
  let(:dev_group) { flipper.group(:devs) }
14
11
 
@@ -26,8 +23,8 @@ RSpec.describe Flipper do
26
23
  double 'Non Flipper Thing', flipper_id: 1, admin?: nil, dev?: false
27
24
  end
28
25
 
29
- let(:pitt) { actor_class.new(1) }
30
- let(:clooney) { actor_class.new(10) }
26
+ let(:pitt) { Flipper::Actor.new(1) }
27
+ let(:clooney) { Flipper::Actor.new(10) }
31
28
 
32
29
  let(:five_percent_of_actors) { flipper.actors(5) }
33
30
  let(:five_percent_of_time) { flipper.time(5) }
@@ -123,7 +120,7 @@ RSpec.describe Flipper do
123
120
 
124
121
  it 'enables feature for actor within percentage' do
125
122
  enabled = (1..100).select do |i|
126
- thing = actor_class.new(i)
123
+ thing = Flipper::Actor.new(i)
127
124
  feature.enabled?(thing)
128
125
  end.size
129
126
 
@@ -202,7 +199,7 @@ RSpec.describe Flipper do
202
199
 
203
200
  it 'disables actor in percentage of actors' do
204
201
  enabled = (1..100).select do |i|
205
- thing = actor_class.new(i)
202
+ thing = Flipper::Actor.new(i)
206
203
  feature.enabled?(thing)
207
204
  end.size
208
205
 
@@ -285,7 +282,7 @@ RSpec.describe Flipper do
285
282
 
286
283
  it 'disables feature' do
287
284
  enabled = (1..100).select do |i|
288
- thing = actor_class.new(i)
285
+ thing = Flipper::Actor.new(i)
289
286
  feature.enabled?(thing)
290
287
  end.size
291
288
 
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.11.0.beta1
4
+ version: 0.11.0.beta3
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-02 00:00:00.000000000 Z
11
+ date: 2017-04-10 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
@@ -44,6 +44,7 @@ files:
44
44
  - examples/group.rb
45
45
  - examples/group_dynamic_lookup.rb
46
46
  - examples/group_with_members.rb
47
+ - examples/importing.rb
47
48
  - examples/individual_actor.rb
48
49
  - examples/instrumentation.rb
49
50
  - examples/percentage_of_actors.rb
@@ -52,6 +53,7 @@ files:
52
53
  - examples/percentage_of_time.rb
53
54
  - flipper.gemspec
54
55
  - lib/flipper.rb
56
+ - lib/flipper/actor.rb
55
57
  - lib/flipper/adapter.rb
56
58
  - lib/flipper/adapters/http.rb
57
59
  - lib/flipper/adapters/http/client.rb
@@ -96,6 +98,8 @@ files:
96
98
  - lib/flipper/types/percentage_of_time.rb
97
99
  - lib/flipper/version.rb
98
100
  - spec/fixtures/feature.json
101
+ - spec/flipper/actor_spec.rb
102
+ - spec/flipper/adapter_spec.rb
99
103
  - spec/flipper/adapters/http_spec.rb
100
104
  - spec/flipper/adapters/instrumented_spec.rb
101
105
  - spec/flipper/adapters/memoizable_spec.rb
@@ -163,6 +167,8 @@ specification_version: 4
163
167
  summary: Feature flipper for ANYTHING
164
168
  test_files:
165
169
  - spec/fixtures/feature.json
170
+ - spec/flipper/actor_spec.rb
171
+ - spec/flipper/adapter_spec.rb
166
172
  - spec/flipper/adapters/http_spec.rb
167
173
  - spec/flipper/adapters/instrumented_spec.rb
168
174
  - spec/flipper/adapters/memoizable_spec.rb