feature_flipper 1.0.0 → 1.1.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/README.md CHANGED
@@ -39,7 +39,7 @@ more detailed description, a ticket number, a date when it was started, etc.
39
39
  Features are always defined in a state, you cannot define a feature which
40
40
  doesn't belong to a state.
41
41
 
42
- in_status :dev do
42
+ in_state :dev do
43
43
  feature :rating_game, :description => 'play a game to get recommendations'
44
44
  end
45
45
 
@@ -68,11 +68,11 @@ Example config file
68
68
  -------------------
69
69
 
70
70
  FeatureFlipper.features do
71
- in_status :dev do
71
+ in_state :dev do
72
72
  feature :rating_game, :description => 'play a game to get recommendations'
73
73
  end
74
74
 
75
- in_status :live do
75
+ in_state :live do
76
76
  feature :city_feed, :description => 'stream of content for each city'
77
77
  end
78
78
  end
@@ -84,14 +84,15 @@ Example config file
84
84
 
85
85
  This is your complete features.rb config file. In the example there are two
86
86
  states: `:dev` is active on development boxes and `:live` is always active
87
- (this is the last state a feature is in).
87
+ (this is the last state a feature goes through).
88
88
 
89
89
  The feature `:rating_game` is still in development and not shown on the
90
90
  production site. The feature `:city_feed` is done and already enabled
91
91
  everywhere. You transition features between states by just moving the line to
92
92
  the new state block.
93
93
 
94
- You can take a look at this example in detail in the 'examples' folder.
94
+ You can take a look at the `static_states.rb` in the 'examples' folder to
95
+ see this in detail.
95
96
 
96
97
  Cleaning up
97
98
  -----------
@@ -100,6 +101,53 @@ The drawback of this approach is that your code can get quite ugly with all
100
101
  these if/else branches. So you have to be strict about removing (we call it
101
102
  de-featurizing) features after they have gone live.
102
103
 
104
+ Dynamic feature groups
105
+ ----------------------
106
+
107
+ As soon as we have the feature_flipper infrastructure in place, we can start
108
+ doing more interesting things with it. For example, dynamic features which
109
+ are enabled on a per user basis. This allows you to release features to
110
+ employees only, to a private beta group, etc.
111
+
112
+ ### Defining dynamic states
113
+
114
+ A dynamic state is defined a bit different than a normal, static state.
115
+
116
+ FeatureFlipper::Config.states = {
117
+ :dev => ['development', 'test'].include?(Rails.env),
118
+ :employees => { :required_state => :dev, :feature_group => :employees }
119
+ }
120
+
121
+ It has a required state and a feature group. The feature group defines
122
+ the name of the group of users which should see this feature. The required
123
+ state is the state that gets looked at for all other users that aren't in
124
+ the feature group. The required_state must also be defined as a separate state.
125
+
126
+ ### Setting the feature group
127
+
128
+ The current feature group is set globally and is active for the whole thread.
129
+ In Rails you would define a before_filter like this:
130
+
131
+ class ApplicationController < ActionController::Base
132
+ before_filter :set_current_feature_group
133
+
134
+ def set_current_feature_group
135
+ # we need to reset the feature group in each request,
136
+ # otherwise it persists (which is not want we want).
137
+ FeatureFlipper.reset_current_feature_groups
138
+
139
+ if logged_in? && current_user.employee?
140
+ FeatureFlipper.current_feature_groups << :employees
141
+ end
142
+ end
143
+
144
+ It's really important to reset the feature group, otherwise it's not dynamic.
145
+ The condition if someone is in a feature group can be anything: You can
146
+ store it in the database, in Redis, look at request parameters, etc.
147
+
148
+ Take a look at `dynamic_states.rb` in the examples folder to see this
149
+ in detail.
150
+
103
151
  Meta
104
152
  ----
105
153
 
