flipper 0.11.0.beta6 → 0.11.0.beta7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +2 -0
- data/README.md +18 -10
- data/docs/Adapters.md +1 -0
- data/docs/Optimization.md +31 -0
- data/examples/basic.rb +21 -17
- data/examples/configuring_default.rb +23 -0
- data/examples/percentage_of_actors.rb +3 -4
- data/examples/percentage_of_time.rb +3 -3
- data/flipper-cache-store.gemspec +24 -0
- data/lib/flipper.rb +59 -9
- data/lib/flipper/configuration.rb +32 -0
- data/lib/flipper/dsl.rb +0 -1
- data/lib/flipper/errors.rb +9 -2
- data/lib/flipper/gate_values.rb +2 -2
- data/lib/flipper/gates/group.rb +2 -6
- data/lib/flipper/gates/percentage_of_actors.rb +3 -1
- data/lib/flipper/typecast.rb +24 -0
- data/lib/flipper/types/percentage.rb +1 -1
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/configuration_spec.rb +16 -0
- data/spec/flipper/dsl_spec.rb +18 -0
- data/spec/flipper/gates/percentage_of_actors_spec.rb +29 -7
- data/spec/flipper/gates/percentage_of_time_spec.rb +30 -0
- data/spec/flipper/typecast_spec.rb +55 -0
- data/spec/flipper_spec.rb +200 -30
- data/spec/helper.rb +1 -0
- data/spec/integration_spec.rb +68 -3
- metadata +7 -5
- data/lib/flipper/adapters/cache_store.rb +0 -104
- data/spec/flipper/adapters/cache_store_spec.rb +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 258c0cfa4877a2bea9e7927f5d39ed1339c3f698
|
4
|
+
data.tar.gz: 4402f1aa545f09f2bf0dd97937d6e7564ec6d008
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7596ed84b459acc7b052a11e7a86e5be07d4bc52b083e31dcd14749b56e026f7a6ee0a6b3aac50fdc61950afcc764bdf64834bcecd4668baadc62e06a60d248b
|
7
|
+
data.tar.gz: 18a608871470cd7ae57db9b4dccf661e9ea8fe68e55efced9fea8cec26b9374a9c528b324516da9f263cad8cd4da28d697501f321d8f9a2b0280a351e1ed057c
|
data/Changelog.md
CHANGED
@@ -23,6 +23,8 @@
|
|
23
23
|
* Allow setting debug output of http adapter (https://github.com/jnunemaker/flipper/pull/256 and https://github.com/jnunemaker/flipper/pull/258).
|
24
24
|
* Allow setting env key for middleware (https://github.com/jnunemaker/flipper/pull/259).
|
25
25
|
* Added ActiveSupport cache store adapter for use with Rails.cache (https://github.com/jnunemaker/flipper/pull/265).
|
26
|
+
* Added support for up to 3 decimal places in percentage based rollouts (https://github.com/jnunemaker/flipper/pull/274).
|
27
|
+
* Removed Flipper::GroupNotRegistered error as it is now unused (https://github.com/jnunemaker/flipper/pull/270).
|
26
28
|
|
27
29
|
## 0.10.2
|
28
30
|
|
data/README.md
CHANGED
@@ -41,26 +41,34 @@ The goal of the API for flipper was to have everything revolve around features a
|
|
41
41
|
|
42
42
|
```ruby
|
43
43
|
require 'flipper'
|
44
|
-
|
45
|
-
# pick an adapter
|
46
44
|
require 'flipper/adapters/memory'
|
47
|
-
adapter = Flipper::Adapters::Memory.new
|
48
45
|
|
49
|
-
|
50
|
-
|
46
|
+
Flipper.configure do |config|
|
47
|
+
config.default do
|
48
|
+
# pick an adapter, this uses memory, any will do
|
49
|
+
adapter = Flipper::Adapters::Memory.new
|
51
50
|
|
52
|
-
#
|
53
|
-
|
51
|
+
# pass adapter to handy DSL instance
|
52
|
+
Flipper.new(adapter)
|
53
|
+
end
|
54
|
+
end
|
54
55
|
|
55
|
-
# check if
|
56
|
-
if
|
56
|
+
# check if search is enabled
|
57
|
+
if Flipper.enabled?(:search)
|
57
58
|
puts 'Search away!'
|
58
59
|
else
|
59
60
|
puts 'No search for you!'
|
60
61
|
end
|
61
62
|
|
62
63
|
puts 'Enabling Search...'
|
63
|
-
|
64
|
+
Flipper.enable(:search)
|
65
|
+
|
66
|
+
# check if search is enabled
|
67
|
+
if Flipper.enabled?(:search)
|
68
|
+
puts 'Search away!'
|
69
|
+
else
|
70
|
+
puts 'No search for you!'
|
71
|
+
end
|
64
72
|
```
|
65
73
|
|
66
74
|
Of course there are more [examples for you to peruse](examples/). You could also check out the [DSL](lib/flipper/dsl.rb) and [Feature](lib/flipper/feature.rb) classes for code/docs.
|
data/docs/Adapters.md
CHANGED
@@ -5,6 +5,7 @@ I plan on supporting the adapters in the flipper repo. Other adapters are welcom
|
|
5
5
|
## Officially Supported
|
6
6
|
|
7
7
|
* [ActiveRecord adapter](https://github.com/jnunemaker/flipper/blob/master/docs/active_record) - Rails 3, 4, and 5.
|
8
|
+
* [CacheStore adapter](https://github.com/jnunemaker/flipper/blob/master/docs/cache_store) - ActiveSupport::Cache::Store
|
8
9
|
* [Cassanity adapter](https://github.com/jnunemaker/flipper-cassanity)
|
9
10
|
* [Http adapter](https://github.com/jnunemaker/flipper/blob/master/docs/http)
|
10
11
|
* [memory adapter](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adapters/memory.rb) – great for tests
|
data/docs/Optimization.md
CHANGED
@@ -95,3 +95,34 @@ Example using the RedisCache adapter with the Memory adapter and a TTL of 4800 s
|
|
95
95
|
adapter = Flipper::Adapters::RedisCache.new(memory_adapter, redis, 4800)
|
96
96
|
flipper = Flipper.new(adapter)
|
97
97
|
```
|
98
|
+
|
99
|
+
### CacheStore
|
100
|
+
|
101
|
+
Rails applications can cache Flipper calls in any [ActiveSupport::Cache::Store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html) implementation.
|
102
|
+
|
103
|
+
Add this line to your application's Gemfile:
|
104
|
+
|
105
|
+
gem 'flipper-cache-store'
|
106
|
+
|
107
|
+
And then execute:
|
108
|
+
|
109
|
+
$ bundle
|
110
|
+
|
111
|
+
Or install it yourself with:
|
112
|
+
|
113
|
+
$ gem install flipper-cache-store
|
114
|
+
|
115
|
+
Example using the CacheStore adapter with ActiveSupport's [MemoryStore](http://api.rubyonrails.org/classes/ActiveSupport/Cache/MemoryStore.html), Flipper's [Memory adapter](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adapters/memory.rb), and a TTL of 5 minutes.
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
require 'active_support/cache'
|
119
|
+
require 'flipper/adapters/memory'
|
120
|
+
require 'flipper/adapters/cache_store'
|
121
|
+
|
122
|
+
memory_adapter = Flipper::Adapters::Memory.new
|
123
|
+
cache = ActiveSupport::Cache::MemoryStore.new
|
124
|
+
adapter = Flipper::Adapters::CacheStore.new(memory_adapter, cache, expires_in: 5.minutes)
|
125
|
+
flipper = Flipper.new(adapter)
|
126
|
+
```
|
127
|
+
|
128
|
+
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/examples/basic.rb
CHANGED
@@ -3,25 +3,29 @@ require File.expand_path('../example_setup', __FILE__)
|
|
3
3
|
require 'flipper'
|
4
4
|
require 'flipper/adapters/memory'
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
Flipper.configure do |config|
|
7
|
+
config.default do
|
8
|
+
# pick an adapter, this uses memory, any will do
|
9
|
+
adapter = Flipper::Adapters::Memory.new
|
8
10
|
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
# grab a feature
|
13
|
-
search = flipper[:search]
|
14
|
-
|
15
|
-
perform = lambda do
|
16
|
-
# check if that feature is enabled
|
17
|
-
if search.enabled?
|
18
|
-
puts 'Search away!'
|
19
|
-
else
|
20
|
-
puts 'No search for you!'
|
11
|
+
# pass adapter to handy DSL instance
|
12
|
+
Flipper.new(adapter)
|
21
13
|
end
|
22
14
|
end
|
23
15
|
|
24
|
-
|
16
|
+
# check if search is enabled
|
17
|
+
if Flipper.enabled?(:search)
|
18
|
+
puts 'Search away!'
|
19
|
+
else
|
20
|
+
puts 'No search for you!'
|
21
|
+
end
|
22
|
+
|
25
23
|
puts 'Enabling Search...'
|
26
|
-
|
27
|
-
|
24
|
+
Flipper.enable(:search)
|
25
|
+
|
26
|
+
# check if search is enabled
|
27
|
+
if Flipper.enabled?(:search)
|
28
|
+
puts 'Search away!'
|
29
|
+
else
|
30
|
+
puts 'No search for you!'
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path('../example_setup', __FILE__)
|
2
|
+
|
3
|
+
require 'flipper'
|
4
|
+
require 'flipper/adapters/memory'
|
5
|
+
|
6
|
+
# sets up default adapter so Flipper works like Flipper::DSL
|
7
|
+
Flipper.configure do |config|
|
8
|
+
config.default do
|
9
|
+
Flipper.new Flipper::Adapters::Memory.new
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
puts Flipper.enabled?(:search) # => false
|
14
|
+
Flipper.enable(:search)
|
15
|
+
puts Flipper.enabled?(:search) # => true
|
16
|
+
Flipper.disable(:search)
|
17
|
+
|
18
|
+
enabled_actor = Flipper::Actor.new("1")
|
19
|
+
disabled_actor = Flipper::Actor.new("2")
|
20
|
+
Flipper.enable_actor(:search, enabled_actor)
|
21
|
+
|
22
|
+
puts Flipper.enabled?(:search, enabled_actor)
|
23
|
+
puts Flipper.enabled?(:search, disabled_actor)
|
@@ -19,7 +19,7 @@ class User
|
|
19
19
|
alias_method :flipper_id, :id
|
20
20
|
end
|
21
21
|
|
22
|
-
total =
|
22
|
+
total = 100_000
|
23
23
|
|
24
24
|
# create array of fake users
|
25
25
|
users = (1..total).map { |n| User.new(n) }
|
@@ -31,13 +31,12 @@ perform_test = lambda { |number|
|
|
31
31
|
flipper[:stats].enabled?(user) ? true : nil
|
32
32
|
}.compact
|
33
33
|
|
34
|
-
actual = (enabled.size / total.to_f * 100).round(
|
34
|
+
actual = (enabled.size / total.to_f * 100).round(3)
|
35
35
|
|
36
36
|
puts "percentage: #{actual.to_s.rjust(6, ' ')} vs #{number.to_s.rjust(3, ' ')}"
|
37
37
|
}
|
38
38
|
|
39
39
|
puts "percentage: Actual vs Hoped For"
|
40
|
-
|
41
|
-
[1, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 100].each do |number|
|
40
|
+
[0.001, 0.01, 0.1, 1, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 100].each do |number|
|
42
41
|
perform_test.call number
|
43
42
|
end
|
@@ -10,7 +10,7 @@ logging = flipper[:logging]
|
|
10
10
|
perform_test = lambda do |number|
|
11
11
|
logging.enable flipper.time(number)
|
12
12
|
|
13
|
-
total =
|
13
|
+
total = 100_000
|
14
14
|
enabled = []
|
15
15
|
disabled = []
|
16
16
|
|
@@ -18,7 +18,7 @@ perform_test = lambda do |number|
|
|
18
18
|
logging.enabled? ? true : nil
|
19
19
|
}.compact
|
20
20
|
|
21
|
-
actual = (enabled.size / total.to_f * 100).round(
|
21
|
+
actual = (enabled.size / total.to_f * 100).round(3)
|
22
22
|
|
23
23
|
# puts "#{enabled.size} / #{total}"
|
24
24
|
puts "percentage: #{actual.to_s.rjust(6, ' ')} vs #{number.to_s.rjust(3, ' ')}"
|
@@ -26,6 +26,6 @@ end
|
|
26
26
|
|
27
27
|
puts "percentage: Actual vs Hoped For"
|
28
28
|
|
29
|
-
[1, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 100].each do |number|
|
29
|
+
[0.001, 0.01, 0.1, 1, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 100].each do |number|
|
30
30
|
perform_test.call number
|
31
31
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/flipper/version', __FILE__)
|
3
|
+
|
4
|
+
flipper_cache_store_files = lambda do |file|
|
5
|
+
file =~ /cache_store/
|
6
|
+
end
|
7
|
+
|
8
|
+
Gem::Specification.new do |gem|
|
9
|
+
gem.authors = ['John Nunemaker']
|
10
|
+
gem.email = ['nunemaker@gmail.com']
|
11
|
+
gem.summary = 'ActiveSupport::Cache::Store adapter for Flipper'
|
12
|
+
gem.description = 'ActiveSupport::Cache::Store adapter for Flipper'
|
13
|
+
gem.license = 'MIT'
|
14
|
+
gem.homepage = 'https://github.com/jnunemaker/flipper'
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split("\n").select(&flipper_cache_store_files) + ['lib/flipper/version.rb']
|
17
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n").select(&flipper_cache_store_files)
|
18
|
+
gem.name = 'flipper-cache-store'
|
19
|
+
gem.require_paths = ['lib']
|
20
|
+
gem.version = Flipper::VERSION
|
21
|
+
|
22
|
+
gem.add_dependency 'flipper', "~> #{Flipper::VERSION}"
|
23
|
+
gem.add_dependency 'activesupport', '>= 3.2', '< 6'
|
24
|
+
end
|
data/lib/flipper.rb
CHANGED
@@ -1,13 +1,62 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
1
3
|
module Flipper
|
4
|
+
extend self # rubocop:disable Style/ModuleFunction
|
5
|
+
extend Forwardable
|
6
|
+
|
2
7
|
# Private: The namespace for all instrumented events.
|
3
8
|
InstrumentationNamespace = :flipper
|
4
9
|
|
5
10
|
# Public: Start here. Given an adapter returns a handy DSL to all the flipper
|
6
11
|
# goodness. To see supported options, check out dsl.rb.
|
7
|
-
def
|
12
|
+
def new(adapter, options = {})
|
8
13
|
DSL.new(adapter, options)
|
9
14
|
end
|
10
15
|
|
16
|
+
# Public: Configure flipper.
|
17
|
+
#
|
18
|
+
# Flipper.configure do |config|
|
19
|
+
# config.default { ... }
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# Yields Flipper::Configuration instance.
|
23
|
+
def configure
|
24
|
+
yield configuration if block_given?
|
25
|
+
end
|
26
|
+
|
27
|
+
# Public: Returns Flipper::Configuration instance.
|
28
|
+
def configuration
|
29
|
+
@configuration ||= Configuration.new
|
30
|
+
end
|
31
|
+
|
32
|
+
# Public: Sets Flipper::Configuration instance.
|
33
|
+
def configuration=(configuration)
|
34
|
+
@configuration = configuration
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Default per thread flipper instance if configured. You should not
|
38
|
+
# need to use this directly as most of the Flipper::DSL methods are delegated
|
39
|
+
# from Flipper module itself. Instead of doing Flipper.instance.enabled?(:search),
|
40
|
+
# you can use Flipper.enabled?(:search) for the same result.
|
41
|
+
#
|
42
|
+
# Returns Flipper::DSL instance.
|
43
|
+
def instance
|
44
|
+
Thread.current[:flipper_instance] ||= configuration.default
|
45
|
+
end
|
46
|
+
|
47
|
+
# Public: All the methods delegated to instance. These should match the
|
48
|
+
# interface of Flipper::DSL.
|
49
|
+
def_delegators :instance,
|
50
|
+
:enabled?, :enable, :disable, :bool, :boolean,
|
51
|
+
:enable_actor, :disable_actor, :actor,
|
52
|
+
:enable_group, :disable_group,
|
53
|
+
:enable_percentage_of_actors, :disable_percentage_of_actors,
|
54
|
+
:actors, :percentage_of_actors,
|
55
|
+
:enable_percentage_of_time, :disable_percentage_of_time,
|
56
|
+
:time, :percentage_of_time,
|
57
|
+
:features, :feature, :[], :preload, :preload_all,
|
58
|
+
:add, :remove, :import
|
59
|
+
|
11
60
|
# Public: Use this to register a group by name.
|
12
61
|
#
|
13
62
|
# name - The Symbol name of the group.
|
@@ -22,7 +71,7 @@ module Flipper
|
|
22
71
|
#
|
23
72
|
# Returns a Flipper::Group.
|
24
73
|
# Raises Flipper::DuplicateGroup if the group is already registered.
|
25
|
-
def
|
74
|
+
def register(name, &block)
|
26
75
|
group = Types::Group.new(name, &block)
|
27
76
|
groups_registry.add(group.name, group)
|
28
77
|
group
|
@@ -31,28 +80,28 @@ module Flipper
|
|
31
80
|
end
|
32
81
|
|
33
82
|
# Public: Returns a Set of registered Types::Group instances.
|
34
|
-
def
|
83
|
+
def groups
|
35
84
|
groups_registry.values.to_set
|
36
85
|
end
|
37
86
|
|
38
87
|
# Public: Returns a Set of symbols where each symbol is a registered
|
39
88
|
# group name. If you just want the names, this is more efficient than doing
|
40
89
|
# `Flipper.groups.map(&:name)`.
|
41
|
-
def
|
90
|
+
def group_names
|
42
91
|
groups_registry.keys.to_set
|
43
92
|
end
|
44
93
|
|
45
94
|
# Public: Clears the group registry.
|
46
95
|
#
|
47
96
|
# Returns nothing.
|
48
|
-
def
|
97
|
+
def unregister_groups
|
49
98
|
groups_registry.clear
|
50
99
|
end
|
51
100
|
|
52
101
|
# Public: Check if a group exists
|
53
102
|
#
|
54
103
|
# Returns boolean
|
55
|
-
def
|
104
|
+
def group_exists?(name)
|
56
105
|
groups_registry.key?(name)
|
57
106
|
end
|
58
107
|
|
@@ -65,22 +114,23 @@ module Flipper
|
|
65
114
|
# Flipper.group(:admins)
|
66
115
|
#
|
67
116
|
# Returns Flipper::Group.
|
68
|
-
def
|
117
|
+
def group(name)
|
69
118
|
groups_registry.get(name) || Types::Group.new(name)
|
70
119
|
end
|
71
120
|
|
72
121
|
# Internal: Registry of all groups_registry.
|
73
|
-
def
|
122
|
+
def groups_registry
|
74
123
|
@groups_registry ||= Registry.new
|
75
124
|
end
|
76
125
|
|
77
126
|
# Internal: Change the groups_registry registry.
|
78
|
-
def
|
127
|
+
def groups_registry=(registry)
|
79
128
|
@groups_registry = registry
|
80
129
|
end
|
81
130
|
end
|
82
131
|
|
83
132
|
require 'flipper/actor'
|
133
|
+
require 'flipper/configuration'
|
84
134
|
require 'flipper/adapter'
|
85
135
|
require 'flipper/dsl'
|
86
136
|
require 'flipper/errors'
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Flipper
|
2
|
+
class Configuration
|
3
|
+
def initialize
|
4
|
+
@default = -> { raise DefaultNotSet }
|
5
|
+
end
|
6
|
+
|
7
|
+
# Controls the default instance for flipper. When used with a block it
|
8
|
+
# assigns a new default block to use to generate an instance. When used
|
9
|
+
# without a block, it performs a block invocation and returns the result.
|
10
|
+
#
|
11
|
+
# configuration = Flipper::Configuration.new
|
12
|
+
# configuration.default # => raises DefaultNotSet error.
|
13
|
+
#
|
14
|
+
# # sets the default block to generate a new instance using Memory adapter
|
15
|
+
# configuration.default do
|
16
|
+
# require "flipper/adapters/memory"
|
17
|
+
# Flipper.new(Flipper::Adapters::Memory.new)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# configuration.default # => Flipper::DSL instance using Memory adapter
|
21
|
+
#
|
22
|
+
# Returns result of default block invocation if called without block. If
|
23
|
+
# called with block, assigns the default block.
|
24
|
+
def default(&block)
|
25
|
+
if block_given?
|
26
|
+
@default = block
|
27
|
+
else
|
28
|
+
@default.call
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/flipper/dsl.rb
CHANGED
data/lib/flipper/errors.rb
CHANGED
@@ -12,6 +12,13 @@ module Flipper
|
|
12
12
|
# Raised when attempting to declare a group name that has already been used.
|
13
13
|
class DuplicateGroup < Error; end
|
14
14
|
|
15
|
-
# Raised when
|
16
|
-
|
15
|
+
# Raised when default instance not configured but there is an attempt to
|
16
|
+
# use it.
|
17
|
+
class DefaultNotSet < Flipper::Error
|
18
|
+
def initialize(message = nil)
|
19
|
+
default = "Default flipper instance not configured. See " \
|
20
|
+
"Flipper.configure for how to configure the default instance."
|
21
|
+
super(message || default)
|
22
|
+
end
|
23
|
+
end
|
17
24
|
end
|