feature_flipper 1.0.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/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
+