abstract_feature_branch 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 60dae0db7cb3e49d719eb59a65c186f9884a69e9
4
- data.tar.gz: 017e10b3fa79ec92f3b8644896f6a1726b9e2284
3
+ metadata.gz: 56c9f13ee57d4f800bba4ab8028e76ff0b5fecdb
4
+ data.tar.gz: 091ac1a493c92555c32402ff470ed523198777e2
5
5
  SHA512:
6
- metadata.gz: 885a2e9d627e97709b811f2d01f4ec46f31728cda35bff3b2e65be40e2eb3306a1e83a067c0282017059021b0e569d3df43d7e08eb45b4feef7b71bd72bf1b68
7
- data.tar.gz: e23f2de2aa5481f26b2dfca13e7140eefb2e183337899b3aa3e2e6d5e192cd3e0e1d849aafbdea95832e6d4ea83a006a5ddaa03b7c884f0dcb120670a68c1359
6
+ metadata.gz: d6a9fb212dd6c931e5cb5b74ac7376a98671cebe88091807cd09c3465dd83acb2d6344200a09b9ab707de113fdc979e7ffb5ac3ae87923e230994c6559baa539
7
+ data.tar.gz: ff9599ee6671b064457f4a1dc5f7e4df27bd6adab3fbd3b5d7db308c01779911829c039f4905a44f1d945c62319e1cb9449d8076c024431dcd372b729be28025
@@ -1,4 +1,6 @@
1
1
  language: ruby
2
+ services:
3
+ - redis-server
2
4
  rvm:
3
5
  - 2.0.0
4
6
  - 1.9.3
@@ -22,11 +24,17 @@ matrix:
22
24
  gemfile: ruby187.Gemfile
23
25
  - rvm: ree
24
26
  gemfile: Gemfile
27
+ - rvm: ree
28
+ gemfile: ruby187.Gemfile
25
29
  - rvm: jruby-18mode
26
30
  gemfile: Gemfile
27
31
  - rvm: jruby-18mode
28
32
  gemfile: ruby187.Gemfile
29
33
  - rvm: jruby-19mode
30
34
  gemfile: ruby187.Gemfile
35
+ - rvm: jruby-19mode
36
+ gemfile: Gemfile
31
37
  - rvm: rbx-2.1.1
32
38
  gemfile: Gemfile
39
+ - rvm: rbx-2.1.1
40
+ gemfile: ruby187.Gemfile
data/README.md CHANGED
@@ -79,6 +79,7 @@ enabled (true) or disabled (false) per environment (e.g. production).
79
79
  > feature1: true
80
80
  > feature2: true
81
81
  > feature3: false
82
+ > feature4: per_user
82
83
  >
83
84
  > development:
84
85
  > <<: *defaults
@@ -122,8 +123,7 @@ Note that <code>feature_enabled?</code> returns false if the feature is disabled
122
123
 
123
124
  ### Per-User Feature Enablement
124
125
 
125
- It is possible to restrict enablement of features per specific users. This works in concert with having a feature enabled
126
- in features.yml (or one of the overrides like features.local.yml or environment variable overrides)
126
+ It is possible to restrict enablement of features per specific users by setting a feature value to <code>per_user</code>.
127
127
 
128
128
  1. Use <code>toggle_features_for_user</code> in Ruby code to enable features per user ID (e.g. email address or database ID). This loads Redis client gem into memory and stores per-user feature configuration in Redis.
