feature 0.2.1 → 0.3.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.
- 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
|
+
[](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
|
|