feature 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,6 +11,17 @@ module Feature
11
11
  # repository = YamlRepository.new('/path/to/yaml/file')
12
12
  # # use repository with Feature
13
13
  #
14
+ # A yaml config also can have this format:
15
+ # features:
16
+ # development:
17
+ # a_feature: true
18
+ # production:
19
+ # a_feature: false
20
+ #
21
+ # This way you have to use:
22
+ # repository = YamlRepository.new('/path/to/yaml/file', '_your_environment_')
23
+ # # use repository with Feature
24
+ #
14
25
  class YamlRepository
15
26
  require 'erb'
16
27
  require 'yaml'
@@ -20,7 +31,7 @@ module Feature
20
31
  # @param [String] yaml_file_name the yaml config filename
21
32
  # @param [String] environment optional environment to use from config
22
33
  #
23
- def initialize(yaml_file_name, environment='')
34
+ def initialize(yaml_file_name, environment = '')
24
35
  @yaml_file_name = yaml_file_name
25
36
  @environment = environment
26
37
  end
@@ -30,37 +41,44 @@ module Feature
30
41
  # @return [Array<Symbol>] list of active features
31
42
  #
32
43
  def active_features
33
- features_hash = read_and_parse_file_data
34
- features = features_hash.keys.select { |feature_key| features_hash[feature_key] }
35
- features.sort.map(&:to_sym)
44
+ data = read_file(@yaml_file_name)
45
+ get_active_features(data, @environment)
36
46
  end
37
47
 
38
- # Read and parses the feature config file
48
+ # Read given file, perform erb evaluation and yaml parsing
39
49
  #
40
- # @return [Hash] Hash containing :feature => true/false entries for representing active/inactive features
50
+ # @param file_name [String] the file name fo the yaml config
51
+ # @return [Hash]
41
52
  #
42
- def read_and_parse_file_data
43
- raw_data = File.read(@yaml_file_name)
53
+ def read_file(file_name)
54
+ raw_data = File.read(file_name)
44
55
  evaluated_data = ERB.new(raw_data).result
45
- data = YAML.load(evaluated_data)
46
- data = data[@environment] unless @environment.empty?
56
+ YAML.load(evaluated_data)
57
+ end
58
+ private :read_file
47
59
 
48
- if !data.is_a?(Hash) or !data.has_key?('features')
49
- raise ArgumentError, "content of #{@yaml_file_name} does not contain proper config"
60
+ # Extracts active features from given hash
61
+ #
62
+ # @param data [Hash] hash parsed from yaml file
63
+ # @param selector [String] uses the value for this key as source of feature data
64
+ #
65
+ def get_active_features(data, selector)
66
+ data = data[selector] unless selector.empty?
67
+
68
+ if !data.is_a?(Hash) || !data.key?('features')
69
+ fail ArgumentError, 'yaml config does not contain proper config'
50
70
  end
51
71
 
52
- if data['features']
53
- invalid_value = data['features'].values.detect { |value| ![true, false].include?(value) }
54
- if invalid_value
55
- raise ArgumentError, "#{invalid_value} is not allowed value in config, use true/false"
56
- end
72
+ return [] unless data['features']
57
73
 
58
- data['features']
59
- else
60
- {}
74
+ invalid_value = data['features'].values.select { |value| ![true, false].include?(value) }
75
+ unless invalid_value.empty?
76
+ fail ArgumentError, "#{invalid_value.first} is not allowed value in config, use true/false"
61
77
  end
78
+
79
+ data['features'].keys.select { |key| data['features'][key] }.map(&:to_sym)
62
80
  end
63
- private :read_and_parse_file_data
81
+ private :get_active_features
64
82
  end
65
83
  end
66
84
  end
@@ -7,7 +7,6 @@ require 'feature'
7
7
  # To enable Feature testing capabilities do:
8
8
  # require 'feature/testing'
9
9
  module Feature
10
-
11
10
  # Execute the code block with the given feature active
12
11
  #
13
12
  # Example usage:
@@ -5,36 +5,42 @@ include Feature::Repository
5
5
  describe Feature::Repository::ActiveRecordRepository do
6
6
  before(:each) do
7
7
  # Mock the model
8
- @features = double("FeatureToggle")
8
+ @features = double('FeatureToggle')
9
9
  @repository = ActiveRecordRepository.new(@features)
10
10
  end
11
11
 
12
- it "should have no active features after initialization" do
13
- allow(@features).to receive(:where) { Hash.new }
12
+ it 'should have no active features after initialization' do
13
+ allow(@features).to receive(:where) { [] }
14
14
 
15
15
  expect(@repository.active_features).to eq([])
16
16
  end
17
17
 