129
129
  In the example below, current_user is a method that provides the current signed in user (e.g. using Rails [Devise] (https://github.com/plataformatec/devise) library).
@@ -155,6 +155,13 @@ Examples:
155
155
  > # THIS ONE WILL NOT EXECUTE
156
156
  > end
157
157
 
158
+ Note:
159
+
160
+ If a feature is enabled as <code>true</code> or disabled as <code>false</code> in features.yml (or one of the overrides
161
+ like features.local.yml or environment variable overrides), then it overrides toggled per-user restrictions, becoming
162
+ enabled or disabled globally.
163
+
164
+
158
165
  Recommendations
159
166
  ---------------
160
167
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.1
1
+ 1.2.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "abstract_feature_branch"
8
- s.version = "1.1.1"
8
+ s.version = "1.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Annas \"Andy\" Maleh"]
12
- s.date = "2014-01-14"
12
+ s.date = "2014-01-19"
13
13
  s.description = "abstract_feature_branch is a Rails gem that enables developers to easily branch by abstraction as per this pattern:\nhttp://paulhammant.com/blog/branch_by_abstraction.html\n\nIt is a productivity and fault tolerance enhancing team practice that has been utilized by professional software development\nteams at large corporations, such as Sears and Groupon.\n\nIt provides the ability to wrap blocks of code with an abstract feature branch name, and then\nspecify in a configuration file which features to be switched on or off.\n\nThe goal is to build out upcoming features in the same source code repository branch, regardless of whether all are\ncompleted by the next release date or not, thus increasing team productivity by preventing integration delays.\nDevelopers then disable in-progress features until they are ready to be switched on in production, yet enable them\nlocally and in staging environments for in-progress testing.\n\nThis gives developers the added benefit of being able to switch a feature off after release should big problems arise\nfor a high risk feature.\n\nabstract_feature_branch additionally supports DDD's pattern of\nBounded Contexts by allowing developers to configure\ncontext-specific feature files if needed.\n"
14
14
  s.extra_rdoc_files = [
15
15
  "LICENSE.txt",
@@ -150,7 +150,13 @@ module AbstractFeatureBranch
150
150
  end
151
151
 
152
152
  def booleanize_values(hash)
153
- Hash[hash.map {|k, v| [k, v.to_s.downcase == 'true']}]
153
+ hash_values = hash.map do |k, v|
154
+ normalized_value = v.to_s.downcase
155
+ boolean_value = normalized_value == 'true'
156
+ new_value = normalized_value == 'per_user' ? 'per_user' : boolean_value
157
+ [k, new_value]
158
+ end
159
+ Hash[hash_values]
154
160
  end
155
161
 
156
162
  def downcase_keys(hash)
@@ -9,8 +9,12 @@ class Object
9
9
  raise 'Abstract feature branch conflicts with another Ruby library' if respond_to?(:feature_enabled?)
10
10
  def self.feature_enabled?(feature_name, user_id = nil)
11
11
  normalized_feature_name = feature_name.to_s.downcase
12
- AbstractFeatureBranch.application_features[normalized_feature_name] &&
13
- (user_id.nil? || AbstractFeatureBranch.user_features_storage.sismember("#{AbstractFeatureBranch::ENV_FEATURE_PREFIX}#{normalized_feature_name}", user_id))
12
+
13
+ value = AbstractFeatureBranch.application_features[normalized_feature_name]
14
+ if value == 'per_user'
15
+ value = AbstractFeatureBranch.user_features_storage.sismember("#{AbstractFeatureBranch::ENV_FEATURE_PREFIX}#{normalized_feature_name}", user_id)
16
+ end
17
+ value
14
18
  end
15
19
 
16
20
  raise 'Abstract feature branch conflicts with another Ruby library' if Object.new.respond_to?(:feature_branch)
@@ -5,7 +5,7 @@ GEM
5
5
  builder (3.2.2)
6
6
  deep_merge (1.0.0)
7
7
  diff-lcs (1.2.5)
8
- faraday (0.8.8)
8
+ faraday (0.8.9)
9
9
  multipart-post (~> 1.2.0)
10
10
  git (1.2.6)
11
11
  github_api (0.10.1)
@@ -28,9 +28,9 @@ GEM
28
28
  rake
29
29
  rdoc
30
30
  json (1.8.1)
31
- jwt (0.1.8)
31
+ jwt (0.1.10)
32
32
  multi_json (>= 1.5)
33
- multi_json (1.8.2)
33
+ multi_json (1.8.4)
34
34
  multi_xml (0.5.5)
35
35
  multipart-post (1.2.0)
36
36
  nokogiri (1.5.10)
@@ -42,8 +42,8 @@ GEM
42
42
  multi_xml (~> 0.5)
43
43
  rack (~> 1.2)
44
44
  rack (1.5.2)
45
- rake (10.1.0)
46
- rdoc (4.0.1)
45
+ rake (10.1.1)
46
+ rdoc (4.1.1)
47
47
  json (~> 1.4)
48
48
  rspec (2.14.1)
49
49
  rspec-core (~> 2.14.0)
@@ -32,56 +32,52 @@ describe 'feature_branch object extensions' do
32
32
  user_id = 'email1@example.com'
33
33
  Process.fork do
34
34
  AbstractFeatureBranch.initialize_user_features_storage
35
- AbstractFeatureBranch.toggle_features_for_user(user_id, :feature1 => true, :feature2 => false, :feature3 => true, :feature5 => true)
35
+ AbstractFeatureBranch.toggle_features_for_user(user_id, :feature1 => false, :feature3 => true, :feature6 => true, :feature7 => false)
36
36
  end
37
37
  Process.wait
38
38
  features_enabled = []
39
39
  feature_branch :feature1, user_id do
40
40
  features_enabled << :feature1
41
41
  end
42
- feature_branch :feature2, user_id do
43
- features_enabled << :feature2
44
- end
45
42
  feature_branch :feature3, user_id do
46
43
  features_enabled << :feature3
47
44
  end
48
- feature_branch :feature5, user_id do
49
- features_enabled << :feature5
45
+ feature_branch :feature6, user_id do
46
+ features_enabled << :feature6
47
+ end
48
+ feature_branch :feature6, 'otheruser@example.com' do
49
+ features_enabled << :feature6_otheruser
50
+ end
51
+ feature_branch :feature6 do
52
+ features_enabled << :feature6_nouserspecified
50
53
  end
51
- feature_branch :feature5, 'otheruser@example.com' do
52
- features_enabled << :feature5_otheruser
54
+ feature_branch :feature7, user_id do
55
+ features_enabled << :feature7
53
56
  end
54
- features_enabled.should include(:feature1)
55
- features_enabled.should_not include(:feature2)
56
- features_enabled.should_not include(:feature3)
57
- features_enabled.should include(:feature5)
58
- features_enabled.should_not include(:feature5_otheruser)
57
+ features_enabled.should include(:feature1) #remains like features.yml
58
+ features_enabled.should_not include(:feature3) #remains like features.yml
59
+ features_enabled.should include(:feature6) #per user honored as true
60
+ features_enabled.should_not include(:feature6_otheruser) #per user honored as false
61
+ features_enabled.should_not include(:feature6_nouserspecified) #per user requires user id or it returns false
62
+ features_enabled.should_not include(:feature7) #per user honored as false
59
63
  end
60
64
  it 'update feature branching (disabling some features) after having stored feature configuration per user in a separate process (ensuring persistence)' do
61
65
  user_id = 'email1@example.com'
62
66
  Process.fork do
63
67
  AbstractFeatureBranch.initialize_user_features_storage
64
- AbstractFeatureBranch.toggle_features_for_user(user_id, :feature1 => true, :feature2 => false, :feature3 => true, :feature5 => true)
65
- AbstractFeatureBranch.toggle_features_for_user(user_id, :feature1 => false, :feature2 => true, :feature3 => false, :feature5 => false)
68
+ AbstractFeatureBranch.toggle_features_for_user(user_id, :feature6 => true, :feature7 => false)
69
+ AbstractFeatureBranch.toggle_features_for_user(user_id, :feature6 => false, :feature7 => true)
66
70
  end
67
71
  Process.wait
68
72
  features_enabled = []
69
- feature_branch :feature1, user_id do
70
- features_enabled << :feature1
71
- end
72
- feature_branch :feature2, user_id do
73
- features_enabled << :feature2
74
- end
75
- feature_branch :feature3, user_id do
76
- features_enabled << :feature3
73
+ feature_branch :feature6, user_id do
74
+ features_enabled << :feature6
77
75
  end
78
- feature_branch :feature5, user_id do
79
- features_enabled << :feature5
76
+ feature_branch :feature7, user_id do
77
+ features_enabled << :feature7
80
78
  end
81
- features_enabled.should_not include(:feature1)
82
- features_enabled.should include(:feature2)
83
- features_enabled.should_not include(:feature3)
84
- features_enabled.should_not include(:feature5)
79
+ features_enabled.should_not include(:feature6)
80
+ features_enabled.should include(:feature7)
85
81
  end
86
82
 
87
83
  end
@@ -65,7 +65,30 @@ describe 'feature_branch object extensions' do
65
65
  feature_enabled?(:feature4a).should be_nil
66
66
  feature_enabled?(:feature5).should be_nil
67
67
  end
68
-
68
+ context 'per user' do
69
+ let(:user_id) { 'email1@example.com' }
70
+ let(:another_user_id) { 'another_email@example.com' }
71
+ before do
72
+ AbstractFeatureBranch.user_features_storage.del("#{AbstractFeatureBranch::ENV_FEATURE_PREFIX}feature6")
73
+ end
74
+ after do
75
+ AbstractFeatureBranch.user_features_storage.del("#{AbstractFeatureBranch::ENV_FEATURE_PREFIX}feature6")
76
+ end
77
+ it 'behaves as expected if member list is empty, regardless of the user provided' do
78
+ feature_enabled?('feature6').should == false
79
+ feature_enabled?('feature6', nil).should == false
80
+ feature_enabled?('feature6', user_id).should == false
81
+ end
82
+ it 'behaves as expected if member list is not empty, and user provided is in member list' do
83
+ AbstractFeatureBranch.toggle_features_for_user(user_id, :feature6 => true)
84
+ feature_enabled?('feature6').should == false
85
+ feature_enabled?('feature6', user_id).should == true
86
+ end
87
+ it 'behaves as expected if member list is not empty, and user provided is not in member list' do
88
+ AbstractFeatureBranch.toggle_features_for_user(another_user_id, :feature6 => true)
89
+ feature_enabled?('feature6', user_id).should == false
90
+ end
91
+ end
69
92
  context 'cacheability' do
70
93
  before do
71
94
  @development_application_root = File.expand_path(File.join(__FILE__, '..', '..', 'fixtures', 'application_development_config'))
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abstract_feature_branch
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Annas "Andy" Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-14 00:00:00.000000000 Z
11
+ date: 2014-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deep_merge