flipper 0.7.5 → 0.8.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/Changelog.md +12 -0
- data/Gemfile +1 -0
- data/Rakefile +9 -4
- data/docs/Optimization.md +8 -2
- data/lib/flipper/adapters/instrumented.rb +21 -17
- data/lib/flipper/adapters/memoizable.rb +20 -13
- data/lib/flipper/adapters/memory.rb +1 -1
- data/lib/flipper/adapters/operation_logger.rb +49 -12
- data/lib/flipper/adapters/pstore.rb +1 -1
- data/lib/flipper/adapters/read_only.rb +51 -0
- data/lib/flipper/dsl.rb +9 -0
- data/lib/flipper/feature.rb +8 -20
- data/lib/flipper/gate.rb +0 -4
- data/lib/flipper/gate_values.rb +4 -2
- data/lib/flipper/gates/actor.rb +1 -1
- data/lib/flipper/gates/boolean.rb +1 -1
- data/lib/flipper/gates/group.rb +1 -1
- data/lib/flipper/gates/percentage_of_actors.rb +3 -5
- data/lib/flipper/gates/percentage_of_time.rb +2 -3
- data/lib/flipper/instrumentation/log_subscriber.rb +0 -28
- data/lib/flipper/instrumentation/subscriber.rb +0 -22
- data/lib/flipper/middleware/memoizer.rb +2 -3
- data/lib/flipper/spec/shared_adapter_specs.rb +32 -0
- data/lib/flipper/test/shared_adapter_test.rb +249 -0
- data/lib/flipper/typecast.rb +1 -1
- data/lib/flipper/types/group.rb +1 -1
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/adapters/instrumented_spec.rb +6 -0
- data/spec/flipper/adapters/memoizable_spec.rb +6 -0
- data/spec/flipper/adapters/read_only_spec.rb +94 -0
- data/spec/flipper/dsl_spec.rb +14 -2
- data/spec/flipper/feature_spec.rb +11 -0
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +0 -10
- data/spec/flipper/instrumentation/metriks_subscriber_spec.rb +0 -10
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +0 -10
- data/spec/flipper/types/group_spec.rb +14 -0
- data/spec/helper.rb +4 -0
- data/spec/integration_spec.rb +11 -0
- data/spec/support/spec_helpers.rb +4 -0
- data/test/adapters/memory_test.rb +10 -0
- data/test/adapters/pstore_test.rb +17 -0
- data/test/helper.rb +0 -2
- data/test/test_helper.rb +6 -0
- metadata +12 -4
- data/lib/flipper/adapters/decorator.rb +0 -11
- data/lib/flipper/decorator.rb +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: afbd6c749fed154fc4f131d9a100b6f475d5963c
|
4
|
+
data.tar.gz: 28a2fb5f3bf64365f003399cc6287e823da4ae23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9edcf82bb9e5e600ff797c726aef6bb59c96f9ce86a5ccdc085048553c7d7112b45eb8cdf37782660f0b7d793a2f1abe4a2886d186aac06b721e80ef917bbc18
|
7
|
+
data.tar.gz: c0f9f0df3475ba5adee9467512fce9580d32b9398497389173cc9fb12ed5d853d9e26c6e8e4d1c98f8cfd5a4dcc3d61014bd5f366f4af0f2fd1466b432eb5852
|
data/Changelog.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
## 0.8
|
2
|
+
|
3
|
+
* removed Flipper::Decorator and Flipper::Adapters::Decorator in favor of just calling methods on wrapped adapter
|
4
|
+
* fix bug where certain versions of AR left off quotes for key column which caused issues with MySQL https://github.com/jnunemaker/flipper/issues/120
|
5
|
+
* fix bug where AR would store multiple gate values for percentage gates for each enable/disable and then nondeterministically pick one on read (https://github.com/jnunemaker/flipper/pull/122 and https://github.com/jnunemaker/flipper/pull/124)
|
6
|
+
* added readonly adapter (https://github.com/jnunemaker/flipper/pull/111)
|
7
|
+
* flipper groups now match for truthy values rather than explicitly only true (https://github.com/jnunemaker/flipper/issues/110)
|
8
|
+
* removed gate operation instrumentation (https://github.com/jnunemaker/flipper/commit/32f14ed1fb25c64961b23c6be3dc6773143a06c8); I don't think it was useful and never found myself instrumenting it in reality
|
9
|
+
* initial implementation of flipper api - very limited functionality right now (get/delete feature, boolean gate for feature) but more is on the way
|
10
|
+
* made it easy to remove a feature (https://github.com/jnunemaker/flipper/pull/126)
|
11
|
+
* add minitest shared tests for adapters that work the same as the shared specs for rspec (https://github.com/jnunemaker/flipper/pull/127)
|
12
|
+
|
1
13
|
## 0.7.5
|
2
14
|
|
3
15
|
* support for rails 5 beta/ rack 2 alpha
|
data/Gemfile
CHANGED
@@ -14,6 +14,7 @@ gem 'rspec', '~> 3.0'
|
|
14
14
|
gem 'rack-test', '~> 0.6.3'
|
15
15
|
gem 'sqlite3', '~> 1.3.11'
|
16
16
|
gem 'rails', "~> #{ENV["RAILS_VERSION"] || '4.2.5'}"
|
17
|
+
gem 'minitest', '~> 5.8.0'
|
17
18
|
|
18
19
|
# for active support tests in test/ and only needed for ruby 2.2.x
|
19
20
|
gem 'test-unit', '~> 3.0'
|
data/Rakefile
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
2
|
$LOAD_PATH.push File.expand_path("../lib", __FILE__)
|
3
|
+
require 'rake/testtask'
|
3
4
|
require "flipper/version"
|
4
5
|
|
5
6
|
# gem install pkg/*.gem
|
@@ -35,10 +36,14 @@ namespace :spec do
|
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
38
|
-
|
39
|
+
Rake::TestTask.new do |t|
|
40
|
+
t.libs = ['lib', 'test']
|
41
|
+
t.pattern = "test/**/*_test.rb"
|
42
|
+
end
|
39
43
|
|
40
|
-
|
41
|
-
|
44
|
+
Rake::TestTask.new(:shared_test) do |t|
|
45
|
+
t.libs = ['lib', 'test']
|
46
|
+
t.pattern = "lib/flipper/shared/test/**_test.rb"
|
42
47
|
end
|
43
48
|
|
44
|
-
task :default => :test
|
49
|
+
task :default => [:spec, :test, :shared_test]
|
data/docs/Optimization.md
CHANGED
@@ -18,10 +18,16 @@ If you set your flipper instance up in an initializer, you can pass a block to t
|
|
18
18
|
|
19
19
|
```ruby
|
20
20
|
# config/initializers/flipper.rb
|
21
|
-
|
21
|
+
module MyRailsApp
|
22
|
+
def self.flipper
|
23
|
+
@flipper ||= Flipper.new(...)
|
24
|
+
end
|
25
|
+
end
|
22
26
|
|
23
27
|
# config/application.rb
|
24
|
-
config.middleware.use Flipper::Middleware::Memoizer, lambda {
|
28
|
+
config.middleware.use Flipper::Middleware::Memoizer, lambda {
|
29
|
+
MyRailsApp.flipper
|
30
|
+
}
|
25
31
|
```
|
26
32
|
|
27
33
|
**Note**: Be sure that the middleware is high enough up in your stack that all feature checks are wrapped.
|
@@ -1,17 +1,21 @@
|
|
1
|
-
require 'flipper/adapters/decorator'
|
2
1
|
require 'flipper/instrumenters/noop'
|
3
2
|
|
4
3
|
module Flipper
|
5
4
|
module Adapters
|
6
5
|
# Internal: Adapter that wraps another adapter and instruments all adapter
|
7
6
|
# operations. Used by flipper dsl to provide instrumentatin for flipper.
|
8
|
-
class Instrumented
|
7
|
+
class Instrumented
|
8
|
+
include ::Flipper::Adapter
|
9
|
+
|
9
10
|
# Private: The name of instrumentation events.
|
10
11
|
InstrumentationName = "adapter_operation.#{InstrumentationNamespace}"
|
11
12
|
|
12
13
|
# Private: What is used to instrument all the things.
|
13
14
|
attr_reader :instrumenter
|
14
15
|
|
16
|
+
# Public: The name of the adapter.
|
17
|
+
attr_reader :name
|
18
|
+
|
15
19
|
# Internal: Initializes a new adapter instance.
|
16
20
|
#
|
17
21
|
# adapter - Vanilla adapter instance to wrap.
|
@@ -20,7 +24,7 @@ module Flipper
|
|
20
24
|
# :instrumenter - What to use to instrument all the things.
|
21
25
|
#
|
22
26
|
def initialize(adapter, options = {})
|
23
|
-
|
27
|
+
@adapter = adapter
|
24
28
|
@name = :instrumented
|
25
29
|
@instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
|
26
30
|
end
|
@@ -29,11 +33,11 @@ module Flipper
|
|
29
33
|
def features
|
30
34
|
payload = {
|
31
35
|
:operation => :features,
|
32
|
-
:adapter_name => name,
|
36
|
+
:adapter_name => @adapter.name,
|
33
37
|
}
|
34
38
|
|
35
39
|
@instrumenter.instrument(InstrumentationName, payload) { |payload|
|
36
|
-
payload[:result] =
|
40
|
+
payload[:result] = @adapter.features
|
37
41
|
}
|
38
42
|
end
|
39
43
|
|
@@ -41,12 +45,12 @@ module Flipper
|
|
41
45
|
def add(feature)
|
42
46
|
payload = {
|
43
47
|
:operation => :add,
|
44
|
-
:adapter_name => name,
|
48
|
+
:adapter_name => @adapter.name,
|
45
49
|
:feature_name => feature.name,
|
46
50
|
}
|
47
51
|
|
48
52
|
@instrumenter.instrument(InstrumentationName, payload) { |payload|
|
49
|
-
payload[:result] =
|
53
|
+
payload[:result] = @adapter.add(feature)
|
50
54
|
}
|
51
55
|
end
|
52
56
|
|
@@ -54,12 +58,12 @@ module Flipper
|
|
54
58
|
def remove(feature)
|
55
59
|
payload = {
|
56
60
|
:operation => :remove,
|
57
|
-
:adapter_name => name,
|
61
|
+
:adapter_name => @adapter.name,
|
58
62
|
:feature_name => feature.name,
|
59
63
|
}
|
60
64
|
|
61
65
|
@instrumenter.instrument(InstrumentationName, payload) { |payload|
|
62
|
-
payload[:result] =
|
66
|
+
payload[:result] = @adapter.remove(feature)
|
63
67
|
}
|
64
68
|
end
|
65
69
|
|
@@ -67,12 +71,12 @@ module Flipper
|
|
67
71
|
def clear(feature)
|
68
72
|
payload = {
|
69
73
|
:operation => :clear,
|
70
|
-
:adapter_name => name,
|
74
|
+
:adapter_name => @adapter.name,
|
71
75
|
:feature_name => feature.name,
|
72
76
|
}
|
73
77
|
|
74
78
|
@instrumenter.instrument(InstrumentationName, payload) { |payload|
|
75
|
-
payload[:result] =
|
79
|
+
payload[:result] = @adapter.clear(feature)
|
76
80
|
}
|
77
81
|
end
|
78
82
|
|
@@ -80,12 +84,12 @@ module Flipper
|
|
80
84
|
def get(feature)
|
81
85
|
payload = {
|
82
86
|
:operation => :get,
|
83
|
-
:adapter_name => name,
|
87
|
+
:adapter_name => @adapter.name,
|
84
88
|
:feature_name => feature.name,
|
85
89
|
}
|
86
90
|
|
87
91
|
@instrumenter.instrument(InstrumentationName, payload) { |payload|
|
88
|
-
payload[:result] =
|
92
|
+
payload[:result] = @adapter.get(feature)
|
89
93
|
}
|
90
94
|
end
|
91
95
|
|
@@ -93,13 +97,13 @@ module Flipper
|
|
93
97
|
def enable(feature, gate, thing)
|
94
98
|
payload = {
|
95
99
|
:operation => :enable,
|
96
|
-
:adapter_name => name,
|
100
|
+
:adapter_name => @adapter.name,
|
97
101
|
:feature_name => feature.name,
|
98
102
|
:gate_name => gate.name,
|
99
103
|
}
|
100
104
|
|
101
105
|
@instrumenter.instrument(InstrumentationName, payload) { |payload|
|
102
|
-
payload[:result] =
|
106
|
+
payload[:result] = @adapter.enable(feature, gate, thing)
|
103
107
|
}
|
104
108
|
end
|
105
109
|
|
@@ -107,13 +111,13 @@ module Flipper
|
|
107
111
|
def disable(feature, gate, thing)
|
108
112
|
payload = {
|
109
113
|
:operation => :disable,
|
110
|
-
:adapter_name => name,
|
114
|
+
:adapter_name => @adapter.name,
|
111
115
|
:feature_name => feature.name,
|
112
116
|
:gate_name => gate.name,
|
113
117
|
}
|
114
118
|
|
115
119
|
@instrumenter.instrument(InstrumentationName, payload) { |payload|
|
116
|
-
payload[:result] =
|
120
|
+
payload[:result] = @adapter.disable(feature, gate, thing)
|
117
121
|
}
|
118
122
|
end
|
119
123
|
end
|
@@ -1,19 +1,26 @@
|
|
1
|
-
require 'flipper/adapters/decorator'
|
2
|
-
|
3
1
|
module Flipper
|
4
2
|
module Adapters
|
5
3
|
# Internal: Adapter that wraps another adapter with the ability to memoize
|
6
4
|
# adapter calls in memory. Used by flipper dsl and the memoizer middleware
|
7
5
|
# to make it possible to memoize adapter calls for the duration of a request.
|
8
|
-
class Memoizable
|
6
|
+
class Memoizable
|
7
|
+
include ::Flipper::Adapter
|
8
|
+
|
9
9
|
FeaturesKey = :flipper_features
|
10
10
|
|
11
11
|
# Internal
|
12
12
|
attr_reader :cache
|
13
13
|
|
14
|
+
# Public: The name of the adapter.
|
15
|
+
attr_reader :name
|
16
|
+
|
17
|
+
# Internal: The adapter this adapter is wrapping.
|
18
|
+
attr_reader :adapter
|
19
|
+
|
14
20
|
# Public
|
15
21
|
def initialize(adapter, cache = nil)
|
16
|
-
|
22
|
+
@adapter = adapter
|
23
|
+
@name = :memoizable
|
17
24
|
@cache = cache || {}
|
18
25
|
@memoize = false
|
19
26
|
end
|
@@ -22,23 +29,23 @@ module Flipper
|
|
22
29
|
def features
|
23
30
|
if memoizing?
|
24
31
|
cache.fetch(FeaturesKey) {
|
25
|
-
cache[FeaturesKey] =
|
32
|
+
cache[FeaturesKey] = @adapter.features
|
26
33
|
}
|
27
34
|
else
|
28
|
-
|
35
|
+
@adapter.features
|
29
36
|
end
|
30
37
|
end
|
31
38
|
|
32
39
|
# Public
|
33
40
|
def add(feature)
|
34
|
-
result =
|
41
|
+
result = @adapter.add(feature)
|
35
42
|
cache.delete(FeaturesKey) if memoizing?
|
36
43
|
result
|
37
44
|
end
|
38
45
|
|
39
46
|
# Public
|
40
47
|
def remove(feature)
|
41
|
-
result =
|
48
|
+
result = @adapter.remove(feature)
|
42
49
|
if memoizing?
|
43
50
|
cache.delete(FeaturesKey)
|
44
51
|
cache.delete(feature)
|
@@ -48,7 +55,7 @@ module Flipper
|
|
48
55
|
|
49
56
|
# Public
|
50
57
|
def clear(feature)
|
51
|
-
result =
|
58
|
+
result = @adapter.clear(feature)
|
52
59
|
cache.delete(feature) if memoizing?
|
53
60
|
result
|
54
61
|
end
|
@@ -56,22 +63,22 @@ module Flipper
|
|
56
63
|
# Public
|
57
64
|
def get(feature)
|
58
65
|
if memoizing?
|
59
|
-
cache.fetch(feature) { cache[feature] =
|
66
|
+
cache.fetch(feature) { cache[feature] = @adapter.get(feature) }
|
60
67
|
else
|
61
|
-
|
68
|
+
@adapter.get(feature)
|
62
69
|
end
|
63
70
|
end
|
64
71
|
|
65
72
|
# Public
|
66
73
|
def enable(feature, gate, thing)
|
67
|
-
result =
|
74
|
+
result = @adapter.enable(feature, gate, thing)
|
68
75
|
cache.delete(feature) if memoizing?
|
69
76
|
result
|
70
77
|
end
|
71
78
|
|
72
79
|
# Public
|
73
80
|
def disable(feature, gate, thing)
|
74
|
-
result =
|
81
|
+
result = @adapter.disable(feature, gate, thing)
|
75
82
|
cache.delete(feature) if memoizing?
|
76
83
|
result
|
77
84
|
end
|
@@ -1,11 +1,11 @@
|
|
1
|
-
require 'flipper/adapters/decorator'
|
2
|
-
|
3
1
|
module Flipper
|
4
2
|
module Adapters
|
5
3
|
# Public: Adapter that wraps another adapter and stores the operations.
|
6
4
|
#
|
7
5
|
# Useful in tests to verify calls and such. Never use outside of testing.
|
8
|
-
class OperationLogger
|
6
|
+
class OperationLogger
|
7
|
+
include ::Flipper::Adapter
|
8
|
+
|
9
9
|
Operation = Struct.new(:type, :args)
|
10
10
|
|
11
11
|
OperationTypes = [
|
@@ -21,20 +21,57 @@ module Flipper
|
|
21
21
|
# Internal: An array of the operations that have happened.
|
22
22
|
attr_reader :operations
|
23
23
|
|
24
|
+
# Internal: The name of the adapter.
|
25
|
+
attr_reader :name
|
26
|
+
|
24
27
|
# Public
|
25
28
|
def initialize(adapter, operations = nil)
|
26
|
-
|
29
|
+
@adapter = adapter
|
30
|
+
@name = :operation_logger
|
27
31
|
@operations = operations || []
|
28
32
|
end
|
29
33
|
|
30
|
-
#
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
# Public: The set of known features.
|
35
|
+
def features
|
36
|
+
@operations << Operation.new(:features, [])
|
37
|
+
@adapter.features
|
38
|
+
end
|
39
|
+
|
40
|
+
# Public: Adds a feature to the set of known features.
|
41
|
+
def add(feature)
|
42
|
+
@operations << Operation.new(:add, [feature])
|
43
|
+
@adapter.add(feature)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Public: Removes a feature from the set of known features and clears
|
47
|
+
# all the values for the feature.
|
48
|
+
def remove(feature)
|
49
|
+
@operations << Operation.new(:remove, [feature])
|
50
|
+
@adapter.remove(feature)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Public: Clears all the gate values for a feature.
|
54
|
+
def clear(feature)
|
55
|
+
@operations << Operation.new(:clear, [feature])
|
56
|
+
@adapter.clear(feature)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Public
|
60
|
+
def get(feature)
|
61
|
+
@operations << Operation.new(:get, [feature])
|
62
|
+
@adapter.get(feature)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Public
|
66
|
+
def enable(feature, gate, thing)
|
67
|
+
@operations << Operation.new(:enable, [feature, gate, thing])
|
68
|
+
@adapter.enable(feature, gate, thing)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Public
|
72
|
+
def disable(feature, gate, thing)
|
73
|
+
@operations << Operation.new(:disable, [feature, gate, thing])
|
74
|
+
@adapter.disable(feature, gate, thing)
|
38
75
|
end
|
39
76
|
|
40
77
|
# Public: Count the number of times a certain operation happened.
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Flipper
|
2
|
+
module Adapters
|
3
|
+
# Public: Adapter that wraps another adapter and raises for any writes.
|
4
|
+
class ReadOnly
|
5
|
+
include ::Flipper::Adapter
|
6
|
+
|
7
|
+
class WriteAttempted < Error
|
8
|
+
def initialize(message = nil)
|
9
|
+
super(message || "write attempted while in read only mode")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Internal: The name of the adapter.
|
14
|
+
attr_reader :name
|
15
|
+
|
16
|
+
# Public
|
17
|
+
def initialize(adapter)
|
18
|
+
@adapter = adapter
|
19
|
+
@name = :read_only
|
20
|
+
end
|
21
|
+
|
22
|
+
def features
|
23
|
+
@adapter.features
|
24
|
+
end
|
25
|
+
|
26
|
+
def get(feature)
|
27
|
+
@adapter.get(feature)
|
28
|
+
end
|
29
|
+
|
30
|
+
def add(feature)
|
31
|
+
raise WriteAttempted
|
32
|
+
end
|
33
|
+
|
34
|
+
def remove(feature)
|
35
|
+
raise WriteAttempted
|
36
|
+
end
|
37
|
+
|
38
|
+
def clear(feature)
|
39
|
+
raise WriteAttempted
|
40
|
+
end
|
41
|
+
|
42
|
+
def enable(feature, gate, thing)
|
43
|
+
raise WriteAttempted
|
44
|
+
end
|
45
|
+
|
46
|
+
def disable(feature, gate, thing)
|
47
|
+
raise WriteAttempted
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|