flipper 0.11.0.beta6 → 0.11.0.beta7
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 +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
|