flipper 0.21.0.rc1 → 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/Changelog.md +36 -0
- data/README.md +1 -1
- data/docs/Adapters.md +9 -10
- data/docs/Caveats.md +2 -2
- data/docs/Optimization.md +70 -47
- data/docs/http/README.md +12 -11
- data/docs/read-only/README.md +8 -5
- data/examples/configuring_default.rb +1 -3
- data/examples/memoizing.rb +2 -5
- data/lib/flipper.rb +3 -1
- data/lib/flipper/adapters/pstore.rb +4 -0
- data/lib/flipper/configuration.rb +28 -2
- 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/configuration_spec.rb +16 -1
- data/spec/flipper/middleware/memoizer_spec.rb +94 -34
- data/spec/flipper/railtie_spec.rb +69 -0
- data/spec/support/spec_helpers.rb +17 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1cdc35a74141977ee7aa3f92a8ddba338f9e268aee9ae9ed709c2f9c391cac8a
|
4
|
+
data.tar.gz: f4f387595c12a6219dea6f0451e5c47c2e12cfb3d830fd602f3e648e23a2d0ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 314fe25d0dfd891c038c8296ab3f07dfe539fbbbd33bbf1e9fec93267048f8f979025d0166f1b81ee629aee1b06d19ae891ab6bce45ca74039b82ff4978a747a
|
7
|
+
data.tar.gz: 515e5b5a19e9394a50fab5c6d0bd93b55e9f7c05c49ab8d1c88b6d9ff838aa5b0e2e57980e25e668cda71659cefdcdb1ee3f282eb03b08e9769bdc37c236400f
|
data/Changelog.md
CHANGED
@@ -7,6 +7,41 @@
|
|
7
7
|
* Added cloud recommendation to flipper-ui. Can be disabled with `Flipper::UI.configure { |config| config.cloud_recommendation = false }`. Just want to raise awareness that more is available if people want it (https://github.com/jnunemaker/flipper/pull/504)
|
8
8
|
* Added default `flipper_id` implementation via `Flipper::Identifier` and automatically included it in ActiveRecord and Sequel models (https://github.com/jnunemaker/flipper/pull/505)
|
9
9
|
* Deprecate superflous sync_method setting (https://github.com/jnunemaker/flipper/pull/511)
|
10
|
+
* Flipper is now pre-configured when used with Rails. By default, it will [memoize and preload all features for each request](docs/Optimization.md#memoization). (https://github.com/jnunemaker/flipper/pull/506)
|
11
|
+
|
12
|
+
### Upgrading
|
13
|
+
|
14
|
+
You should be able to upgrade to 0.21 without any breaking changes. However, if you want to simplify your setup, you can remove some configuration that is now handled automatically:
|
15
|
+
|
16
|
+
1. Adapters are configured when on require, so unless you are using caching or other customizations, you can remove adapter configuration.
|
17
|
+
|
18
|
+
```diff
|
19
|
+
# config/initializers/flipper.rb
|
20
|
+
- Flipper.configure do |config|
|
21
|
+
- config.default { Flipper.new(Flipper::Adapters::ActiveRecord.new) }
|
22
|
+
- end
|
23
|
+
```
|
24
|
+
|
25
|
+
2. `Flipper::Middleware::Memoizer` will be enabled by default.
|
26
|
+
|
27
|
+
```diff
|
28
|
+
# config/initializers/flipper.rb
|
29
|
+
- Rails.configuration.middleware.use Flipper::Middleware::Memoizer,
|
30
|
+
- preload: [:stats, :search, :some_feature]
|
31
|
+
+ Rails.application.configure do
|
32
|
+
+ # Uncomment to configure which features to preload on all requests
|
33
|
+
+ # config.flipper.preload = [:stats, :search, :some_feature]
|
34
|
+
+ end
|
35
|
+
```
|
36
|
+
|
37
|
+
3. `#flipper_id`, which is used to enable features for specific actors, is now defined by [Flipper::Identifier](lib/flipper/identifier.rb) on all ActiveRecord and Sequel models. You can remove your implementation if it is in the form of `ModelName;id`.
|
38
|
+
|
39
|
+
4. When using `flipper-cloud`, The `Flipper::Cloud.app` webhook receiver is now mounted at `/_flipper` by default.
|
40
|
+
|
41
|
+
```diff
|
42
|
+
# config/routes.rb
|
43
|
+
- mount Flipper::Cloud.app, at: "/_flipper"
|
44
|
+
```
|
10
45
|
|
11
46
|
## 0.20.4
|
12
47
|
|
@@ -14,6 +49,7 @@
|
|
14
49
|
|
15
50
|
* Allow actors and time gates to deal with decimal percentages (https://github.com/jnunemaker/flipper/pull/492)
|
16
51
|
* Change Flipper::Cloud::Middleware to receive webhooks at / in addition to /webhooks.
|
52
|
+
* Add `write_through` option to ActiveSupportCacheStore adapter to support write-through caching (https://github.com/jnunemaker/flipper/pull/512)
|
17
53
|
|
18
54
|
## 0.20.3
|
19
55
|
|
data/README.md
CHANGED
data/docs/Adapters.md
CHANGED
@@ -4,18 +4,17 @@ I plan on supporting the adapters in the flipper repo. Other adapters are welcom
|
|
4
4
|
|
5
5
|
## Officially Supported
|
6
6
|
|
7
|
-
* [ActiveRecord adapter](https://github.com/jnunemaker/flipper/blob/master/docs/active_record) - Rails
|
8
|
-
* [
|
9
|
-
* [
|
10
|
-
* [Http adapter](https://github.com/jnunemaker/flipper/blob/master/docs/http)
|
11
|
-
* [memory adapter](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adapters/memory.rb) – great for tests
|
12
|
-
* [Moneta adapter](https://github.com/jnunemaker/flipper/blob/master/docs/moneta)
|
7
|
+
* [ActiveRecord adapter](https://github.com/jnunemaker/flipper/blob/master/docs/active_record) - Rails 5 and 6.
|
8
|
+
* [Sequel adapter](https://github.com/jnunemaker/flipper/blob/master/docs/sequel)
|
9
|
+
* [Redis adapter](https://github.com/jnunemaker/flipper/blob/master/docs/redis)
|
13
10
|
* [Mongo adapter](https://github.com/jnunemaker/flipper/blob/master/docs/mongo)
|
14
11
|
* [PStore adapter](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adapters/pstore.rb) – great for when a local file is enough
|
15
|
-
* [
|
16
|
-
* [
|
17
|
-
* [
|
18
|
-
* [
|
12
|
+
* [Http adapter](https://github.com/jnunemaker/flipper/blob/master/docs/http) - great for using with `Flipper::Api`
|
13
|
+
* [Moneta adapter](https://github.com/jnunemaker/flipper/blob/master/docs/moneta) - great for a variety of data stores
|
14
|
+
* [ActiveSupportCacheStore adapter](https://github.com/jnunemaker/flipper/blob/master/docs/active_support_cache_store) - great for Rails caching
|
15
|
+
* [Memory adapter](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adapters/memory.rb) – great for tests
|
16
|
+
* [Read-only adapter](https://github.com/jnunemaker/flipper/blob/master/docs/read-only) - great for preventing writes from production console
|
17
|
+
* [Rollout adapter](rollout/README.md) - great for switching from rollout to flipper
|
19
18
|
|
20
19
|
## Community Supported
|
21
20
|
|
data/docs/Caveats.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
1
|
# Caveats
|
2
2
|
|
3
|
-
1. The [individual actor gate](https://github.com/jnunemaker/flipper/blob/master/docs/Gates.md#2-individual-actor) is typically not designed for hundreds or thousands of actors to be enabled. This is an explicit choice to make it easier to batch load data from the adapters instead of performing individual checks for actors over and over. If you need to enable something for more than
|
4
|
-
2. The disable method exists only to clear something that is enabled. If the thing you are disabling is not enabled, the disable is pointless. This means that if you enable one group an actor is in and disable another group, the feature will be enabled for the actor. ([related issue](https://github.com/jnunemaker/flipper/issues/71))
|
3
|
+
1. The [individual actor gate](https://github.com/jnunemaker/flipper/blob/master/docs/Gates.md#2-individual-actor) is typically not designed for hundreds or thousands of actors to be enabled. This is an explicit choice to make it easier to batch load data from the adapters instead of performing individual checks for actors over and over. If you need to enable something for more than 100 individual actors, I would recommend using a [group](https://github.com/jnunemaker/flipper/blob/master/docs/Gates.md#5-group).
|
4
|
+
2. The `disable` method exists only to clear something that is enabled. If the thing you are disabling is not enabled, the disable is pointless. This means that if you enable one group an actor is in and disable another group, the feature will be enabled for the actor. ([related issue](https://github.com/jnunemaker/flipper/issues/71))
|
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**:
|
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
|
```
|
@@ -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
|
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
|
|
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.
|
@@ -164,3 +164,5 @@ require 'flipper/types/percentage'
|
|
164
164
|
require 'flipper/types/percentage_of_actors'
|
165
165
|
require 'flipper/types/percentage_of_time'
|
166
166
|
require 'flipper/typecast'
|
167
|
+
|
168
|
+
require "flipper/railtie" if defined?(Rails::Railtie)
|
@@ -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-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
|
@@ -8,8 +8,7 @@ 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
|
#
|
@@ -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,38 @@
|
|
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
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer "flipper.default", before: :load_config_initializers do |app|
|
13
|
+
Flipper.configure do |config|
|
14
|
+
config.default do
|
15
|
+
Flipper.new(config.adapter, instrumenter: app.config.flipper.instrumenter)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
initializer "flipper.memoizer", after: :load_config_initializers do |app|
|
21
|
+
config = app.config.flipper
|
22
|
+
|
23
|
+
if config.memoize
|
24
|
+
app.middleware.use Flipper::Middleware::Memoizer, {
|
25
|
+
env_key: config.env_key,
|
26
|
+
preload: config.preload,
|
27
|
+
if: config.memoize.respond_to?(:call) ? config.memoize : nil
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
initializer "flipper.identifier" do
|
33
|
+
ActiveSupport.on_load(:active_record) do
|
34
|
+
ActiveRecord::Base.include Flipper::Identifier
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/flipper/version.rb
CHANGED
@@ -2,10 +2,25 @@ require 'helper'
|
|
2
2
|
require 'flipper/configuration'
|
3
3
|
|
4
4
|
RSpec.describe Flipper::Configuration do
|
5
|
+
describe '#adapter' do
|
6
|
+
it 'returns instance using Memory adapter' do
|
7
|
+
expect(subject.adapter).to be_a(Flipper::Adapters::Memory)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'can be set' do
|
11
|
+
instance = Flipper::Adapters::Memory.new
|
12
|
+
expect(subject.adapter).not_to be(instance)
|
13
|
+
subject.adapter { instance }
|
14
|
+
expect(subject.adapter).to be(instance)
|
15
|
+
# All adapters are wrapped in Memoizable
|
16
|
+
expect(subject.default.adapter.adapter).to be(instance)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
5
20
|
describe '#default' do
|
6
21
|
it 'returns instance using Memory adapter' do
|
7
22
|
expect(subject.default).to be_a(Flipper::DSL)
|
8
|
-
# All
|
23
|
+
# All adapters are wrapped in Memoizable
|
9
24
|
expect(subject.default.adapter.adapter).to be_a(Flipper::Adapters::Memory)
|
10
25
|
end
|
11
26
|
|
@@ -15,10 +15,6 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
15
15
|
let(:flipper) { Flipper.new(adapter) }
|
16
16
|
let(:env) { { 'flipper' => flipper } }
|
17
17
|
|
18
|
-
after do
|
19
|
-
flipper.memoize = nil
|
20
|
-
end
|
21
|
-
|
22
18
|
it 'raises if initialized with app and flipper instance' do
|
23
19
|
expect do
|
24
20
|
described_class.new(app, flipper)
|
@@ -103,14 +99,14 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
103
99
|
end
|
104
100
|
end
|
105
101
|
|
106
|
-
context 'with
|
102
|
+
context 'with preload: true' do
|
107
103
|
let(:app) do
|
108
104
|
# ensure scoped for builder block, annoying...
|
109
105
|
instance = flipper
|
110
106
|
middleware = described_class
|
111
107
|
|
112
108
|
Rack::Builder.new do
|
113
|
-
use middleware,
|
109
|
+
use middleware, preload: true
|
114
110
|
|
115
111
|
map '/' do
|
116
112
|
run ->(_env) { [200, {}, []] }
|
@@ -139,7 +135,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
139
135
|
[200, {}, []]
|
140
136
|
end
|
141
137
|
|
142
|
-
middleware = described_class.new(app,
|
138
|
+
middleware = described_class.new(app, preload: true)
|
143
139
|
middleware.call(env)
|
144
140
|
|
145
141
|
expect(adapter.operations.size).to be(1)
|
@@ -156,7 +152,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
156
152
|
[200, {}, []]
|
157
153
|
end
|
158
154
|
|
159
|
-
middleware = described_class.new(app,
|
155
|
+
middleware = described_class.new(app, preload: true)
|
160
156
|
middleware.call(env)
|
161
157
|
|
162
158
|
expect(adapter.count(:get)).to be(1)
|
@@ -222,6 +218,44 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
222
218
|
end
|
223
219
|
end
|
224
220
|
|
221
|
+
context 'with multiple instances' do
|
222
|
+
let(:app) do
|
223
|
+
# ensure scoped for builder block, annoying...
|
224
|
+
instance = flipper
|
225
|
+
middleware = described_class
|
226
|
+
|
227
|
+
Rack::Builder.new do
|
228
|
+
use middleware, preload: %i(stats)
|
229
|
+
# Second instance should be a noop
|
230
|
+
use middleware, preload: true
|
231
|
+
|
232
|
+
map '/' do
|
233
|
+
run ->(_env) { [200, {}, []] }
|
234
|
+
end
|
235
|
+
|
236
|
+
map '/fail' do
|
237
|
+
run ->(_env) { raise 'FAIL!' }
|
238
|
+
end
|
239
|
+
end.to_app
|
240
|
+
end
|
241
|
+
|
242
|
+
def get(uri, params = {}, env = {}, &block)
|
243
|
+
silence { super(uri, params, env, &block) }
|
244
|
+
end
|
245
|
+
|
246
|
+
include_examples 'flipper middleware'
|
247
|
+
|
248
|
+
it 'does not call preload in second instance' do
|
249
|
+
expect(flipper).not_to receive(:preload_all)
|
250
|
+
|
251
|
+
output = get '/', {}, 'flipper' => flipper
|
252
|
+
|
253
|
+
expect(output).to match(/Flipper::Middleware::Memoizer appears to be running twice/)
|
254
|
+
expect(adapter.count(:get_multi)).to be(1)
|
255
|
+
expect(adapter.last(:get_multi).args).to eq([[flipper[:stats]]])
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
225
259
|
context 'when an app raises an exception' do
|
226
260
|
it 'resets memoize' do
|
227
261
|
begin
|
@@ -259,10 +293,9 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
259
293
|
context 'with Flipper setup in env' do
|
260
294
|
it 'caches getting a feature for duration of request' do
|
261
295
|
Flipper.configure do |config|
|
262
|
-
config.
|
296
|
+
config.adapter do
|
263
297
|
memory = Flipper::Adapters::Memory.new
|
264
|
-
|
265
|
-
Flipper.new(logged_adapter)
|
298
|
+
Flipper::Adapters::OperationLogger.new(memory)
|
266
299
|
end
|
267
300
|
end
|
268
301
|
Flipper.enable(:stats)
|
@@ -308,14 +341,16 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
308
341
|
end
|
309
342
|
end
|
310
343
|
|
311
|
-
context 'with
|
344
|
+
context 'with preload:true' do
|
345
|
+
let(:options) { {preload: true} }
|
346
|
+
|
312
347
|
let(:app) do
|
313
348
|
# ensure scoped for builder block, annoying...
|
314
349
|
middleware = described_class
|
350
|
+
opts = options
|
315
351
|
|
316
352
|
Rack::Builder.new do
|
317
|
-
use middleware,
|
318
|
-
unless: ->(request) { request.path.start_with?("/assets") }
|
353
|
+
use middleware, opts
|
319
354
|
|
320
355
|
map '/' do
|
321
356
|
run ->(_env) { [200, {}, []] }
|
@@ -327,18 +362,53 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
327
362
|
end.to_app
|
328
363
|
end
|
329
364
|
|
330
|
-
|
331
|
-
|
332
|
-
|
365
|
+
|
366
|
+
context 'and unless option' do
|
367
|
+
before do
|
368
|
+
options[:unless] = ->(request) { request.path.start_with?("/assets") }
|
369
|
+
end
|
370
|
+
|
371
|
+
it 'does NOT preload if request matches unless block' do
|
372
|
+
expect(flipper).to receive(:preload_all).never
|
373
|
+
get '/assets/foo.png', {}, 'flipper' => flipper
|
374
|
+
end
|
375
|
+
|
376
|
+
it 'does preload if request does NOT match unless block' do
|
377
|
+
expect(flipper).to receive(:preload_all).once
|
378
|
+
get '/some/other/path', {}, 'flipper' => flipper
|
379
|
+
end
|
333
380
|
end
|
334
381
|
|
335
|
-
|
336
|
-
|
337
|
-
|
382
|
+
context 'and if option' do
|
383
|
+
before do
|
384
|
+
options[:if] = ->(request) { !request.path.start_with?("/assets") }
|
385
|
+
end
|
386
|
+
|
387
|
+
it 'does NOT preload if request does not match if block' do
|
388
|
+
expect(flipper).to receive(:preload_all).never
|
389
|
+
get '/assets/foo.png', {}, 'flipper' => flipper
|
390
|
+
end
|
391
|
+
|
392
|
+
it 'does preload if request matches if block' do
|
393
|
+
expect(flipper).to receive(:preload_all).once
|
394
|
+
get '/some/other/path', {}, 'flipper' => flipper
|
395
|
+
end
|
338
396
|
end
|
339
397
|
end
|
340
398
|
|
341
|
-
context 'with
|
399
|
+
context 'with preload:true and caching adapter' do
|
400
|
+
let(:app) do
|
401
|
+
app = lambda do |_env|
|
402
|
+
flipper[:stats].enabled?
|
403
|
+
flipper[:stats].enabled?
|
404
|
+
flipper[:shiny].enabled?
|
405
|
+
flipper[:shiny].enabled?
|
406
|
+
[200, {}, []]
|
407
|
+
end
|
408
|
+
|
409
|
+
described_class.new(app, preload: true)
|
410
|
+
end
|
411
|
+
|
342
412
|
it 'eagerly caches known features for duration of request' do
|
343
413
|
memory = Flipper::Adapters::Memory.new
|
344
414
|
logged_memory = Flipper::Adapters::OperationLogger.new(memory)
|
@@ -355,25 +425,15 @@ RSpec.describe Flipper::Middleware::Memoizer do
|
|
355
425
|
logged_memory.reset
|
356
426
|
logged_cached.reset
|
357
427
|
|
358
|
-
|
359
|
-
flipper[:stats].enabled?
|
360
|
-
flipper[:stats].enabled?
|
361
|
-
flipper[:shiny].enabled?
|
362
|
-
flipper[:shiny].enabled?
|
363
|
-
[200, {}, []]
|
364
|
-
end
|
365
|
-
|
366
|
-
middleware = described_class.new(app, preload_all: true)
|
367
|
-
|
368
|
-
middleware.call('flipper' => flipper)
|
428
|
+
get '/', {}, 'flipper' => flipper
|
369
429
|
expect(logged_cached.count(:get_all)).to be(1)
|
370
430
|
expect(logged_memory.count(:get_all)).to be(1)
|
371
431
|
|
372
|
-
|
432
|
+
get '/', {}, 'flipper' => flipper
|
373
433
|
expect(logged_cached.count(:get_all)).to be(2)
|
374
434
|
expect(logged_memory.count(:get_all)).to be(1)
|
375
435
|
|
376
|
-
|
436
|
+
get '/', {}, 'flipper' => flipper
|
377
437
|
expect(logged_cached.count(:get_all)).to be(3)
|
378
438
|
expect(logged_memory.count(:get_all)).to be(1)
|
379
439
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'rails'
|
3
|
+
require 'flipper/railtie'
|
4
|
+
|
5
|
+
RSpec.describe Flipper::Railtie do
|
6
|
+
let(:application) do
|
7
|
+
app = Class.new(Rails::Application).new(
|
8
|
+
railties: [Flipper::Railtie],
|
9
|
+
ordered_railties: [Flipper::Railtie]
|
10
|
+
)
|
11
|
+
app.config.eager_load = false
|
12
|
+
app.config.logger = ActiveSupport::Logger.new($stdout)
|
13
|
+
app.run_load_hooks!
|
14
|
+
end
|
15
|
+
|
16
|
+
before do
|
17
|
+
Rails.application = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
subject do
|
21
|
+
application.initialize!
|
22
|
+
application
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'initializers' do
|
26
|
+
it 'sets defaults' do
|
27
|
+
expect(application.config.flipper.env_key).to eq("flipper")
|
28
|
+
expect(application.config.flipper.memoize).to be(true)
|
29
|
+
expect(application.config.flipper.preload).to be(true)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "configures instrumentor on default instance" do
|
33
|
+
subject
|
34
|
+
|
35
|
+
expect(Flipper.instance.instrumenter).to eq(ActiveSupport::Notifications)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'uses Memoizer middleware if config.memoize = true' do
|
39
|
+
expect(subject.middleware.last).to eq(Flipper::Middleware::Memoizer)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'does not use Memoizer middleware if config.memoize = false' do
|
43
|
+
# load but don't initialize
|
44
|
+
application.config.flipper.memoize = false
|
45
|
+
|
46
|
+
expect(subject.middleware.last).not_to eq(Flipper::Middleware::Memoizer)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'passes config to memoizer' do
|
50
|
+
# load but don't initialize
|
51
|
+
application.config.flipper.update(
|
52
|
+
env_key: 'my_flipper',
|
53
|
+
preload: [:stats, :search]
|
54
|
+
)
|
55
|
+
|
56
|
+
expect(Flipper::Middleware::Memoizer).to receive(:new).with(application.routes,
|
57
|
+
env_key: 'my_flipper', preload: [:stats, :search], if: nil
|
58
|
+
)
|
59
|
+
|
60
|
+
subject # initialize
|
61
|
+
end
|
62
|
+
|
63
|
+
it "defines #flipper_id on AR::Base" do
|
64
|
+
subject
|
65
|
+
require 'active_record'
|
66
|
+
expect(ActiveRecord::Base.ancestors).to include(Flipper::Identifier)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -61,6 +61,23 @@ module SpecHelpers
|
|
61
61
|
def with_modified_env(options, &block)
|
62
62
|
ClimateControl.modify(options, &block)
|
63
63
|
end
|
64
|
+
|
65
|
+
def silence
|
66
|
+
# Store the original stderr and stdout in order to restore them later
|
67
|
+
original_stderr = $stderr
|
68
|
+
original_stdout = $stdout
|
69
|
+
|
70
|
+
# Redirect stderr and stdout
|
71
|
+
output = $stderr = $stdout = StringIO.new
|
72
|
+
|
73
|
+
yield
|
74
|
+
|
75
|
+
$stderr = original_stderr
|
76
|
+
$stdout = original_stdout
|
77
|
+
|
78
|
+
# Return output
|
79
|
+
output.string
|
80
|
+
end
|
64
81
|
end
|
65
82
|
|
66
83
|
RSpec.configure do |config|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flipper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.21.0.
|
4
|
+
version: 0.21.0.rc2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Nunemaker
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-05-
|
11
|
+
date: 2021-05-08 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -92,6 +92,7 @@ files:
|
|
92
92
|
- lib/flipper/metadata.rb
|
93
93
|
- lib/flipper/middleware/memoizer.rb
|
94
94
|
- lib/flipper/middleware/setup_env.rb
|
95
|
+
- lib/flipper/railtie.rb
|
95
96
|
- lib/flipper/registry.rb
|
96
97
|
- lib/flipper/spec/shared_adapter_specs.rb
|
97
98
|
- lib/flipper/test/shared_adapter_test.rb
|
@@ -137,6 +138,7 @@ files:
|
|
137
138
|
- spec/flipper/instrumenters/noop_spec.rb
|
138
139
|
- spec/flipper/middleware/memoizer_spec.rb
|
139
140
|
- spec/flipper/middleware/setup_env_spec.rb
|
141
|
+
- spec/flipper/railtie_spec.rb
|
140
142
|
- spec/flipper/registry_spec.rb
|
141
143
|
- spec/flipper/typecast_spec.rb
|
142
144
|
- spec/flipper/types/actor_spec.rb
|
@@ -213,6 +215,7 @@ test_files:
|
|
213
215
|
- spec/flipper/instrumenters/noop_spec.rb
|
214
216
|
- spec/flipper/middleware/memoizer_spec.rb
|
215
217
|
- spec/flipper/middleware/setup_env_spec.rb
|
218
|
+
- spec/flipper/railtie_spec.rb
|
216
219
|
- spec/flipper/registry_spec.rb
|
217
220
|
- spec/flipper/typecast_spec.rb
|
218
221
|
- spec/flipper/types/actor_spec.rb
|