@@ -0,0 +1,47 @@
1
+ # Setup
2
+ #
3
+
4
+ # just need this to make it work from within the library
5
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
6
+
7
+ # fake production Rails environment
8
+ module Rails
9
+ def self.env
10
+ 'production'
11
+ end
12
+ end
13
+
14
+
15
+ # Configuration
16
+ #
17
+
18
+ require 'feature_flipper'
19
+
20
+ # set the path to your app specific config file
21
+ FeatureFlipper::Config.path_to_file = "features.rb"
22
+
23
+
24
+ # Usage
25
+ #
26
+
27
+ puts "=== first example:"
28
+
29
+ # no current_feature_group set, so the required_state of badges is looked at
30
+ if show_feature?(:badges)
31
+ puts "shiny new badges not live on prod yet"
32
+ else
33
+ puts "no new badges"
34
+ end
35
+
36
+ puts "\n=== second example:"
37
+
38
+ # now we set the current_feature_group. Usually depending on the logged in user
39
+
40
+ FeatureFlipper.reset_current_feature_groups
41
+ FeatureFlipper.current_feature_groups << :employees
42
+
43
+ if show_feature?(:badges)
44
+ puts "shiny new badges for this user"
45
+ else
46
+ puts "no new badges"
47
+ end
data/examples/features.rb CHANGED
@@ -4,16 +4,21 @@
4
4
  #
5
5
 
6
6
  FeatureFlipper.features do
7
- in_status :dev do
7
+ in_state :dev do
8
8
  feature :rating_game, :description => 'play a game to get recommendations'
9
9
  end
10
10
 
11
- in_status :live do
11
+ in_state :live do
12
12
  feature :city_feed, :description => 'stream of content for each city'
13
13
  end
14
+
15
+ in_state :employees do
16
+ feature :badges, :description => 'new badges'
17
+ end
14
18
  end
15
19
 
16
20
  FeatureFlipper::Config.states = {
17
- :dev => ['development', 'test'].include?(Rails.env),
18
- :live => true
21
+ :dev => ['development', 'test'].include?(Rails.env),
22
+ :employees => { :required_state => :dev, :feature_group => :employees },
23
+ :live => true
19
24
  }
@@ -24,6 +24,8 @@ FeatureFlipper::Config.path_to_file = "features.rb"
24
24
  # Usage
25
25
  #
26
26
 
27
+ puts "=== first example:"
28
+
27
29
  # rating_game is still in development, so shouldn't be shown on production
28
30
  if show_feature?(:rating_game)
29
31
  puts "Rating Game"
@@ -31,6 +33,8 @@ else
31
33
  puts "old stuff"
32
34
  end
33
35
 
36
+ puts "\n=== second example:"
37
+
34
38
  # city_feed is enabled everywhere
35
39
  if show_feature?(:city_feed)
36
40
  puts "City Feed"
@@ -38,16 +38,20 @@ module FeatureFlipper
38
38
  @states = states
39
39
  end
40
40
 
41
- def self.get_status(feature_name)
41
+ def self.get_state(feature_name)
42
42
  feature = features[feature_name]
43
- feature ? feature[:status] : nil
43
+ feature ? feature[:state] : nil
44
44
  end
45
45
 
46
- def self.active_status?(status)
47
- active = states[status]
46
+ def self.active_state?(state)
47
+ active = states[state]
48
48
  if active.is_a?(Hash)
49
- group, group_status = active.to_a.flatten
50
- FeatureFlipper.current_feature_group == group || states[group_status] == true
49
+ if active.has_key?(:feature_group)
50
+ group, required_state = active[:feature_group], active[:required_state]
51
+ else
52
+ group, required_state = active.to_a.flatten
53
+ end
54
+ (FeatureFlipper.current_feature_groups.include?(group)) || (states[required_state] == true)
51
55
  else
52
56
  active == true
53
57
  end
@@ -56,42 +60,42 @@ module FeatureFlipper
56
60
  def self.is_active?(feature_name)
57
61
  ensure_config_is_loaded
58
62
 
59
- status = get_status(feature_name)
60
- if status.is_a?(Symbol)
61
- active_status?(status)
62
- elsif status.is_a?(Proc)
63
- status.call == true
63
+ state = get_state(feature_name)
64
+ if state.is_a?(Symbol)
65
+ active_state?(state)
66
+ elsif state.is_a?(Proc)
67
+ state.call == true
64
68
  else
65
- status == true
69
+ state == true
66
70
  end
67
71
  end
68
72
  end
69
73
 
70
74
  class Mapper
71
- def initialize(status)
72
- @status = status
75
+ def initialize(state)
76
+ @state = state
73
77
  end
74
78
 
75
79
  def feature(name, options = {})
76
- FeatureFlipper::Config.features[name] = options.merge(:status => @status)
80
+ FeatureFlipper::Config.features[name] = options.merge(:state => @state)
77
81
  end