18
- it "should add an active feature" do
19
- expect(@features).to receive(:exists?).with("feature_a").and_return(false)
20
- expect(@features).to receive(:new).with(name: "feature_a", active: true).and_return(double(save: true))
18
+ it 'should have active features' do
19
+ allow(@features).to receive(:where).with(active: true) { [double(name: 'active')] }
20
+
21
+ expect(@repository.active_features).to eq([:active])
22
+ end
23
+
24
+ it 'should add an active feature' do
25
+ expect(@features).to receive(:exists?).with('feature_a').and_return(false)
26
+ expect(@features).to receive(:create!).with(name: 'feature_a', active: true)
21
27
 
22
28
  @repository.add_active_feature :feature_a
23
29
  end
24
30
 
25
- it "should raise an exception when adding not a symbol as active feature" do
26
- expect {
31
+ it 'should raise an exception when adding not a symbol as active feature' do
32
+ expect do
27
33
  @repository.add_active_feature 'feature_a'
28
- }.to raise_error(ArgumentError, "given feature feature_a is not a symbol")
34
+ end.to raise_error(ArgumentError, 'feature_a is not a symbol')
29
35
  end
30
36
 
31
- it "should raise an exception when adding a active feature already added as active" do
32
- expect(@features).to receive(:new).with(name: "feature_a", active: true).and_return(double(save: true))
37
+ it 'should raise an exception when adding a active feature already added as active' do
38
+ expect(@features).to receive(:create!).with(name: 'feature_a', active: true)
33
39
  allow(@features).to receive(:exists?).and_return(false, true)
34
40
 
35
41
  @repository.add_active_feature :feature_a
36
- expect {
42
+ expect do
37
43
  @repository.add_active_feature :feature_a
38
- }.to raise_error(ArgumentError, "feature :feature_a already added to list of active features")
44
+ end.to raise_error(ArgumentError, 'feature :feature_a already added')
39
45
  end
40
46
  end
@@ -1,89 +1,121 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Feature do
4
- context "without FeatureRepository" do
5
- it "should raise an exception when calling active?" do
4
+ context 'without FeatureRepository' do
5
+ it 'should raise an exception when calling active?' do
6
6
  expect do
7
7
  Feature.active?(:feature_a)
8
- end.to raise_error("Feature is missing Repository for obtaining feature lists")
8
+ end.to raise_error('missing Repository for obtaining feature lists')
9
9
  end
10
10
 
11
- it "should raise an exception when calling inactive?" do
11
+ it 'should raise an exception when calling inactive?' do
12
12
  expect do
13
13
  Feature.inactive?(:feature_a)
14
- end.to raise_error("Feature is missing Repository for obtaining feature lists")
14
+ end.to raise_error('missing Repository for obtaining feature lists')
15
15
  end
16
16
 
17
- it "should raise an exception when calling with" do
17
+ it 'should raise an exception when calling with' do
18
18
  expect do
19
19
  Feature.with(:feature_a) do
20
20
  end
21
- end.to raise_error("Feature is missing Repository for obtaining feature lists")
21
+ end.to raise_error('missing Repository for obtaining feature lists')
22
22
  end
23
23
 
24
- it "should raise an exception when calling without" do
24
+ it 'should raise an exception when calling without' do
25
25
  expect do
26
26
  Feature.without(:feature_a) do
27
27
  end
28
- end.to raise_error("Feature is missing Repository for obtaining feature lists")
28
+ end.to raise_error('missing Repository for obtaining feature lists')
29
29
  end
30
30
  end
31
31
 
32
- context "setting Repository" do
32
+ context 'setting Repository' do
33
33
  before(:each) do
34
34
  @repository = Feature::Repository::SimpleRepository.new
35
- Feature.set_repository @repository
36
35
  end
37
36
 
38
- it "should raise an exception when add repository with wrong class" do
39
- expect do
40
- Feature.set_repository("not a repository")
41
- end.to raise_error(ArgumentError, "given repository does not respond to active_features")
37
+ context 'with auto_refresh set to false' do
38
+ before(:each) do
39
+ Feature.set_repository @repository
40
+ end
41
+ it 'should raise an exception when add repository with wrong class' do
42
+ expect do
43
+ Feature.set_repository('not a repository')
44
+ end.to raise_error(ArgumentError, 'given repository does not respond to active_features')
45
+ end
46
+
47
+ it 'should get active features lazy on first usage' do
48
+ @repository.add_active_feature(:feature_a)
49
+ # the line below will be the first usage of feature in this case
50
+ expect(Feature.active?(:feature_a)).to be_truthy
51
+ end
52
+
53
+ it 'should get active features from repository once' do
54
+ Feature.active?(:does_not_matter)
55
+ @repository.add_active_feature(:feature_a)
56
+ expect(Feature.active?(:feature_a)).to be_falsey
57
+ end
58
+
59
+ it 'should reload active features on first call only' do
60
+ @repository.add_active_feature(:feature_a)
61
+ expect(@repository).to receive(:active_features).exactly(1).times
62
+ .and_return(@repository.active_features)
63
+ Feature.active?(:feature_a)
64
+ Feature.active?(:feature_a)
65
+ end
42
66
  end
