feature_flipper 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.
- data/README.md +60 -55
- data/examples/dynamic_states.rb +4 -4
- data/examples/features.rb +4 -4
- data/lib/feature_flipper/config.rb +5 -5
- data/lib/feature_flipper/version.rb +1 -1
- data/test/feature_flipper_test.rb +6 -6
- metadata +3 -3
data/README.md
CHANGED
@@ -18,6 +18,36 @@ configuration file after requiring FeatureFlipper:
|
|
18
18
|
require 'feature_flipper'
|
19
19
|
FeatureFlipper::Config.path_to_file = "#{RAILS_ROOT}/config/features.rb"
|
20
20
|
|
21
|
+
Example config file
|
22
|
+
-------------------
|
23
|
+
|
24
|
+
FeatureFlipper.features do
|
25
|
+
in_state :development do
|
26
|
+
feature :rating_game, :description => 'play a game to get recommendations'
|
27
|
+
end
|
28
|
+
|
29
|
+
in_state :live do
|
30
|
+
feature :city_feed, :description => 'stream of content for each city'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
FeatureFlipper::Config.states = {
|
35
|
+
:development => ['development', 'test'].include?(Rails.env),
|
36
|
+
:live => true
|
37
|
+
}
|
38
|
+
|
39
|
+
This is your complete features.rb config file. In the example there are two
|
40
|
+
states: `:development` is active on development boxes and `:live` is always active
|
41
|
+
(this is the last state a feature goes through).
|
42
|
+
|
43
|
+
The feature `:rating_game` is still in development and not shown on the
|
44
|
+
production site. The feature `:city_feed` is done and already enabled
|
45
|
+
everywhere. You transition features between states by just moving the line to
|
46
|
+
the new state block.
|
47
|
+
|
48
|
+
You can take a look at the `static_states.rb` in the examples folder to
|
49
|
+
see this in detail.
|
50
|
+
|
21
51
|
Configuration
|
22
52
|
-------------
|
23
53
|
|
@@ -27,10 +57,11 @@ FeatureFlipper cares about:
|
|
27
57
|
* states
|
28
58
|
* features
|
29
59
|
|
30
|
-
You first define multiple 'states' which normally depend on
|
31
|
-
(for example: the state '
|
32
|
-
you add 'features' which correspond to logical chunks of work in
|
33
|
-
These features then move through the different states
|
60
|
+
You first define multiple 'states' which normally depend on the environment
|
61
|
+
(for example: the state 'development' is only active on development boxes).
|
62
|
+
After that you add 'features' which correspond to logical chunks of work in
|
63
|
+
your project. These features then move through the different states
|
64
|
+
as they get developed (for example: :development -> :staging -> :live).
|
34
65
|
|
35
66
|
### Defining features
|
36
67
|
|
@@ -39,22 +70,24 @@ more detailed description, a ticket number, a date when it was started, etc.
|
|
39
70
|
Features are always defined in a state, you cannot define a feature which
|
40
71
|
doesn't belong to a state.
|
41
72
|
|
42
|
-
in_state :
|
73
|
+
in_state :development do
|
43
74
|
feature :rating_game, :description => 'play a game to get recommendations'
|
44
75
|
end
|
45
76
|
|
46
77
|
### Defining states
|
47
78
|
|
48
79
|
A state is just a name and a boolean check. The check needs to evaluate to
|
49
|
-
|
80
|
+
`true` when it is active. For a Rails app you can just use environments:
|
50
81
|
|
51
|
-
|
82
|
+
FeatureFlipper::Config.states = {
|
83
|
+
:development => ['development', 'test'].include?(Rails.env)
|
84
|
+
}
|
52
85
|
|
53
86
|
Usage
|
54
87
|
-----
|
55
88
|
|
56
89
|
In your code you then use the `show_feature?` method to branch depending on
|
57
|
-
wether
|
90
|
+
wether a feature is active or not:
|
58
91
|
|
59
92
|
if show_feature?(:rating_game)
|
60
93
|
# new code
|
@@ -64,42 +97,12 @@ wether the feature is active or not:
|
|
64
97
|
|
65
98
|
The `show_feature?` method is defined on Object, so you can use it everywhere.
|
66
99
|
|
67
|
-
Example config file
|
68
|
-
-------------------
|
69
|
-
|
70
|
-
FeatureFlipper.features do
|
71
|
-
in_state :dev do
|
72
|
-
feature :rating_game, :description => 'play a game to get recommendations'
|
73
|
-
end
|
74
|
-
|
75
|
-
in_state :live do
|
76
|
-
feature :city_feed, :description => 'stream of content for each city'
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
FeatureFlipper::Config.states = {
|
81
|
-
:dev => ['development', 'test'].include?(Rails.env),
|
82
|
-
:live => true
|
83
|
-
}
|
84
|
-
|
85
|
-
This is your complete features.rb config file. In the example there are two
|
86
|
-
states: `:dev` is active on development boxes and `:live` is always active
|
87
|
-
(this is the last state a feature goes through).
|
88
|
-
|
89
|
-
The feature `:rating_game` is still in development and not shown on the
|
90
|
-
production site. The feature `:city_feed` is done and already enabled
|
91
|
-
everywhere. You transition features between states by just moving the line to
|
92
|
-
the new state block.
|
93
|
-
|
94
|
-
You can take a look at the `static_states.rb` in the 'examples' folder to
|
95
|
-
see this in detail.
|
96
|
-
|
97
100
|
Cleaning up
|
98
101
|
-----------
|
99
102
|
|
100
103
|
The drawback of this approach is that your code can get quite ugly with all
|
101
|
-
these if/else branches. So you have to be strict about removing
|
102
|
-
de-featurizing)
|
104
|
+
these if/else branches. So you have to be strict about removing features
|
105
|
+
(we call it de-featurizing) after they have gone live.
|
103
106
|
|
104
107
|
Dynamic feature groups
|
105
108
|
----------------------
|
@@ -107,43 +110,45 @@ Dynamic feature groups
|
|
107
110
|
As soon as we have the feature_flipper infrastructure in place, we can start
|
108
111
|
doing more interesting things with it. For example, dynamic features which
|
109
112
|
are enabled on a per user basis. This allows you to release features to
|
110
|
-
employees only
|
113
|
+
employees only or to a private beta group, etc.
|
111
114
|
|
112
115
|
### Defining dynamic states
|
113
116
|
|
114
117
|
A dynamic state is defined a bit different than a normal, static state.
|
115
118
|
|
116
119
|
FeatureFlipper::Config.states = {
|
117
|
-
:
|
118
|
-
:employees
|
120
|
+
:development => ['development', 'test'].include?(Rails.env),
|
121
|
+
:employees => { :required_state => :development, :feature_group => :employees }
|
119
122
|
}
|
120
123
|
|
121
124
|
It has a required state and a feature group. The feature group defines
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
+
a symbolic name for the group of users who should see this feature. You
|
126
|
+
can name this whatever you want. The required state is the state that gets
|
127
|
+
looked at for all other users that aren't in the feature group. The required
|
128
|
+
state (:development) must be a defined, static state.
|
125
129
|
|
126
130
|
### Setting the feature group
|
127
131
|
|
128
|
-
The
|
132
|
+
The feature group is set globally and is active for the whole thread.
|
129
133
|
In Rails you would define a before_filter like this:
|
130
134
|
|
131
135
|
class ApplicationController < ActionController::Base
|
132
|
-
before_filter :
|
136
|
+
before_filter :set_active_feature_group
|
133
137
|
|
134
|
-
def
|
138
|
+
def set_active_feature_group
|
135
139
|
# we need to reset the feature group in each request,
|
136
|
-
# otherwise it
|
137
|
-
FeatureFlipper.
|
140
|
+
# otherwise it's also active for the following requests.
|
141
|
+
FeatureFlipper.reset_active_feature_groups
|
138
142
|
|
139
143
|
if logged_in? && current_user.employee?
|
140
|
-
FeatureFlipper.
|
144
|
+
FeatureFlipper.active_feature_groups << :employees
|
141
145
|
end
|
142
146
|
end
|
143
147
|
|
144
|
-
|
145
|
-
The condition if someone is in a feature group
|
146
|
-
store it in the database, in Redis,
|
148
|
+
Don't forget to reset the feature group, without it the feature group
|
149
|
+
is active forever. The condition if someone is in a feature group
|
150
|
+
can be anything: You can store it in the database, in Redis,
|
151
|
+
look at request parameters, based on the current time, etc.
|
147
152
|
|
148
153
|
Take a look at `dynamic_states.rb` in the examples folder to see this
|
149
154
|
in detail.
|
data/examples/dynamic_states.rb
CHANGED
@@ -26,7 +26,7 @@ FeatureFlipper::Config.path_to_file = "features.rb"
|
|
26
26
|
|
27
27
|
puts "=== first example:"
|
28
28
|
|
29
|
-
# no
|
29
|
+
# no active feature_group set, so the required_state of badges is looked at
|
30
30
|
if show_feature?(:badges)
|
31
31
|
puts "shiny new badges not live on prod yet"
|
32
32
|
else
|
@@ -35,10 +35,10 @@ end
|
|
35
35
|
|
36
36
|
puts "\n=== second example:"
|
37
37
|
|
38
|
-
# now we set the
|
38
|
+
# now we set the active feature_group. Usually depending on the logged in user
|
39
39
|
|
40
|
-
FeatureFlipper.
|
41
|
-
FeatureFlipper.
|
40
|
+
FeatureFlipper.reset_active_feature_groups
|
41
|
+
FeatureFlipper.active_feature_groups << :employees
|
42
42
|
|
43
43
|
if show_feature?(:badges)
|
44
44
|
puts "shiny new badges for this user"
|
data/examples/features.rb
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
#
|
5
5
|
|
6
6
|
FeatureFlipper.features do
|
7
|
-
in_state :
|
7
|
+
in_state :development do
|
8
8
|
feature :rating_game, :description => 'play a game to get recommendations'
|
9
9
|
end
|
10
10
|
|
@@ -18,7 +18,7 @@ FeatureFlipper.features do
|
|
18
18
|
end
|
19
19
|
|
20
20
|
FeatureFlipper::Config.states = {
|
21
|
-
:
|
22
|
-
:employees
|
23
|
-
:live
|
21
|
+
:development => ['development', 'test'].include?(Rails.env),
|
22
|
+
:employees => { :required_state => :development, :feature_group => :employees },
|
23
|
+
:live => true
|
24
24
|
}
|
@@ -51,7 +51,7 @@ module FeatureFlipper
|
|
51
51
|
else
|
52
52
|
group, required_state = active.to_a.flatten
|
53
53
|
end
|
54
|
-
(FeatureFlipper.
|
54
|
+
(FeatureFlipper.active_feature_groups.include?(group)) || (states[required_state] == true)
|
55
55
|
else
|
56
56
|
active == true
|
57
57
|
end
|
@@ -91,11 +91,11 @@ module FeatureFlipper
|
|
91
91
|
StateMapper.new.instance_eval(&block)
|
92
92
|
end
|
93
93
|
|
94
|
-
def self.
|
95
|
-
Thread.current[:
|
94
|
+
def self.active_feature_groups
|
95
|
+
Thread.current[:feature_system_active_feature_groups] ||= []
|
96
96
|
end
|
97
97
|
|
98
|
-
def self.
|
99
|
-
|
98
|
+
def self.reset_active_feature_groups
|
99
|
+
active_feature_groups.clear
|
100
100
|
end
|
101
101
|
end
|
@@ -76,12 +76,12 @@ context 'dynamic feature groups' do
|
|
76
76
|
setup do
|
77
77
|
FeatureFlipper::Config.path_to_file = 'features.rb'
|
78
78
|
FeatureFlipper::Config.reload_config
|
79
|
-
FeatureFlipper.
|
79
|
+
FeatureFlipper.reset_active_feature_groups
|
80
80
|
end
|
81
81
|
|
82
82
|
test 'should show a beta feature to the feature group' do
|
83
83
|
Rails.stubs(:env).returns('production')
|
84
|
-
FeatureFlipper.
|
84
|
+
FeatureFlipper.active_feature_groups << :beta_users
|
85
85
|
|
86
86
|
assert show_feature?(:beta_feature_old)
|
87
87
|
assert show_feature?(:beta_feature_new)
|
@@ -89,7 +89,7 @@ context 'dynamic feature groups' do
|
|
89
89
|
|
90
90
|
test 'should not show a beta feature if not in the group' do
|
91
91
|
Rails.stubs(:env).returns('production')
|
92
|
-
FeatureFlipper.
|
92
|
+
FeatureFlipper.active_feature_groups << :different_feature_group
|
93
93
|
|
94
94
|
assert !show_feature?(:beta_feature_old)
|
95
95
|
assert !show_feature?(:beta_feature_new)
|
@@ -97,7 +97,7 @@ context 'dynamic feature groups' do
|
|
97
97
|
|
98
98
|
test 'should always show a beta feature on dev' do
|
99
99
|
Rails.stubs(:env).returns('development')
|
100
|
-
FeatureFlipper.
|
100
|
+
FeatureFlipper.active_feature_groups << nil
|
101
101
|
|
102
102
|
assert show_feature?(:beta_feature_old)
|
103
103
|
assert show_feature?(:beta_feature_new)
|
@@ -105,8 +105,8 @@ context 'dynamic feature groups' do
|
|
105
105
|
|
106
106
|
test 'can be in two feature groups at the same time' do
|
107
107
|
Rails.stubs(:env).returns('production')
|
108
|
-
FeatureFlipper.
|
109
|
-
FeatureFlipper.
|
108
|
+
FeatureFlipper.active_feature_groups << :beta_users
|
109
|
+
FeatureFlipper.active_feature_groups << :employees
|
110
110
|
|
111
111
|
assert show_feature?(:beta_feature_new)
|
112
112
|
assert show_feature?(:employee_feature)
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 1
|
7
|
-
-
|
7
|
+
- 2
|
8
8
|
- 0
|
9
|
-
version: 1.
|
9
|
+
version: 1.2.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Florian Munz
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-06-01 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|