feature 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +5 -0
- data/README.md +4 -0
- data/lib/feature/repository/abstract_repository.rb +2 -15
- data/lib/feature/repository/simple_repository.rb +15 -46
- data/lib/feature/repository/yaml_repository.rb +14 -42
- data/lib/feature/repository.rb +1 -0
- data/lib/feature.rb +18 -24
- data/spec/feature/feature_spec.rb +0 -12
- data/spec/feature/simple_repository_spec.rb +0 -36
- data/spec/feature/yaml_repository_spec.rb +15 -8
- metadata +5 -5
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -5,6 +5,10 @@ Feature is a [feature toggle](http://martinfowler.com/bliki/FeatureToggle.html)
|
|
5
5
|
The feature toggle functionality has to be configured by feature repositories. A feature repository simply provides lists of active and inctive features.
|
6
6
|
With this approach Feature is higly configurable and not bound to a specific kind of configuration.
|
7
7
|
|
8
|
+
## CI status
|
9
|
+
|
10
|
+
[![Travis-CI Build Status](https://secure.travis-ci.org/mgsnova/feature.png)](https://secure.travis-ci.org/mgsnova/feature)
|
11
|
+
|
8
12
|
## Installation
|
9
13
|
|
10
14
|
gem install feature
|
@@ -1,12 +1,9 @@
|
|
1
1
|
module Feature
|
2
2
|
module Repository
|
3
|
-
##
|
4
3
|
# Abstract class for subclassing and building repository classes which
|
5
|
-
# provide lists of active
|
4
|
+
# provide lists of active features
|
6
5
|
#
|
7
6
|
class AbstractRepository
|
8
|
-
|
9
|
-
##
|
10
7
|
# Constructor
|
11
8
|
#
|
12
9
|
# Should be overridden in derived class to initialize repository with all data needed
|
@@ -15,23 +12,13 @@ module Feature
|
|
15
12
|
raise "abstract class #{self.class.name}!"
|
16
13
|
end
|
17
14
|
|
18
|
-
##
|
19
15
|
# Returns list of active features
|
20
16
|
#
|
21
|
-
#
|
17
|
+
# @return [Array<Symbol>] list of active features
|
22
18
|
#
|
23
19
|
def active_features
|
24
20
|
raise "#{__method__} has to be overridden in derived class"
|
25
21
|
end
|
26
|
-
|
27
|
-
##
|
28
|
-
# Returns list of inactive features
|
29
|
-
#
|
30
|
-
# @return Array<Symbol>
|
31
|
-
#
|
32
|
-
def inactive_features
|
33
|
-
raise "#{__method__} has to be overridden in derived class"
|
34
|
-
end
|
35
22
|
end
|
36
23
|
end
|
37
24
|
end
|
@@ -1,86 +1,55 @@
|
|
1
1
|
module Feature
|
2
2
|
module Repository
|
3
|
-
|
4
|
-
#
|
5
|
-
# Simply add features to both lists, not config or data sources required
|
3
|
+
# SimpleRepository for active feature list
|
4
|
+
# Simply add features to that should be active, no config or data sources required
|
6
5
|
#
|
7
6
|
class SimpleRepository < AbstractRepository
|
8
|
-
##
|
9
7
|
# Constructor
|
10
8
|
#
|
11
9
|
def initialize
|
12
10
|
@active_features = []
|
13
|
-
@inactive_features = []
|
14
11
|
end
|
15
12
|
|
16
|
-
##
|
17
13
|
# Returns list of active features
|
18
14
|
#
|
19
|
-
#
|
15
|
+
# @return [Array<Symbol>] list of active features
|
20
16
|
#
|
21
17
|
def active_features
|
22
18
|
@active_features.dup
|
23
19
|
end
|
24
20
|
|
25
|
-
|
26
|
-
# Returns list of inactive features
|
21
|
+
# Add an active feature to repository
|
27
22
|
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
def inactive_features
|
31
|
-
@inactive_features.dup
|
32
|
-
end
|
33
|
-
|
34
|
-
##
|
35
|
-
# Add an active feature
|
36
|
-
#
|
37
|
-
# @param Sybmol, feature
|
23
|
+
# @param [Symbol] feature the feature to be added
|
38
24
|
#
|
39
25
|
def add_active_feature(feature)
|
40
|
-
|
41
|
-
|
26
|
+
check_feature_is_not_symbol(feature)
|
27
|
+
check_feature_already_in_list(feature)
|
42
28
|
@active_features << feature
|
43
29
|
end
|
44
30
|
|
45
|
-
|
46
|
-
# Add an inactive feature
|
47
|
-
#
|
48
|
-
# @param Sybmol, feature
|
49
|
-
#
|
50
|
-
def add_inactive_feature(feature)
|
51
|
-
check_feature_is_symbol(feature)
|
52
|
-
check_feature_already_in_lists(feature)
|
53
|
-
@inactive_features << feature
|
54
|
-
end
|
55
|
-
|
56
|
-
##
|
57
|
-
# Checks if the given feature is a symbol and raises and raises an exception if so
|
31
|
+
# Checks if the given feature is a not symbol and raises an exception if so
|
58
32
|
#
|
59
|
-
#
|
33
|
+
# @param [Sybmol] feature the feature to be checked
|
60
34
|
#
|
61
|
-
def
|
35
|
+
def check_feature_is_not_symbol(feature)
|
62
36
|
if !feature.is_a?(Symbol)
|
63
37
|
raise ArgumentError, "given feature #{feature} is not a symbol"
|
64
38
|
end
|
65
39
|
end
|
66
|
-
private :
|
40
|
+
private :check_feature_is_not_symbol
|
67
41
|
|
68
|
-
|
69
|
-
# Checks if given feature is already added to list of active or inactive features
|
42
|
+
# Checks if given feature is already added to list of active features
|
70
43
|
# and raises an exception if so
|
71
44
|
#
|
72
|
-
#
|
45
|
+
# @param [Symbol] feature the feature to be checked
|
73
46
|
#
|
74
|
-
def
|
47
|
+
def check_feature_already_in_list(feature)
|
75
48
|
if @active_features.include?(feature)
|
76
49
|
raise ArgumentError, "feature :#{feature} already added to list of active features"
|
77
50
|
end
|
78
|
-
|
79
|
-
if @inactive_features.include?(feature)
|
80
|
-
raise ArgumentError, "feature :#{feature} already added to list of inactive features"
|
81
|
-
end
|
82
51
|
end
|
83
|
-
private :
|
52
|
+
private :check_feature_already_in_list
|
84
53
|
end
|
85
54
|
end
|
86
55
|
end
|
@@ -1,78 +1,50 @@
|
|
1
1
|
module Feature
|
2
2
|
module Repository
|
3
|
-
|
4
|
-
# YamlRepository for active and inactive feature list
|
3
|
+
# YamlRepository for active and inactive features
|
5
4
|
# The yaml config file should look like this:
|
6
5
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
6
|
+
# features:
|
7
|
+
# an_active_feature: true
|
8
|
+
# an_inactive_feature: false
|
10
9
|
#
|
11
10
|
class YamlRepository < AbstractRepository
|
12
11
|
require 'yaml'
|
13
12
|
|
14
|
-
##
|
15
13
|
# Constructor
|
16
14
|
#
|
17
|
-
#
|
15
|
+
# @param [String] yaml_file_name the yaml config filename
|
18
16
|
#
|
19
17
|
def initialize(yaml_file_name)
|
20
18
|
@yaml_file_name = yaml_file_name
|
21
19
|
end
|
22
20
|
|
23
|
-
##
|
24
21
|
# Returns list of active features
|
25
22
|
#
|
26
|
-
#
|
23
|
+
# @return [Array<Symbol>] list of active features
|
27
24
|
#
|
28
25
|
def active_features
|
29
|
-
get_active_features_from_file
|
30
|
-
end
|
31
|
-
|
32
|
-
##
|
33
|
-
# Return a list of active features read from the config file
|
34
|
-
#
|
35
|
-
# @return Array<Symbol>
|
36
|
-
#
|
37
|
-
def get_active_features_from_file
|
38
26
|
features_hash = read_and_parse_file_data
|
39
27
|
features = features_hash.keys.select { |feature_key| features_hash[feature_key] }
|
40
28
|
features.sort.map(&:to_sym)
|
41
29
|
end
|
42
|
-
private :get_active_features_from_file
|
43
|
-
|
44
|
-
##
|
45
|
-
# Returns list of inactive features
|
46
|
-
#
|
47
|
-
# @return Array<Symbol>
|
48
|
-
#
|
49
|
-
def inactive_features
|
50
|
-
get_inactive_features_from_file
|
51
|
-
end
|
52
|
-
|
53
|
-
##
|
54
|
-
# Return a list of inactive features read from the config file
|
55
|
-
#
|
56
|
-
# @return Array<Symbol>
|
57
|
-
#
|
58
|
-
def get_inactive_features_from_file
|
59
|
-
features_hash = read_and_parse_file_data
|
60
|
-
features = features_hash.keys.select { |feature_key| !features_hash[feature_key] }
|
61
|
-
features.sort.map(&:to_sym)
|
62
|
-
end
|
63
|
-
private :get_inactive_features_from_file
|
64
30
|
|
65
|
-
##
|
66
31
|
# Read and parses the feature config file
|
67
32
|
#
|
68
|
-
#
|
33
|
+
# @return [Hash] Hash containing :feature => true/false entries for representing active/inactive features
|
69
34
|
#
|
70
35
|
def read_and_parse_file_data
|
71
36
|
raw_data = File.read(@yaml_file_name)
|
72
37
|
data = YAML.load(raw_data)
|
38
|
+
|
73
39
|
if !data.is_a?(Hash) or !data.has_key?('features')
|
74
40
|
raise ArgumentError, "content of #{@yaml_file_name} does not contain proper config"
|
75
41
|
end
|
42
|
+
|
43
|
+
invalid_value = data['features'].values.detect { |value| ![true, false].include?(value) }
|
44
|
+
if invalid_value
|
45
|
+
raise ArgumentError, "#{invalid_value} is not allowed value in config, use true/false"
|
46
|
+
end
|
47
|
+
|
76
48
|
data['features']
|
77
49
|
end
|
78
50
|
private :read_and_parse_file_data
|
data/lib/feature/repository.rb
CHANGED
data/lib/feature.rb
CHANGED
@@ -1,21 +1,20 @@
|
|
1
|
-
##
|
2
1
|
# Feature module provides all methods
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
2
|
+
# - to set a feature repository
|
3
|
+
# - to check if a feature (represented by a symbol) is active or inactive
|
4
|
+
# - for conditional block execution with or without a feature
|
5
|
+
# - to refresh the feature lists (request them from repository)
|
6
|
+
#
|
7
|
+
# @note all features not active will be handled has inactive
|
7
8
|
#
|
8
9
|
module Feature
|
9
10
|
require 'feature/repository'
|
10
11
|
|
11
12
|
@repository = nil
|
12
13
|
@active_features = nil
|
13
|
-
@inactive_features = nil
|
14
14
|
|
15
|
-
##
|
16
15
|
# Set the feature repository
|
17
16
|
#
|
18
|
-
#
|
17
|
+
# @param [Feature::Repository::AbstractRepository] repository the repository to get the features from
|
19
18
|
#
|
20
19
|
def self.set_repository(repository)
|
21
20
|
if !repository.is_a?(Feature::Repository::AbstractRepository)
|
@@ -26,19 +25,17 @@ module Feature
|
|
26
25
|
refresh!
|
27
26
|
end
|
28
27
|
|
29
|
-
|
30
|
-
# Obtains list of active and inactive features from repository
|
28
|
+
# Obtains list of active features from repository
|
31
29
|
#
|
32
30
|
def self.refresh!
|
33
31
|
@active_features = @repository.active_features
|
34
|
-
@inactive_features = @repository.inactive_features
|
35
32
|
end
|
36
33
|
|
37
34
|
##
|
38
35
|
# Requests if feature is active
|
39
36
|
#
|
40
|
-
#
|
41
|
-
#
|
37
|
+
# @param [Symbol] feature
|
38
|
+
# @return [Boolean]
|
42
39
|
#
|
43
40
|
def self.active?(feature)
|
44
41
|
if !@repository
|
@@ -48,23 +45,19 @@ module Feature
|
|
48
45
|
@active_features.include?(feature)
|
49
46
|
end
|
50
47
|
|
51
|
-
|
52
|
-
# Requests if feature is inactive
|
48
|
+
# Requests if feature is inactive (or unknown)
|
53
49
|
#
|
54
|
-
#
|
55
|
-
#
|
50
|
+
# @param [Symbol] feature
|
51
|
+
# @return [Boolean]
|
56
52
|
#
|
57
53
|
def self.inactive?(feature)
|
58
|
-
|
59
|
-
raise "Feature is missing Repository for obtaining feature lists"
|
60
|
-
end
|
61
|
-
|
62
|
-
@inactive_features.include?(feature)
|
54
|
+
!self.active?(feature)
|
63
55
|
end
|
64
56
|
|
65
|
-
##
|
66
57
|
# Execute the given block if feature is active
|
67
58
|
#
|
59
|
+
# @param [Symbol] feature
|
60
|
+
#
|
68
61
|
def self.with(feature)
|
69
62
|
if !block_given?
|
70
63
|
raise ArgumentError, "no block given to #{__method__}"
|
@@ -75,9 +68,10 @@ module Feature
|
|
75
68
|
end
|
76
69
|
end
|
77
70
|
|
78
|
-
##
|
79
71
|
# Execute the given block if feature is inactive
|
80
72
|
#
|
73
|
+
# @param [Symbol] feature
|
74
|
+
#
|
81
75
|
def self.without(feature)
|
82
76
|
if !block_given?
|
83
77
|
raise ArgumentError, "no block given to #{__method__}"
|
@@ -55,11 +55,6 @@ describe Feature do
|
|
55
55
|
@repository.add_active_feature(:feature_a)
|
56
56
|
Feature.active?(:feature_a).should be_false
|
57
57
|
end
|
58
|
-
|
59
|
-
it "should get inactive features from repository once" do
|
60
|
-
@repository.add_inactive_feature(:feature_a)
|
61
|
-
Feature.inactive?(:feature_a).should be_false
|
62
|
-
end
|
63
58
|
end
|
64
59
|
|
65
60
|
context "refresh features" do
|
@@ -73,19 +68,12 @@ describe Feature do
|
|
73
68
|
Feature.refresh!
|
74
69
|
Feature.active?(:feature_a).should be_true
|
75
70
|
end
|
76
|
-
|
77
|
-
it "should refresh inactive feature lists from repository" do
|
78
|
-
@repository.add_inactive_feature(:feature_a)
|
79
|
-
Feature.refresh!
|
80
|
-
Feature.inactive?(:feature_a).should be_true
|
81
|
-
end
|
82
71
|
end
|
83
72
|
|
84
73
|
context "request features" do
|
85
74
|
before(:each) do
|
86
75
|
repository = Feature::Repository::SimpleRepository.new
|
87
76
|
repository.add_active_feature :feature_active
|
88
|
-
repository.add_inactive_feature :feature_inactive
|
89
77
|
Feature.set_repository repository
|
90
78
|
end
|
91
79
|
|
@@ -11,10 +11,6 @@ describe Feature::Repository::SimpleRepository do
|
|
11
11
|
@repository.active_features.should == []
|
12
12
|
end
|
13
13
|
|
14
|
-
it "should have no inactive features after initialization" do
|
15
|
-
@repository.inactive_features.should == []
|
16
|
-
end
|
17
|
-
|
18
14
|
it "should add an active feature" do
|
19
15
|
@repository.add_active_feature :feature_a
|
20
16
|
@repository.active_features.should == [:feature_a]
|
@@ -32,36 +28,4 @@ describe Feature::Repository::SimpleRepository do
|
|
32
28
|
@repository.add_active_feature :feature_a
|
33
29
|
end.should raise_error(ArgumentError, "feature :feature_a already added to list of active features")
|
34
30
|
end
|
35
|
-
|
36
|
-
it "should raise an exception when adding a active feature already added as inactive" do
|
37
|
-
@repository.add_inactive_feature :feature_a
|
38
|
-
lambda do
|
39
|
-
@repository.add_active_feature :feature_a
|
40
|
-
end.should raise_error(ArgumentError, "feature :feature_a already added to list of inactive features")
|
41
|
-
end
|
42
|
-
|
43
|
-
it "should add an inactive feature" do
|
44
|
-
@repository.add_inactive_feature :feature_a
|
45
|
-
@repository.inactive_features.should == [:feature_a]
|
46
|
-
end
|
47
|
-
|
48
|
-
it "should raise an exception when adding not a symbol as inactive feature" do
|
49
|
-
lambda do
|
50
|
-
@repository.add_inactive_feature 'feature_a'
|
51
|
-
end.should raise_error(ArgumentError, "given feature feature_a is not a symbol")
|
52
|
-
end
|
53
|
-
|
54
|
-
it "should raise an exception when adding a inactive feature already added as inactive" do
|
55
|
-
@repository.add_inactive_feature :feature_a
|
56
|
-
lambda do
|
57
|
-
@repository.add_inactive_feature :feature_a
|
58
|
-
end.should raise_error(ArgumentError, "feature :feature_a already added to list of inactive features")
|
59
|
-
end
|
60
|
-
|
61
|
-
it "should raise an exception when adding a inactive feature already added as active" do
|
62
|
-
@repository.add_active_feature :feature_a
|
63
|
-
lambda do
|
64
|
-
@repository.add_inactive_feature :feature_a
|
65
|
-
end.should raise_error(ArgumentError, "feature :feature_a already added to list of active features")
|
66
|
-
end
|
67
31
|
end
|
@@ -28,10 +28,6 @@ EOF
|
|
28
28
|
@repo.active_features.should == [:feature_a_active, :feature_b_active]
|
29
29
|
end
|
30
30
|
|
31
|
-
it "should read inactive features from a config file" do
|
32
|
-
@repo.inactive_features.should == [:feature_c_inactive, :feature_d_inactive]
|
33
|
-
end
|
34
|
-
|
35
31
|
context "re-read config file" do
|
36
32
|
before(:each) do
|
37
33
|
fp = File.new(@filename, 'w')
|
@@ -46,10 +42,6 @@ EOF
|
|
46
42
|
it "should read active features new on each request" do
|
47
43
|
@repo.active_features.should == [:feature_a_active]
|
48
44
|
end
|
49
|
-
|
50
|
-
it "should read inactive features new on each request" do
|
51
|
-
@repo.inactive_features.should == [:feature_c_inactive]
|
52
|
-
end
|
53
45
|
end
|
54
46
|
end
|
55
47
|
|
@@ -71,4 +63,19 @@ EOF
|
|
71
63
|
repo.active_features
|
72
64
|
end.should raise_error(ArgumentError, "content of #{@filename} does not contain proper config")
|
73
65
|
end
|
66
|
+
|
67
|
+
it "should raise exception on not true/false value in config" do
|
68
|
+
@filename = Tempfile.new(['feature_config', '.yaml']).path
|
69
|
+
fp = File.new(@filename, 'w')
|
70
|
+
fp.write <<"EOF";
|
71
|
+
features:
|
72
|
+
invalid_feature: neither_true_or_false
|
73
|
+
EOF
|
74
|
+
fp.close
|
75
|
+
|
76
|
+
repo = YamlRepository.new(@filename)
|
77
|
+
lambda do
|
78
|
+
repo.active_features
|
79
|
+
end.should raise_error(ArgumentError, "neither_true_or_false is not allowed value in config, use true/false")
|
80
|
+
end
|
74
81
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: feature
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 19
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 3
|
9
|
+
- 0
|
10
|
+
version: 0.3.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Markus Gerdes
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-11-
|
18
|
+
date: 2011-11-23 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|