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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de6dae8c2c1631e95a47c0a6bb99f5e42c3ca2716f7ac4feb045fbd72a279133
4
- data.tar.gz: 8619614ec4718f56ca20e1617d398ddb69bd1a81f0412568ed95c2215f7e7c2c
3
+ metadata.gz: 8d02210f5b573bd3ebb01a75794885b729981a740412bf045b713a8d9db37a75
4
+ data.tar.gz: cd9f4ab0e6058029e8a5cab354d98a0b676363a05582169f52d90578c2613b37
5
5
  SHA512:
6
- metadata.gz: e930f492fd481b66cd8924b603a4e067872ab2b2d864de8402bfb28e24504d81c5b3149596f0c08c255eaf84b74ca98a82657d3ac2f1ef54c9c538facfcf3428
7
- data.tar.gz: 851c0d1b9eab08442232ee9c3681b3e90b559ed0fc01e4e631c7e3ec26915731682b393ceb1d5d447d1565c20896fbd138fcd18c7678aabfd2047a5158867250
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
@@ -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
- @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
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
- @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
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
- @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
45
56
  end
46
57
 
47
- def released_features_to_all
48
- @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
49
62
  end
50
63
 
51
- def released_to_all?(feature_key)
52
- @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
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
@@ -4,7 +4,7 @@ module FeatureFlagger
4
4
  def self.detached_feature_keys
5
5
  persisted_features = FeatureFlagger.control.feature_keys
6
6
  mapped_feature_keys = FeatureFlagger.config.mapped_feature_keys
7
-
7
+
8
8
  persisted_features - mapped_feature_keys
9
9
  end
10
10
 
@@ -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
@@ -48,7 +48,9 @@ module FeatureFlagger
48
48
  def migrate_release(key)
49
49
  resource_ids = @from_redis.smembers(key)
50
50
 
51
- @to_control.release(key, resource_ids)
51
+ resource_ids.each do |id|
52
+ @to_control.release(key, id)
53
+ end
52
54
  end
53
55
  end
54
56
  end
@@ -9,7 +9,6 @@ module FeatureFlagger
9
9
 
10
10
  def self.extract_resource_name_from_feature_key(feature_key)
11
11
  feature_paths = feature_key.split(':')
12
-
13
12
  raise InvalidResourceNameError if feature_paths.size < MINIMUM_VALID_FEATURE_PATH
14
13
 
15
14
  feature_paths.first
@@ -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)
@@ -1,3 +1,3 @@
1
1
  module FeatureFlagger
2
- VERSION = "1.2.0"
2
+ VERSION = "2.1.1"
3
3
  end
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.2.0
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: 2020-08-03 00:00:00.000000000 Z
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: '0.17'
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: '0.17'
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.1.4
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: []