78
82
  end
79
83
 
80
- class StatusMapper
81
- def in_status(status, &block)
82
- Mapper.new(status).instance_eval(&block)
84
+ class StateMapper
85
+ def in_state(state, &block)
86
+ Mapper.new(state).instance_eval(&block)
83
87
  end
84
88
  end
85
89
 
86
90
  def self.features(&block)
87
- StatusMapper.new.instance_eval(&block)
91
+ StateMapper.new.instance_eval(&block)
88
92
  end
89
93
 
90
- def self.current_feature_group
91
- Thread.current[:feature_system_current_feature_group]
94
+ def self.current_feature_groups
95
+ Thread.current[:feature_system_current_feature_groups] ||= []
92
96
  end
93
97
 
94
- def self.current_feature_group=(feature_group)
95
- Thread.current[:feature_system_current_feature_group] = feature_group
98
+ def self.reset_current_feature_groups
99
+ current_feature_groups.clear
96
100
  end
97
101
  end
@@ -1,3 +1,3 @@
1
1
  module FeatureFlipper
2
- Version = VERSION = '1.0.0'
2
+ Version = VERSION = '1.1.0'
3
3
  end
@@ -27,34 +27,13 @@ context 'hash based FeatureFlipper' do
27
27
  assert show_feature?(:proc_feature)
28
28
  end
29
29
 
30
- test 'should show a beta feature to the feature group' do
31
- Rails.stubs(:env).returns('production')
32
- FeatureFlipper.stubs(:current_feature_group).returns(:beta_users)
33
-
34
- assert show_feature?(:beta_feature)
35
- end
36
-
37
- test 'should not show a beta feature if not in the group' do
38
- Rails.stubs(:env).returns('production')
39
- FeatureFlipper.stubs(:current_feature_group).returns(nil)
40
-
41
- assert !show_feature?(:beta_feature)
42
- end
43
-
44
- test 'should always show a beta feature on dev' do
45
- Rails.stubs(:env).returns('development')
46
- FeatureFlipper.stubs(:current_feature_group).returns(nil)
47
-
48
- assert show_feature?(:beta_feature)
49
- end
50
-
51
30
  test 'should be able to get features' do
52
31
  FeatureFlipper::Config.ensure_config_is_loaded
53
32
  all_features = FeatureFlipper::Config.features
54
33
 
55
34
  assert_not_nil all_features
56
35
  assert all_features.is_a?(Hash)
57
- assert_equal :dev, all_features[:dev_feature][:status]
36
+ assert_equal :dev, all_features[:dev_feature][:state]
58
37
  assert_equal 'dev feature', all_features[:dev_feature][:description]
59
38
  end
60
39
  end
@@ -88,7 +67,48 @@ context 'DSL based FeatureFlipper' do
88
67
 
89
68
  assert_not_nil all_features
90
69
  assert all_features.is_a?(Hash)
91
- assert_equal :dev, all_features[:dev_feature][:status]
70
+ assert_equal :dev, all_features[:dev_feature][:state]
92
71
  assert_equal 'dev feature', all_features[:dev_feature][:description]
93
72
  end
94
73
  end
