feature_flagger 2.0.1 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89d925393dab41d5de3c0fd8872e9d0914936ed98140d5dea69e9ff9286cde7f
4
- data.tar.gz: 68cd51804dbc9a1b8547ed1ddbd0be9630704afd0493e99b3c1a740f4c460c99
3
+ metadata.gz: 00bb78fb3108046b58f0f12ad0f961e064704e5fb7e4a52e7599554cf256603a
4
+ data.tar.gz: 84ddec88634be5e22845e2f93dd6d90893bf04475e7ec02e1389eb045eda3a8b
5
5
  SHA512:
6
- metadata.gz: 6d1be4855ef2b133f1c0233f12f21833bdfd81bf35b3dd172e562fdd404bd6d674c429aeef4ec67a0f3062741b84f1ee99ba5bf6d317ec12b7f6517a9ab77ce1
7
- data.tar.gz: 434ad5fda2e78b6bc63157bdee07000fcfde62ae102db74e543a09852914441c4bdd72fd15ddbf72bf1dec55e7acc2ff735d69bf9ca49e8482d56356ea032d6c
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, :yaml_filepath, :notifier_callback
3
+ attr_accessor :storage, :cache_store, :manifest_source, :notifier_callback
4
4
 
5
5
  def initialize
6
6
  @storage ||= Storage::Redis.default_client
7
- @yaml_filepath ||= default_yaml_filepath
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
- @info ||= YAML.load_file(yaml_filepath) if yaml_filepath
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
- @storage.has_value?(RELEASED_FEATURES, feature_key) || @storage.has_value?(feature_key, resource_id)
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
- @storage.fetch_releases(resource_name, resource_id, RELEASED_FEATURES)
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
- @storage.all_values(feature_key)
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
- @storage.all_values(RELEASED_FEATURES)
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
- @storage.has_value?(RELEASED_FEATURES, feature_key)
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,13 @@
1
+ module FeatureFlagger
2
+ module ManifestSources
3
+ class StorageOnly
4
+ def initialize(storage)
5
+ @storage = storage
6
+ end
7
+
8
+ def resolved_info
9
+ YAML.load(@storage.read_manifest_backup)
10
+ end
11
+ end
12
+ end
13
+ 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 = :feature_flagger
9
- RESOURCE_PREFIX = "_r".freeze
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)
@@ -1,3 +1,3 @@
1
1
  module FeatureFlagger
2
- VERSION = "2.0.1"
2
+ VERSION = "2.2.1"
3
3
  end
@@ -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.0.1
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: 2021-01-27 00:00:00.000000000 Z
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.2.3
173
+ rubygems_version: 3.0.3
157
174
  signing_key:
158
175
  specification_version: 4
159
176
  summary: Partial release your features.