feature_flagger 2.0.1 → 2.2.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 +47 -0
- data/lib/feature_flagger/configuration.rb +11 -7
- data/lib/feature_flagger/control.rb +33 -11
- data/lib/feature_flagger/manifest_sources/storage_only.rb +13 -0
- data/lib/feature_flagger/manifest_sources/with_yaml_file.rb +14 -0
- data/lib/feature_flagger/manifest_sources/yaml_with_backup_to_storage.rb +18 -0
- data/lib/feature_flagger/model.rb +12 -12
- data/lib/feature_flagger/storage/redis.rb +13 -2
- data/lib/feature_flagger/version.rb +1 -1
- data/lib/feature_flagger.rb +4 -1
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 00bb78fb3108046b58f0f12ad0f961e064704e5fb7e4a52e7599554cf256603a
|
4
|
+
data.tar.gz: 84ddec88634be5e22845e2f93dd6d90893bf04475e7ec02e1389eb045eda3a8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9f425c46c9c74000b1fdbc456d17db656665b7020c28f76812ec323b807d7b40b235b8bd9c8ca44d6b85fd209faafb856aa15909bf3b045ee6c6353603f43151
|
7
|
+
data.tar.gz: d55017ad557e4775638dc909cb5348afaa260c5941e0be777ef2ccfdaeb676533b59efcf384187e009bdfcad6f4c76330ae552a42abd7d81f8e51a7895e8ffb3
|
data/README.md
CHANGED
@@ -43,6 +43,15 @@ FeatureFlagger.configure do |config|
|
|
43
43
|
end
|
44
44
|
```
|
45
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
|
+
|
46
55
|
1. Create a `rollout.yml` in _config_ path and declare a rollout:
|
47
56
|
```yml
|
48
57
|
account: # model name
|
@@ -96,6 +105,10 @@ account.release(:email_marketing, :new_email_flow)
|
|
96
105
|
account.released?(:email_marketing, :new_email_flow)
|
97
106
|
#=> true
|
98
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
|
+
|
99
112
|
# Remove feature for given account
|
100
113
|
account.unrelease(:email_marketing, :new_email_flow)
|
101
114
|
#=> true
|
@@ -108,6 +121,10 @@ FeatureFlagger::KeyNotFoundError: ["account", "email_marketing", "new_email_flo"
|
|
108
121
|
Account.released_id?(42, :email_marketing, :new_email_flow)
|
109
122
|
#=> true
|
110
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
|
+
|
111
128
|
# Release a feature for a specific account id
|
112
129
|
Account.release_id(42, :email_marketing, :new_email_flow)
|
113
130
|
#=> true
|
@@ -123,6 +140,10 @@ Account.unrelease_to_all(:email_marketing, :new_email_flow)
|
|
123
140
|
|
124
141
|
# Return an array with all features released for all
|
125
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
|
+
|
126
147
|
```
|
127
148
|
|
128
149
|
## Clean up action
|
@@ -140,6 +161,32 @@ to ensure the data stored in Redis storage is right. Check [#67](https://github.
|
|
140
161
|
|
141
162
|
$ bundle exec rake feature_flagger:migrate_to_resource_keys
|
142
163
|
|
164
|
+
## Extra options
|
165
|
+
|
166
|
+
There are a few options to store/retrieve your rollout manifest (a.k.a rollout.yml):
|
167
|
+
|
168
|
+
If you have a rollout.yml file and want to use Redis to keep a backup, add the follow code to the configuration block:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
FeatureFlagger.configure do |config|
|
172
|
+
...
|
173
|
+
config.manifest_source = FeatureFlagger::ManifestSources::YAMLWithBackupToStorage.new(config.storage)
|
174
|
+
...
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
If you already have your manifest on Redis and prefer not to keep a copy in your application, add the following code to the configuration block:
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
FeatureFlagger.configure do |config|
|
182
|
+
...
|
183
|
+
config.manifest_source = FeatureFlagger::ManifestSources::StorageOnly.new(config.storage)
|
184
|
+
...
|
185
|
+
end
|
186
|
+
```
|
187
|
+
|
188
|
+
If you have the YAML file and don't need a backup, it is unnecessary to do any different configuration.
|
189
|
+
|
143
190
|
## Contributing
|
144
191
|
|
145
192
|
Bug reports and pull requests are welcome!
|
@@ -1,15 +1,23 @@
|
|
1
1
|
module FeatureFlagger
|
2
2
|
class Configuration
|
3
|
-
attr_accessor :storage, :
|
3
|
+
attr_accessor :storage, :cache_store, :manifest_source, :notifier_callback
|
4
4
|
|
5
5
|
def initialize
|
6
6
|
@storage ||= Storage::Redis.default_client
|
7
|
-
@
|
7
|
+
@manifest_source ||= FeatureFlagger::ManifestSources::WithYamlFile.new
|
8
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)
|
9
17
|
end
|
10
18
|
|
11
19
|
def info
|
12
|
-
@
|
20
|
+
@manifest_source.resolved_info
|
13
21
|
end
|
14
22
|
|
15
23
|
def mapped_feature_keys(resource_name = nil)
|
@@ -21,10 +29,6 @@ module FeatureFlagger
|
|
21
29
|
|
22
30
|
private
|
23
31
|
|
24
|
-
def default_yaml_filepath
|
25
|
-
"#{Rails.root}/config/rollout.yml" if defined?(Rails)
|
26
|
-
end
|
27
|
-
|
28
32
|
def make_keys_recursively(hash, keys = [], composed_key = [])
|
29
33
|
unless hash.values[0].is_a?(Hash)
|
30
34
|
keys.push(composed_key)
|
@@ -4,13 +4,16 @@ module FeatureFlagger
|
|
4
4
|
|
5
5
|
RELEASED_FEATURES = 'released_features'
|
6
6
|
|
7
|
-
def initialize(storage, notifier)
|
7
|
+
def initialize(storage, notifier, cache_store = nil)
|
8
8
|
@storage = storage
|
9
9
|
@notifier = notifier
|
10
|
+
@cache_store = cache_store
|
10
11
|
end
|
11
12
|
|
12
|
-
def released?(feature_key, resource_id)
|
13
|
-
|
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
|
14
17
|
end
|
15
18
|
|
16
19
|
def release(feature_key, resource_id)
|
@@ -22,8 +25,10 @@ module FeatureFlagger
|
|
22
25
|
@storage.add(feature_key, resource_name, resource_id)
|
23
26
|
end
|
24
27
|
|
25
|
-
def releases(resource_name, resource_id)
|
26
|
-
|
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
|
27
32
|
end
|
28
33
|
|
29
34
|
def release_to_all(feature_key)
|
@@ -44,16 +49,22 @@ module FeatureFlagger
|
|
44
49
|
@storage.remove_all(RELEASED_FEATURES, feature_key)
|
45
50
|
end
|
46
51
|
|
47
|
-
def resource_ids(feature_key)
|
48
|
-
|
52
|
+
def resource_ids(feature_key, options = {})
|
53
|
+
cache "all_values/#{feature_key}", options do
|
54
|
+
@storage.all_values(feature_key)
|
55
|
+
end
|
49
56
|
end
|
50
57
|
|
51
|
-
def released_features_to_all
|
52
|
-
|
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
|
53
62
|
end
|
54
63
|
|
55
|
-
def released_to_all?(feature_key)
|
56
|
-
|
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
|
57
68
|
end
|
58
69
|
|
59
70
|
# DEPRECATED: this method will be removed from public api on v2.0 version.
|
@@ -65,5 +76,16 @@ module FeatureFlagger
|
|
65
76
|
def feature_keys
|
66
77
|
@storage.feature_keys - [FeatureFlagger::Control::RELEASED_FEATURES]
|
67
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
|
88
|
+
end
|
89
|
+
|
68
90
|
end
|
69
91
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module FeatureFlagger
|
2
|
+
module ManifestSources
|
3
|
+
class WithYamlFile
|
4
|
+
def initialize(yaml_path = nil)
|
5
|
+
@yaml_path = yaml_path
|
6
|
+
@yaml_path ||= "#{Rails.root}/config/rollout.yml" if defined?(Rails)
|
7
|
+
end
|
8
|
+
|
9
|
+
def resolved_info
|
10
|
+
@resolved_info ||= ::YAML.load_file(@yaml_path) if @yaml_path
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module FeatureFlagger
|
2
|
+
module ManifestSources
|
3
|
+
class YAMLWithBackupToStorage
|
4
|
+
def initialize(storage, yaml_path = nil)
|
5
|
+
@yaml_path = yaml_path || ("#{Rails.root}/config/rollout.yml" if defined?(Rails))
|
6
|
+
@storage = storage
|
7
|
+
end
|
8
|
+
|
9
|
+
def resolved_info
|
10
|
+
@resolved_info ||= begin
|
11
|
+
yaml_data = YAML.load_file(@yaml_path) if @yaml_path
|
12
|
+
@storage.write_manifest_backup(YAML.dump(yaml_data))
|
13
|
+
yaml_data
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
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
|
@@ -5,8 +5,10 @@ require_relative './keys'
|
|
5
5
|
module FeatureFlagger
|
6
6
|
module Storage
|
7
7
|
class Redis
|
8
|
-
DEFAULT_NAMESPACE
|
9
|
-
RESOURCE_PREFIX
|
8
|
+
DEFAULT_NAMESPACE = :feature_flagger
|
9
|
+
RESOURCE_PREFIX = "_r".freeze
|
10
|
+
MANIFEST_PREFIX = "_m".freeze
|
11
|
+
MANIFEST_KEY = "manifest_file".freeze
|
10
12
|
SCAN_EACH_BATCH_SIZE = 1000.freeze
|
11
13
|
|
12
14
|
def initialize(redis)
|
@@ -75,6 +77,7 @@ module FeatureFlagger
|
|
75
77
|
# Reject keys related to feature responsible for return
|
76
78
|
# released features for a given account.
|
77
79
|
next if key.start_with?("#{RESOURCE_PREFIX}:")
|
80
|
+
next if key.start_with?("#{MANIFEST_PREFIX}:")
|
78
81
|
|
79
82
|
feature_keys << key
|
80
83
|
end
|
@@ -89,6 +92,14 @@ module FeatureFlagger
|
|
89
92
|
).call
|
90
93
|
end
|
91
94
|
|
95
|
+
def read_manifest_backup
|
96
|
+
@redis.get("#{MANIFEST_PREFIX}:#{MANIFEST_KEY}")
|
97
|
+
end
|
98
|
+
|
99
|
+
def write_manifest_backup(yaml_as_string)
|
100
|
+
@redis.set("#{MANIFEST_PREFIX}:#{MANIFEST_KEY}", yaml_as_string)
|
101
|
+
end
|
102
|
+
|
92
103
|
private
|
93
104
|
|
94
105
|
def resource_key(resource_name, resource_id)
|
data/lib/feature_flagger.rb
CHANGED
@@ -11,6 +11,9 @@ require 'feature_flagger/configuration'
|
|
11
11
|
require 'feature_flagger/manager'
|
12
12
|
require 'feature_flagger/railtie'
|
13
13
|
require 'feature_flagger/notifier'
|
14
|
+
require 'feature_flagger/manifest_sources/with_yaml_file'
|
15
|
+
require 'feature_flagger/manifest_sources/yaml_with_backup_to_storage'
|
16
|
+
require 'feature_flagger/manifest_sources/storage_only'
|
14
17
|
|
15
18
|
module FeatureFlagger
|
16
19
|
class << self
|
@@ -30,7 +33,7 @@ module FeatureFlagger
|
|
30
33
|
end
|
31
34
|
|
32
35
|
def control
|
33
|
-
@@control ||= Control.new(config.storage, notifier)
|
36
|
+
@@control ||= Control.new(config.storage, notifier, config.cache_store)
|
34
37
|
end
|
35
38
|
end
|
36
39
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: feature_flagger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nando Sousa
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2022-01-10 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
|
@@ -125,6 +139,9 @@ files:
|
|
125
139
|
- lib/feature_flagger/core_ext.rb
|
126
140
|
- lib/feature_flagger/feature.rb
|
127
141
|
- lib/feature_flagger/manager.rb
|
142
|
+
- lib/feature_flagger/manifest_sources/storage_only.rb
|
143
|
+
- lib/feature_flagger/manifest_sources/with_yaml_file.rb
|
144
|
+
- lib/feature_flagger/manifest_sources/yaml_with_backup_to_storage.rb
|
128
145
|
- lib/feature_flagger/model.rb
|
129
146
|
- lib/feature_flagger/model_settings.rb
|
130
147
|
- lib/feature_flagger/notifier.rb
|
@@ -153,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
153
170
|
- !ruby/object:Gem::Version
|
154
171
|
version: 2.0.0
|
155
172
|
requirements: []
|
156
|
-
rubygems_version: 3.
|
173
|
+
rubygems_version: 3.0.3
|
157
174
|
signing_key:
|
158
175
|
specification_version: 4
|
159
176
|
summary: Partial release your features.
|