43
67
 
44
- it "should get active features from repository once" do
45
- @repository.add_active_feature(:feature_a)
46
- expect(Feature.active?(:feature_a)).to be_falsey
68
+ context 'with auto_refresh set to true' do
69
+ before(:each) do
70
+ Feature.set_repository @repository, true
71
+ end
72
+ it 'should reload active features on every call' do
73
+ @repository.add_active_feature(:feature_a)
74
+ expect(@repository).to receive(:active_features).exactly(2).times
75
+ .and_return(@repository.active_features)
76
+ Feature.active?(:feature_a)
77
+ Feature.active?(:feature_a)
78
+ end
47
79
  end
48
80
  end
49
81
 
50
- context "refresh features" do
82
+ context 'refresh features' do
51
83
  before(:each) do
52
84
  @repository = Feature::Repository::SimpleRepository.new
53
85
  Feature.set_repository @repository
54
86
  end
55
87
 
56
- it "should refresh active feature lists from repository" do
88
+ it 'should refresh active feature lists from repository' do
57
89
  @repository.add_active_feature(:feature_a)
58
90
  Feature.refresh!
59
91
  expect(Feature.active?(:feature_a)).to be_truthy
60
92
  end
61
93
  end
62
94
 
63
- context "request features" do
95
+ context 'request features' do
64
96
  before(:each) do
65
97
  repository = Feature::Repository::SimpleRepository.new
66
98
  repository.add_active_feature :feature_active
67
99
  Feature.set_repository repository
68
100
  end
69
101
 
70
- it "should affirm active feature is active" do
102
+ it 'should affirm active feature is active' do
71
103
  expect(Feature.active?(:feature_active)).to be_truthy
72
104
  end
73
105
 
74
- it "should not affirm active feature is inactive" do
106
+ it 'should not affirm active feature is inactive' do
75
107
  expect(Feature.inactive?(:feature_active)).to be_falsey
76
108
  end
77
109
 
78
- it "should affirm inactive feature is inactive" do
110
+ it 'should affirm inactive feature is inactive' do
79
111
  expect(Feature.inactive?(:feature_inactive)).to be_truthy
80
112
  end
81
113
 
82
- it "should not affirm inactive feature is active" do
114
+ it 'should not affirm inactive feature is active' do
83
115
  expect(Feature.active?(:feature_inactive)).to be_falsey
84
116
  end
85
117
 
86
- it "should call block with active feature in active list" do
118
+ it 'should call block with active feature in active list' do
87
119
  reached = false
88
120
 
89
121
  Feature.with(:feature_active) do
@@ -93,7 +125,7 @@ describe Feature do
93
125
  expect(reached).to be_truthy
94
126
  end
95
127
 
96
- it "should not call block with active feature not in active list" do
128
+ it 'should not call block with active feature not in active list' do
97
129
  reached = false
98
130
 
99
131
  Feature.with(:feature_inactive) do
@@ -103,13 +135,13 @@ describe Feature do
103
135
  expect(reached).to be_falsey
104
136
  end
105
137
 
106
- it "should raise exception when no block given to with" do
138
+ it 'should raise exception when no block given to with' do
107
139
  expect do
108
140
  Feature.with(:feature_active)
109
- end.to raise_error(ArgumentError, "no block given to with")
141
+ end.to raise_error(ArgumentError, 'no block given to with')
110
142
  end
111
143
 
112
- it "should call block without inactive feature in inactive list" do
144
+ it 'should call block without inactive feature in inactive list' do
113
145
  reached = false
114
146
 
115
147
  Feature.without(:feature_inactive) do
@@ -119,7 +151,7 @@ describe Feature do
119
151
  expect(reached).to be_truthy
120
152
  end
121
153
 
122
- it "should not call block without inactive feature in inactive list" do
154
+ it 'should not call block without inactive feature in inactive list' do
123
155
  reached = false
124
156
 
125
157
  Feature.without(:feature_active) do
@@ -129,33 +161,33 @@ describe Feature do
129
161
  expect(reached).to be_falsey
130
162
  end
131
163
 
132
- it "should raise exception when no block given to without" do
164
+ it 'should raise exception when no block given to without' do
133
165
  expect do
134
166
  Feature.without(:feature_inactive)