74
+
75
+ context 'dynamic feature groups' do
76
+ setup do
77
+ FeatureFlipper::Config.path_to_file = 'features.rb'
78
+ FeatureFlipper::Config.reload_config
79
+ FeatureFlipper.reset_current_feature_groups
80
+ end
81
+
82
+ test 'should show a beta feature to the feature group' do
83
+ Rails.stubs(:env).returns('production')
84
+ FeatureFlipper.current_feature_groups << :beta_users
85
+
86
+ assert show_feature?(:beta_feature_old)
87
+ assert show_feature?(:beta_feature_new)
88
+ end
89
+
90
+ test 'should not show a beta feature if not in the group' do
91
+ Rails.stubs(:env).returns('production')
92
+ FeatureFlipper.current_feature_groups << :different_feature_group
93
+
94
+ assert !show_feature?(:beta_feature_old)
95
+ assert !show_feature?(:beta_feature_new)
96
+ end
97
+
98
+ test 'should always show a beta feature on dev' do
99
+ Rails.stubs(:env).returns('development')
100
+ FeatureFlipper.current_feature_groups << nil
101
+
102
+ assert show_feature?(:beta_feature_old)
103
+ assert show_feature?(:beta_feature_new)
104
+ end
105
+
106
+ test 'can be in two feature groups at the same time' do
107
+ Rails.stubs(:env).returns('production')
108
+ FeatureFlipper.current_feature_groups << :beta_users
109
+ FeatureFlipper.current_feature_groups << :employees
110
+
111
+ assert show_feature?(:beta_feature_new)
112
+ assert show_feature?(:employee_feature)
113
+ end
114
+ end
data/test/features.rb CHANGED
@@ -1,16 +1,20 @@
1
1
  FeatureFlipper::Config.features = {
2
- :live_feature => { :status => :live },
3
- :disabled_feature => { :status => :disabled },
4
- :dev_feature => { :status => :dev, :description => 'dev feature' },
5
- :boolean_feature => { :status => true },
6
- :proc_feature => { :status => Proc.new { Date.today > Date.today - 84000 } },
7
- :beta_feature => { :status => :beta }
2
+ :live_feature => { :state => :live },
3
+ :disabled_feature => { :state => :disabled },
4
+ :dev_feature => { :state => :dev, :description => 'dev feature' },
5
+ :boolean_feature => { :state => true },
6
+ :proc_feature => { :state => Proc.new { Date.today > Date.today - 84000 } },
7
+ :beta_feature_old => { :state => :beta_old },
8
+ :beta_feature_new => { :state => :beta_new },
9
+ :employee_feature => { :state => :employees }
8
10
  }
9
11
 
10
12
 
11
13
  FeatureFlipper::Config.states = {
12
- :disabled => false,
13
- :dev => ['development', 'test'].include?(Rails.env),
14
- :beta => { :beta_users => :dev },
15
- :live => true
14
+ :disabled => false,
15
+ :dev => ['development', 'test'].include?(Rails.env),
16
+ :beta_old => { :beta_users => :dev },
17
+ :beta_new => { :required_state => :dev, :feature_group => :beta_users },
18
+ :employees => { :required_state => :dev, :feature_group => :employees },
19
+ :live => true
16
20
  }
data/test/features_dsl.rb CHANGED
@@ -1,17 +1,17 @@
1
1
  FeatureFlipper.features do
2
- in_status :dev do
2
+ in_state :dev do
3
3
  feature :dev_feature, :description => 'dev feature'
4
4
  end
5
5
 
6
- in_status :live do
6
+ in_state :live do
7
7
  feature :live_feature
8
8
  end
9
9
 
10
- in_status true do
10
+ in_state true do
11
11
  feature :boolean_feature
12
12
  end
13
13
 
14
- in_status Proc.new { Date.today > Date.today - 84000 } do
14
+ in_state Proc.new { Date.today > Date.today - 84000 } do
15
15
  feature :proc_feature
16
16
  end
17
17
  end
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: feature_flipper
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 1
8
+ - 0
9
+ version: 1.1.0
5
10
  platform: ruby
6
11
  authors:
7
12
  - Florian Munz
@@ -9,7 +14,7 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2010-05-07 00:00:00 +02:00
17
+ date: 2010-05-21 00:00:00 +02:00
13
18
  default_executable:
14
19
  dependencies: []
15
20
 
@@ -37,8 +42,9 @@ files:
37
42
  - test/features.rb
38
43
  - test/features_dsl.rb
39
44
  - test/test_helper.rb
45
+ - examples/dynamic_states.rb
40
46
  - examples/features.rb
41
- - examples/simple.rb
47
+ - examples/static_states.rb
42
48
  has_rdoc: true
43
49
  homepage: http://github.com/qype/feature_flipper
44
50
  licenses: []
@@ -52,18 +58,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
52
58
  requirements:
53
59
  - - ">="
54
60
  - !ruby/object:Gem::Version
61
+ segments:
62
+ - 0
55
63
  version: "0"
56
- version:
57
64
  required_rubygems_version: !ruby/object:Gem::Requirement
58
65
  requirements:
59
66
  - - ">="
60
67
  - !ruby/object:Gem::Version
68
+ segments:
69
+ - 0
61
70
  version: "0"
62
- version:
63
71
  requirements: []
64
72
 
65
73
  rubyforge_project:
66
- rubygems_version: 1.3.5
74
+ rubygems_version: 1.3.6
67
75
  signing_key:
68
76
  specification_version: 3
69
77
  summary: FeatureFlipper helps you flipping features