flipper 0.20.2 → 0.21.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +9 -1
- data/Changelog.md +59 -0
- data/Gemfile +1 -0
- data/README.md +103 -47
- data/docs/Adapters.md +9 -9
- data/docs/Caveats.md +2 -2
- data/docs/Gates.md +74 -74
- data/docs/Optimization.md +70 -47
- data/docs/http/README.md +12 -11
- data/docs/images/banner.jpg +0 -0
- data/docs/read-only/README.md +8 -5
- data/examples/basic.rb +1 -12
- data/examples/configuring_default.rb +2 -5
- data/examples/dsl.rb +13 -24
- data/examples/enabled_for_actor.rb +8 -15
- data/examples/group.rb +3 -6
- data/examples/group_dynamic_lookup.rb +5 -19
- data/examples/group_with_members.rb +4 -14
- data/examples/importing.rb +1 -1
- data/examples/individual_actor.rb +2 -5
- data/examples/instrumentation.rb +1 -2
- data/examples/memoizing.rb +3 -7
- data/examples/percentage_of_actors.rb +6 -16
- data/examples/percentage_of_actors_enabled_check.rb +7 -10
- data/examples/percentage_of_actors_group.rb +5 -18
- data/examples/percentage_of_time.rb +3 -6
- data/lib/flipper.rb +4 -1
- data/lib/flipper/adapters/memory.rb +20 -94
- data/lib/flipper/adapters/pstore.rb +4 -0
- data/lib/flipper/configuration.rb +33 -7
- data/lib/flipper/errors.rb +2 -3
- data/lib/flipper/identifier.rb +17 -0
- data/lib/flipper/middleware/memoizer.rb +29 -14
- data/lib/flipper/railtie.rb +38 -0
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/adapters/memory_spec.rb +21 -1
- data/spec/flipper/configuration_spec.rb +20 -2
- data/spec/flipper/identifier_spec.rb +14 -0
- data/spec/flipper/middleware/memoizer_spec.rb +95 -35
- data/spec/flipper/middleware/setup_env_spec.rb +0 -16
- data/spec/flipper/railtie_spec.rb +69 -0
- data/spec/flipper_spec.rb +0 -1
- data/spec/helper.rb +2 -2
- data/spec/support/spec_helpers.rb +20 -0
- data/test/test_helper.rb +1 -0
- metadata +9 -3
- data/examples/example_setup.rb +0 -8
data/examples/importing.rb
CHANGED
@@ -1,10 +1,7 @@
|
|
1
|
-
require
|
2
|
-
|
1
|
+
require 'bundler/setup'
|
3
2
|
require 'flipper'
|
4
3
|
|
5
|
-
|
6
|
-
flipper = Flipper.new(adapter)
|
7
|
-
stats = flipper[:stats]
|
4
|
+
stats = Flipper[:stats]
|
8
5
|
|
9
6
|
# Some class that represents what will be trying to do something
|
10
7
|
class User
|
data/examples/instrumentation.rb
CHANGED
data/examples/memoizing.rb
CHANGED
@@ -1,16 +1,12 @@
|
|
1
|
-
require
|
2
|
-
|
1
|
+
require 'bundler/setup'
|
3
2
|
require 'flipper'
|
4
3
|
require 'flipper/adapters/operation_logger'
|
5
4
|
require 'flipper/instrumentation/log_subscriber'
|
6
5
|
|
7
6
|
Flipper.configure do |config|
|
8
|
-
config.
|
7
|
+
config.adapter do
|
9
8
|
# pick an adapter, this uses memory, any will do
|
10
|
-
|
11
|
-
|
12
|
-
# pass adapter to handy DSL instance
|
13
|
-
Flipper.new(adapter)
|
9
|
+
Flipper::Adapters::OperationLogger.new(Flipper::Adapters::Memory.new)
|
14
10
|
end
|
15
11
|
end
|
16
12
|
|
@@ -1,21 +1,11 @@
|
|
1
|
-
require
|
2
|
-
|
1
|
+
require 'bundler/setup'
|
3
2
|
require 'flipper'
|
4
3
|
|
5
|
-
|
6
|
-
flipper = Flipper.new(adapter)
|
7
|
-
stats = flipper[:stats]
|
4
|
+
stats = Flipper[:stats]
|
8
5
|
|
9
6
|
# Some class that represents what will be trying to do something
|
10
|
-
class User
|
11
|
-
|
12
|
-
|
13
|
-
def initialize(id)
|
14
|
-
@id = id
|
15
|
-
end
|
16
|
-
|
17
|
-
# Must respond to flipper_id
|
18
|
-
alias_method :flipper_id, :id
|
7
|
+
class User < Struct.new(:id)
|
8
|
+
include Flipper::Identifier
|
19
9
|
end
|
20
10
|
|
21
11
|
total = 100_000
|
@@ -24,10 +14,10 @@ total = 100_000
|
|
24
14
|
users = (1..total).map { |n| User.new(n) }
|
25
15
|
|
26
16
|
perform_test = lambda { |number|
|
27
|
-
|
17
|
+
Flipper.enable_percentage_of_actors :stats, number
|
28
18
|
|
29
19
|
enabled = users.map { |user|
|
30
|
-
|
20
|
+
Flipper.enabled?(:stats, user) ? true : nil
|
31
21
|
}.compact
|
32
22
|
|
33
23
|
actual = (enabled.size / total.to_f * 100).round(3)
|
@@ -1,10 +1,6 @@
|
|
1
|
-
require
|
2
|
-
|
1
|
+
require 'bundler/setup'
|
3
2
|
require 'flipper'
|
4
3
|
|
5
|
-
adapter = Flipper::Adapters::Memory.new
|
6
|
-
flipper = Flipper.new(adapter)
|
7
|
-
|
8
4
|
# Some class that represents what will be trying to do something
|
9
5
|
class User
|
10
6
|
attr_reader :id
|
@@ -18,15 +14,16 @@ class User
|
|
18
14
|
end
|
19
15
|
|
20
16
|
# checking a bunch
|
21
|
-
gate = Flipper::Gates::PercentageOfActors.new
|
22
|
-
feature_name = "data_migration"
|
23
|
-
percentage_enabled = 10
|
24
17
|
total = 20_000
|
25
18
|
enabled = []
|
19
|
+
percentage_enabled = 10
|
20
|
+
|
21
|
+
feature = Flipper[:data_migration]
|
22
|
+
feature.enable_percentage_of_actors 10
|
26
23
|
|
27
24
|
(1..total).each do |id|
|
28
25
|
user = User.new(id)
|
29
|
-
if
|
26
|
+
if feature.enabled? user
|
30
27
|
enabled << user
|
31
28
|
end
|
32
29
|
end
|
@@ -35,4 +32,4 @@ p actual: enabled.size, expected: total * (percentage_enabled * 0.01)
|
|
35
32
|
|
36
33
|
# checking one
|
37
34
|
user = User.new(1)
|
38
|
-
p user_1_enabled:
|
35
|
+
p user_1_enabled: feature.enabled?(user)
|
@@ -3,25 +3,12 @@
|
|
3
3
|
# feature for actors in a particular location or on a particular plan, but only
|
4
4
|
# for a percentage of them. The percentage is a constant, but could easily be
|
5
5
|
# plucked from memcached, redis, mysql or whatever.
|
6
|
-
require
|
6
|
+
require 'bundler/setup'
|
7
7
|
require 'flipper'
|
8
8
|
|
9
|
-
adapter = Flipper::Adapters::Memory.new
|
10
|
-
flipper = Flipper.new(adapter)
|
11
|
-
stats = flipper[:stats]
|
12
|
-
|
13
9
|
# Some class that represents what will be trying to do something
|
14
|
-
class User
|
15
|
-
|
16
|
-
|
17
|
-
def initialize(id)
|
18
|
-
@id = id
|
19
|
-
end
|
20
|
-
|
21
|
-
# Must respond to flipper_id
|
22
|
-
def flipper_id
|
23
|
-
"User;#{@id}"
|
24
|
-
end
|
10
|
+
class User < Struct.new(:id)
|
11
|
+
include Flipper::Identifier
|
25
12
|
end
|
26
13
|
|
27
14
|
PERCENTAGE = 50
|
@@ -34,13 +21,13 @@ Flipper.register(:experimental) do |actor|
|
|
34
21
|
end
|
35
22
|
|
36
23
|
# enable the experimental group
|
37
|
-
|
24
|
+
Flipper.enable_group :stats, :experimental
|
38
25
|
|
39
26
|
# create a bunch of fake users and see how many are enabled
|
40
27
|
total = 10_000
|
41
28
|
users = (1..total).map { |n| User.new(n) }
|
42
29
|
enabled = users.map { |user|
|
43
|
-
|
30
|
+
Flipper.enabled?(:stats, user) ? true : nil
|
44
31
|
}.compact
|
45
32
|
|
46
33
|
# show the results
|
@@ -1,13 +1,10 @@
|
|
1
|
-
require
|
2
|
-
|
1
|
+
require 'bundler/setup'
|
3
2
|
require 'flipper'
|
4
3
|
|
5
|
-
|
6
|
-
flipper = Flipper.new(adapter)
|
7
|
-
logging = flipper[:logging]
|
4
|
+
logging = Flipper[:logging]
|
8
5
|
|
9
6
|
perform_test = lambda do |number|
|
10
|
-
logging.
|
7
|
+
logging.enable_percentage_of_time number
|
11
8
|
|
12
9
|
total = 100_000
|
13
10
|
enabled = []
|
data/lib/flipper.rb
CHANGED
@@ -16,7 +16,7 @@ module Flipper
|
|
16
16
|
# Public: Configure flipper.
|
17
17
|
#
|
18
18
|
# Flipper.configure do |config|
|
19
|
-
# config.
|
19
|
+
# config.adapter { ... }
|
20
20
|
# end
|
21
21
|
#
|
22
22
|
# Yields Flipper::Configuration instance.
|
@@ -152,6 +152,7 @@ require 'flipper/feature'
|
|
152
152
|
require 'flipper/gate'
|
153
153
|
require 'flipper/instrumenters/memory'
|
154
154
|
require 'flipper/instrumenters/noop'
|
155
|
+
require 'flipper/identifier'
|
155
156
|
require 'flipper/middleware/memoizer'
|
156
157
|
require 'flipper/middleware/setup_env'
|
157
158
|
require 'flipper/registry'
|
@@ -163,3 +164,5 @@ require 'flipper/types/percentage'
|
|
163
164
|
require 'flipper/types/percentage_of_actors'
|
164
165
|
require 'flipper/types/percentage_of_time'
|
165
166
|
require 'flipper/typecast'
|
167
|
+
|
168
|
+
require "flipper/railtie" if defined?(Rails::Railtie)
|
@@ -20,58 +20,57 @@ module Flipper
|
|
20
20
|
|
21
21
|
# Public: The set of known features.
|
22
22
|
def features
|
23
|
-
|
23
|
+
@source.keys.to_set
|
24
24
|
end
|
25
25
|
|
26
26
|
# Public: Adds a feature to the set of known features.
|
27
27
|
def add(feature)
|
28
|
-
|
28
|
+
@source[feature.key] ||= default_config
|
29
29
|
true
|
30
30
|
end
|
31
31
|
|
32
32
|
# Public: Removes a feature from the set of known features and clears
|
33
33
|
# all the values for the feature.
|
34
34
|
def remove(feature)
|
35
|
-
|
36
|
-
clear(feature)
|
35
|
+
@source.delete(feature.key)
|
37
36
|
true
|
38
37
|
end
|
39
38
|
|
40
39
|
# Public: Clears all the gate values for a feature.
|
41
40
|
def clear(feature)
|
42
|
-
feature.
|
43
|
-
delete key(feature, gate)
|
44
|
-
end
|
41
|
+
@source[feature.key] = default_config
|
45
42
|
true
|
46
43
|
end
|
47
44
|
|
48
45
|
# Public
|
49
46
|
def get(feature)
|
50
|
-
|
47
|
+
@source[feature.key] || default_config
|
51
48
|
end
|
52
49
|
|
53
50
|
def get_multi(features)
|
54
|
-
|
51
|
+
result = {}
|
52
|
+
features.each do |feature|
|
53
|
+
result[feature.key] = @source[feature.key] || default_config
|
54
|
+
end
|
55
|
+
result
|
55
56
|
end
|
56
57
|
|
57
58
|
def get_all
|
58
|
-
|
59
|
-
Flipper::Feature.new(key, self)
|
60
|
-
end
|
61
|
-
|
62
|
-
read_many_features(features)
|
59
|
+
@source
|
63
60
|
end
|
64
61
|
|
65
62
|
# Public
|
66
63
|
def enable(feature, gate, thing)
|
64
|
+
@source[feature.key] ||= default_config
|
65
|
+
|
67
66
|
case gate.data_type
|
68
67
|
when :boolean
|
69
68
|
clear(feature)
|
70
|
-
|
69
|
+
@source[feature.key][gate.key] = thing.value.to_s
|
71
70
|
when :integer
|
72
|
-
|
71
|
+
@source[feature.key][gate.key] = thing.value.to_s
|
73
72
|
when :set
|
74
|
-
|
73
|
+
@source[feature.key][gate.key] << thing.value.to_s
|
75
74
|
else
|
76
75
|
raise "#{gate} is not supported by this adapter yet"
|
77
76
|
end
|
@@ -81,13 +80,15 @@ module Flipper
|
|
81
80
|
|
82
81
|
# Public
|
83
82
|
def disable(feature, gate, thing)
|
83
|
+
@source[feature.key] ||= default_config
|
84
|
+
|
84
85
|
case gate.data_type
|
85
86
|
when :boolean
|
86
87
|
clear(feature)
|
87
88
|
when :integer
|
88
|
-
|
89
|
+
@source[feature.key][gate.key] = thing.value.to_s
|
89
90
|
when :set
|
90
|
-
|
91
|
+
@source[feature.key][gate.key].delete thing.value.to_s
|
91
92
|
else
|
92
93
|
raise "#{gate} is not supported by this adapter yet"
|
93
94
|
end
|
@@ -103,81 +104,6 @@ module Flipper
|
|
103
104
|
]
|
104
105
|
"#<#{self.class.name}:#{object_id} #{attributes.join(', ')}>"
|
105
106
|
end
|
106
|
-
|
107
|
-
private
|
108
|
-
|
109
|
-
def read_feature_keys
|
110
|
-
set_members(FeaturesKey)
|
111
|
-
end
|
112
|
-
|
113
|
-
# Private
|
114
|
-
def key(feature, gate)
|
115
|
-
"feature/#{feature.key}/#{gate.key}"
|
116
|
-
end
|
117
|
-
|
118
|
-
def read_many_features(features)
|
119
|
-
result = {}
|
120
|
-
features.each do |feature|
|
121
|
-
result[feature.key] = read_feature(feature)
|
122
|
-
end
|
123
|
-
result
|
124
|
-
end
|
125
|
-
|
126
|
-
def read_feature(feature)
|
127
|
-
result = {}
|
128
|
-
|
129
|
-
feature.gates.each do |gate|
|
130
|
-
result[gate.key] =
|
131
|
-
case gate.data_type
|
132
|
-
when :boolean, :integer
|
133
|
-
read key(feature, gate)
|
134
|
-
when :set
|
135
|
-
set_members key(feature, gate)
|
136
|
-
else
|
137
|
-
raise "#{gate} is not supported by this adapter yet"
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
result
|
142
|
-
end
|
143
|
-
|
144
|
-
# Private
|
145
|
-
def read(key)
|
146
|
-
@source[key.to_s]
|
147
|
-
end
|
148
|
-
|
149
|
-
# Private
|
150
|
-
def write(key, value)
|
151
|
-
@source[key.to_s] = value.to_s
|
152
|
-
end
|
153
|
-
|
154
|
-
# Private
|
155
|
-
def delete(key)
|
156
|
-
@source.delete(key.to_s)
|
157
|
-
end
|
158
|
-
|
159
|
-
# Private
|
160
|
-
def set_add(key, value)
|
161
|
-
ensure_set_initialized(key)
|
162
|
-
@source[key.to_s].add(value.to_s)
|
163
|
-
end
|
164
|
-
|
165
|
-
# Private
|
166
|
-
def set_delete(key, value)
|
167
|
-
ensure_set_initialized(key)
|
168
|
-
@source[key.to_s].delete(value.to_s)
|
169
|
-
end
|
170
|
-
|
171
|
-
# Private
|
172
|
-
def set_members(key)
|
173
|
-
ensure_set_initialized(key)
|
174
|
-
@source[key.to_s]
|
175
|
-
end
|
176
|
-
|
177
|
-
# Private
|
178
|
-
def ensure_set_initialized(key)
|
179
|
-
@source[key.to_s] ||= Set.new
|
180
|
-
end
|
181
107
|
end
|
182
108
|
end
|
183
109
|
end
|
@@ -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.
|