135
- end.to raise_error(ArgumentError, "no block given to without")
167
+ end.to raise_error(ArgumentError, 'no block given to without')
136
168
  end
137
169
 
138
170
  describe 'switch()' do
139
171
  context 'given a value' do
140
- it "should return the first value if the feature is active" do
172
+ it 'should return the first value if the feature is active' do
141
173
  retval = Feature.switch(:feature_active, 1, 2)
142
174
  expect(retval).to eq(1)
143
175
  end
144
176
 
145
- it "should return the second value if the feature is inactive" do
177
+ it 'should return the second value if the feature is inactive' do
146
178
  retval = Feature.switch(:feature_inactive, 1, 2)
147
179
  expect(retval).to eq(2)
148
180
  end
149
181
  end
150
182
 
151
183
  context 'given a proc/lambda' do
152
- it "should call the first proc/lambda if the feature is active" do
153
- retval = Feature.switch(:feature_active, lambda { 1 }, lambda { 2 })
184
+ it 'should call the first proc/lambda if the feature is active' do
185
+ retval = Feature.switch(:feature_active, -> { 1 }, -> { 2 })
154
186
  expect(retval).to eq(1)
155
187
  end
156
188
 
157
- it "should call the second proc/lambda if the feature is active" do
158
- retval = Feature.switch(:feature_inactive, lambda { 1 }, lambda { 2 })
189
+ it 'should call the second proc/lambda if the feature is active' do
190
+ retval = Feature.switch(:feature_inactive, -> { 1 }, -> { 2 })
159
191
  expect(retval).to eq(2)
160
192
  end
161
193
  end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ include Feature::Repository
4
+
5
+ describe Feature::Repository::RedisRepository do
6
+ before(:each) do
7
+ @repository = RedisRepository.new('application_features')
8
+ end
9
+
10
+ it 'should have no active features after initialization' do
11
+ expect(@repository.active_features).to eq([])
12
+ end
13
+
14
+ it 'should add an active feature' do
15
+ @repository.add_active_feature :feature_a
16
+ expect(@repository.active_features).to eq([:feature_a])
17
+ end
18
+
19
+ it 'should only show active feature' do
20
+ Redis.current.hset('application_features', 'inactive_a', false)
21
+ Redis.current.hset('application_features', 'inactive_b', false)
22
+ Redis.current.hset('application_features', 'feature_a', true)
23
+ Redis.current.hset('application_features', 'feature_b', true)
24
+
25
+ expect(@repository.active_features).to eq([:feature_a, :feature_b])
26
+ end
27
+
28
+ it 'should raise an exception when adding not a symbol as active feature' do
29
+ expect do
30
+ @repository.add_active_feature 'feature_a'
31
+ end.to raise_error(ArgumentError, 'feature_a is not a symbol')
32
+ end
33
+
34
+ it 'should raise an exception when adding a active feature already added as active' do
35
+ @repository.add_active_feature :feature_a
36
+ expect do
37
+ @repository.add_active_feature :feature_a
38
+ end.to raise_error(ArgumentError, 'feature :feature_a already added')
39
+ end
40
+
41
+ end
@@ -7,25 +7,31 @@ describe Feature::Repository::SimpleRepository do
7
7
  @repository = SimpleRepository.new
8
8
  end
9
9
 
10
- it "should have no active features after initialization" do
10
+ it 'should have no active features after initialization' do
11
11
  expect(@repository.active_features).to eq([])
12
12
  end
13
13
 
14
- it "should add an active feature" do
14
+ it 'should add an active feature' do
15
15
  @repository.add_active_feature :feature_a
16
16
  expect(@repository.active_features).to eq([:feature_a])
17
17
  end
18
18
 
19
- it "should raise an exception when adding not a symbol as active feature" do
19
+ it 'should add an feature without having impact on internal structure' do
20
+ list = @repository.active_features
21
+ @repository.add_active_feature :feature_a
22
+ expect(list).to eq([])
23
+ end
24
+
25
+ it 'should raise an exception when adding not a symbol as active feature' do
20
26
  expect do
21
27
  @repository.add_active_feature 'feature_a'
22
- end.to raise_error(ArgumentError, "given feature feature_a is not a symbol")
28
+ end.to raise_error(ArgumentError, 'feature_a is not a symbol')
23
29
  end
24
30
 
25
- it "should raise an exception when adding a active feature already added as active" do
31
+ it 'should raise an exception when adding a active feature already added as active' do
26
32
  @repository.add_active_feature :feature_a
27
33
  expect do
28
34
  @repository.add_active_feature :feature_a
29
- end.to raise_error(ArgumentError, "feature :feature_a already added to list of active features")
35
+ end.to raise_error(ArgumentError, 'feature :feature_a already added')
30
36
  end
31
37
  end