feature_flagger 0.7.2 → 1.2.0

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
- SHA1:
3
- metadata.gz: 7a86dad466ea3472db2912775bba5d1b7412b86d
4
- data.tar.gz: fe6eefd07a2d78b1b52661ff06a15399c30881ca
2
+ SHA256:
3
+ metadata.gz: de6dae8c2c1631e95a47c0a6bb99f5e42c3ca2716f7ac4feb045fbd72a279133
4
+ data.tar.gz: 8619614ec4718f56ca20e1617d398ddb69bd1a81f0412568ed95c2215f7e7c2c
5
5
  SHA512:
6
- metadata.gz: 176eaee71d771fbe687ae92e099f39f1fc268eef4cc8ff8c382692d6af4a027243472046f49efdf40727811f3b1c569cc70482efd558be9ff86a15c7c9bf49dc
7
- data.tar.gz: 22593e2d0c21ed1b7712d6bb8c7dc38efafe9e3cad89b939674188271b96605eda29d2ac2625ac4dcfdb8a785664ed64db9a311aeab47f058f466391243d4c2f
6
+ metadata.gz: e930f492fd481b66cd8924b603a4e067872ab2b2d864de8402bfb28e24504d81c5b3149596f0c08c255eaf84b74ca98a82657d3ac2f1ef54c9c538facfcf3428
7
+ data.tar.gz: 851c0d1b9eab08442232ee9c3681b3e90b559ed0fc01e4e631c7e3ec26915731682b393ceb1d5d447d1565c20896fbd138fcd18c7678aabfd2047a5158867250
data/README.md CHANGED
@@ -4,6 +4,14 @@
4
4
 
5
5
  Partially release your features.
6
6
 
7
+ ## Working with Docker
8
+
9
+ Open IRB
10
+ `docker-compose run feature_flagger`
11
+
12
+ Running tests
13
+ `docker-compose run feature_flagger rspec`
14
+
7
15
  ## Installation
8
16
 
9
17
  Add this line to your application's Gemfile:
@@ -24,20 +32,19 @@ Or install it yourself as:
24
32
  ## Configuration
25
33
 
26
34
  By default, feature_flagger uses the REDIS_URL env var to setup it's storage.
27
- You can configure this by using `configure` like such:
28
-
29
- 1. In a initializer file (e.g. `config/initializers/feature_flagger.rb`):
35
+ You can set up FeatureFlagger by creating a file called ```config/initializers/feature_flagger``` with the following lines:
30
36
  ```ruby
31
37
  require 'redis-namespace'
32
38
  require 'feature_flagger'
33
39
 
34
40
  FeatureFlagger.configure do |config|
35
- namespaced = ::Redis::Namespace.new("feature_flagger", redis: $redis)
41
+ redis = Redis.new(host: ENV['REDIS_URL'])
42
+ namespaced = Redis::Namespace.new('feature_flagger', redis: redis)
36
43
  config.storage = FeatureFlagger::Storage::Redis.new(namespaced)
37
44
  end
38
45
  ```
39
46
 
40
- 2. Create a `rollout.yml` in _config_ path and declare a rollout:
47
+ 1. Create a `rollout.yml` in _config_ path and declare a rollout:
41
48
  ```yml
42
49
  account: # model name
43
50
  email_marketing: # namespace (optional)
@@ -46,7 +53,7 @@ account: # model name
46
53
  @dispatch team uses this rollout to introduce a new email flow for certains users. Read more at [link]
47
54
  ```
48
55
 
