feature 1.1.0 → 1.2.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.
@@ -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