feature_flagger 1.2.0 → 2.1.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: 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: []