feature_flagger 1.2.0 → 2.1.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/README.md +44 -1
- data/lib/feature_flagger.rb +8 -1
- data/lib/feature_flagger/configuration.rb +10 -1
- data/lib/feature_flagger/control.rb +39 -13
- data/lib/feature_flagger/manager.rb +1 -1
- data/lib/feature_flagger/model.rb +12 -12
- data/lib/feature_flagger/notifier.rb +45 -0
- data/lib/feature_flagger/storage/feature_keys_migration.rb +3 -1
- data/lib/feature_flagger/storage/keys.rb +0 -1
- data/lib/feature_flagger/storage/redis.rb +3 -1
- data/lib/feature_flagger/version.rb +1 -1
- metadata +23 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d02210f5b573bd3ebb01a75794885b729981a740412bf045b713a8d9db37a75
|
4
|
+
data.tar.gz: cd9f4ab0e6058029e8a5cab354d98a0b676363a05582169f52d90578c2613b37
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d10511abc507466990deaae846843474ebeb9e3ace09e61b2e08bead320a26849f1e8cf04b8ecc52a91de7e7e01348435e5d92332dd98ceea878866da3548be6
|
7
|
+
data.tar.gz: 3c3564c0c34b2ed86552637a47b4af544f66c22100d31d7b89e54d22316e7fa4049118f7973fe1cdf05c837ce0668aa163720fe8bebb43f86011c1d3e9abeeda
|
data/README.md
CHANGED
@@ -28,7 +28,6 @@ Or install it yourself as:
|
|
28
28
|
|
29
29
|
$ gem install feature_flagger
|
30
30
|
|
31
|
-
|
32
31
|
## Configuration
|
33
32
|
|
34
33
|
By default, feature_flagger uses the REDIS_URL env var to setup it's storage.
|
@@ -44,6 +43,15 @@ FeatureFlagger.configure do |config|
|
|
44
43
|
end
|
45
44
|
```
|
46
45
|
|
46
|
+
It's also possible to configure an additional cache layer by using ActiveSupport::Cache APIs. You can configure it the same way you would setup cache_store for Rails Apps. Caching is not enabled by default.
|
47
|
+
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
configuration.cache_store = :memory_store, { expires_in: 100 }
|
51
|
+
|
52
|
+
```
|
53
|
+
|
54
|
+
|
47
55
|
1. Create a `rollout.yml` in _config_ path and declare a rollout:
|
48
56
|
```yml
|
49
57
|
account: # model name
|
@@ -60,6 +68,29 @@ class Account < ActiveRecord::Base
|
|
60
68
|
# ....
|
61
69
|
end
|
62
70
|
```
|
71
|
+
#### Notifier
|
72
|
+
The notifier_callback property in config, enables the dispatch of events when a release operation happens.
|
73
|
+
```ruby
|
74
|
+
config.notifier_callback = -> {|event| do something with event }
|
75
|
+
```
|
76
|
+
|
77
|
+
|
78
|
+
It accepts a lambda function that will receive a hash with the operation triggered like:
|
79
|
+
```ruby
|
80
|
+
{
|
81
|
+
type: 'release',
|
82
|
+
model: 'account',
|
83
|
+
key: 'somefeature:somerolloutkey'
|
84
|
+
id: 'account_id' #In realease_to_all and unrelease_to_all operations id will be nil
|
85
|
+
}
|
86
|
+
```
|
87
|
+
|
88
|
+
The supported operations are:
|
89
|
+
* release
|
90
|
+
* unrelease
|
91
|
+
* release_to_all
|
92
|
+
* unrelease_to_all
|
93
|
+
|
63
94
|
|
64
95
|
## Usage
|
65
96
|
|
@@ -74,6 +105,10 @@ account.release(:email_marketing, :new_email_flow)
|
|
74
105
|
account.released?(:email_marketing, :new_email_flow)
|
75
106
|
#=> true
|
76
107
|
|
108
|
+
# In order to bypass the cache if cache_store is configured
|
109
|
+
account.released?(:email_marketing, :new_email_flow, skip_cache: true)
|
110
|
+
#=> true
|
111
|
+
|
77
112
|
# Remove feature for given account
|
78
113
|
account.unrelease(:email_marketing, :new_email_flow)
|
79
114
|
#=> true
|
@@ -86,6 +121,10 @@ FeatureFlagger::KeyNotFoundError: ["account", "email_marketing", "new_email_flo"
|
|
86
121
|
Account.released_id?(42, :email_marketing, :new_email_flow)
|
87
122
|
#=> true
|
88
123
|
|
124
|
+
# In order to bypass the cache if cache_store is configured
|
125
|
+
Account.released_id?(42, :email_marketing, :new_email_flow, skip_cache: true)
|
126
|
+
#=> true
|
127
|
+
|
89
128
|
# Release a feature for a specific account id
|
90
129
|
Account.release_id(42, :email_marketing, :new_email_flow)
|
91
130
|
#=> true
|
@@ -101,6 +140,10 @@ Account.unrelease_to_all(:email_marketing, :new_email_flow)
|
|
101
140
|
|
102
141
|
# Return an array with all features released for all
|
103
142
|
Account.released_features_to_all
|
143
|
+
|
144
|
+
# In order to bypass the cache if cache_store is configured
|
145
|
+
Account.released_features_to_all(skip_cache: true)
|
146
|
+
|
104
147
|
```
|
105
148
|
|
106
149
|
## Clean up action
|
data/lib/feature_flagger.rb
CHANGED
@@ -2,6 +2,7 @@ require 'yaml'
|
|
2
2
|
|
3
3
|
require 'feature_flagger/version'
|
4
4
|
require 'feature_flagger/storage/redis'
|
5
|
+
require 'feature_flagger/storage/feature_keys_migration'
|
5
6
|
require 'feature_flagger/control'
|
6
7
|
require 'feature_flagger/model'
|
7
8
|
require 'feature_flagger/model_settings'
|
@@ -9,12 +10,14 @@ require 'feature_flagger/feature'
|
|
9
10
|
require 'feature_flagger/configuration'
|
10
11
|
require 'feature_flagger/manager'
|
11
12
|
require 'feature_flagger/railtie'
|
13
|
+
require 'feature_flagger/notifier'
|
12
14
|
|
13
15
|
module FeatureFlagger
|
14
16
|
class << self
|
15
17
|
def configure
|
16
18
|
@@configuration = nil
|
17
19
|
@@control = nil
|
20
|
+
@@notifier = nil
|
18
21
|
yield config if block_given?
|
19
22
|
end
|
20
23
|
|
@@ -22,8 +25,12 @@ module FeatureFlagger
|
|
22
25
|
@@configuration ||= Configuration.new
|
23
26
|
end
|
24
27
|
|
28
|
+
def notifier
|
29
|
+
@@notifier ||= Notifier.new(config.notifier_callback)
|
30
|
+
end
|
31
|
+
|
25
32
|
def control
|
26
|
-
@@control ||= Control.new(config.storage)
|
33
|
+
@@control ||= Control.new(config.storage, notifier, config.cache_store)
|
27
34
|
end
|
28
35
|
end
|
29
36
|
end
|
@@ -1,10 +1,19 @@
|
|
1
1
|
module FeatureFlagger
|
2
2
|
class Configuration
|
3
|
-
attr_accessor :storage, :yaml_filepath
|
3
|
+
attr_accessor :storage, :cache_store, :yaml_filepath, :notifier_callback
|
4
4
|
|
5
5
|
def initialize
|
6
6
|
@storage ||= Storage::Redis.default_client
|
7
7
|
@yaml_filepath ||= default_yaml_filepath
|
8
|
+
@notifier_callback = nil
|
9
|
+
@cache_store = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def cache_store=(cache_store)
|
13
|
+
raise ArgumentError, "Cache is only support when used with ActiveSupport" unless defined?(ActiveSupport)
|
14
|
+
|
15
|
+
cache_store = :null_store if cache_store.nil?
|
16
|
+
@cache_store = ActiveSupport::Cache.lookup_store(*cache_store)
|
8
17
|
end
|
9
18
|
|
10
19
|
def info
|
@@ -4,12 +4,16 @@ module FeatureFlagger
|
|
4
4
|
|
5
5
|
RELEASED_FEATURES = 'released_features'
|
6
6
|
|
7
|
-
def initialize(storage)
|
7
|
+
def initialize(storage, notifier, cache_store = nil)
|
8
8
|
@storage = storage
|
9
|
+
@notifier = notifier
|
10
|
+
@cache_store = cache_store
|
9
11
|
end
|
10
12
|
|
11
|
-
def released?(feature_key, resource_id)
|
12
|
-
|
13
|
+
def released?(feature_key, resource_id, options = {})
|
14
|
+
cache "released/#{feature_key}/#{resource_id}", options do
|
15
|
+
@storage.has_value?(RELEASED_FEATURES, feature_key) || @storage.has_value?(feature_key, resource_id)
|
16
|
+
end
|
13
17
|
end
|
14
18
|
|
15
19
|
def release(feature_key, resource_id)
|
@@ -17,14 +21,18 @@ module FeatureFlagger
|
|
17
21
|
feature_key
|
18
22
|
)
|
19
23
|
|
24
|
+
@notifier.send(FeatureFlagger::Notifier::RELEASE, feature_key, resource_id)
|
20
25
|
@storage.add(feature_key, resource_name, resource_id)
|
21
26
|
end
|
22
27
|
|
23
|
-
def releases(resource_name, resource_id)
|
24
|
-
|
28
|
+
def releases(resource_name, resource_id, options = {})
|
29
|
+
cache "releases/#{resource_name}/#{resource_id}", options do
|
30
|
+
@storage.fetch_releases(resource_name, resource_id, RELEASED_FEATURES)
|
31
|
+
end
|
25
32
|
end
|
26
33
|
|
27
34
|
def release_to_all(feature_key)
|
35
|
+
@notifier.send(FeatureFlagger::Notifier::RELEASE_TO_ALL, feature_key)
|
28
36
|
@storage.add_all(RELEASED_FEATURES, feature_key)
|
29
37
|
end
|
30
38
|
|
@@ -32,24 +40,31 @@ module FeatureFlagger
|
|
32
40
|
resource_name = Storage::Keys.extract_resource_name_from_feature_key(
|
33
41
|
feature_key
|
34
42
|
)
|
35
|
-
|
43
|
+
@notifier.send(FeatureFlagger::Notifier::UNRELEASE, feature_key, resource_id)
|
36
44
|
@storage.remove(feature_key, resource_name, resource_id)
|
37
45
|
end
|
38
46
|
|
39
47
|
def unrelease_to_all(feature_key)
|
48
|
+
@notifier.send(FeatureFlagger::Notifier::UNRELEASE_TO_ALL, feature_key)
|
40
49
|
@storage.remove_all(RELEASED_FEATURES, feature_key)
|
41
50
|
end
|
42
51
|
|
43
|
-
def resource_ids(feature_key)
|
44
|
-
|
52
|
+
def resource_ids(feature_key, options = {})
|
53
|
+
cache "all_values/#{feature_key}", options do
|
54
|
+
@storage.all_values(feature_key)
|
55
|
+
end
|
45
56
|
end
|
46
57
|
|
47
|
-
def released_features_to_all
|
48
|
-
|
58
|
+
def released_features_to_all(options = {})
|
59
|
+
cache "released_features_to_all/#{RELEASED_FEATURES}", options do
|
60
|
+
@storage.all_values(RELEASED_FEATURES)
|
61
|
+
end
|
49
62
|
end
|
50
63
|
|
51
|
-
def released_to_all?(feature_key)
|
52
|
-
|
64
|
+
def released_to_all?(feature_key, options = {})
|
65
|
+
cache "has_value/#{RELEASED_FEATURES}/#{feature_key}", options do
|
66
|
+
@storage.has_value?(RELEASED_FEATURES, feature_key)
|
67
|
+
end
|
53
68
|
end
|
54
69
|
|
55
70
|
# DEPRECATED: this method will be removed from public api on v2.0 version.
|
@@ -59,7 +74,18 @@ module FeatureFlagger
|
|
59
74
|
end
|
60
75
|
|
61
76
|
def feature_keys
|
62
|
-
@storage.feature_keys
|
77
|
+
@storage.feature_keys - [FeatureFlagger::Control::RELEASED_FEATURES]
|
78
|
+
end
|
79
|
+
|
80
|
+
def cache(name, options, &block)
|
81
|
+
if @cache_store
|
82
|
+
@cache_store.fetch(name, force: options[:skip_cache]) do
|
83
|
+
block.call
|
84
|
+
end
|
85
|
+
else
|
86
|
+
block.call
|
87
|
+
end
|
63
88
|
end
|
89
|
+
|
64
90
|
end
|
65
91
|
end
|
@@ -12,8 +12,8 @@ module FeatureFlagger
|
|
12
12
|
base.extend ClassMethods
|
13
13
|
end
|
14
14
|
|
15
|
-
def released?(*feature_key)
|
16
|
-
self.class.released_id?(feature_flagger_identifier, feature_key)
|
15
|
+
def released?(*feature_key, **options)
|
16
|
+
self.class.released_id?(feature_flagger_identifier, feature_key, options)
|
17
17
|
end
|
18
18
|
|
19
19
|
def release(*feature_key)
|
@@ -26,9 +26,9 @@ module FeatureFlagger
|
|
26
26
|
FeatureFlagger.control.unrelease(feature.key, id)
|
27
27
|
end
|
28
28
|
|
29
|
-
def releases
|
29
|
+
def releases(options = {})
|
30
30
|
resource_name = self.class.feature_flagger_model_settings.entity_name
|
31
|
-
FeatureFlagger.control.releases(resource_name, id)
|
31
|
+
FeatureFlagger.control.releases(resource_name, id, options)
|
32
32
|
end
|
33
33
|
|
34
34
|
private
|
@@ -43,9 +43,9 @@ module FeatureFlagger
|
|
43
43
|
yield feature_flagger_model_settings
|
44
44
|
end
|
45
45
|
|
46
|
-
def released_id?(resource_id, *feature_key)
|
46
|
+
def released_id?(resource_id, *feature_key, **options)
|
47
47
|
feature = Feature.new(feature_key, feature_flagger_model_settings.entity_name)
|
48
|
-
FeatureFlagger.control.released?(feature.key, resource_id)
|
48
|
+
FeatureFlagger.control.released?(feature.key, resource_id, options)
|
49
49
|
end
|
50
50
|
|
51
51
|
def release_id(resource_id, *feature_key)
|
@@ -58,10 +58,10 @@ module FeatureFlagger
|
|
58
58
|
FeatureFlagger.control.unrelease(feature.key, resource_id)
|
59
59
|
end
|
60
60
|
|
61
|
-
def all_released_ids_for(*feature_key)
|
61
|
+
def all_released_ids_for(*feature_key, **options)
|
62
62
|
feature_key.flatten!
|
63
63
|
feature = Feature.new(feature_key, feature_flagger_model_settings.entity_name)
|
64
|
-
FeatureFlagger.control.resource_ids(feature.key)
|
64
|
+
FeatureFlagger.control.resource_ids(feature.key, options)
|
65
65
|
end
|
66
66
|
|
67
67
|
def release_to_all(*feature_key)
|
@@ -74,13 +74,13 @@ module FeatureFlagger
|
|
74
74
|
FeatureFlagger.control.unrelease_to_all(feature.key)
|
75
75
|
end
|
76
76
|
|
77
|
-
def released_features_to_all
|
78
|
-
FeatureFlagger.control.released_features_to_all
|
77
|
+
def released_features_to_all(options = {})
|
78
|
+
FeatureFlagger.control.released_features_to_all(options)
|
79
79
|
end
|
80
80
|
|
81
|
-
def released_to_all?(*feature_key)
|
81
|
+
def released_to_all?(*feature_key, **options)
|
82
82
|
feature = Feature.new(feature_key, feature_flagger_model_settings.entity_name)
|
83
|
-
FeatureFlagger.control.released_to_all?(feature.key)
|
83
|
+
FeatureFlagger.control.released_to_all?(feature.key, options)
|
84
84
|
end
|
85
85
|
|
86
86
|
def detached_feature_keys
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module FeatureFlagger
|
2
|
+
class Notifier
|
3
|
+
attr_reader :notify
|
4
|
+
|
5
|
+
RELEASE = 'release'.freeze
|
6
|
+
UNRELEASE = 'unrelease'.freeze
|
7
|
+
RELEASE_TO_ALL = 'release_to_all'.freeze
|
8
|
+
UNRELEASE_TO_ALL = 'unrelease_to_all'.freeze
|
9
|
+
|
10
|
+
def initialize(notify = nil)
|
11
|
+
@notify = valid_notify?(notify) ? notify : nullNotify
|
12
|
+
end
|
13
|
+
|
14
|
+
def send(operation, feature_key, resource_id = nil)
|
15
|
+
@notify.call(build_event(operation, extract_resource_from_key(feature_key), feature_key, resource_id))
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def nullNotify
|
21
|
+
lambda {|e| }
|
22
|
+
end
|
23
|
+
|
24
|
+
def valid_notify?(notify)
|
25
|
+
!notify.nil? && notify.is_a?(Proc)
|
26
|
+
end
|
27
|
+
|
28
|
+
def extract_resource_from_key(key)
|
29
|
+
Storage::Keys.extract_resource_name_from_feature_key(
|
30
|
+
key
|
31
|
+
)
|
32
|
+
rescue FeatureFlagger::Storage::Keys::InvalidResourceNameError
|
33
|
+
"legacy key"
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_event(operation, resource_name, feature_key, resource_id)
|
37
|
+
{
|
38
|
+
type: operation,
|
39
|
+
model: resource_name,
|
40
|
+
feature: feature_key,
|
41
|
+
id: resource_id
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -21,7 +21,9 @@ module FeatureFlagger
|
|
21
21
|
|
22
22
|
def fetch_releases(resource_name, resource_id, global_key)
|
23
23
|
resource_key = resource_key(resource_name, resource_id)
|
24
|
-
@redis.sunion(resource_key, global_key)
|
24
|
+
releases = @redis.sunion(resource_key, global_key)
|
25
|
+
|
26
|
+
releases.select{ |release| release.start_with?(resource_name) }
|
25
27
|
end
|
26
28
|
|
27
29
|
def has_value?(key, value)
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: feature_flagger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 2.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nando Sousa
|
8
8
|
- Geison Biazus
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2021-07-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
@@ -39,6 +39,20 @@ dependencies:
|
|
39
39
|
- - ">"
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '1.3'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: activesupport
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '6.0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '6.0'
|
42
56
|
- !ruby/object:Gem::Dependency
|
43
57
|
name: bundler
|
44
58
|
requirement: !ruby/object:Gem::Requirement
|
@@ -87,14 +101,14 @@ dependencies:
|
|
87
101
|
requirements:
|
88
102
|
- - '='
|
89
103
|
- !ruby/object:Gem::Version
|
90
|
-
version:
|
104
|
+
version: 0.21.2
|
91
105
|
type: :development
|
92
106
|
prerelease: false
|
93
107
|
version_requirements: !ruby/object:Gem::Requirement
|
94
108
|
requirements:
|
95
109
|
- - '='
|
96
110
|
- !ruby/object:Gem::Version
|
97
|
-
version:
|
111
|
+
version: 0.21.2
|
98
112
|
- !ruby/object:Gem::Dependency
|
99
113
|
name: fakeredis
|
100
114
|
requirement: !ruby/object:Gem::Requirement
|
@@ -127,6 +141,7 @@ files:
|
|
127
141
|
- lib/feature_flagger/manager.rb
|
128
142
|
- lib/feature_flagger/model.rb
|
129
143
|
- lib/feature_flagger/model_settings.rb
|
144
|
+
- lib/feature_flagger/notifier.rb
|
130
145
|
- lib/feature_flagger/railtie.rb
|
131
146
|
- lib/feature_flagger/storage/feature_keys_migration.rb
|
132
147
|
- lib/feature_flagger/storage/keys.rb
|
@@ -137,7 +152,7 @@ homepage: http://github.com/ResultadosDigitais/feature_flagger
|
|
137
152
|
licenses:
|
138
153
|
- MIT
|
139
154
|
metadata: {}
|
140
|
-
post_install_message:
|
155
|
+
post_install_message:
|
141
156
|
rdoc_options: []
|
142
157
|
require_paths:
|
143
158
|
- lib
|
@@ -152,8 +167,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
167
|
- !ruby/object:Gem::Version
|
153
168
|
version: 2.0.0
|
154
169
|
requirements: []
|
155
|
-
rubygems_version: 3.
|
156
|
-
signing_key:
|
170
|
+
rubygems_version: 3.2.3
|
171
|
+
signing_key:
|
157
172
|
specification_version: 4
|
158
173
|
summary: Partial release your features.
|
159
174
|
test_files: []
|