feature_flipper 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Florian Munz
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,118 @@
1
+ FeatureFlipper
2
+ ==============
3
+
4
+ FeatureFlipper is a simple library that allows you to restrict certain blocks
5
+ of code to certain environments. This is mainly useful in projects where
6
+ you deploy your application from HEAD and don't use branches.
7
+
8
+ Install
9
+ -------
10
+
11
+ FeatureFlipper is packaged as a gem:
12
+
13
+ $ gem install feature_flipper
14
+
15
+ In your project you have to configure the path to the app specific
16
+ configuration file after requiring FeatureFlipper:
17
+
18
+ require 'feature_flipper'
19
+ FeatureFlipper::Config.path_to_file = "#{RAILS_ROOT}/config/features.rb"
20
+
21
+ Configuration
22
+ -------------
23
+
24
+ You need to create a configuration file which defines the two entities
25
+ FeatureFlipper cares about:
26
+
27
+ * states
28
+ * features
29
+
30
+ You first define multiple 'states' which normally depend on an environment
31
+ (for example: the state 'dev' is only active on development boxes). After that
32
+ you add 'features' which correspond to logical chunks of work in your project.
33
+ These features then move through the different states as they get developed.
34
+
35
+ ### Defining features
36
+
37
+ A feature needs to have a name and you can add additional information like a
38
+ more detailed description, a ticket number, a date when it was started, etc.
39
+ Features are always defined in a state, you cannot define a feature which
40
+ doesn't belong to a state.
41
+
42
+ in_status :dev do
43
+ feature :rating_game, :description => 'play a game to get recommendations'
44
+ end
45
+
46
+ ### Defining states
47
+
48
+ A state is just a name and a boolean check. The check needs to evaluate to
49
+ ´true´ when it is active. For a Rails app you can just use environments:
50
+
51
+ :dev => ['development', 'test'].include?(Rails.env),
52
+
53
+ Usage
54
+ -----
55
+
56
+ In your code you then use the `show_feature?` method to branch depending on
57
+ wether the feature is active or not:
58
+
59
+ if show_feature?(:rating_game)
60
+ # new code
61
+ else
62
+ # old code
63
+ end
64
+
65
+ The `show_feature?` method is defined on Object, so you can use it everywhere.
66
+
67
+ Example config file
68
+ -------------------
69
+
70
+ FeatureFlipper.features do
71
+ in_status :dev do
72
+ feature :rating_game, :description => 'play a game to get recommendations'
73
+ end
74
+
75
+ in_status :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 is in).
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 this example in detail in the 'examples' folder.
95
+
96
+ Cleaning up
97
+ -----------
98
+
99
+ The drawback of this approach is that your code can get quite ugly with all
100
+ these if/else branches. So you have to be strict about removing (we call it
101
+ de-featurizing) features after they have gone live.
102
+
103
+ Meta
104
+ ----
105
+
106
+ * Code: `git clone git://github.com/qype/feature_flipper.git`
107
+ * Home: <http://github.com/qype/feature_flipper>
108
+ * Bugs: <http://github.com/qype/feature_flipper/issues>
109
+
110
+ This project uses [Semantic Versioning][sv].
111
+
112
+ Author
113
+ ------
114
+
115
+ Florian Munz, Qype GmbH - florian@qype.com
116
+
117
+
118
+ [sv]: http://semver.org/
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ desc 'Default: run unit tests'
5
+ task :default => :test
6
+
7
+ desc 'run the feature_flipper tests'
8
+ Rake::TestTask.new(:test) do |t|
9
+ t.libs << 'lib'
10
+ t.libs << 'test'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ begin
16
+ require 'mg'
17
+ MG.new('feature_flipper.gemspec')
18
+ rescue LoadError
19
+ warn 'mg not available.'
20
+ warn 'Install it with: gem install mg'
21
+ end
@@ -0,0 +1,19 @@
1
+ #
2
+ # This is an example configuration file. It defines two states and
3
+ # two features.
4
+ #
5
+
6
+ FeatureFlipper.features do
7
+ in_status :dev do
8
+ feature :rating_game, :description => 'play a game to get recommendations'
9
+ end
10
+
11
+ in_status :live do
12
+ feature :city_feed, :description => 'stream of content for each city'
13
+ end
14
+ end
15
+
16
+ FeatureFlipper::Config.states = {
17
+ :dev => ['development', 'test'].include?(Rails.env),
18
+ :live => true
19
+ }
@@ -0,0 +1,39 @@
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
+ # rating_game is still in development, so shouldn't be shown on production
28
+ if show_feature?(:rating_game)
29
+ puts "Rating Game"
30
+ else
31
+ puts "old stuff"
32
+ end
33
+
34
+ # city_feed is enabled everywhere
35
+ if show_feature?(:city_feed)
36
+ puts "City Feed"
37
+ else
38
+ puts "old stuff"
39
+ end
@@ -0,0 +1,97 @@
1
+ module FeatureFlipper
2
+ module Config
3
+ @features = {}
4
+ @states = {}
5
+
6
+ def self.path_to_file
7
+ @path_to_file || File.join(Rails.root, 'config', 'features.rb')
8
+ end
9
+
10
+ def self.path_to_file=(path_to_file)
11
+ @path_to_file = path_to_file
12
+ end
13
+
14
+ def self.ensure_config_is_loaded
15
+ return if @config_loaded
16
+
17
+ load path_to_file
18
+ @config_loaded = true
19
+ end
20
+
21
+ def self.reload_config
22
+ @config_loaded = false
23
+ end
24
+
25
+ def self.features
26
+ @features
27
+ end
28
+
29
+ def self.features=(features)
30
+ @features = features
31
+ end
32
+
33
+ def self.states
34
+ @states
35
+ end
36
+
37
+ def self.states=(states)
38
+ @states = states
39
+ end
40
+
41
+ def self.get_status(feature_name)
42
+ feature = features[feature_name]
43
+ feature ? feature[:status] : nil
44
+ end
45
+
46
+ def self.active_status?(status)
47
+ active = states[status]
48
+ if active.is_a?(Hash)
49
+ group, group_status = active.to_a.flatten
50
+ FeatureFlipper.current_feature_group == group || states[group_status] == true
51
+ else
52
+ active == true
53
+ end
54
+ end
55
+
56
+ def self.is_active?(feature_name)
57
+ ensure_config_is_loaded
58
+
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
64
+ else
65
+ status == true
66
+ end
67
+ end
68
+ end
69
+
70
+ class Mapper
71
+ def initialize(status)
72
+ @status = status
73
+ end
74
+
75
+ def feature(name, options = {})
76
+ FeatureFlipper::Config.features[name] = options.merge(:status => @status)
77
+ end
78
+ end
79
+
80
+ class StatusMapper
81
+ def in_status(status, &block)
82
+ Mapper.new(status).instance_eval(&block)
83
+ end
84
+ end
85
+
86
+ def self.features(&block)
87
+ StatusMapper.new.instance_eval(&block)
88
+ end
89
+
90
+ def self.current_feature_group
91
+ Thread.current[:feature_system_current_feature_group]
92
+ end
93
+
94
+ def self.current_feature_group=(feature_group)
95
+ Thread.current[:feature_system_current_feature_group] = feature_group
96
+ end
97
+ end
@@ -0,0 +1,13 @@
1
+ module FeatureFlipper
2
+ module Show
3
+ extend self
4
+
5
+ def self.included(base)
6
+ base.extend(self)
7
+ end
8
+
9
+ def show_feature?(feature_name)
10
+ FeatureFlipper::Config.is_active?(feature_name)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module FeatureFlipper
2
+ Version = VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,5 @@
1
+ require 'feature_flipper/show'
2
+ require 'feature_flipper/config'
3
+
4
+ # we need the show_feature? method everywhere
5
+ Object.send(:include, FeatureFlipper::Show)
@@ -0,0 +1,94 @@
1
+ require 'test_helper'
2
+
3
+ context 'hash based FeatureFlipper' do
4
+ setup do
5
+ FeatureFlipper::Config.path_to_file = 'features.rb'
6
+ FeatureFlipper::Config.reload_config
7
+ end
8
+
9
+ test 'should show enabled features' do
10
+ assert show_feature?(:live_feature)
11
+ end
12
+
13
+ test 'should not show disabled features' do
14
+ assert !show_feature?(:disabled_feature)
15
+ end
16
+
17
+ test 'should not show a feature when on a higher environment' do
18
+ Rails.stubs(:env).returns('production')
19
+ assert !show_feature?(:dev_feature)
20
+ end
21
+
22
+ test 'show feature should work with booleans' do
23
+ assert show_feature?(:boolean_feature)
24
+ end
25
+
26
+ test 'show feature should work with procs' do
27
+ assert show_feature?(:proc_feature)
28
+ end
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
+ test 'should be able to get features' do
52
+ FeatureFlipper::Config.ensure_config_is_loaded
53
+ all_features = FeatureFlipper::Config.features
54
+
55
+ assert_not_nil all_features
56
+ assert all_features.is_a?(Hash)
57
+ assert_equal :dev, all_features[:dev_feature][:status]
58
+ assert_equal 'dev feature', all_features[:dev_feature][:description]
59
+ end
60
+ end
61
+
62
+ context 'DSL based FeatureFlipper' do
63
+ setup do
64
+ FeatureFlipper::Config.path_to_file = 'features_dsl.rb'
65
+ FeatureFlipper::Config.reload_config
66
+ end
67
+
68
+ test 'should show enabled features' do
69
+ assert show_feature?(:live_feature)
70
+ end
71
+
72
+ test 'should not show a feature when on a higher environment' do
73
+ Rails.stubs(:env).returns('production')
74
+ assert !show_feature?(:dev_feature)
75
+ end
76
+
77
+ test 'show feature should work with booleans' do
78
+ assert show_feature?(:boolean_feature)
79
+ end
80
+
81
+ test 'show feature should work with procs' do
82
+ assert show_feature?(:proc_feature)
83
+ end
84
+
85
+ test 'should be able to get features' do
86
+ FeatureFlipper::Config.ensure_config_is_loaded
87
+ all_features = FeatureFlipper::Config.features
88
+
89
+ assert_not_nil all_features
90
+ assert all_features.is_a?(Hash)
91
+ assert_equal :dev, all_features[:dev_feature][:status]
92
+ assert_equal 'dev feature', all_features[:dev_feature][:description]
93
+ end
94
+ end
data/test/features.rb ADDED
@@ -0,0 +1,16 @@
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 }
8
+ }
9
+
10
+
11
+ FeatureFlipper::Config.states = {
12
+ :disabled => false,
13
+ :dev => ['development', 'test'].include?(Rails.env),
14
+ :beta => { :beta_users => :dev },
15
+ :live => true
16
+ }
@@ -0,0 +1,23 @@
1
+ FeatureFlipper.features do
2
+ in_status :dev do
3
+ feature :dev_feature, :description => 'dev feature'
4
+ end
5
+
6
+ in_status :live do
7
+ feature :live_feature
8
+ end
9
+
10
+ in_status true do
11
+ feature :boolean_feature
12
+ end
13
+
14
+ in_status Proc.new { Date.today > Date.today - 84000 } do
15
+ feature :proc_feature
16
+ end
17
+ end
18
+
19
+
20
+ FeatureFlipper::Config.states = {
21
+ :dev => ['development', 'test'].include?(Rails.env),
22
+ :live => true
23
+ }
@@ -0,0 +1,38 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+
4
+ require 'mocha'
5
+
6
+ ##
7
+ # test/spec/mini 5
8
+ # http://gist.github.com/307649
9
+ # chris@ozmm.org
10
+ #
11
+ def context(*args, &block)
12
+ return super unless (name = args.first) && block
13
+ require 'test/unit'
14
+ klass = Class.new(defined?(ActiveSupport::TestCase) ? ActiveSupport::TestCase : Test::Unit::TestCase) do
15
+ def self.test(name, &block)
16
+ define_method("test_#{name.to_s.gsub(/\W/,'_')}", &block) if block
17
+ end
18
+ def self.xtest(*args) end
19
+ def self.context(*args, &block) instance_eval(&block) end
20
+ def self.setup(&block)
21
+ define_method(:setup) { self.class.setups.each { |s| instance_eval(&s) } }
22
+ setups << block
23
+ end
24
+ def self.setups; @setups ||= [] end
25
+ def self.teardown(&block) define_method(:teardown, &block) end
26
+ end
27
+ (class << klass; self end).send(:define_method, :name) { name.gsub(/\W/,'_') }
28
+ klass.class_eval &block
29
+ end
30
+
31
+
32
+ module Rails
33
+ def self.env
34
+ 'test'
35
+ end
36
+ end
37
+
38
+ require 'feature_flipper'
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: feature_flipper
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Florian Munz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-05-07 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: |
17
+ FeatureFlipper is a simple library that allows you to restrict certain blocks
18
+ of code to certain environments. This is mainly useful in projects where
19
+ you deploy your application from HEAD and don't use branches.
20
+
21
+ email: florian@qype.com
22
+ executables: []
23
+
24
+ extensions: []
25
+
26
+ extra_rdoc_files: []
27
+
28
+ files:
29
+ - README.md
30
+ - Rakefile
31
+ - LICENSE
32
+ - lib/feature_flipper/config.rb
33
+ - lib/feature_flipper/show.rb
34
+ - lib/feature_flipper/version.rb
35
+ - lib/feature_flipper.rb
36
+ - test/feature_flipper_test.rb
37
+ - test/features.rb
38
+ - test/features_dsl.rb
39
+ - test/test_helper.rb
40
+ - examples/features.rb
41
+ - examples/simple.rb
42
+ has_rdoc: true
43
+ homepage: http://github.com/qype/feature_flipper
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options: []
48
+
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.5
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: FeatureFlipper helps you flipping features
70
+ test_files: []
71
+