flipper 0.20.3 → 0.22.0
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/.github/workflows/ci.yml +18 -9
- data/Changelog.md +62 -0
- data/Gemfile +2 -0
- data/README.md +104 -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/api/README.md +5 -5
- data/docs/http/README.md +12 -11
- data/docs/images/banner.jpg +0 -0
- data/docs/read-only/README.md +8 -5
- data/examples/api/basic.ru +19 -0
- data/examples/api/custom_memoized.ru +37 -0
- data/examples/api/memoized.ru +43 -0
- 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/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 +30 -15
- data/lib/flipper/railtie.rb +46 -0
- data/lib/flipper/version.rb +1 -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 +12 -3
- 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/api/README.md
CHANGED
@@ -23,7 +23,7 @@ Or install it yourself as:
|
|
23
23
|
```ruby
|
24
24
|
# config/routes.rb
|
25
25
|
YourRailsApp::Application.routes.draw do
|
26
|
-
mount Flipper::Api.app(
|
26
|
+
mount Flipper::Api.app(Flipper) => '/flipper/api'
|
27
27
|
end
|
28
28
|
```
|
29
29
|
|
@@ -34,8 +34,8 @@ There can be more than one router in your application. Make sure if you choose a
|
|
34
34
|
*bad:*
|
35
35
|
```ruby
|
36
36
|
YourRailsApp::Application.routes.draw do
|
37
|
-
mount Flipper::UI.app(
|
38
|
-
mount Flipper::Api.app(
|
37
|
+
mount Flipper::UI.app(Flipper) => '/flipper'
|
38
|
+
mount Flipper::Api.app(Flipper) => '/flipper/api'
|
39
39
|
end
|
40
40
|
```
|
41
41
|
|
@@ -44,8 +44,8 @@ In this case any requests to /flipper\* will be routed to Flipper::UI - includin
|
|
44
44
|
*good:*
|
45
45
|
```ruby
|
46
46
|
YourRailsApp::Application.routes.draw do
|
47
|
-
mount Flipper::Api.app(
|
48
|
-
mount Flipper::UI.app(
|
47
|
+
mount Flipper::Api.app(Flipper) => '/flipper/api'
|
48
|
+
mount Flipper::UI.app(Flipper) => '/flipper'
|
49
49
|
end
|
50
50
|
````
|
51
51
|
For more advanced mounting techniques and for suggestions on how to mount in a non-Rails application, it is recommend that you review the [`Flipper::UI` usage documentation](https://github.com/jnunemaker/flipper/blob/master/docs/ui/README.md#usage) as the same approaches apply to `Flipper::Api`.
|
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
|
```
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#
|
2
|
+
# Usage:
|
3
|
+
# # if you want it to not reload and be really fast
|
4
|
+
# bin/rackup examples/api/basic.ru -p 9999
|
5
|
+
#
|
6
|
+
# # if you want reloading
|
7
|
+
# bin/shotgun examples/api/basic.ru -p 9999
|
8
|
+
#
|
9
|
+
# http://localhost:9999/
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'bundler/setup'
|
13
|
+
require "flipper/api"
|
14
|
+
require "flipper/adapters/pstore"
|
15
|
+
|
16
|
+
# You can uncomment this to get some default data:
|
17
|
+
# Flipper.enable :logging
|
18
|
+
|
19
|
+
run Flipper::Api.app
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#
|
2
|
+
# Usage:
|
3
|
+
# # if you want it to not reload and be really fast
|
4
|
+
# bin/rackup examples/api/custom_memoized.ru -p 9999
|
5
|
+
#
|
6
|
+
# # if you want reloading
|
7
|
+
# bin/shotgun examples/api/custom_memoized.ru -p 9999
|
8
|
+
#
|
9
|
+
# http://localhost:9999/
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'bundler/setup'
|
13
|
+
require "active_support/notifications"
|
14
|
+
require "flipper/api"
|
15
|
+
require "flipper/adapters/pstore"
|
16
|
+
|
17
|
+
adapter = Flipper::Adapters::Instrumented.new(
|
18
|
+
Flipper::Adapters::PStore.new,
|
19
|
+
instrumenter: ActiveSupport::Notifications,
|
20
|
+
)
|
21
|
+
flipper = Flipper.new(adapter)
|
22
|
+
|
23
|
+
ActiveSupport::Notifications.subscribe(/.*/, ->(*args) {
|
24
|
+
name, start, finish, id, data = args
|
25
|
+
case name
|
26
|
+
when "adapter_operation.flipper"
|
27
|
+
p data[:adapter_name] => data[:operation]
|
28
|
+
end
|
29
|
+
})
|
30
|
+
|
31
|
+
# You can uncomment this to get some default data:
|
32
|
+
# flipper[:logging].enable_percentage_of_time 5
|
33
|
+
|
34
|
+
run Flipper::Api.app(flipper) { |builder|
|
35
|
+
builder.use Flipper::Middleware::SetupEnv, flipper
|
36
|
+
builder.use Flipper::Middleware::Memoizer, preload: true
|
37
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#
|
2
|
+
# Usage:
|
3
|
+
# # if you want it to not reload and be really fast
|
4
|
+
# bin/rackup examples/api/memoized.ru -p 9999
|
5
|
+
#
|
6
|
+
# # if you want reloading
|
7
|
+
# bin/shotgun examples/api/memoized.ru -p 9999
|
8
|
+
#
|
9
|
+
# http://localhost:9999/
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'bundler/setup'
|
13
|
+
require "active_support/notifications"
|
14
|
+
require "flipper/api"
|
15
|
+
require "flipper/adapters/pstore"
|
16
|
+
|
17
|
+
Flipper.configure do |config|
|
18
|
+
config.adapter {
|
19
|
+
Flipper::Adapters::Instrumented.new(
|
20
|
+
Flipper::Adapters::PStore.new,
|
21
|
+
instrumenter: ActiveSupport::Notifications,
|
22
|
+
)
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
ActiveSupport::Notifications.subscribe(/.*/, ->(*args) {
|
27
|
+
name, start, finish, id, data = args
|
28
|
+
case name
|
29
|
+
when "adapter_operation.flipper"
|
30
|
+
p data[:adapter_name] => data[:operation]
|
31
|
+
end
|
32
|
+
})
|
33
|
+
|
34
|
+
Flipper.register(:admins) { |actor|
|
35
|
+
actor.respond_to?(:admin?) && actor.admin?
|
36
|
+
}
|
37
|
+
|
38
|
+
# You can uncomment this to get some default data:
|
39
|
+
# Flipper.enable :logging
|
40
|
+
|
41
|
+
run Flipper::Api.app { |builder|
|
42
|
+
builder.use Flipper::Middleware::Memoizer, preload: true
|
43
|
+
}
|
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|
|