feature 0.2.1

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.
@@ -0,0 +1,11 @@
1
+ ## 0.2.1 (2011-11-22)
2
+
3
+ * refactored gemspec
4
+
5
+ ## 0.2.0 (2011-11-22)
6
+
7
+ * add yaml config file repository
8
+
9
+ ## 0.1.0 (2011-11-18)
10
+
11
+ * first version with basic functionality and simple feature repository
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+
3
+ gem "rake"
4
+ gem "rspec"
@@ -0,0 +1,26 @@
1
+ # Feature
2
+
3
+ Feature is a [feature toggle](http://martinfowler.com/bliki/FeatureToggle.html) library for ruby.
4
+
5
+ The feature toggle functionality has to be configured by feature repositories. A feature repository simply provides lists of active and inctive features.
6
+ With this approach Feature is higly configurable and not bound to a specific kind of configuration.
7
+
8
+ ## Installation
9
+
10
+ gem install feature
11
+
12
+ ## Example usage
13
+
14
+ require 'feature'
15
+
16
+ repo = Feature::Repository::SimpleRepository.new
17
+ repo.add_active_feature :be_nice
18
+
19
+ Feature.set_repository repo
20
+
21
+ Feature.active?(:be_nice)
22
+ # => true
23
+
24
+ Feature.with(:be_nice) do
25
+ puts "you can read this"
26
+ end
@@ -0,0 +1,10 @@
1
+ require 'rake/testtask'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec) do |spec|
5
+ spec.pattern = 'spec/**/*_spec.rb'
6
+ spec.rspec_opts = ['--colour', '-f documentation', '--backtrace']
7
+ end
8
+
9
+ task :default => [:spec] do
10
+ end
@@ -0,0 +1,90 @@
1
+ ##
2
+ # Feature module provides all methods
3
+ # - to set a feature repository
4
+ # - to check if a feature (represented by a symbol) is acitve or inactive
5
+ # - for conditional block execution with or without a feature
6
+ # - to refresh the feature lists (request them from repository)
7
+ #
8
+ module Feature
9
+ require 'feature/repository'
10
+
11
+ @repository = nil
12
+ @active_features = nil
13
+ @inactive_features = nil
14
+
15
+ ##
16
+ # Set the feature repository
17
+ #
18
+ # @param Feature::Repository::AbstractRepository, repository
19
+ #
20
+ def self.set_repository(repository)
21
+ if !repository.is_a?(Feature::Repository::AbstractRepository)
22
+ raise ArgumentError, "given argument is not a repository"
23
+ end
24
+
25
+ @repository = repository
26
+ refresh!
27
+ end
28
+
29
+ ##
30
+ # Obtains list of active and inactive features from repository
31
+ #
32
+ def self.refresh!
33
+ @active_features = @repository.active_features
34
+ @inactive_features = @repository.inactive_features
35
+ end
36
+
37
+ ##
38
+ # Requests if feature is active
39
+ #
40
+ # @param Symbol, feature
41
+ # @return Boolean
42
+ #
43
+ def self.active?(feature)
44
+ if !@repository
45
+ raise "Feature is missing Repository for obtaining feature lists"
46
+ end
47
+
48
+ @active_features.include?(feature)
49
+ end
50
+
51
+ ##
52
+ # Requests if feature is inactive
53
+ #
54
+ # @param Symbol, feature
55
+ # @return Boolean
56
+ #
57
+ def self.inactive?(feature)
58
+ if !@repository
59
+ raise "Feature is missing Repository for obtaining feature lists"
60
+ end
61
+
62
+ @inactive_features.include?(feature)
63
+ end
64
+
65
+ ##
66
+ # Execute the given block if feature is active
67
+ #
68
+ def self.with(feature)
69
+ if !block_given?
70
+ raise ArgumentError, "no block given to #{__method__}"
71
+ end
72
+
73
+ if active?(feature)
74
+ yield
75
+ end
76
+ end
77
+
78
+ ##
79
+ # Execute the given block if feature is inactive
80
+ #
81
+ def self.without(feature)
82
+ if !block_given?
83
+ raise ArgumentError, "no block given to #{__method__}"
84
+ end
85
+
86
+ if inactive?(feature)
87
+ yield
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,7 @@
1
+ module Feature
2
+ module Repository
3
+ require 'feature/repository/abstract_repository'
4
+ require 'feature/repository/simple_repository'
5
+ require 'feature/repository/yaml_repository'
6
+ end
7
+ end
@@ -0,0 +1,37 @@
1
+ module Feature
2
+ module Repository
3
+ ##
4
+ # Abstract class for subclassing and building repository classes which
5
+ # provide lists of active and inactive features
6
+ #
7
+ class AbstractRepository
8
+
9
+ ##
10
+ # Constructor
11
+ #
12
+ # Should be overridden in derived class to initialize repository with all data needed
13
+ #
14
+ def initialize
15
+ raise "abstract class #{self.class.name}!"
16
+ end
17
+
18
+ ##
19
+ # Returns list of active features
20
+ #
21
+ # @return Array<Symbol>
22
+ #
23
+ def active_features
24
+ raise "#{__method__} has to be overridden in derived class"
25
+ 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
+ end
36
+ end
37
+ end
@@ -0,0 +1,86 @@
1
+ module Feature
2
+ module Repository
3
+ ##
4
+ # SimpleRepository for active and inactive feature list
5
+ # Simply add features to both lists, not config or data sources required
6
+ #
7
+ class SimpleRepository < AbstractRepository
8
+ ##
9
+ # Constructor
10
+ #
11
+ def initialize
12
+ @active_features = []
13
+ @inactive_features = []
14
+ end
15
+
16
+ ##
17
+ # Returns list of active features
18
+ #
19
+ # @return Array<Symbol>
20
+ #
21
+ def active_features
22
+ @active_features.dup
23
+ end
24
+
25
+ ##
26
+ # Returns list of inactive features
27
+ #
28
+ # @return Array<Symbol>
29
+ #
30
+ def inactive_features
31
+ @inactive_features.dup
32
+ end
33
+
34
+ ##
35
+ # Add an active feature
36
+ #
37
+ # @param Sybmol, feature
38
+ #
39
+ def add_active_feature(feature)
40
+ check_feature_is_symbol(feature)
41
+ check_feature_already_in_lists(feature)
42
+ @active_features << feature
43
+ end
44
+
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
58
+ #
59
+ # @param Sybmol, feature
60
+ #
61
+ def check_feature_is_symbol(feature)
62
+ if !feature.is_a?(Symbol)
63
+ raise ArgumentError, "given feature #{feature} is not a symbol"
64
+ end
65
+ end
66
+ private :check_feature_is_symbol
67
+
68
+ ##
69
+ # Checks if given feature is already added to list of active or inactive features
70
+ # and raises an exception if so
71
+ #
72
+ # @param Symbol, feature
73
+ #
74
+ def check_feature_already_in_lists(feature)
75
+ if @active_features.include?(feature)
76
+ raise ArgumentError, "feature :#{feature} already added to list of active features"
77
+ end
78
+
79
+ if @inactive_features.include?(feature)
80
+ raise ArgumentError, "feature :#{feature} already added to list of inactive features"
81
+ end
82
+ end
83
+ private :check_feature_already_in_lists
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,81 @@
1
+ module Feature
2
+ module Repository
3
+ ##
4
+ # YamlRepository for active and inactive feature list
5
+ # The yaml config file should look like this:
6
+ #
7
+ # features:
8
+ # an_active_feature: true
9
+ # an_inactive_feature: false
10
+ #
11
+ class YamlRepository < AbstractRepository
12
+ require 'yaml'
13
+
14
+ ##
15
+ # Constructor
16
+ #
17
+ # @param String, yaml_file_name
18
+ #
19
+ def initialize(yaml_file_name)
20
+ @yaml_file_name = yaml_file_name
21
+ end
22
+
23
+ ##
24
+ # Returns list of active features
25
+ #
26
+ # @return Array<Symbol>
27
+ #
28
+ 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
+ features_hash = read_and_parse_file_data
39
+ features = features_hash.keys.select { |feature_key| features_hash[feature_key] }
40
+ features.sort.map(&:to_sym)
41
+ 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
+
65
+ ##
66
+ # Read and parses the feature config file
67
+ #
68
+ # @return Hash (with :feature => true/false meaning active/inactive)
69
+ #
70
+ def read_and_parse_file_data
71
+ raw_data = File.read(@yaml_file_name)
72
+ data = YAML.load(raw_data)
73
+ if !data.is_a?(Hash) or !data.has_key?('features')
74
+ raise ArgumentError, "content of #{@yaml_file_name} does not contain proper config"
75
+ end
76
+ data['features']
77
+ end
78
+ private :read_and_parse_file_data
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Feature::Repository::AbstractRepository do
4
+ it "should raise error on creating a new instance" do
5
+ lambda do
6
+ Feature::Repository::AbstractRepository.new
7
+ end.should raise_error("abstract class Feature::Repository::AbstractRepository!")
8
+ end
9
+ end
@@ -0,0 +1,161 @@
1
+ require 'spec_helper'
2
+
3
+ describe Feature do
4
+ context "without FeatureRepository" do
5
+ it "should raise an exception when calling active?" do
6
+ lambda do
7
+ Feature.active?(:feature_a)
8
+ end.should raise_error("Feature is missing Repository for obtaining feature lists")
9
+ end
10
+
11
+ it "should raise an exception when calling inactive?" do
12
+ lambda do
13
+ Feature.inactive?(:feature_a)
14
+ end.should raise_error("Feature is missing Repository for obtaining feature lists")
15
+ end
16
+
17
+ it "should raise an exception when calling with" do
18
+ lambda do
19
+ Feature.with(:feature_a) do
20
+ end
21
+ end.should raise_error("Feature is missing Repository for obtaining feature lists")
22
+ end
23
+
24
+ it "should raise an exception when calling without" do
25
+ lambda do
26
+ Feature.without(:feature_a) do
27
+ end
28
+ end.should raise_error("Feature is missing Repository for obtaining feature lists")
29
+ end
30
+ end
31
+
32
+ context "setting Repository" do
33
+ before(:each) do
34
+ @repository = Feature::Repository::SimpleRepository.new
35
+ Feature.set_repository @repository
36
+ end
37
+
38
+ it "should raise an exception when add repository with wrong class" do
39
+ lambda do
40
+ Feature.set_repository("not a repository")
41
+ end.should raise_error(ArgumentError, "given argument is not a repository")
42
+ end
43
+
44
+ it "should set a feature repository" do
45
+ lambda do
46
+ Feature.active?(:feature_a)
47
+ end.should_not raise_error
48
+
49
+ lambda do
50
+ Feature.inactive?(:feature_a)
51
+ end.should_not raise_error
52
+ end
53
+
54
+ it "should get active features from repository once" do
55
+ @repository.add_active_feature(:feature_a)
56
+ Feature.active?(:feature_a).should be_false
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
+ end
64
+
65
+ context "refresh features" do
66
+ before(:each) do
67
+ @repository = Feature::Repository::SimpleRepository.new
68
+ Feature.set_repository @repository
69
+ end
70
+
71
+ it "should refresh active feature lists from repository" do
72
+ @repository.add_active_feature(:feature_a)
73
+ Feature.refresh!
74
+ Feature.active?(:feature_a).should be_true
75
+ 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
+ end
83
+
84
+ context "request features" do
85
+ before(:each) do
86
+ repository = Feature::Repository::SimpleRepository.new
87
+ repository.add_active_feature :feature_active
88
+ repository.add_inactive_feature :feature_inactive
89
+ Feature.set_repository repository
90
+ end
91
+
92
+ it "should affirm active feature is active" do
93
+ Feature.active?(:feature_active).should be_true
94
+ end
95
+
96
+ it "should not affirm active feature is inactive" do
97
+ Feature.inactive?(:feature_active).should be_false
98
+ end
99
+
100
+ it "should affirm inactive feature is inactive" do
101
+ Feature.inactive?(:feature_inactive).should be_true
102
+ end
103
+
104
+ it "should not affirm inactive feature is active" do
105
+ Feature.active?(:feature_inactive).should be_false
106
+ end
107
+
108
+ it "should call block with active feature in active list" do
109
+ reached = false
110
+
111
+ Feature.with(:feature_active) do
112
+ reached = true
113
+ end
114
+
115
+ reached.should be_true
116
+ end
117
+
118
+ it "should not call block with active feature not in active list" do
119
+ reached = false
120
+
121
+ Feature.with(:feature_inactive) do
122
+ reached = true
123
+ end
124
+
125
+ reached.should be_false
126
+ end
127
+
128
+ it "should raise exception when no block given to with" do
129
+ lambda do
130
+ Feature.with(:feature_active)
131
+ end.should raise_error(ArgumentError, "no block given to with")
132
+ end
133
+
134
+ it "should call block without inactive feature in inactive list" do
135
+ reached = false
136
+
137
+ Feature.without(:feature_inactive) do
138
+ reached = true
139
+ end
140
+
141
+ reached.should be_true
142
+ end
143
+
144
+ it "should not call block without inactive feature in inactive list" do
145
+ reached = false
146
+
147
+ Feature.without(:feature_active) do
148
+ reached = true
149
+ end
150
+
151
+ reached.should be_false
152
+ end
153
+
154
+ it "should raise exception when no block given to without" do
155
+ lambda do
156
+ Feature.without(:feature_inactive)
157
+ end.should raise_error(ArgumentError, "no block given to without")
158
+ end
159
+
160
+ end
161
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ include Feature::Repository
4
+
5
+ describe Feature::Repository::SimpleRepository do
6
+ before(:each) do
7
+ @repository = SimpleRepository.new
8
+ end
9
+
10
+ it "should have no active features after initialization" do
11
+ @repository.active_features.should == []
12
+ end
13
+
14
+ it "should have no inactive features after initialization" do
15
+ @repository.inactive_features.should == []
16
+ end
17
+
18
+ it "should add an active feature" do
19
+ @repository.add_active_feature :feature_a
20
+ @repository.active_features.should == [:feature_a]
21
+ end
22
+
23
+ it "should raise an exception when adding not a symbol as active feature" do
24
+ lambda do
25
+ @repository.add_active_feature 'feature_a'
26
+ end.should raise_error(ArgumentError, "given feature feature_a is not a symbol")
27
+ end
28
+
29
+ it "should raise an exception when adding a active feature already added as active" do
30
+ @repository.add_active_feature :feature_a
31
+ lambda do
32
+ @repository.add_active_feature :feature_a
33
+ end.should raise_error(ArgumentError, "feature :feature_a already added to list of active features")
34
+ 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
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+ require 'tempfile'
3
+
4
+ include Feature::Repository
5
+
6
+ describe Feature::Repository::YamlRepository do
7
+ context "proper config file" do
8
+ before(:each) do
9
+ @filename = Tempfile.new(['feature_config', '.yaml']).path
10
+ fp = File.new(@filename, 'w')
11
+ fp.write <<"EOF";
12
+ features:
13
+ feature_a_active: true
14
+ feature_b_active: true
15
+ feature_c_inactive: false
16
+ feature_d_inactive: false
17
+ EOF
18
+ fp.close
19
+
20
+ @repo = YamlRepository.new(@filename)
21
+ end
22
+
23
+ after(:each) do
24
+ File.delete(@filename)
25
+ end
26
+
27
+ it "should read active features from a config file" do
28
+ @repo.active_features.should == [:feature_a_active, :feature_b_active]
29
+ end
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
+ context "re-read config file" do
36
+ before(:each) do
37
+ fp = File.new(@filename, 'w')
38
+ fp.write <<"EOF";
39
+ features:
40
+ feature_a_active: true
41
+ feature_c_inactive: false
42
+ EOF
43
+ fp.close
44
+ end
45
+
46
+ it "should read active features new on each request" do
47
+ @repo.active_features.should == [:feature_a_active]
48
+ end
49
+
50
+ it "should read inactive features new on each request" do
51
+ @repo.inactive_features.should == [:feature_c_inactive]
52
+ end
53
+ end
54
+ end
55
+
56
+ it "should raise exception on no file found" do
57
+ repo = YamlRepository.new("/this/file/should/not/exist")
58
+ lambda do
59
+ repo.active_features
60
+ end.should raise_error(Errno::ENOENT, "No such file or directory - /this/file/should/not/exist")
61
+ end
62
+
63
+ it "should raise exception on invalid yaml" do
64
+ @filename = Tempfile.new(['feature_config', '.yaml']).path
65
+ fp = File.new(@filename, 'w')
66
+ fp.write "this is not valid feature config"
67
+ fp.close
68
+
69
+ repo = YamlRepository.new(@filename)
70
+ lambda do
71
+ repo.active_features
72
+ end.should raise_error(ArgumentError, "content of #{@filename} does not contain proper config")
73
+ end
74
+ end
@@ -0,0 +1,9 @@
1
+ require "pathname"
2
+ require 'pp'
3
+
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+
7
+ SPEC_ROOT = Pathname(__FILE__).dirname.expand_path
8
+
9
+ require SPEC_ROOT.parent + 'lib/feature'
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: feature
3
+ version: !ruby/object:Gem::Version
4
+ hash: 21
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 1
10
+ version: 0.2.1
11
+ platform: ruby
12
+ authors:
13
+ - Markus Gerdes
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-11-22 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description:
23
+ email: github@mgsnova.de
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - lib/feature.rb
32
+ - lib/feature/repository/simple_repository.rb
33
+ - lib/feature/repository/abstract_repository.rb
34
+ - lib/feature/repository/yaml_repository.rb
35
+ - lib/feature/repository.rb
36
+ - spec/feature/simple_repository_spec.rb
37
+ - spec/feature/yaml_repository_spec.rb
38
+ - spec/feature/abstract_repository_spec.rb
39
+ - spec/feature/feature_spec.rb
40
+ - spec/spec_helper.rb
41
+ - Rakefile
42
+ - Gemfile
43
+ - README.md
44
+ - CHANGELOG.md
45
+ has_rdoc: true
46
+ homepage: http://github.com/mgsnova/feature
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options: []
51
+
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ hash: 3
69
+ segments:
70
+ - 0
71
+ version: "0"
72
+ requirements: []
73
+
74
+ rubyforge_project:
75
+ rubygems_version: 1.3.7
76
+ signing_key:
77
+ specification_version: 3
78
+ summary: Feature Toggle library for ruby
79
+ test_files: []
80
+