flipper 0.20.1 → 0.21.0.rc2
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 +66 -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/http.rb +32 -28
- data/lib/flipper/adapters/memory.rb +20 -94
- data/lib/flipper/adapters/pstore.rb +4 -0
- data/lib/flipper/adapters/sync/interval_synchronizer.rb +1 -1
- 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/http_spec.rb +74 -8
- 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/support/spec_helpers.rb +20 -0
- data/test/test_helper.rb +1 -0
- metadata +12 -5
- data/examples/example_setup.rb +0 -8
data/docs/Optimization.md
CHANGED
@@ -1,52 +1,63 @@
|
|
1
1
|
# Optimization
|
2
2
|
|
3
|
-
##
|
3
|
+
## Memoization
|
4
4
|
|
5
|
-
|
5
|
+
By default, Flipper will preload and memoize all features to ensure one adapter call per request. This means no matter how many times you check features, Flipper will only make one network request to Postgres, MySQL, Redis, Mongo or whatever adapter you are using for the length of the request.
|
6
6
|
|
7
|
-
|
7
|
+
### Preloading
|
8
|
+
|
9
|
+
Flipper will preload all features before each request by default, which is recommended if you have a limited number of features (< 100?) and they are used on most requests. If you have a lot of features, but only a few are used on most requests, you may want to customize preloading:
|
8
10
|
|
9
11
|
```ruby
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
# config/initializers/flipper.rb
|
13
|
+
Rails.application.configure do
|
14
|
+
# Load specific features that are used on most requests
|
15
|
+
config.flipper.preload = [:stats, :search, :some_feature]
|
16
|
+
|
17
|
+
# Or completely disable preloading
|
18
|
+
config.flipper.preload = false
|
15
19
|
end
|
20
|
+
```
|
21
|
+
|
22
|
+
Features that are not preloaded are still memoized, ensuring one adapter call per feature during a request.
|
23
|
+
|
24
|
+
### Skip memoization
|
25
|
+
|
26
|
+
Prevent preloading and memoization on specific requests by setting `memoize` to a proc that evaluates to false.
|
16
27
|
|
17
|
-
|
18
|
-
|
28
|
+
```ruby
|
29
|
+
# config/initializers/flipper.rb
|
30
|
+
Rails.application.configure do
|
31
|
+
config.flipper.memoize = ->(request) { !request.path.start_with?("/assets") }
|
32
|
+
end
|
19
33
|
```
|
20
34
|
|
21
|
-
|
35
|
+
### Disable memoization
|
22
36
|
|
23
|
-
|
37
|
+
To disable memoization entirely:
|
24
38
|
|
25
39
|
```ruby
|
26
|
-
Rails.
|
27
|
-
|
40
|
+
Rails.application.configure do
|
41
|
+
config.flipper.memoize = false
|
42
|
+
end
|
28
43
|
```
|
29
44
|
|
30
|
-
###
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
# skip preloading and memoizing if path starts with /assets
|
47
|
-
Rails.configuration.middleware.use Flipper::Middleware::Memoizer,
|
48
|
-
unless: ->(request) { request.path.start_with?("/assets") }
|
49
|
-
```
|
45
|
+
### Advanced
|
46
|
+
|
47
|
+
Memoization is implemented as a Rack middleware, which can be used manually in any Ruby app:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
use Flipper::Middleware::Memoizer,
|
51
|
+
preload: true,
|
52
|
+
unless: ->(request) { request.path.start_with?("/assets") }
|
53
|
+
```
|
54
|
+
|
55
|
+
**Also Note**: If you need to customize the instance of Flipper used by the memoizer, you can pass the instance to `SetupEnv`:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
use Flipper::Middleware::SetupEnv, -> { Flipper.new(...) }
|
59
|
+
use Flipper::Middleware::Memoizer
|
60
|
+
```
|
50
61
|
|
51
62
|
## Cache Adapters
|
52
63
|
|
@@ -61,11 +72,15 @@ https://github.com/petergoldstein/dalli
|
|
61
72
|
Example using the Dalli cache adapter with the Memory adapter and a TTL of 600 seconds:
|
62
73
|
|
63
74
|
```ruby
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
75
|
+
Flipper.configure do |config|
|
76
|
+
config.adapter do
|
77
|
+
dalli = Dalli::Client.new('localhost:11211')
|
78
|
+
adapter = Flipper::Adapters::Memory.new
|
79
|
+
Flipper::Adapters::Dalli.new(adapter, dalli, 600)
|
80
|
+
end
|
81
|
+
end
|
68
82
|
```
|
83
|
+
|
69
84
|
### RedisCache
|
70
85
|
|
71
86
|
Applications using [Redis](https://redis.io/) via the [redis-rb](https://github.com/redis/redis-rb) client can take advantage of the RedisCache adapter.
|
@@ -75,12 +90,15 @@ Initialize `RedisCache` with a flipper [adapter](https://github.com/jnunemaker/
|
|
75
90
|
Example using the RedisCache adapter with the Memory adapter and a TTL of 4800 seconds:
|
76
91
|
|
77
92
|
```ruby
|
78
|
-
|
93
|
+
require 'flipper/adapters/redis_cache'
|
79
94
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
95
|
+
Flipper.configure do |config|
|
96
|
+
config.adapter do
|
97
|
+
redis = Redis.new(url: ENV['REDIS_URL'])
|
98
|
+
memory_adapter = Flipper::Adapters::Memory.new
|
99
|
+
Flipper::Adapters::RedisCache.new(memory_adapter, redis, 4800)
|
100
|
+
end
|
101
|
+
end
|
84
102
|
```
|
85
103
|
|
86
104
|
### ActiveSupportCacheStore
|
@@ -105,10 +123,15 @@ Example using the ActiveSupportCacheStore adapter with ActiveSupport's [MemorySt
|
|
105
123
|
require 'active_support/cache'
|
106
124
|
require 'flipper/adapters/active_support_cache_store'
|
107
125
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
126
|
+
Flipper.configure do |config|
|
127
|
+
config.adapter do
|
128
|
+
Flipper::Adapters::ActiveSupportCacheStore.new(
|
129
|
+
Flipper::Adapters::Memory.new,
|
130
|
+
ActiveSupport::Cache::MemoryStore.new # Or Rails.cache,
|
131
|
+
expires_in: 5.minutes
|
132
|
+
)
|
133
|
+
end
|
134
|
+
end
|
112
135
|
```
|
113
136
|
|
114
137
|
Setting `expires_in` is optional and will set an expiration time on Flipper cache keys. If specified, all flipper keys will use this `expires_in` over the `expires_in` passed to your ActiveSupport cache constructor.
|
data/docs/http/README.md
CHANGED
@@ -8,17 +8,18 @@ Initialize the HTTP adapter with a configuration Hash.
|
|
8
8
|
```ruby
|
9
9
|
require 'flipper/adapters/http'
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
11
|
+
Flipper.configure do |config|
|
12
|
+
config.adapter do
|
13
|
+
Flipper::Adapters::Http.new({
|
14
|
+
url: 'http://app.com/mount-point', # required
|
15
|
+
headers: { 'X-Custom-Header' => 'foo' },
|
16
|
+
basic_auth_username: 'user123',
|
17
|
+
basic_auth_password: 'password123'
|
18
|
+
read_timeout: 5,
|
19
|
+
open_timeout: 2,
|
20
|
+
})
|
21
|
+
end
|
22
|
+
end
|
22
23
|
```
|
23
24
|
|
24
25
|
**Required keys**:
|
Binary file
|
data/docs/read-only/README.md
CHANGED
@@ -5,17 +5,20 @@ A [read-only](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adap
|
|
5
5
|
Use this adapter to wrap another adapter and raise an exception for any writes.
|
6
6
|
|
7
7
|
Any attempted write raises `Flipper::Adapters::ReadOnly::WriteAttempted` with message `'write attempted while in read only mode'`
|
8
|
+
|
8
9
|
## Usage
|
10
|
+
|
9
11
|
```ruby
|
10
12
|
# example wrapping memory adapter
|
11
13
|
require 'flipper/adapters/read_only'
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
Flipper.configure do |config|
|
16
|
+
config.adapter do
|
17
|
+
Flipper::Adapters::ReadOnly.new(Flipper::Adapters::Memory.new)
|
18
|
+
end
|
19
|
+
end
|
17
20
|
|
18
21
|
# Enabling a feature
|
19
|
-
>
|
22
|
+
> Flipper[:dashboard_panel].enable
|
20
23
|
=> Flipper::Adapters::ReadOnly::WriteAttempted: write attempted while in read only mode
|
21
24
|
```
|
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!'
|
@@ -1,12 +1,9 @@
|
|
1
|
-
require
|
2
|
-
|
1
|
+
require 'bundler/setup'
|
3
2
|
require 'flipper'
|
4
3
|
|
5
4
|
# sets up default adapter so Flipper works like Flipper::DSL
|
6
5
|
Flipper.configure do |config|
|
7
|
-
config.
|
8
|
-
Flipper.new Flipper::Adapters::Memory.new
|
9
|
-
end
|
6
|
+
config.adapter { Flipper::Adapters::Memory.new }
|
10
7
|
end
|
11
8
|
|
12
9
|
puts Flipper.enabled?(:search) # => false
|
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
|