abstract_feature_branch 1.5.0 → 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +15 -9
- data/VERSION +1 -1
- data/abstract_feature_branch.gemspec +3 -3
- data/lib/abstract_feature_branch.rb +22 -8
- data/lib/ext/feature_branch.rb +12 -12
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b05f473f9e1490d0e627cd6ee46bc065fd99054eeec4fe4276b6009650a3ad4
|
4
|
+
data.tar.gz: 657dbe0b15b9ac0827ef3dcbaa748bc571cb7d1ef95b25e44d3ee8c1029bf4ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cbf7abdfa86da1c21bb55e913809879adc0fb7d049ae047a272dbbdec648f064112036bc1671ed2f66dc0cdec7539d0e6fee0f7d9bc099dcc710229cc8a2d1e4
|
7
|
+
data.tar.gz: 8effdc0bba15209eb0d4366dd719b98bd11c3d5e6f772788156d40a625f2539b17a726ea79ceeedc29576dc58b49a14f1b0a26cabe46a8037473e0fe5c3cc4be
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 1.5.1
|
4
|
+
|
5
|
+
- `AbstractFeatureBranch.toggled_features_for_scope(scope)` API method that returns toggled features for a scope (String)
|
6
|
+
- `AbstractFeatureBranch.scopes_for_feature(feature)` API method that returns scopes for a (scoped) feature (String or Symbol)
|
7
|
+
|
3
8
|
## 1.5.0
|
4
9
|
|
5
10
|
- Generalize "Per-User Feature Enablement" as "Scoped Feature Enablement" (using `scoped` value instead of `per_user` value) to scope features by any scope IDs (e.g. entity IDs or value objects)
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Abstract Feature Branch 1.5.
|
1
|
+
# Abstract Feature Branch 1.5.1
|
2
2
|
[![Gem Version](https://badge.fury.io/rb/abstract_feature_branch.png)](http://badge.fury.io/rb/abstract_feature_branch)
|
3
3
|
[![Build Status](https://api.travis-ci.org/AndyObtiva/abstract_feature_branch.png?branch=master)](https://travis-ci.org/AndyObtiva/abstract_feature_branch)
|
4
4
|
[![Coverage Status](https://coveralls.io/repos/AndyObtiva/abstract_feature_branch/badge.png?branch=master)](https://coveralls.io/r/AndyObtiva/abstract_feature_branch?branch=master)
|
@@ -23,8 +23,6 @@ context-specific feature files if needed.
|
|
23
23
|
|
24
24
|
[`abstract_feature_branch`](https://rubygems.org/gems/abstract_feature_branch) is one of the simplest and most minimalistic "Feature Flags" Ruby gems out there as it enables you to get started very quickly by simply leveraging YAML files without having to set up a data store if you do not need it (albeit, you also have the option to use [Redis](https://redis.com) as a very fast in-memory data store).
|
25
25
|
|
26
|
-
|
27
|
-
|
28
26
|
Requirements
|
29
27
|
------------
|
30
28
|
- Ruby (between `~> 3.1.0` and `~> 1.8.7`)
|
@@ -38,8 +36,8 @@ Setup
|
|
38
36
|
### Rails Application Use
|
39
37
|
|
40
38
|
1. Configure Rubygem
|
41
|
-
- With `rails` between `~> 7.0` and `~> 2.0`: Add the following to Gemfile <pre>gem 'abstract_feature_branch', '~> 1.5.
|
42
|
-
- With `rails` `~> 2.0` only: Add the following to config/environment.rb <pre>config.gem 'abstract_feature_branch', :version => '1.5.
|
39
|
+
- With `rails` between `~> 7.0` and `~> 2.0`: Add the following to Gemfile <pre>gem 'abstract_feature_branch', '~> 1.5.1'</pre>
|
40
|
+
- With `rails` `~> 2.0` only: Add the following to config/environment.rb <pre>config.gem 'abstract_feature_branch', :version => '1.5.1'</pre>
|
43
41
|
2. Generate <code>config/initializers/abstract_feature_branch.rb</code>, <code>lib/tasks/abstract_feature_branch.rake</code>, <code>config/features.yml</code> and <code>config/features.local.yml</code> in your Rails app directory by running <pre>rails g abstract_feature_branch:install</pre>
|
44
42
|
3. [Optional] Generate <code>config/features/[context_path].yml</code> in your Rails app directory by running <pre>rails g abstract_feature_branch:context context_path</pre> (more details under [**instructions**](#instructions))
|
45
43
|
4. [Optional] Customize configuration in <code>config/initializers/abstract_feature_branch.rb</code> (can be useful for changing location of feature files in Rails application, configuring Redis with a Redis or ConnectionPool instance to use for overrides, and [scoped feature enablement](#scoped-feature-enablement) (e.g. per-user), or troubleshooting a specific Rails environment feature configuration)
|
@@ -48,7 +46,7 @@ Setup
|
|
48
46
|
|
49
47
|
### Ruby Application General Use
|
50
48
|
|
51
|
-
1. <pre>gem install abstract_feature_branch -v 1.5.
|
49
|
+
1. <pre>gem install abstract_feature_branch -v 1.5.1</pre>
|
52
50
|
2. Add code <code>require 'abstract_feature_branch'</code>
|
53
51
|
3. Create <code>config/features.yml</code> under <code>AbstractFeatureBranch.application_root</code> and fill it with content similar to that of the sample <code>config/features.yml</code> mentioned under [**instructions**](#instructions).
|
54
52
|
4. [Optional] Create <code>config/features.local.yml</code> under <code>AbstractFeatureBranch.application_root</code> (more details under [**instructions**](#instructions))
|
@@ -138,10 +136,10 @@ It is possible to restrict enablement of features per specific users (or per ent
|
|
138
136
|
1. Use <code>toggle_features_for_scope</code> in Ruby code to enable features per scope ID (e.g. entity ID, comma-separated compound ID, JSON string, or value object), which must be a String or a value that is safely-convertable to a String like Integer (e.g. email address or database ID). This loads Redis client gem into memory and stores scoped feature configuration in Redis.
|
139
137
|
In the example below, current_user is a method that provides the current signed in user (e.g. using Rails [Devise] (https://github.com/plataformatec/devise) library).
|
140
138
|
|
141
|
-
>
|
142
|
-
> AbstractFeatureBranch.toggle_features_for_scope(
|
139
|
+
> scope = current_user.email
|
140
|
+
> AbstractFeatureBranch.toggle_features_for_scope(scope, :feature1 => true, :feature2 => false, :feature3 => true, :feature5 => true)
|
143
141
|
|
144
|
-
Use alternate version of <code>feature_branch</code> and <code>feature_enabled?</code> passing extra <code>
|
142
|
+
Use alternate version of <code>feature_branch</code> and <code>feature_enabled?</code> passing extra <code>scope</code> argument
|
145
143
|
|
146
144
|
Examples:
|
147
145
|
|
@@ -171,6 +169,14 @@ If a feature is enabled as <code>true</code> or disabled as <code>false</code> i
|
|
171
169
|
like features.local.yml or environment variable overrides), then it overrides toggled scoped feature restrictions, becoming
|
172
170
|
enabled or disabled globally.
|
173
171
|
|
172
|
+
API
|
173
|
+
---
|
174
|
+
|
175
|
+
`AbstractFeatureBranch.toggle_features_for_scope(scope, feature1: true, feature2: false, ...)`: API method that toggles features (Strings or Symbols) for a scope
|
176
|
+
|
177
|
+
`AbstractFeatureBranch.toggled_features_for_scope(scope)`: API method that returns toggled features for a scope (String)
|
178
|
+
|
179
|
+
`AbstractFeatureBranch.scopes_for_feature(feature)`: API method that returns scopes for a (scoped) feature (String or Symbol)
|
174
180
|
|
175
181
|
Recommendations
|
176
182
|
---------------
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.5.
|
1
|
+
1.5.1
|
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: abstract_feature_branch 1.5.
|
5
|
+
# stub: abstract_feature_branch 1.5.1 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "abstract_feature_branch".freeze
|
9
|
-
s.version = "1.5.
|
9
|
+
s.version = "1.5.1"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib".freeze]
|
13
13
|
s.authors = ["Andy Maleh".freeze]
|
14
|
-
s.date = "2023-02-
|
14
|
+
s.date = "2023-02-14"
|
15
15
|
s.description = "abstract_feature_branch is a Ruby gem that provides a unique variation on the Branch by Abstraction Pattern by Paul Hammant and the Feature Toggles Pattern by Martin Fowler to enhance team productivity and improve software fault tolerance.\n\nIt provides the ability to wrap blocks of code with an abstract feature branch name, and then specify in a configuration file which features to be switched on or off.\n\nThe goal is to build out upcoming features in the same source code repository branch (i.e. Continuous Integration and Trunk-Based Development), regardless of whether all are completed by the next release date or not, thus increasing team productivity by preventing integration delays. Developers then disable in-progress features until they are ready to be switched on in production, yet enable them locally and in staging environments for in-progress testing.\n\nThis gives developers the added benefit of being able to switch a feature off after release should big problems arise for a high risk feature.\n\nabstract_feature_branch additionally supports Domain Driven Design's pattern of Bounded Contexts by allowing developers to configure context-specific feature files if needed.\n\nabstract_feature_branch is one of the simplest and most minimalistic \"Feature Flags\" Ruby gems out there as it enables you to get started very quickly by simply leveraging YAML files without having to set up a data store if you do not need it (albeit, you also have the option to use Redis as a very fast in-memory data store).\n".freeze
|
16
16
|
s.extra_rdoc_files = [
|
17
17
|
"CHANGELOG.md",
|
@@ -101,10 +101,7 @@ module AbstractFeatureBranch
|
|
101
101
|
normalized_feature_name = feature.to_s.downcase
|
102
102
|
@redis_scoped_features[normalized_feature_name] ||= []
|
103
103
|
begin
|
104
|
-
|
105
|
-
feature_store.
|
106
|
-
smembers("#{AbstractFeatureBranch::ENV_FEATURE_PREFIX}#{normalized_feature_name}")
|
107
|
-
@redis_scoped_features[normalized_feature_name] += scoped_feature_scope_ids
|
104
|
+
@redis_scoped_features[normalized_feature_name] += scopes_for_feature(normalized_feature_name)
|
108
105
|
rescue Exception => error
|
109
106
|
AbstractFeatureBranch.logger.error "AbstractFeatureBranch encountered an error in retrieving Per-User values for feature \"#{normalized_feature_name}\"! Defaulting to no values...\n\nError: #{error.full_message}\n\n"
|
110
107
|
nil
|
@@ -192,17 +189,34 @@ module AbstractFeatureBranch
|
|
192
189
|
end
|
193
190
|
end
|
194
191
|
|
195
|
-
def toggle_features_for_scope(
|
192
|
+
def toggle_features_for_scope(scope, features)
|
196
193
|
features.each do |name, value|
|
197
194
|
if value
|
198
|
-
feature_store.sadd("#{ENV_FEATURE_PREFIX}#{name.to_s.downcase}",
|
195
|
+
feature_store.sadd("#{ENV_FEATURE_PREFIX}#{name.to_s.downcase}", scope)
|
199
196
|
else
|
200
|
-
feature_store.srem("#{ENV_FEATURE_PREFIX}#{name.to_s.downcase}",
|
197
|
+
feature_store.srem("#{ENV_FEATURE_PREFIX}#{name.to_s.downcase}", scope)
|
201
198
|
end
|
202
199
|
end
|
203
200
|
end
|
204
201
|
alias toggle_features_for_user toggle_features_for_scope
|
205
|
-
|
202
|
+
|
203
|
+
def toggled_features_for_scope(scope)
|
204
|
+
AbstractFeatureBranch.feature_store.keys.select do |key|
|
205
|
+
key.start_with?(AbstractFeatureBranch::ENV_FEATURE_PREFIX)
|
206
|
+
end.map do |key|
|
207
|
+
feature = key.sub(AbstractFeatureBranch::ENV_FEATURE_PREFIX, '')
|
208
|
+
end.select do |feature|
|
209
|
+
scopes_for_feature(feature).include?(scope.to_s)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def scopes_for_feature(feature)
|
214
|
+
normalized_feature_name = feature.to_s.downcase
|
215
|
+
AbstractFeatureBranch.
|
216
|
+
feature_store.
|
217
|
+
smembers("#{AbstractFeatureBranch::ENV_FEATURE_PREFIX}#{normalized_feature_name}")
|
218
|
+
end
|
219
|
+
|
206
220
|
private
|
207
221
|
|
208
222
|
def load_specific_features(features_hash, extension)
|
data/lib/ext/feature_branch.rb
CHANGED
@@ -1,20 +1,20 @@
|
|
1
1
|
class Object
|
2
2
|
class << self
|
3
3
|
raise 'Abstract feature branch conflicts with another Ruby library having Object::feature_branch' if respond_to?(:feature_branch)
|
4
|
-
def feature_branch(feature_name,
|
5
|
-
if feature_enabled?(feature_name,
|
4
|
+
def feature_branch(feature_name, scope = nil, &feature_work)
|
5
|
+
if feature_enabled?(feature_name, scope)
|
6
6
|
feature_work.call
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
10
|
raise 'Abstract feature branch conflicts with another Ruby library having Object::feature_enabled?' if respond_to?(:feature_enabled?)
|
11
|
-
def feature_enabled?(feature_name,
|
11
|
+
def feature_enabled?(feature_name, scope = nil)
|
12
12
|
normalized_feature_name = feature_name.to_s.downcase
|
13
13
|
|
14
14
|
redis_override_value = feature_enabled_reddis_override_value(normalized_feature_name)
|
15
15
|
value = !redis_override_value.nil? ? redis_override_value : AbstractFeatureBranch.application_features[normalized_feature_name]
|
16
16
|
if AbstractFeatureBranch.scoped_value?(value)
|
17
|
-
value = !
|
17
|
+
value = !scope.nil? && feature_enabled_scoped_value(feature_name, scope)
|
18
18
|
end
|
19
19
|
value
|
20
20
|
end
|
@@ -34,27 +34,27 @@ class Object
|
|
34
34
|
end
|
35
35
|
|
36
36
|
raise 'Abstract feature branch conflicts with another Ruby library having Object::feature_enabled_scoped_value' if Object.new.respond_to?(:feature_enabled_scoped_value, true)
|
37
|
-
def feature_enabled_scoped_value(normalized_feature_name,
|
37
|
+
def feature_enabled_scoped_value(normalized_feature_name, scope)
|
38
38
|
if AbstractFeatureBranch.configuration.feature_store_live_fetching?
|
39
39
|
begin
|
40
|
-
AbstractFeatureBranch.feature_store.sismember("#{AbstractFeatureBranch::ENV_FEATURE_PREFIX}#{normalized_feature_name}",
|
40
|
+
AbstractFeatureBranch.feature_store.sismember("#{AbstractFeatureBranch::ENV_FEATURE_PREFIX}#{normalized_feature_name}", scope)
|
41
41
|
rescue Exception => error
|
42
|
-
AbstractFeatureBranch.logger.error "AbstractFeatureBranch encountered an error in retrieving Per-User value for feature \"#{normalized_feature_name}\" and
|
42
|
+
AbstractFeatureBranch.logger.error "AbstractFeatureBranch encountered an error in retrieving Per-User value for feature \"#{normalized_feature_name}\" and scope #{scope}! Defaulting to nil value...\n\nError: #{error.full_message}\n\n"
|
43
43
|
nil
|
44
44
|
end
|
45
45
|
else
|
46
|
-
AbstractFeatureBranch.redis_scoped_features[normalized_feature_name]&.include?(
|
46
|
+
AbstractFeatureBranch.redis_scoped_features[normalized_feature_name]&.include?(scope.to_s)
|
47
47
|
end
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
51
|
raise 'Abstract feature branch conflicts with another Ruby library having Object#feature_branch' if Object.new.respond_to?(:feature_branch)
|
52
|
-
def feature_branch(feature_name,
|
53
|
-
Object.feature_branch(feature_name.to_s,
|
52
|
+
def feature_branch(feature_name, scope = nil, &feature_work)
|
53
|
+
Object.feature_branch(feature_name.to_s, scope, &feature_work)
|
54
54
|
end
|
55
55
|
|
56
56
|
raise 'Abstract feature branch conflicts with another Ruby library having Object#feature_enabled?' if Object.new.respond_to?(:feature_enabled?)
|
57
|
-
def feature_enabled?(feature_name,
|
58
|
-
Object.feature_enabled?(feature_name.to_s,
|
57
|
+
def feature_enabled?(feature_name, scope = nil)
|
58
|
+
Object.feature_enabled?(feature_name.to_s, scope)
|
59
59
|
end
|
60
60
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: abstract_feature_branch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.
|
4
|
+
version: 1.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Maleh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-02-
|
11
|
+
date: 2023-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: deep_merge
|