flipper 0.21.0.rc1 → 0.22.1
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 +12 -16
- data/.github/workflows/examples.yml +56 -0
- data/Changelog.md +54 -0
- data/Gemfile +1 -0
- data/README.md +2 -1
- data/docker-compose.yml +37 -34
- data/docs/Adapters.md +9 -10
- data/docs/Caveats.md +2 -2
- data/docs/Optimization.md +70 -47
- data/docs/api/README.md +5 -5
- data/docs/http/README.md +12 -11
- 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/configuring_default.rb +1 -3
- data/examples/instrumentation_last_accessed_at.rb +37 -0
- data/examples/memoizing.rb +2 -5
- data/lib/flipper/adapters/pstore.rb +4 -0
- data/lib/flipper/adapters/sync/synchronizer.rb +2 -1
- data/lib/flipper/configuration.rb +29 -3
- data/lib/flipper/middleware/memoizer.rb +30 -15
- data/lib/flipper/railtie.rb +46 -0
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +3 -1
- data/spec/flipper/configuration_spec.rb +16 -1
- data/spec/flipper/middleware/memoizer_spec.rb +94 -34
- data/spec/flipper/railtie_spec.rb +69 -0
- data/spec/helper.rb +2 -2
- data/spec/support/spec_helpers.rb +17 -0
- metadata +12 -4
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**:
|
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
|
+
}
|
@@ -3,9 +3,7 @@ require 'flipper'
|
|
3
3
|
|
4
4
|
# sets up default adapter so Flipper works like Flipper::DSL
|
5
5
|
Flipper.configure do |config|
|
6
|
-
config.
|
7
|
-
Flipper.new Flipper::Adapters::Memory.new
|
8
|
-
end
|
6
|
+
config.adapter { Flipper::Adapters::Memory.new }
|
9
7
|
end
|
10
8
|
|
11
9
|
puts Flipper.enabled?(:search) # => false
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Quick example of how to keep track of when a feature was last checked.
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'active_support/notifications'
|
5
|
+
require 'flipper'
|
6
|
+
|
7
|
+
class FlipperSubscriber
|
8
|
+
def self.stats
|
9
|
+
@stats ||= {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(name, start, finish, id, payload)
|
13
|
+
if payload[:operation] == :enabled?
|
14
|
+
feature_name = payload.fetch(:feature_name)
|
15
|
+
self.class.stats[feature_name] = Time.now
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
ActiveSupport::Notifications.subscribe(/feature_operation.flipper/, new)
|
20
|
+
end
|
21
|
+
|
22
|
+
Flipper.configure do |config|
|
23
|
+
config.default {
|
24
|
+
Flipper.new(config.adapter, instrumenter: ActiveSupport::Notifications)
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
Flipper.enabled?(:search)
|
29
|
+
Flipper.enabled?(:booyeah)
|
30
|
+
Flipper.enabled?(:hooray)
|
31
|
+
sleep 1
|
32
|
+
Flipper.enabled?(:booyeah)
|
33
|
+
Flipper.enabled?(:hooray)
|
34
|
+
sleep 1
|
35
|
+
Flipper.enabled?(:hooray)
|
36
|
+
|
37
|
+
pp FlipperSubscriber.stats
|
data/examples/memoizing.rb
CHANGED
@@ -4,12 +4,9 @@ require 'flipper/adapters/operation_logger'
|
|
4
4
|
require 'flipper/instrumentation/log_subscriber'
|
5
5
|
|
6
6
|
Flipper.configure do |config|
|
7
|
-
config.
|
7
|
+
config.adapter do
|
8
8
|
# pick an adapter, this uses memory, any will do
|
9
|
-
|
10
|
-
|
11
|
-
# pass adapter to handy DSL instance
|
12
|
-
Flipper.new(adapter)
|
9
|
+
Flipper::Adapters::OperationLogger.new(Flipper::Adapters::Memory.new)
|
13
10
|
end
|
14
11
|
end
|
15
12
|
|
@@ -37,7 +37,8 @@ module Flipper
|
|
37
37
|
# Sync all the gate values.
|
38
38
|
remote_get_all.each do |feature_key, remote_gates_hash|
|
39
39
|
feature = Feature.new(feature_key, @local)
|
40
|
-
|
40
|
+
# Check if feature_key is in hash before accessing to prevent unintended hash modification
|
41
|
+
local_gates_hash = local_get_all.key?(feature_key) ? local_get_all[feature_key] : @local.default_config
|
41
42
|
local_gate_values = GateValues.new(local_gates_hash)
|
42
43
|
remote_gate_values = GateValues.new(remote_gates_hash)
|
43
44
|
FeatureSynchronizer.new(feature, local_gate_values, remote_gate_values).call
|
@@ -1,7 +1,33 @@
|
|
1
1
|
module Flipper
|
2
2
|
class Configuration
|
3
|
-
def initialize
|
4
|
-
@default = -> { Flipper.new(
|
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
|
@@ -13,7 +39,7 @@ module Flipper
|
|
13
39
|
#
|
14
40
|
# # sets the default block to generate a new instance using ActiveRecord adapter
|
15
41
|
# configuration.default do
|
16
|
-
# require "flipper
|
42
|
+
# require "flipper/adapters/active_record"
|
17
43
|
# Flipper.new(Flipper::Adapters::ActiveRecord.new)
|
18
44
|
# end
|
19
45
|
#
|
@@ -8,15 +8,14 @@ module Flipper
|
|
8
8
|
#
|
9
9
|
# app - The app this middleware is included in.
|
10
10
|
# opts - The Hash of options.
|
11
|
-
# :
|
12
|
-
# :preload - Array of Symbol feature names to preload.
|
11
|
+
# :preload - Boolean to preload all features or Array of Symbol feature names to preload.
|
13
12
|
#
|
14
13
|
# Examples
|
15
14
|
#
|
16
15
|
# use Flipper::Middleware::Memoizer
|
17
16
|
#
|
18
17
|
# # using with preload_all features
|
19
|
-
# use Flipper::Middleware::Memoizer,
|
18
|
+
# use Flipper::Middleware::Memoizer, preload: true
|
20
19
|
#
|
21
20
|
# # using with preload specific features
|
22
21
|
# use Flipper::Middleware::Memoizer, preload: [:stats, :search, :some_feature]
|
@@ -26,6 +25,11 @@ module Flipper
|
|
26
25
|
raise 'Flipper::Middleware::Memoizer no longer initializes with a flipper instance or block. Read more at: https://git.io/vSo31.'
|
27
26
|
end
|
28
27
|
|
28
|
+
if opts[:preload_all]
|
29
|
+
warn "Flipper::Middleware::Memoizer: `preload_all` is deprecated, use `preload: true`"
|
30
|
+
opts[:preload] = true
|
31
|
+
end
|
32
|
+
|
29
33
|
@app = app
|
30
34
|
@opts = opts
|
31
35
|
@env_key = opts.fetch(:env_key, 'flipper')
|
@@ -34,39 +38,50 @@ module Flipper
|
|
34
38
|
def call(env)
|
35
39
|
request = Rack::Request.new(env)
|
36
40
|
|
37
|
-
if
|
38
|
-
@app.call(env)
|
39
|
-
else
|
41
|
+
if memoize?(request)
|
40
42
|
memoized_call(env)
|
43
|
+
else
|
44
|
+
@app.call(env)
|
41
45
|
end
|
42
46
|
end
|
43
47
|
|
44
48
|
private
|
45
49
|
|
46
|
-
def
|
47
|
-
|
50
|
+
def memoize?(request)
|
51
|
+
if @opts[:if]
|
52
|
+
@opts[:if].call(request)
|
53
|
+
elsif @opts[:unless]
|
54
|
+
!@opts[:unless].call(request)
|
55
|
+
else
|
56
|
+
true
|
57
|
+
end
|
48
58
|
end
|
49
59
|
|
50
60
|
def memoized_call(env)
|
51
61
|
reset_on_body_close = false
|
52
62
|
flipper = env.fetch(@env_key) { Flipper }
|
53
|
-
original = flipper.memoizing?
|
54
|
-
flipper.memoize = true
|
55
63
|
|
56
|
-
|
64
|
+
# Already memoizing. This instance does not need to do anything.
|
65
|
+
if flipper.memoizing?
|
66
|
+
warn "Flipper::Middleware::Memoizer appears to be running twice. Read how to resolve this at https://github.com/jnunemaker/flipper/pull/523"
|
67
|
+
return @app.call(env)
|
68
|
+
end
|
69
|
+
|
70
|
+
flipper.memoize = true
|
57
71
|
|
58
|
-
|
59
|
-
|
72
|
+
case @opts[:preload]
|
73
|
+
when true then flipper.preload_all
|
74
|
+
when Array then flipper.preload(@opts[:preload])
|
60
75
|
end
|
61
76
|
|
62
77
|
response = @app.call(env)
|
63
78
|
response[2] = Rack::BodyProxy.new(response[2]) do
|
64
|
-
flipper.memoize =
|
79
|
+
flipper.memoize = false
|
65
80
|
end
|
66
81
|
reset_on_body_close = true
|
67
82
|
response
|
68
83
|
ensure
|
69
|
-
flipper.memoize =
|
84
|
+
flipper.memoize = false if flipper && !reset_on_body_close
|
70
85
|
end
|
71
86
|
end
|
72
87
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Flipper
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
config.before_configuration do
|
4
|
+
config.flipper = ActiveSupport::OrderedOptions.new.update(
|
5
|
+
env_key: "flipper",
|
6
|
+
memoize: true,
|
7
|
+
preload: true,
|
8
|
+
instrumenter: ActiveSupport::Notifications,
|
9
|
+
log: true
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
initializer "flipper.default", before: :load_config_initializers do |app|
|
14
|
+
Flipper.configure do |config|
|
15
|
+
config.default do
|
16
|
+
Flipper.new(config.adapter, instrumenter: app.config.flipper.instrumenter)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
initializer "flipper.memoizer", after: :load_config_initializers do |app|
|
22
|
+
config = app.config.flipper
|
23
|
+
|
24
|
+
if config.memoize
|
25
|
+
app.middleware.use Flipper::Middleware::Memoizer, {
|
26
|
+
env_key: config.env_key,
|
27
|
+
preload: config.preload,
|
28
|
+
if: config.memoize.respond_to?(:call) ? config.memoize : nil
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
initializer "flipper.log", after: :load_config_initializers do |app|
|
34
|
+
config = app.config.flipper
|
35
|
+
if config.log && config.instrumenter == ActiveSupport::Notifications
|
36
|
+
require "flipper/instrumentation/log_subscriber"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
initializer "flipper.identifier" do
|
41
|
+
ActiveSupport.on_load(:active_record) do
|
42
|
+
ActiveRecord::Base.include Flipper::Identifier
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|