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 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.