49
- 3. Adds rollout funcionality to your model:
56
+ 2. Adds rollout funcionality to your model:
50
57
  ```ruby
51
58
  class Account < ActiveRecord::Base
52
59
  include FeatureFlagger::Model
@@ -64,7 +71,7 @@ account.release(:email_marketing, :new_email_flow)
64
71
  #=> true
65
72
 
66
73
  # Check feature for a given account
67
- account.rollout?(:email_marketing, :new_email_flow)
74
+ account.released?(:email_marketing, :new_email_flow)
68
75
  #=> true
69
76
 
70
77
  # Remove feature for given account
@@ -72,13 +79,17 @@ account.unrelease(:email_marketing, :new_email_flow)
72
79
  #=> true
73
80
 
74
81
  # If you try to check an inexistent rollout key it will raise an error.
75
- account.rollout?(:email_marketing, :new_email_flow)
82
+ account.released?(:email_marketing, :new_email_flow)
76
83
  FeatureFlagger::KeyNotFoundError: ["account", "email_marketing", "new_email_flo"]
77
84
 
78
85
  # Check feature for a specific account id
79
86
  Account.released_id?(42, :email_marketing, :new_email_flow)
80
87
  #=> true
81
88
 
89
+ # Release a feature for a specific account id
90
+ Account.release_id(42, :email_marketing, :new_email_flow)
91
+ #=> true
92
+
82
93
  # Get an array with all released Account ids
83
94
  Account.all_released_ids_for(:email_marketing, :new_email_flow)
84
95
 
@@ -92,7 +103,22 @@ Account.unrelease_to_all(:email_marketing, :new_email_flow)
92
103
  Account.released_features_to_all
93
104
  ```
94
105
 
