flipper 0.7.0.beta2 → 0.7.0.beta3
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/README.md +29 -0
- data/flipper.gemspec +1 -1
- data/lib/flipper/adapters/decorator.rb +1 -1
- data/lib/flipper/adapters/instrumented.rb +1 -1
- data/lib/flipper/adapters/memory.rb +1 -1
- data/lib/flipper/adapters/pstore.rb +166 -0
- data/lib/flipper/dsl.rb +3 -3
- data/lib/flipper/feature.rb +39 -43
- data/lib/flipper/gate.rb +0 -4
- data/lib/flipper/gates/actor.rb +0 -9
- data/lib/flipper/gates/boolean.rb +10 -9
- data/lib/flipper/gates/group.rb +1 -10
- data/lib/flipper/gates/percentage_of_actors.rb +1 -9
- data/lib/flipper/gates/percentage_of_time.rb +0 -8
- data/lib/flipper/type.rb +5 -0
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +1 -1
- data/spec/flipper/adapters/pstore_spec.rb +13 -0
- data/spec/flipper/feature_spec.rb +81 -33
- data/spec/flipper/gates/actor_spec.rb +0 -15
- data/spec/flipper/gates/boolean_spec.rb +30 -14
- data/spec/flipper/gates/group_spec.rb +0 -15
- data/spec/flipper/gates/percentage_of_actors_spec.rb +0 -14
- data/spec/flipper/gates/percentage_of_time_spec.rb +0 -14
- data/spec/helper.rb +4 -9
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b952f92d9398ba28267085a3084fbc659e571f6f
|
4
|
+
data.tar.gz: 4e535ba61ef3e38e24f419489c305c17cbdf58e1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5457f2328718155eef1ff0014346179f5bec08cd594a9c6f31ccd3e02977a91089477bffee66d53438bf849c04d66b9ca73dc56e06653f8a4dbb6168e69e56c8
|
7
|
+
data.tar.gz: 9c7da626025b191605e70beb3b6cdefd190d124c429354b54451472e7d3ea3fb48b90218356da8843fd30955529981769b320404e2c07e7ffef92da2cf787e40
|
data/README.md
CHANGED
@@ -250,6 +250,35 @@ A good place to start when creating your own adapter is to copy one of the adapt
|
|
250
250
|
|
251
251
|
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.
|
252
252
|
|
253
|
+
## Instrumentation
|
254
|
+
|
255
|
+
Flipper comes with automatic instrumentation. By default these work with ActiveSupport::Notifications, but only require the pieces of ActiveSupport that are needed and only do so if you actually attempt to require the instrumentation files listed below.
|
256
|
+
|
257
|
+
To use the log subscriber:
|
258
|
+
|
259
|
+
```ruby
|
260
|
+
# Gemfile
|
261
|
+
gem "activesupport"
|
262
|
+
|
263
|
+
# config/initializers/flipper.rb (or wherever you want it)
|
264
|
+
require "flipper/instrumentation/log_subscriber"
|
265
|
+
```
|
266
|
+
|
267
|
+
To use the statsd instrumentation:
|
268
|
+
|
269
|
+
```ruby
|
270
|
+
# Gemfile
|
271
|
+
gem "activesupport"
|
272
|
+
gem "statsd-ruby"
|
273
|
+
|
274
|
+
# config/initializers/flipper.rb (or wherever you want it)
|
275
|
+
require "flipper/instrumentation/statsd"
|
276
|
+
Flipper::Instrumentation::StatsdSubscriber.client = Statsd.new # or whatever your statsd instance is
|
277
|
+
```
|
278
|
+
|
279
|
+
You can also do whatever you want with the instrumented events. Check out [this example](https://github.com/jnunemaker/flipper/blob/master/examples/instrumentation.rb) for more.
|
280
|
+
|
281
|
+
|
253
282
|
## Optimization
|
254
283
|
|
255
284
|
One optimization that flipper provides is a memoizing middleware. The memoizing middleware ensures that you only make one adapter call per feature per request.
|
data/flipper.gemspec
CHANGED
@@ -4,7 +4,6 @@ require File.expand_path('../lib/flipper/version', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ["John Nunemaker"]
|
6
6
|
gem.email = ["nunemaker@gmail.com"]
|
7
|
-
gem.description = %q{Feature flipper for any adapter}
|
8
7
|
gem.summary = %q{Feature flipper for any adapter}
|
9
8
|
gem.homepage = "http://jnunemaker.github.com/flipper"
|
10
9
|
|
@@ -14,4 +13,5 @@ Gem::Specification.new do |gem|
|
|
14
13
|
gem.name = "flipper"
|
15
14
|
gem.require_paths = ["lib"]
|
16
15
|
gem.version = Flipper::VERSION
|
16
|
+
gem.license = "MIT"
|
17
17
|
end
|
@@ -20,7 +20,7 @@ module Flipper
|
|
20
20
|
def initialize(adapter, options = {})
|
21
21
|
super(adapter)
|
22
22
|
@name = :instrumented
|
23
|
-
@instrumenter = options.fetch(:instrumenter,
|
23
|
+
@instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
|
24
24
|
end
|
25
25
|
|
26
26
|
# Public
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require "pstore"
|
2
|
+
require "set"
|
3
|
+
|
4
|
+
module Flipper
|
5
|
+
module Adapters
|
6
|
+
class PStore
|
7
|
+
include Adapter
|
8
|
+
|
9
|
+
FeaturesKey = :flipper_features
|
10
|
+
|
11
|
+
# Public: The name of the adapter.
|
12
|
+
attr_reader :name
|
13
|
+
|
14
|
+
# Public: The path to where the file is stored.
|
15
|
+
attr_reader :path
|
16
|
+
|
17
|
+
# Public
|
18
|
+
def initialize(path = "flipper.pstore")
|
19
|
+
@path = path
|
20
|
+
@store = ::PStore.new(path)
|
21
|
+
@name = :pstore
|
22
|
+
end
|
23
|
+
|
24
|
+
# Public: The set of known features.
|
25
|
+
def features
|
26
|
+
set_members FeaturesKey
|
27
|
+
end
|
28
|
+
|
29
|
+
# Public: Adds a feature to the set of known features.
|
30
|
+
def add(feature)
|
31
|
+
set_add FeaturesKey, feature.key
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
# Public: Removes a feature from the set of known features and clears
|
36
|
+
# all the values for the feature.
|
37
|
+
def remove(feature)
|
38
|
+
set_delete FeaturesKey, feature.key
|
39
|
+
clear(feature)
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
# Public: Clears all the gate values for a feature.
|
44
|
+
def clear(feature)
|
45
|
+
feature.gates.each do |gate|
|
46
|
+
delete key(feature, gate)
|
47
|
+
end
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
# Public
|
52
|
+
def get(feature)
|
53
|
+
result = {}
|
54
|
+
|
55
|
+
feature.gates.each do |gate|
|
56
|
+
result[gate.key] = case gate.data_type
|
57
|
+
when :boolean, :integer
|
58
|
+
read key(feature, gate)
|
59
|
+
when :set
|
60
|
+
set_members key(feature, gate)
|
61
|
+
else
|
62
|
+
raise "#{gate} is not supported by this adapter yet"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
result
|
67
|
+
end
|
68
|
+
|
69
|
+
# Public
|
70
|
+
def enable(feature, gate, thing)
|
71
|
+
case gate.data_type
|
72
|
+
when :boolean, :integer
|
73
|
+
write key(feature, gate), thing.value.to_s
|
74
|
+
when :set
|
75
|
+
set_add key(feature, gate), thing.value.to_s
|
76
|
+
else
|
77
|
+
raise "#{gate} is not supported by this adapter yet"
|
78
|
+
end
|
79
|
+
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
# Public
|
84
|
+
def disable(feature, gate, thing)
|
85
|
+
case gate.data_type
|
86
|
+
when :boolean
|
87
|
+
clear(feature)
|
88
|
+
when :integer
|
89
|
+
write key(feature, gate), thing.value.to_s
|
90
|
+
when :set
|
91
|
+
set_delete key(feature, gate), thing.value.to_s
|
92
|
+
else
|
93
|
+
raise "#{gate} is not supported by this adapter yet"
|
94
|
+
end
|
95
|
+
|
96
|
+
true
|
97
|
+
end
|
98
|
+
|
99
|
+
# Public
|
100
|
+
def inspect
|
101
|
+
attributes = [
|
102
|
+
"name=#{@name.inspect}",
|
103
|
+
"path=#{@path.inspect}",
|
104
|
+
"store=#{@store}",
|
105
|
+
]
|
106
|
+
"#<#{self.class.name}:#{object_id} #{attributes.join(', ')}>"
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# Private
|
112
|
+
def key(feature, gate)
|
113
|
+
"#{feature.key}/#{gate.key}"
|
114
|
+
end
|
115
|
+
|
116
|
+
# Private
|
117
|
+
def read(key)
|
118
|
+
@store.transaction do
|
119
|
+
@store[key.to_s]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Private
|
124
|
+
def write(key, value)
|
125
|
+
@store.transaction do
|
126
|
+
@store[key.to_s] = value.to_s
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Private
|
131
|
+
def delete(key)
|
132
|
+
@store.transaction do
|
133
|
+
@store.delete(key.to_s)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Private
|
138
|
+
def set_add(key, value)
|
139
|
+
set_members(key) do |members|
|
140
|
+
members.add(value.to_s)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Private
|
145
|
+
def set_delete(key, value)
|
146
|
+
set_members(key) do |members|
|
147
|
+
members.delete(value.to_s)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Private
|
152
|
+
def set_members(key)
|
153
|
+
key = key.to_s
|
154
|
+
@store.transaction do
|
155
|
+
@store[key] ||= Set.new
|
156
|
+
|
157
|
+
if block_given?
|
158
|
+
yield @store[key]
|
159
|
+
else
|
160
|
+
@store[key]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
data/lib/flipper/dsl.rb
CHANGED
@@ -16,12 +16,12 @@ module Flipper
|
|
16
16
|
# options - The Hash of options.
|
17
17
|
# :instrumenter - What should be used to instrument all the things.
|
18
18
|
def initialize(adapter, options = {})
|
19
|
-
@instrumenter = options.fetch(:instrumenter,
|
19
|
+
@instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
|
20
20
|
|
21
|
-
instrumented =
|
21
|
+
instrumented = Adapters::Instrumented.new(adapter, {
|
22
22
|
:instrumenter => @instrumenter,
|
23
23
|
})
|
24
|
-
memoized =
|
24
|
+
memoized = Adapters::Memoizable.new(instrumented)
|
25
25
|
@adapter = memoized
|
26
26
|
|
27
27
|
@memoized_features = {}
|
data/lib/flipper/feature.rb
CHANGED
@@ -35,14 +35,14 @@ module Flipper
|
|
35
35
|
def initialize(name, adapter, options = {})
|
36
36
|
@name = name
|
37
37
|
@key = name.to_s
|
38
|
-
@instrumenter = options.fetch(:instrumenter,
|
38
|
+
@instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
|
39
39
|
@adapter = adapter
|
40
40
|
end
|
41
41
|
|
42
42
|
# Public: Enable this feature for something.
|
43
43
|
#
|
44
44
|
# Returns the result of Adapter#enable.
|
45
|
-
def enable(thing =
|
45
|
+
def enable(thing = true)
|
46
46
|
instrument(:enable, thing) { |payload|
|
47
47
|
adapter.add self
|
48
48
|
|
@@ -56,7 +56,7 @@ module Flipper
|
|
56
56
|
# Public: Disable this feature for something.
|
57
57
|
#
|
58
58
|
# Returns the result of Adapter#disable.
|
59
|
-
def disable(thing =
|
59
|
+
def disable(thing = false)
|
60
60
|
instrument(:disable, thing) { |payload|
|
61
61
|
adapter.add self
|
62
62
|
|
@@ -110,7 +110,7 @@ module Flipper
|
|
110
110
|
#
|
111
111
|
# Returns result of enable.
|
112
112
|
def enable_group(group)
|
113
|
-
enable
|
113
|
+
enable Types::Group.wrap(group)
|
114
114
|
end
|
115
115
|
|
116
116
|
# Public: Enables a feature a percentage of time.
|
@@ -150,7 +150,7 @@ module Flipper
|
|
150
150
|
#
|
151
151
|
# Returns result of disable.
|
152
152
|
def disable_group(group)
|
153
|
-
disable
|
153
|
+
disable Types::Group.wrap(group)
|
154
154
|
end
|
155
155
|
|
156
156
|
# Public: Disables a feature a percentage of time.
|
@@ -176,10 +176,12 @@ module Flipper
|
|
176
176
|
# Public: Returns state for feature (:on, :off, or :conditional).
|
177
177
|
def state
|
178
178
|
values = gate_values
|
179
|
+
boolean = gate(:boolean)
|
180
|
+
non_boolean_gates = gates - [boolean]
|
179
181
|
|
180
|
-
if
|
182
|
+
if values.boolean || values.percentage_of_actors == 100 || values.percentage_of_time == 100
|
181
183
|
:on
|
182
|
-
elsif
|
184
|
+
elsif non_boolean_gates.detect { |gate| gate.enabled?(values[gate.key]) }
|
183
185
|
:conditional
|
184
186
|
else
|
185
187
|
:off
|
@@ -202,23 +204,6 @@ module Flipper
|
|
202
204
|
state == :conditional
|
203
205
|
end
|
204
206
|
|
205
|
-
# Public: Human readable description of the enabled-ness of the feature.
|
206
|
-
def description
|
207
|
-
values = gate_values
|
208
|
-
conditional_gates = conditional_gates(values)
|
209
|
-
|
210
|
-
if boolean_gate.enabled?(values.boolean) || !conditional_gates.any?
|
211
|
-
boolean_gate.description(values.boolean).capitalize
|
212
|
-
else
|
213
|
-
fragments = conditional_gates.map { |gate|
|
214
|
-
value = values[gate.key]
|
215
|
-
gate.description(value)
|
216
|
-
}
|
217
|
-
|
218
|
-
"Enabled for #{fragments.join(', ')}"
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
207
|
# Public: Returns the raw gate values stored by the adapter.
|
223
208
|
def gate_values
|
224
209
|
GateValues.new(adapter.get(self))
|
@@ -274,6 +259,35 @@ module Flipper
|
|
274
259
|
gate_values.percentage_of_time
|
275
260
|
end
|
276
261
|
|
262
|
+
# Public: Get the gates that have been enabled for the feature.
|
263
|
+
#
|
264
|
+
# Returns an Array of Flipper::Gate instances.
|
265
|
+
def enabled_gates
|
266
|
+
values = gate_values
|
267
|
+
gates.select { |gate| gate.enabled?(values[gate.key]) }
|
268
|
+
end
|
269
|
+
|
270
|
+
# Public: Get the names of the enabled gates.
|
271
|
+
#
|
272
|
+
# Returns an Array of gate names.
|
273
|
+
def enabled_gate_names
|
274
|
+
enabled_gates.map(&:name)
|
275
|
+
end
|
276
|
+
|
277
|
+
# Public: Get the gates that have not been enabled for the feature.
|
278
|
+
#
|
279
|
+
# Returns an Array of Flipper::Gate instances.
|
280
|
+
def disabled_gates
|
281
|
+
gates - enabled_gates
|
282
|
+
end
|
283
|
+
|
284
|
+
# Public: Get the names of the disabled gates.
|
285
|
+
#
|
286
|
+
# Returns an Array of gate names.
|
287
|
+
def disabled_gate_names
|
288
|
+
disabled_gates.map(&:name)
|
289
|
+
end
|
290
|
+
|
277
291
|
# Public: Returns the string representation of the feature.
|
278
292
|
def to_s
|
279
293
|
name.to_s
|
@@ -289,7 +303,7 @@ module Flipper
|
|
289
303
|
attributes = [
|
290
304
|
"name=#{name.inspect}",
|
291
305
|
"state=#{state.inspect}",
|
292
|
-
"
|
306
|
+
"enabled_gate_names=#{enabled_gate_names.inspect}",
|
293
307
|
"adapter=#{adapter.name.inspect}",
|
294
308
|
]
|
295
309
|
"#<#{self.class.name}:#{object_id} #{attributes.join(', ')}>"
|
@@ -328,24 +342,6 @@ module Flipper
|
|
328
342
|
|
329
343
|
private
|
330
344
|
|
331
|
-
# Private: Get the boolean gate.
|
332
|
-
def boolean_gate
|
333
|
-
@boolean_gate ||= gate(:boolean)
|
334
|
-
end
|
335
|
-
|
336
|
-
# Private: Get all gates except the boolean gate.
|
337
|
-
def non_boolean_gates
|
338
|
-
@non_boolean_gates ||= gates - [boolean_gate]
|
339
|
-
end
|
340
|
-
|
341
|
-
# Private: Get all non boolean gates that are enabled in some way.
|
342
|
-
def conditional_gates(gate_values)
|
343
|
-
non_boolean_gates.select { |gate|
|
344
|
-
value = gate_values[gate.key]
|
345
|
-
gate.enabled?(value)
|
346
|
-
}
|
347
|
-
end
|
348
|
-
|
349
345
|
# Private: Instrument a feature operation.
|
350
346
|
def instrument(operation, thing)
|
351
347
|
payload = {
|
data/lib/flipper/gate.rb
CHANGED
data/lib/flipper/gates/actor.rb
CHANGED
@@ -15,15 +15,6 @@ module Flipper
|
|
15
15
|
:set
|
16
16
|
end
|
17
17
|
|
18
|
-
def description(value)
|
19
|
-
if enabled?(value)
|
20
|
-
actor_ids = value.to_a.sort.map { |id| id.inspect }
|
21
|
-
"actors (#{actor_ids.join(', ')})"
|
22
|
-
else
|
23
|
-
'disabled'
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
18
|
def enabled?(value)
|
28
19
|
!Typecast.to_set(value).empty?
|
29
20
|
end
|
@@ -15,14 +15,6 @@ module Flipper
|
|
15
15
|
:boolean
|
16
16
|
end
|
17
17
|
|
18
|
-
def description(value)
|
19
|
-
if enabled?(value)
|
20
|
-
'Enabled'
|
21
|
-
else
|
22
|
-
'Disabled'
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
18
|
def enabled?(value)
|
27
19
|
Typecast.to_boolean(value)
|
28
20
|
end
|
@@ -35,8 +27,17 @@ module Flipper
|
|
35
27
|
value
|
36
28
|
end
|
37
29
|
|
30
|
+
def wrap(thing)
|
31
|
+
Types::Boolean.wrap(thing)
|
32
|
+
end
|
33
|
+
|
38
34
|
def protects?(thing)
|
39
|
-
thing
|
35
|
+
case thing
|
36
|
+
when Types::Boolean, TrueClass, FalseClass
|
37
|
+
true
|
38
|
+
else
|
39
|
+
false
|
40
|
+
end
|
40
41
|
end
|
41
42
|
end
|
42
43
|
end
|
data/lib/flipper/gates/group.rb
CHANGED
@@ -15,15 +15,6 @@ module Flipper
|
|
15
15
|
:set
|
16
16
|
end
|
17
17
|
|
18
|
-
def description(value)
|
19
|
-
if enabled?(value)
|
20
|
-
group_names = value.to_a.sort.map { |name| name.to_sym.inspect }
|
21
|
-
"groups (#{group_names.join(', ')})"
|
22
|
-
else
|
23
|
-
'disabled'
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
18
|
def enabled?(value)
|
28
19
|
!Typecast.to_set(value).empty?
|
29
20
|
end
|
@@ -47,7 +38,7 @@ module Flipper
|
|
47
38
|
end
|
48
39
|
|
49
40
|
def protects?(thing)
|
50
|
-
thing.is_a?(
|
41
|
+
thing.is_a?(Types::Group)
|
51
42
|
end
|
52
43
|
end
|
53
44
|
end
|
@@ -17,14 +17,6 @@ module Flipper
|
|
17
17
|
:integer
|
18
18
|
end
|
19
19
|
|
20
|
-
def description(value)
|
21
|
-
if enabled?(value)
|
22
|
-
"#{value}% of actors"
|
23
|
-
else
|
24
|
-
'disabled'
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
20
|
def enabled?(value)
|
29
21
|
Typecast.to_integer(value) > 0
|
30
22
|
end
|
@@ -46,7 +38,7 @@ module Flipper
|
|
46
38
|
end
|
47
39
|
|
48
40
|
def protects?(thing)
|
49
|
-
thing.is_a?(
|
41
|
+
thing.is_a?(Types::PercentageOfActors)
|
50
42
|
end
|
51
43
|
end
|
52
44
|
end
|
data/lib/flipper/type.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
module Flipper
|
2
2
|
# Internal: Root class for all flipper types. You should never need to use this.
|
3
3
|
class Type
|
4
|
+
def self.wrap(value_or_instance)
|
5
|
+
return value_or_instance if value_or_instance.is_a?(self)
|
6
|
+
new(value_or_instance)
|
7
|
+
end
|
8
|
+
|
4
9
|
def value
|
5
10
|
raise 'Not implemented'
|
6
11
|
end
|
data/lib/flipper/version.rb
CHANGED
data/lib/flipper.rb
CHANGED
@@ -68,7 +68,7 @@ module Flipper
|
|
68
68
|
# Raises Flipper::GroupNotRegistered if group is not registered.
|
69
69
|
def self.group(name)
|
70
70
|
groups_registry.get(name)
|
71
|
-
rescue
|
71
|
+
rescue Registry::KeyNotFound => e
|
72
72
|
raise GroupNotRegistered, "Group #{e.key.inspect} has not been registered"
|
73
73
|
end
|
74
74
|
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'flipper/adapters/pstore'
|
3
|
+
require 'flipper/spec/shared_adapter_specs'
|
4
|
+
|
5
|
+
describe Flipper::Adapters::PStore do
|
6
|
+
subject { described_class.new(FlipperRoot.join("tmp", "flipper.pstore")) }
|
7
|
+
|
8
|
+
it_should_behave_like 'a flipper adapter'
|
9
|
+
|
10
|
+
it "defaults path to flipper.pstore" do
|
11
|
+
described_class.new.path.should eq("flipper.pstore")
|
12
|
+
end
|
13
|
+
end
|
@@ -97,8 +97,13 @@ describe Flipper::Feature do
|
|
97
97
|
string.should include('Flipper::Feature')
|
98
98
|
string.should include('name=:search')
|
99
99
|
string.should include('state=:off')
|
100
|
-
string.should include('
|
100
|
+
string.should include('enabled_gate_names=[]')
|
101
101
|
string.should include("adapter=#{subject.adapter.name.inspect}")
|
102
|
+
|
103
|
+
subject.enable
|
104
|
+
string = subject.inspect
|
105
|
+
string.should include('state=:on')
|
106
|
+
string.should include('enabled_gate_names=[:boolean]')
|
102
107
|
end
|
103
108
|
end
|
104
109
|
|
@@ -178,21 +183,21 @@ describe Flipper::Feature do
|
|
178
183
|
end
|
179
184
|
end
|
180
185
|
|
181
|
-
context "
|
186
|
+
context "percentage of time set to 100" do
|
182
187
|
before do
|
183
|
-
subject.
|
188
|
+
subject.enable_percentage_of_time 100
|
184
189
|
end
|
185
190
|
|
186
|
-
it "returns :
|
187
|
-
subject.state.should be(:
|
191
|
+
it "returns :on" do
|
192
|
+
subject.state.should be(:on)
|
188
193
|
end
|
189
194
|
|
190
|
-
it "returns
|
191
|
-
subject.on?.should be(
|
195
|
+
it "returns true for on?" do
|
196
|
+
subject.on?.should be(true)
|
192
197
|
end
|
193
198
|
|
194
|
-
it "returns
|
195
|
-
subject.off?.should be(
|
199
|
+
it "returns false for off?" do
|
200
|
+
subject.off?.should be(false)
|
196
201
|
end
|
197
202
|
|
198
203
|
it "returns false for conditional?" do
|
@@ -200,59 +205,69 @@ describe Flipper::Feature do
|
|
200
205
|
end
|
201
206
|
end
|
202
207
|
|
203
|
-
context "
|
208
|
+
context "percentage of actors set to 100" do
|
204
209
|
before do
|
205
|
-
subject.
|
210
|
+
subject.enable_percentage_of_actors 100
|
206
211
|
end
|
207
212
|
|
208
|
-
it "returns :
|
209
|
-
subject.state.should be(:
|
213
|
+
it "returns :on" do
|
214
|
+
subject.state.should be(:on)
|
210
215
|
end
|
211
216
|
|
212
|
-
it "returns
|
213
|
-
subject.on?.should be(
|
217
|
+
it "returns true for on?" do
|
218
|
+
subject.on?.should be(true)
|
214
219
|
end
|
215
220
|
|
216
221
|
it "returns false for off?" do
|
217
222
|
subject.off?.should be(false)
|
218
223
|
end
|
219
224
|
|
220
|
-
it "returns
|
221
|
-
subject.conditional?.should be(
|
225
|
+
it "returns false for conditional?" do
|
226
|
+
subject.conditional?.should be(false)
|
222
227
|
end
|
223
228
|
end
|
224
|
-
end
|
225
229
|
|
226
|
-
|
227
|
-
context "fully on" do
|
230
|
+
context "fully off" do
|
228
231
|
before do
|
229
|
-
subject.
|
232
|
+
subject.disable
|
230
233
|
end
|
231
234
|
|
232
|
-
it "returns
|
233
|
-
subject.
|
235
|
+
it "returns :off" do
|
236
|
+
subject.state.should be(:off)
|
234
237
|
end
|
235
|
-
end
|
236
238
|
|
237
|
-
|
238
|
-
|
239
|
-
|
239
|
+
it "returns false for on?" do
|
240
|
+
subject.on?.should be(false)
|
241
|
+
end
|
242
|
+
|
243
|
+
it "returns true for off?" do
|
244
|
+
subject.off?.should be(true)
|
240
245
|
end
|
241
246
|
|
242
|
-
it "returns
|
243
|
-
subject.
|
247
|
+
it "returns false for conditional?" do
|
248
|
+
subject.conditional?.should be(false)
|
244
249
|
end
|
245
250
|
end
|
246
251
|
|
247
252
|
context "partially on" do
|
248
253
|
before do
|
249
|
-
actor = Struct.new(:flipper_id).new(5)
|
250
254
|
subject.enable Flipper::Types::PercentageOfTime.new(5)
|
251
|
-
subject.enable actor
|
252
255
|
end
|
253
256
|
|
254
|
-
it "returns
|
255
|
-
subject.
|
257
|
+
it "returns :conditional" do
|
258
|
+
subject.state.should be(:conditional)
|
259
|
+
end
|
260
|
+
|
261
|
+
it "returns false for on?" do
|
262
|
+
subject.on?.should be(false)
|
263
|
+
end
|
264
|
+
|
265
|
+
it "returns false for off?" do
|
266
|
+
subject.off?.should be(false)
|
267
|
+
end
|
268
|
+
|
269
|
+
it "returns true for conditional?" do
|
270
|
+
subject.conditional?.should be(true)
|
256
271
|
end
|
257
272
|
end
|
258
273
|
end
|
@@ -623,4 +638,37 @@ describe Flipper::Feature do
|
|
623
638
|
end
|
624
639
|
end
|
625
640
|
end
|
641
|
+
|
642
|
+
describe "#enabled/disabled_gates" do
|
643
|
+
before do
|
644
|
+
subject.enable_percentage_of_time 5
|
645
|
+
subject.enable_percentage_of_actors 5
|
646
|
+
end
|
647
|
+
|
648
|
+
it "can return enabled gates" do
|
649
|
+
subject.enabled_gates.map(&:name).to_set.should eq(Set[
|
650
|
+
:percentage_of_actors,
|
651
|
+
:percentage_of_time,
|
652
|
+
])
|
653
|
+
|
654
|
+
subject.enabled_gate_names.to_set.should eq(Set[
|
655
|
+
:percentage_of_actors,
|
656
|
+
:percentage_of_time,
|
657
|
+
])
|
658
|
+
end
|
659
|
+
|
660
|
+
it "can return disabled gates" do
|
661
|
+
subject.disabled_gates.map(&:name).to_set.should eq(Set[
|
662
|
+
:actor,
|
663
|
+
:boolean,
|
664
|
+
:group,
|
665
|
+
])
|
666
|
+
|
667
|
+
subject.disabled_gate_names.to_set.should eq(Set[
|
668
|
+
:actor,
|
669
|
+
:boolean,
|
670
|
+
:group,
|
671
|
+
])
|
672
|
+
end
|
673
|
+
end
|
626
674
|
end
|
@@ -6,19 +6,4 @@ describe Flipper::Gates::Actor do
|
|
6
6
|
subject {
|
7
7
|
described_class.new
|
8
8
|
}
|
9
|
-
|
10
|
-
describe "#description" do
|
11
|
-
context "with actors in set" do
|
12
|
-
it "returns text" do
|
13
|
-
values = Set['bacon', 'ham']
|
14
|
-
subject.description(values).should eq('actors ("bacon", "ham")')
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
context "with no actors in set" do
|
19
|
-
it "returns disabled" do
|
20
|
-
subject.description(Set.new).should eq('disabled')
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
9
|
end
|
@@ -7,20 +7,6 @@ describe Flipper::Gates::Boolean do
|
|
7
7
|
described_class.new
|
8
8
|
}
|
9
9
|
|
10
|
-
describe "#description" do
|
11
|
-
context "for enabled" do
|
12
|
-
it "returns Enabled" do
|
13
|
-
subject.description(true).should eq('Enabled')
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
context "for disabled" do
|
18
|
-
it "returns Disabled" do
|
19
|
-
subject.description(false).should eq('Disabled')
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
10
|
describe "#enabled?" do
|
25
11
|
context "for true value" do
|
26
12
|
it "returns true" do
|
@@ -48,4 +34,34 @@ describe Flipper::Gates::Boolean do
|
|
48
34
|
end
|
49
35
|
end
|
50
36
|
end
|
37
|
+
|
38
|
+
describe "#protects?" do
|
39
|
+
it "returns true for boolean type" do
|
40
|
+
subject.protects?(Flipper::Types::Boolean.new(true)).should be(true)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns true for true" do
|
44
|
+
subject.protects?(true).should be(true)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "returns true for false" do
|
48
|
+
subject.protects?(false).should be(true)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#wrap" do
|
53
|
+
it "returns boolean type for boolean type" do
|
54
|
+
subject.wrap(Flipper::Types::Boolean.new(true)).should be_instance_of(Flipper::Types::Boolean)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "returns boolean type for true" do
|
58
|
+
subject.wrap(true).should be_instance_of(Flipper::Types::Boolean)
|
59
|
+
subject.wrap(true).value.should be(true)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "returns boolean type for true" do
|
63
|
+
subject.wrap(false).should be_instance_of(Flipper::Types::Boolean)
|
64
|
+
subject.wrap(false).value.should be(false)
|
65
|
+
end
|
66
|
+
end
|
51
67
|
end
|
@@ -7,21 +7,6 @@ describe Flipper::Gates::Group do
|
|
7
7
|
described_class.new
|
8
8
|
}
|
9
9
|
|
10
|
-
describe "#description" do
|
11
|
-
context "with groups in set" do
|
12
|
-
it "returns text" do
|
13
|
-
values = Set['bacon', 'ham']
|
14
|
-
subject.description(values).should eq('groups (:bacon, :ham)')
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
context "with no groups in set" do
|
19
|
-
it "returns disabled" do
|
20
|
-
subject.description(Set.new).should eq('disabled')
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
10
|
describe "#open?" do
|
26
11
|
context "with a group in adapter, but not registered" do
|
27
12
|
before do
|
@@ -7,20 +7,6 @@ describe Flipper::Gates::PercentageOfActors do
|
|
7
7
|
described_class.new
|
8
8
|
}
|
9
9
|
|
10
|
-
describe "#description" do
|
11
|
-
context "when enabled" do
|
12
|
-
it "returns text" do
|
13
|
-
subject.description(22).should eq('22% of actors')
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
context "when disabled" do
|
18
|
-
it "returns disabled" do
|
19
|
-
subject.description(0).should eq('disabled')
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
10
|
describe "#open?" do
|
25
11
|
context "when compared against two features" do
|
26
12
|
let(:percentage) { 0.05 }
|
@@ -6,18 +6,4 @@ describe Flipper::Gates::PercentageOfTime do
|
|
6
6
|
subject {
|
7
7
|
described_class.new
|
8
8
|
}
|
9
|
-
|
10
|
-
describe "#description" do
|
11
|
-
context "when enabled" do
|
12
|
-
it "returns text" do
|
13
|
-
subject.description(22).should eq('22% of the time')
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
context "when disabled" do
|
18
|
-
it "returns disabled" do
|
19
|
-
subject.description(0).should eq('disabled')
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
9
|
end
|
data/spec/helper.rb
CHANGED
@@ -3,9 +3,9 @@ $:.unshift(File.expand_path('../../lib', __FILE__))
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'logger'
|
5
5
|
|
6
|
-
|
7
|
-
lib_path =
|
8
|
-
log_path =
|
6
|
+
FlipperRoot = Pathname(__FILE__).dirname.join('..').expand_path
|
7
|
+
lib_path = FlipperRoot.join('lib')
|
8
|
+
log_path = FlipperRoot.join('log')
|
9
9
|
log_path.mkpath
|
10
10
|
|
11
11
|
require 'rubygems'
|
@@ -15,14 +15,9 @@ Bundler.setup(:default)
|
|
15
15
|
|
16
16
|
require 'flipper'
|
17
17
|
|
18
|
-
Dir[
|
18
|
+
Dir[FlipperRoot.join("spec/support/**/*.rb")].each { |f| require f }
|
19
19
|
|
20
20
|
RSpec.configure do |config|
|
21
|
-
config.filter_run :focus => true
|
22
|
-
config.alias_example_to :fit, :focused => true
|
23
|
-
config.alias_example_to :xit, :pending => true
|
24
|
-
config.run_all_when_everything_filtered = true
|
25
|
-
|
26
21
|
config.before(:each) do
|
27
22
|
Flipper.unregister_groups
|
28
23
|
end
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flipper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.0.
|
4
|
+
version: 0.7.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: 2015-04-
|
11
|
+
date: 2015-04-24 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
13
|
+
description:
|
14
14
|
email:
|
15
15
|
- nunemaker@gmail.com
|
16
16
|
executables: []
|
@@ -42,6 +42,7 @@ files:
|
|
42
42
|
- lib/flipper/adapters/memoizable.rb
|
43
43
|
- lib/flipper/adapters/memory.rb
|
44
44
|
- lib/flipper/adapters/operation_logger.rb
|
45
|
+
- lib/flipper/adapters/pstore.rb
|
45
46
|
- lib/flipper/decorator.rb
|
46
47
|
- lib/flipper/dsl.rb
|
47
48
|
- lib/flipper/errors.rb
|
@@ -81,6 +82,7 @@ files:
|
|
81
82
|
- spec/flipper/adapters/memoizable_spec.rb
|
82
83
|
- spec/flipper/adapters/memory_spec.rb
|
83
84
|
- spec/flipper/adapters/operation_logger_spec.rb
|
85
|
+
- spec/flipper/adapters/pstore_spec.rb
|
84
86
|
- spec/flipper/dsl_spec.rb
|
85
87
|
- spec/flipper/feature_spec.rb
|
86
88
|
- spec/flipper/gate_spec.rb
|
@@ -109,7 +111,8 @@ files:
|
|
109
111
|
- spec/integration_spec.rb
|
110
112
|
- spec/support/fake_udp_socket.rb
|
111
113
|
homepage: http://jnunemaker.github.com/flipper
|
112
|
-
licenses:
|
114
|
+
licenses:
|
115
|
+
- MIT
|
113
116
|
metadata: {}
|
114
117
|
post_install_message:
|
115
118
|
rdoc_options: []
|
@@ -136,6 +139,7 @@ test_files:
|
|
136
139
|
- spec/flipper/adapters/memoizable_spec.rb
|
137
140
|
- spec/flipper/adapters/memory_spec.rb
|
138
141
|
- spec/flipper/adapters/operation_logger_spec.rb
|
142
|
+
- spec/flipper/adapters/pstore_spec.rb
|
139
143
|
- spec/flipper/dsl_spec.rb
|
140
144
|
- spec/flipper/feature_spec.rb
|
141
145
|
- spec/flipper/gate_spec.rb
|