flipper 0.20.0 → 0.21.0.rc1
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 +57 -0
- data/Changelog.md +37 -1
- data/Gemfile +1 -0
- data/README.md +103 -47
- data/docs/Adapters.md +1 -0
- data/docs/Gates.md +74 -74
- data/docs/Optimization.md +7 -7
- data/docs/images/banner.jpg +0 -0
- data/examples/basic.rb +1 -12
- data/examples/configuring_default.rb +1 -2
- 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 +1 -2
- 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 +1 -0
- data/lib/flipper/adapters/http.rb +32 -28
- data/lib/flipper/adapters/memory.rb +20 -94
- data/lib/flipper/adapters/sync/interval_synchronizer.rb +1 -1
- data/lib/flipper/configuration.rb +6 -6
- data/lib/flipper/errors.rb +2 -3
- data/lib/flipper/identifier.rb +17 -0
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/adapters/http_spec.rb +74 -8
- data/spec/flipper/adapters/memory_spec.rb +21 -1
- data/spec/flipper/configuration_spec.rb +5 -2
- data/spec/flipper/identifier_spec.rb +14 -0
- data/spec/flipper/middleware/memoizer_spec.rb +1 -1
- data/spec/flipper/middleware/setup_env_spec.rb +0 -16
- data/spec/flipper_spec.rb +0 -1
- data/spec/support/spec_helpers.rb +3 -0
- data/test/test_helper.rb +1 -0
- metadata +9 -5
- data/examples/example_setup.rb +0 -8
data/docs/Optimization.md
CHANGED
@@ -7,7 +7,7 @@ One optimization that flipper provides is a memoizing middleware. The memoizing
|
|
7
7
|
You can use the middleware like so for Rails:
|
8
8
|
|
9
9
|
```ruby
|
10
|
-
# setup default instance (perhaps in config/
|
10
|
+
# setup default instance (perhaps in config/initializers/flipper.rb)
|
11
11
|
Flipper.configure do |config|
|
12
12
|
config.default do
|
13
13
|
Flipper.new(...)
|
@@ -15,7 +15,7 @@ Flipper.configure do |config|
|
|
15
15
|
end
|
16
16
|
|
17
17
|
# This assumes you setup a default flipper instance using configure.
|
18
|
-
|
18
|
+
Rails.configuration.middleware.use Flipper::Middleware::Memoizer
|
19
19
|
```
|
20
20
|
|
21
21
|
**Note**: Be sure that the middleware is high enough up in your stack that all feature checks are wrapped.
|
@@ -23,8 +23,8 @@ config.middleware.use Flipper::Middleware::Memoizer
|
|
23
23
|
**Also Note**: If you haven't setup a default instance, you can pass the instance to `SetupEnv` as `Memoizer` uses whatever is setup in the `env`:
|
24
24
|
|
25
25
|
```ruby
|
26
|
-
|
27
|
-
|
26
|
+
Rails.configuration.middleware.use Flipper::Middleware::SetupEnv, -> { Flipper.new(...) }
|
27
|
+
Rails.configuration.middleware.use Flipper::Middleware::Memoizer
|
28
28
|
```
|
29
29
|
|
30
30
|
### Options
|
@@ -33,18 +33,18 @@ The Memoizer middleware also supports a few options. Use either `preload` or `pr
|
|
33
33
|
|
34
34
|
* **`:preload`** - An `Array` of feature names (`Symbol`) to preload for every request. Useful if you have features that are used on every endpoint. `preload` uses `Adapter#get_multi` to attempt to load the features in one network call instead of N+1 network calls.
|
35
35
|
```ruby
|
36
|
-
|
36
|
+
Rails.configuration.middleware.use Flipper::Middleware::Memoizer,
|
37
37
|
preload: [:stats, :search, :some_feature]
|
38
38
|
```
|
39
39
|
* **`:preload_all`** - A Boolean value (default: false) of whether or not all features should be preloaded. Using this results in a `preload_all` call with the result of `Adapter#get_all`. Any subsequent feature checks will be memoized and perform no network calls. I wouldn't recommend using this unless you have few features (< 100?) and nearly all of them are used on every request.
|
40
40
|
```ruby
|
41
|
-
|
41
|
+
Rails.configuration.middleware.use Flipper::Middleware::Memoizer,
|
42
42
|
preload_all: true
|
43
43
|
```
|
44
44
|
* **`:unless`** - A block that prevents preloading and memoization if it evaluates to true.
|
45
45
|
```ruby
|
46
46
|
# skip preloading and memoizing if path starts with /assets
|
47
|
-
|
47
|
+
Rails.configuration.middleware.use Flipper::Middleware::Memoizer,
|
48
48
|
unless: ->(request) { request.path.start_with?("/assets") }
|
49
49
|
```
|
50
50
|
|
Binary file
|
data/examples/basic.rb
CHANGED
@@ -1,17 +1,6 @@
|
|
1
|
-
require
|
2
|
-
|
1
|
+
require 'bundler/setup'
|
3
2
|
require 'flipper'
|
4
3
|
|
5
|
-
Flipper.configure do |config|
|
6
|
-
config.default do
|
7
|
-
# pick an adapter, this uses memory, any will do
|
8
|
-
adapter = Flipper::Adapters::Memory.new
|
9
|
-
|
10
|
-
# pass adapter to handy DSL instance
|
11
|
-
Flipper.new(adapter)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
4
|
# check if search is enabled
|
16
5
|
if Flipper.enabled?(:search)
|
17
6
|
puts 'Search away!'
|
data/examples/dsl.rb
CHANGED
@@ -1,20 +1,9 @@
|
|
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
|
# create a thing with an identifier
|
9
|
-
class Person
|
10
|
-
|
11
|
-
|
12
|
-
def initialize(id)
|
13
|
-
@id = id
|
14
|
-
end
|
15
|
-
|
16
|
-
# Must respond to flipper_id
|
17
|
-
alias_method :flipper_id, :id
|
5
|
+
class Person < Struct.new(:id)
|
6
|
+
include Flipper::Identifier
|
18
7
|
end
|
19
8
|
|
20
9
|
person = Person.new(1)
|
@@ -22,14 +11,14 @@ person = Person.new(1)
|
|
22
11
|
puts "Stats are disabled by default\n\n"
|
23
12
|
|
24
13
|
# is a feature enabled
|
25
|
-
puts "flipper.enabled? :stats: #{
|
14
|
+
puts "flipper.enabled? :stats: #{Flipper.enabled? :stats}"
|
26
15
|
|
27
16
|
# is a feature on or off for a particular person
|
28
|
-
puts "
|
17
|
+
puts "Flipper.enabled? :stats, person: #{Flipper.enabled? :stats, person}"
|
29
18
|
|
30
19
|
# get at a feature
|
31
|
-
puts "\nYou can also get an individual feature like this:\nstats =
|
32
|
-
stats =
|
20
|
+
puts "\nYou can also get an individual feature like this:\nstats = Flipper[:stats]\n\n"
|
21
|
+
stats = Flipper[:stats]
|
33
22
|
|
34
23
|
# is that feature enabled
|
35
24
|
puts "stats.enabled?: #{stats.enabled?}"
|
@@ -39,7 +28,7 @@ puts "stats.enabled? person: #{stats.enabled? person}"
|
|
39
28
|
|
40
29
|
# enable a feature by name
|
41
30
|
puts "\nEnabling stats\n\n"
|
42
|
-
|
31
|
+
Flipper.enable :stats
|
43
32
|
|
44
33
|
# or, you can use the feature to enable
|
45
34
|
stats.enable
|
@@ -49,7 +38,7 @@ puts "stats.enabled? person: #{stats.enabled? person}"
|
|
49
38
|
|
50
39
|
# oh, no, let's turn this baby off
|
51
40
|
puts "\nDisabling stats\n\n"
|
52
|
-
|
41
|
+
Flipper.disable :stats
|
53
42
|
|
54
43
|
# or we can disable using feature obviously
|
55
44
|
stats.disable
|
@@ -59,18 +48,18 @@ puts "stats.enabled? person: #{stats.enabled? person}"
|
|
59
48
|
puts
|
60
49
|
|
61
50
|
# get an instance of the percentage of time type set to 5
|
62
|
-
puts
|
51
|
+
puts Flipper.time(5).inspect
|
63
52
|
|
64
53
|
# get an instance of the percentage of actors type set to 15
|
65
|
-
puts
|
54
|
+
puts Flipper.actors(15).inspect
|
66
55
|
|
67
56
|
# get an instance of an actor using an object that responds to flipper_id
|
68
57
|
responds_to_flipper_id = Struct.new(:flipper_id).new(10)
|
69
|
-
puts
|
58
|
+
puts Flipper.actor(responds_to_flipper_id).inspect
|
70
59
|
|
71
60
|
# get an instance of an actor using an object
|
72
61
|
thing = Struct.new(:flipper_id).new(22)
|
73
|
-
puts
|
62
|
+
puts Flipper.actor(thing).inspect
|
74
63
|
|
75
64
|
# register a top level group
|
76
65
|
admins = Flipper.register(:admins) { |actor|
|
@@ -1,5 +1,4 @@
|
|
1
|
-
require
|
2
|
-
|
1
|
+
require 'bundler/setup'
|
3
2
|
require 'flipper'
|
4
3
|
|
5
4
|
# Some class that represents what will be trying to do something
|
@@ -22,21 +21,15 @@ end
|
|
22
21
|
user1 = User.new(1, true)
|
23
22
|
user2 = User.new(2, false)
|
24
23
|
|
25
|
-
# pick an adapter
|
26
|
-
adapter = Flipper::Adapters::Memory.new
|
27
|
-
|
28
|
-
# get a handy dsl instance
|
29
|
-
flipper = Flipper.new(adapter)
|
30
|
-
|
31
24
|
Flipper.register :admins do |actor|
|
32
25
|
actor.admin?
|
33
26
|
end
|
34
27
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
28
|
+
Flipper.enable :search
|
29
|
+
Flipper.enable_actor :stats, user1
|
30
|
+
Flipper.enable_percentage_of_actors :pro_stats, 50
|
31
|
+
Flipper.enable_group :tweets, :admins
|
32
|
+
Flipper.enable_actor :posts, user2
|
40
33
|
|
41
|
-
pp
|
42
|
-
pp
|
34
|
+
pp Flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name)
|
35
|
+
pp Flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name)
|
data/examples/group.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
|
# Register group
|
10
7
|
Flipper.register(:admins) do |actor|
|
@@ -35,7 +32,7 @@ puts "Stats for admin: #{stats.enabled?(admin)}"
|
|
35
32
|
puts "Stats for non_admin: #{stats.enabled?(non_admin)}"
|
36
33
|
|
37
34
|
puts "\nEnabling Stats for admins...\n\n"
|
38
|
-
stats.
|
35
|
+
stats.enable_group :admins
|
39
36
|
|
40
37
|
puts "Stats for admin: #{stats.enabled?(admin)}"
|
41
38
|
puts "Stats for non_admin: #{stats.enabled?(non_admin)}"
|
@@ -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
|
# Register group
|
10
7
|
Flipper.register(:enabled_team_member) do |actor, context|
|
@@ -15,19 +12,12 @@ Flipper.register(:enabled_team_member) do |actor, context|
|
|
15
12
|
end
|
16
13
|
|
17
14
|
# Some class that represents actor that will be trying to do something
|
18
|
-
class User
|
19
|
-
|
20
|
-
|
21
|
-
def initialize(id)
|
22
|
-
@id = id
|
23
|
-
end
|
24
|
-
|
25
|
-
def flipper_id
|
26
|
-
"User;#{@id}"
|
27
|
-
end
|
15
|
+
class User < Struct.new(:id)
|
16
|
+
include Flipper::Identifier
|
28
17
|
end
|
29
18
|
|
30
19
|
class Team
|
20
|
+
include Flipper::Identifier
|
31
21
|
attr_reader :name
|
32
22
|
|
33
23
|
def self.all
|
@@ -51,10 +41,6 @@ class Team
|
|
51
41
|
def member?(actor)
|
52
42
|
@members.map(&:id).include?(actor.id)
|
53
43
|
end
|
54
|
-
|
55
|
-
def flipper_id
|
56
|
-
"Team:#{@name}"
|
57
|
-
end
|
58
44
|
end
|
59
45
|
|
60
46
|
jnunemaker = User.new("jnunemaker")
|
@@ -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
|
# Register group
|
10
7
|
Flipper.register(:team_actor) do |actor|
|
@@ -12,15 +9,8 @@ Flipper.register(:team_actor) do |actor|
|
|
12
9
|
end
|
13
10
|
|
14
11
|
# Some class that represents actor that will be trying to do something
|
15
|
-
class User
|
16
|
-
|
17
|
-
|
18
|
-
def initialize(id)
|
19
|
-
@id = id
|
20
|
-
end
|
21
|
-
|
22
|
-
# Must respond to flipper_id
|
23
|
-
alias_method :flipper_id, :id
|
12
|
+
class User < Struct.new(:id)
|
13
|
+
include Flipper::Identifier
|
24
14
|
end
|
25
15
|
|
26
16
|
class Team
|
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,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
|