106
+ ## Clean up action
107
+
108
+ By default when a key is removed from `rollout.yml` file, its data still in the storage.
109
+
110
+ To clean it up, execute or schedule the rake:
111
+
112
+ $ bundle exec rake feature_flagger:cleanup_removed_rollouts
113
+
114
+ ## Upgrading
115
+
116
+ When upgrading from `1.1.x` to `1.2.x` the following command must be executed
117
+ to ensure the data stored in Redis storage is right. Check [#67](https://github.com/ResultadosDigitais/feature_flagger/pull/67) and [#68](https://github.com/ResultadosDigitais/feature_flagger/pull/68) for more info.
118
+
119
+ $ bundle exec rake feature_flagger:migrate_to_resource_keys
120
+
95
121
  ## Contributing
96
122
 
97
- Bug reports and pull requests are welcome on GitHub at
98
- https://github.com/ResultadosDigitais/feature_flagger.
123
+ Bug reports and pull requests are welcome!
124
+ Please take a look at our guidelines [here](CONTRIBUTING.md).
@@ -4,8 +4,11 @@ require 'feature_flagger/version'
4
4
  require 'feature_flagger/storage/redis'
5
5
  require 'feature_flagger/control'
6
6
  require 'feature_flagger/model'
7
+ require 'feature_flagger/model_settings'
7
8
  require 'feature_flagger/feature'
8
9
  require 'feature_flagger/configuration'
10
+ require 'feature_flagger/manager'
11
+ require 'feature_flagger/railtie'
9
12
 
10
13
  module FeatureFlagger
11
14
  class << self
@@ -11,10 +11,36 @@ module FeatureFlagger
11
11
  @info ||= YAML.load_file(yaml_filepath) if yaml_filepath
12
12
  end
13
13
 
14
+ def mapped_feature_keys(resource_name = nil)
15
+ info_filtered = resource_name ? info[resource_name] : info
16
+ [].tap do |keys|
17
+ make_keys_recursively(info_filtered).each { |key| keys.push(join_key(resource_name, key)) }
18
+ end
19
+ end
20
+
14
21
  private
15
22
 
16
23
  def default_yaml_filepath
17
24
  "#{Rails.root}/config/rollout.yml" if defined?(Rails)
18
25
  end
26
+
27
+ def make_keys_recursively(hash, keys = [], composed_key = [])
28
+ unless hash.values[0].is_a?(Hash)
29
+ keys.push(composed_key)
30
+ return
31
+ end
32
+
33
+ hash.each do |key, value|
34
+ composed_key_cloned = composed_key.clone
35
+ composed_key_cloned.push(key.to_sym)
36
+ make_keys_recursively(value, keys, composed_key_cloned)
37
+ end
38
+ keys
39
+ end
40
+
41
+ def join_key(resource_name, key)
42
+ key.unshift resource_name if resource_name
43
+ key.join(":")
44
+ end
19
45
  end
20
46
  end
@@ -8,36 +8,36 @@ module FeatureFlagger
8
8
  @storage = storage
9
9
  end
10
10
 
11
- def rollout?(feature_key, resource_id)
11
+ def released?(feature_key, resource_id)
12
12
  @storage.has_value?(RELEASED_FEATURES, feature_key) || @storage.has_value?(feature_key, resource_id)
13
13
  end
14
14
 
15
15
  def release(feature_key, resource_id)
16
- @storage.add(feature_key, resource_id)
16
+ resource_name = Storage::Keys.extract_resource_name_from_feature_key(
17
+ feature_key
18
+ )
19
+
20
+ @storage.add(feature_key, resource_name, resource_id)
17
21
  end
18
22
 
19
- def release_to_all(feature_key)
20
- @storage.add(RELEASED_FEATURES, feature_key)
23
+ def releases(resource_name, resource_id)
24
+ @storage.fetch_releases(resource_name, resource_id, RELEASED_FEATURES)
21
25
  end
22
26
 
23
- # <b>DEPRECATED:</b> Please use <tt>release</tt> instead.
24
- def release!(feature_key, resource_id)
25
- warn "[DEPRECATION] `release!` is deprecated. Please use `release` instead."
26
- release(feature_key, resource_id)
27
+ def release_to_all(feature_key)
28
+ @storage.add_all(RELEASED_FEATURES, feature_key)
27
29
  end
28
30
 
29
31
  def unrelease(feature_key, resource_id)
30
- @storage.remove(feature_key, resource_id)
31
- end
32
+ resource_name = Storage::Keys.extract_resource_name_from_feature_key(
33
+ feature_key
34
+ )
32
35
 
33
- def unrelease_to_all(feature_key)
34
- @storage.remove(RELEASED_FEATURES, feature_key)
36
+ @storage.remove(feature_key, resource_name, resource_id)
35
37
  end
36
38
 
37
- # <b>DEPRECATED:</b> Please use <tt>unrelease</tt> instead.
38
- def unrelease!(feature_key, resource_id)
39
- warn "[DEPRECATION] `unrelease!` is deprecated. Please use `unrelease` instead."
40
- unrelease(feature_key, resource_id)
39
+ def unrelease_to_all(feature_key)
40
+ @storage.remove_all(RELEASED_FEATURES, feature_key)
41
41
  end
42
42
 
43
43
  def resource_ids(feature_key)
@@ -47,5 +47,19 @@ module FeatureFlagger
47
47
  def released_features_to_all
48
48
  @storage.all_values(RELEASED_FEATURES)
49
49
  end
50
+
51
+ def released_to_all?(feature_key)
52
+ @storage.has_value?(RELEASED_FEATURES, feature_key)
53
+ end
54
+
55
+ # DEPRECATED: this method will be removed from public api on v2.0 version.
56
+ # use instead the feature_keys method.
57
+ def search_keys(query)
58
+ @storage.search_keys(query)
59
+ end
60
+
61
+ def feature_keys
62
+ @storage.feature_keys
63
+ end
50
64
  end
51
65
  end
@@ -0,0 +1,18 @@
1
+ begin
2
+ require 'active_support/core_ext/string/inflections'
3
+ rescue LoadError
4
+ unless ''.respond_to?(:constantize)
5
+ class String
6
+ def constantize
7
+ names = split('::')
8
+ names.shift if names.empty? || names.first.empty?
9
+
10
+ constant = Object
11
+ names.each do |name|
12
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
13
+ end
14
+ constant
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module FeatureFlagger
2
+ class Manager
3
+
4
+ def self.detached_feature_keys
5
+ persisted_features = FeatureFlagger.control.feature_keys
6
+ mapped_feature_keys = FeatureFlagger.config.mapped_feature_keys
7
+
8
+ persisted_features - mapped_feature_keys
9
+ end
10
+
11
+ def self.cleanup_detached(resource_name, *feature_key)
12
+ complete_feature_key = feature_key.map(&:to_s).insert(0, resource_name.to_s)
13
+ key_value = FeatureFlagger.config.info.dig(*complete_feature_key)
14
+ raise "key is still mapped" if key_value
15
+ FeatureFlagger.control.unrelease_to_all(complete_feature_key.join(':'))
16
+ end
17
+
18
+ end
19
+ end
@@ -12,53 +12,65 @@ module FeatureFlagger
12
12
  base.extend ClassMethods
13
13
  end
14
14
 
15
- def rollout?(*feature_key)
16
- self.class.released_id?(id, feature_key)
15
+ def released?(*feature_key)
16
+ self.class.released_id?(feature_flagger_identifier, feature_key)
17
17
  end
18
18
 
19
- # <b>DEPRECATED:</b> Please use <tt>release</tt> instead.
20
- def release!(*feature_key)
21
- warn "[DEPRECATION] `release!` is deprecated. Please use `release` instead."
22
- release(*feature_key)
19
+ def release(*feature_key)
20
+ self.class.release_id(feature_flagger_identifier, *feature_key)
23
21
  end
24
22
 
25
- def release(*feature_key)
26
- resource_name = self.class.rollout_resource_name
23
+ def unrelease(*feature_key)
24
+ resource_name = self.class.feature_flagger_model_settings.entity_name
27
25
  feature = Feature.new(feature_key, resource_name)
28
- FeatureFlagger.control.release(feature.key, id)
26
+ FeatureFlagger.control.unrelease(feature.key, id)
29
27
  end
30
28
 
31
- # <b>DEPRECATED:</b> Please use <tt>unrelease</tt> instead.
32
- def unrelease!(*feature_key)
33
- warn "[DEPRECATION] `unrelease!` is deprecated. Please use `unrelease` instead."
34
- unrelease(*feature_key)
29
+ def releases
30
+ resource_name = self.class.feature_flagger_model_settings.entity_name
31
+ FeatureFlagger.control.releases(resource_name, id)
35
32
  end
36
33
 
37
- def unrelease(*feature_key)
38
- resource_name = self.class.rollout_resource_name
39
- feature = Feature.new(feature_key, resource_name)
40
- FeatureFlagger.control.unrelease(feature.key, id)
34
+ private
35
+
36
+ def feature_flagger_identifier
37
+ public_send(self.class.feature_flagger_model_settings.identifier_field)
41
38
  end
42
39
 
43
40
  module ClassMethods
41
+ def feature_flagger
42
+ raise ArgumentError unless block_given?
43
+ yield feature_flagger_model_settings
44
+ end
45
+
44
46
  def released_id?(resource_id, *feature_key)
45
- feature = Feature.new(feature_key, rollout_resource_name)
46
- FeatureFlagger.control.rollout?(feature.key, resource_id)
47
+ feature = Feature.new(feature_key, feature_flagger_model_settings.entity_name)
48
+ FeatureFlagger.control.released?(feature.key, resource_id)
49
+ end
50
+
51
+ def release_id(resource_id, *feature_key)
52
+ feature = Feature.new(feature_key, feature_flagger_model_settings.entity_name)
53
+ FeatureFlagger.control.release(feature.key, resource_id)
54
+ end
55
+
56
+ def unrelease_id(resource_id, *feature_key)
57
+ feature = Feature.new(feature_key, feature_flagger_model_settings.entity_name)
58
+ FeatureFlagger.control.unrelease(feature.key, resource_id)
47
59
  end
48
60
 
49
61
  def all_released_ids_for(*feature_key)
50
62
  feature_key.flatten!
51
- feature = Feature.new(feature_key, rollout_resource_name)
63
+ feature = Feature.new(feature_key, feature_flagger_model_settings.entity_name)
52
64
  FeatureFlagger.control.resource_ids(feature.key)
53
65
  end
54
66
 
55
67
  def release_to_all(*feature_key)
56
- feature = Feature.new(feature_key, rollout_resource_name)
68
+ feature = Feature.new(feature_key, feature_flagger_model_settings.entity_name)
57
69
  FeatureFlagger.control.release_to_all(feature.key)
58
70
  end
59
71
 
60
72
  def unrelease_to_all(*feature_key)
61
- feature = Feature.new(feature_key, rollout_resource_name)
73
+ feature = Feature.new(feature_key, feature_flagger_model_settings.entity_name)
62
74
  FeatureFlagger.control.unrelease_to_all(feature.key)
63
75
  end
64
76
 
@@ -66,6 +78,25 @@ module FeatureFlagger
66
78
  FeatureFlagger.control.released_features_to_all
67
79
  end
68
80
 
81
+ def released_to_all?(*feature_key)
82
+ feature = Feature.new(feature_key, feature_flagger_model_settings.entity_name)
83
+ FeatureFlagger.control.released_to_all?(feature.key)
84
+ end
85
+
86
+ def detached_feature_keys
87
+ rollout_resource_name = feature_flagger_model_settings.entity_name
88
+ persisted_features = FeatureFlagger.control.search_keys("#{rollout_resource_name}:*").to_a
89
+ mapped_feature_keys = FeatureFlagger.config.mapped_feature_keys(rollout_resource_name)
90
+ (persisted_features - mapped_feature_keys).map { |key| key.sub("#{rollout_resource_name}:",'') }
91
+ end
92
+
93
+ def cleanup_detached(*feature_key)
94
+ complete_feature_key = feature_key.map(&:to_s).insert(0, feature_flagger_model_settings.entity_name)
95
+ key_value = FeatureFlagger.config.info.dig(*complete_feature_key)
96
+ raise "key is still mapped" if key_value
97
+ FeatureFlagger.control.unrelease_to_all(complete_feature_key.join(':'))
98
+ end
99
+
69
100
  def rollout_resource_name
70
101
  klass_name = self.to_s
71
102
  klass_name.gsub!(/::/, '_')
@@ -75,6 +106,17 @@ module FeatureFlagger
75
106
  klass_name.downcase!
76
107
  klass_name
77
108
  end
109
+
110
+ def feature_flagger_model_settings
111
+ @feature_flagger_model_settings ||= FeatureFlagger::ModelSettings.new(
112
+ identifier_field: :id,
113
+ entity_name: rollout_resource_name
114
+ )
115
+ end
116
+
117
+ def feature_flagger_identifier
118
+ public_send(feature_flagger_model_settings.identifier_field)
119
+ end
78
120
  end
79
121
  end
80
122
  end
@@ -0,0 +1,36 @@
1
+ module FeatureFlagger
2
+ class ModelSettings
3
+ def initialize(arguments)
4
+ arguments.each do |field, value|
5
+ self.public_send("#{field}=", value)
6
+ end
7
+ end
8
+
9
+ # Public: identifier_field Refers to which field must represent the unique model
10
+ # id.
11
+ attr_accessor :identifier_field
12
+
13
+ # Public: entity_name to which entity the model is targeting.
14
+ # Take this yaml file as example:
15
+ #
16
+ # account:
17
+ # email_marketing:
18
+ # whitelabel:
19
+ # description: a rollout
20
+ # owner: core
21
+ # account_in_migration:
22
+ # email_marketing:
23
+ # whitelabel:
24
+ # description: a rollout
25
+ # owner: core
26
+ #
27
+ # class Account < ActiveRecord::Base
28
+ # include FeatureFlagger::Model
29
+ #
30
+ # feature_flagger do |config|
31
+ # config.identifier_field = :cdp_tenant_id
32
+ # config.entity_name = :account_in_migration
33
+ # end
34
+ attr_accessor :entity_name
35
+ end
36
+ end
@@ -0,0 +1,9 @@
1
+ if defined?(Rails)
2
+ module FeatureFlagger
3
+ class Railtie < Rails::Railtie
4
+ rake_tasks do
5
+ load 'tasks/feature_flagger.rake'
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FeatureFlagger
4
+ module Storage
5
+ class FeatureKeysMigration
6
+
7
+ def initialize(from_redis, to_control)
8
+ @from_redis = from_redis
9
+ @to_control = to_control
10
+ end
11
+
12
+ # call migrates features key from the old fashioned to the new
13
+ # format.
14
+ #
15
+ # It must replicate feature keys with changes:
16
+ #
17
+ # from "avenue:traffic_lights" => 42
18
+ # to "avenue:42" => traffic_lights
19
+ def call
20
+ @from_redis.scan_each(match: "*", count: FeatureFlagger::Storage::Redis::SCAN_EACH_BATCH_SIZE) do |redis_key|
21
+ # filter out resource_keys
22
+ next if redis_key.start_with?("#{FeatureFlagger::Storage::Redis::RESOURCE_PREFIX}:")
23
+
24
+ migrate_key(redis_key)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def migrate_key(key)
31
+ return migrate_release_to_all(key) if feature_released_to_all?(key)
32
+
33
+ migrate_release(key)
34
+ end
35
+
36
+ def migrate_release_to_all(key)
37
+ features = @from_redis.smembers(key)
38
+
39
+ features.each do |feature_key|
40
+ @to_control.release_to_all(feature_key)
41
+ end
42
+ end
43
+
44
+ def feature_released_to_all?(key)
45
+ FeatureFlagger::Control::RELEASED_FEATURES == key
46
+ end
47
+
48
+ def migrate_release(key)
49
+ resource_ids = @from_redis.smembers(key)
50
+
51
+ @to_control.release(key, resource_ids)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,21 @@
1
+ module FeatureFlagger
2
+ module Storage
3
+ module Keys
4
+ MINIMUM_VALID_FEATURE_PATH = 2.freeze
5
+
6
+ def self.resource_key(prefix, resource_name, resource_id)
7
+ "#{prefix}:#{resource_name}:#{resource_id}"
8
+ end
9
+
10
+ def self.extract_resource_name_from_feature_key(feature_key)
11
+ feature_paths = feature_key.split(':')
12
+
13
+ raise InvalidResourceNameError if feature_paths.size < MINIMUM_VALID_FEATURE_PATH
14
+
15
+ feature_paths.first
16
+ end
17
+
18
+ class InvalidResourceNameError < StandardError; end
19
+ end
20
+ end
21
+ end
@@ -1,11 +1,13 @@
1
1
  require 'redis'
2
2
  require 'redis-namespace'
3
+ require_relative './keys'
3
4
 
4
5
  module FeatureFlagger
5
6
  module Storage
6
7
  class Redis
7
-
8
8
  DEFAULT_NAMESPACE = :feature_flagger
9
+ RESOURCE_PREFIX = "_r".freeze
10
+ SCAN_EACH_BATCH_SIZE = 1000.freeze
9
11
 
10
12
  def initialize(redis)
11
13
  @redis = redis
@@ -17,21 +19,102 @@ module FeatureFlagger
17
19
  new(ns)
18
20
  end
19
21
 
22
+ def fetch_releases(resource_name, resource_id, global_key)
23
+ resource_key = resource_key(resource_name, resource_id)
24
+ @redis.sunion(resource_key, global_key)
25
+ end
26
+
20
27
  def has_value?(key, value)
21
28
  @redis.sismember(key, value)
22
29
  end
23
30
 
24
- def add(key, value)
25
- @redis.sadd(key, value)
31
+ def add(feature_key, resource_name, resource_id)
32
+ resource_key = resource_key(resource_name, resource_id)
33
+
34
+ @redis.multi do |redis|
35
+ redis.sadd(feature_key, resource_id)
36
+ redis.sadd(resource_key, feature_key)
37
+ end
38
+ end
39
+
40
+ def remove(feature_key, resource_name, resource_id)
41
+ resource_key = resource_key(resource_name, resource_id)
42
+
43
+ @redis.multi do |redis|
44
+ redis.srem(feature_key, resource_id)
45
+ redis.srem(resource_key, feature_key)
46
+ end
26
47
  end
27
48
 
28
- def remove(key, value)
29
- @redis.srem(key, value)
49
+ def remove_all(global_key, feature_key)
50
+ @redis.srem(global_key, feature_key)
51
+ remove_feature_key_from_resources(feature_key)
52
+ end
53
+
54
+ def add_all(global_key, key)
55
+ @redis.sadd(global_key, key)
56
+ remove_feature_key_from_resources(key)
30
57
  end
31
58
 
32
59
  def all_values(key)
33
60
  @redis.smembers(key)
34
61
  end
62
+
63
+ # DEPRECATED: this method will be removed from public api on v2.0 version.
64
+ # use instead the feature_keys method.
65
+ def search_keys(query)
66
+ @redis.scan_each(match: query)
67
+ end
68
+
69
+ def feature_keys
70
+ feature_keys = []
71
+
72
+ @redis.scan_each(match: "*") do |key|
73
+ # Reject keys related to feature responsible for return
74
+ # released features for a given account.
75
+ next if key.start_with?("#{RESOURCE_PREFIX}:")
76
+
77
+ feature_keys << key
78
+ end
79
+
80
+ feature_keys
81
+ end
82
+
83
+ def synchronize_feature_and_resource
84
+ FeatureFlagger::Storage::FeatureKeysMigration.new(
85
+ @redis,
86
+ FeatureFlagger.control,
87
+ ).call
88
+ end
89
+
90
+ private
91
+
92
+ def resource_key(resource_name, resource_id)
93
+ FeatureFlagger::Storage::Keys.resource_key(
94
+ RESOURCE_PREFIX,
95
+ resource_name,
96
+ resource_id,
97
+ )
98
+ end
99
+
100
+ def remove_feature_key_from_resources(feature_key)
101
+ cursor = 0
102
+ resource_name = feature_key.split(":").first
103
+
104
+ loop do
105
+ cursor, resource_ids = @redis.sscan(feature_key, cursor, count: SCAN_EACH_BATCH_SIZE)
106
+
107
+ @redis.multi do |redis|
108
+ resource_ids.each do |resource_id|
109
+ key = resource_key(resource_name, resource_id)
110
+ redis.srem(key, feature_key)
111
+ redis.srem(feature_key, resource_id)
112
+ end
113
+ end
114
+
115
+ break if cursor == "0"
116
+ end
117
+ end
35
118
  end
36
119
  end
37
120
  end
@@ -1,3 +1,3 @@
1
1
  module FeatureFlagger
2
- VERSION = "0.7.2"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -0,0 +1,41 @@
1
+ namespace :feature_flagger do
2
+ desc "cleaning up keys from storage that are no longer in the rollout.yml file"
3
+ task :cleanup_removed_rollouts => :environment do
4
+ keys = FeatureFlagger::Manager.detached_feature_keys
5
+ puts "Found keys to remove: #{keys}"
6
+ keys.each do |key|
7
+ FeatureFlagger::Manager.cleanup_detached key
8
+ end
9
+ end
10
+
11
+ desc "Synchronizes resource_keys with feature_keys, recommended to apps that installed feature flagger before v.1.2.0"
12
+ task :migrate_to_resource_keys => :environment do
13
+ storage = FeatureFlagger.config.storage
14
+ storage.synchronize_feature_and_resource
15
+ end
16
+
17
+ desc "Release feature to given identifiers, Usage: `$ bundle exec rake feature_flagger:release\[Account,email_marketing:whitelabel,1,2,3,4\]`"
18
+ task :release, [:entity_name, :feature_key] => :environment do |_, args|
19
+ entity = args.entity_name.constantize
20
+ entity_ids = args.extras
21
+ entity.release_id(entity_ids, *args.feature_key.split(':'))
22
+ end
23
+
24
+ desc "Unrelease feature to given identifiers, Usage: `$ bundle exec rake feature_flagger:unrelease\[Account,email_marketing:whitelabel,1,2,3,4\]`"
25
+ task :unrelease, [:entity_name, :feature_key] => :environment do |_, args|
26
+ entity, entity_ids = args.entity_name.constantize, args.extras
27
+ entity.unrelease_id(entity_ids, *args.feature_key.split(':'))
28
+ end
29
+
30
+ desc "Release one feature to all entity ids, Usage: `$ bundle exec rake feature_flagger:release_to_all\[Account,email_marketing:whitelabel\]`"
31
+ task :release_to_all, [:entity_name, :feature_key] => :environment do |_, args|
32
+ entity = args.entity_name.constantize
33
+ entity.release_to_all(*args.feature_key.split(':'))
34
+ end
35
+
36
+ desc "Unrelease one feature to all entity ids, Usage: `$ bundle exec rake feature_flagger:unrelease_to_all\[Account,email_marketing:whitelabel\]`"
37
+ task :unrelease_to_all, [:entity_name, :feature_key] => :environment do |_, args|
38
+ entity = args.entity_name.constantize
39
+ entity.unrelease_to_all(*args.feature_key.split(':'))
40
+ end
41
+ 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: 0.7.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nando Sousa
@@ -9,64 +9,64 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-09-19 00:00:00.000000000 Z
12
+ date: 2020-08-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - "~>"
18
+ - - ">"
19
19
  - !ruby/object:Gem::Version
20
20
  version: '3.2'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - "~>"
25
+ - - ">"
26
26
  - !ruby/object:Gem::Version
27
27
  version: '3.2'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: redis-namespace
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - "~>"
32
+ - - ">"
33
33
  - !ruby/object:Gem::Version
34
34
  version: '1.3'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - "~>"
39
+ - - ">"
40
40
  - !ruby/object:Gem::Version
41
41
  version: '1.3'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: bundler
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
- - - "~>"
46
+ - - ">="
47
47
  - !ruby/object:Gem::Version
48
- version: '1.12'
48
+ version: '0'
49
49
  type: :development
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - "~>"
53
+ - - ">="
54
54
  - !ruby/object:Gem::Version
55
- version: '1.12'
55
+ version: '0'
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: rake
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
60
  - - "~>"
61
61
  - !ruby/object:Gem::Version
62
- version: '10.0'
62
+ version: '13.0'
63
63
  type: :development
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
- version: '10.0'
69
+ version: '13.0'
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: rspec
72
72
  requirement: !ruby/object:Gem::Requirement
@@ -81,20 +81,34 @@ dependencies:
81
81
  - - "~>"
82
82
  - !ruby/object:Gem::Version
83
83
  version: '3.0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: simplecov
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - '='
89
+ - !ruby/object:Gem::Version
90
+ version: '0.17'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - '='
96
+ - !ruby/object:Gem::Version
97
+ version: '0.17'
84
98
  - !ruby/object:Gem::Dependency
85
99
  name: fakeredis
86
100
  requirement: !ruby/object:Gem::Requirement
87
101
  requirements:
88
- - - ">="
102
+ - - '='
89
103
  - !ruby/object:Gem::Version
90
- version: '0'
104
+ version: 0.8.0
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'
111
+ version: 0.8.0
98
112
  description: Management tool to make it easier rollouting features to customers.
99
113
  email:
100
114
  - nandosousafr@gmail.com
@@ -108,10 +122,17 @@ files:
108
122
  - lib/feature_flagger.rb
109
123
  - lib/feature_flagger/configuration.rb
110
124
  - lib/feature_flagger/control.rb
125
+ - lib/feature_flagger/core_ext.rb
111
126
  - lib/feature_flagger/feature.rb
127
+ - lib/feature_flagger/manager.rb
112
128
  - lib/feature_flagger/model.rb
129
+ - lib/feature_flagger/model_settings.rb
130
+ - lib/feature_flagger/railtie.rb
131
+ - lib/feature_flagger/storage/feature_keys_migration.rb
132
+ - lib/feature_flagger/storage/keys.rb
113
133
  - lib/feature_flagger/storage/redis.rb
114
134
  - lib/feature_flagger/version.rb
135
+ - lib/tasks/feature_flagger.rake
115
136
  homepage: http://github.com/ResultadosDigitais/feature_flagger
116
137
  licenses:
117
138
  - MIT
@@ -124,17 +145,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
124
145
  requirements:
125
146
  - - ">="
126
147
  - !ruby/object:Gem::Version
127
- version: '0'
148
+ version: '2.5'
128
149
  required_rubygems_version: !ruby/object:Gem::Requirement
129
150
  requirements:
130
151
  - - ">="
131
152
  - !ruby/object:Gem::Version
132
- version: '0'
153
+ version: 2.0.0
133
154
  requirements: []
134
- rubyforge_project:
135
- rubygems_version: 2.4.8
155
+ rubygems_version: 3.1.4
136
156
  signing_key:
137
157
  specification_version: 4
138
158
  summary: Partial release your features.
139
159
  test_files: []
140
- has_rdoc: