ruby_flipper 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ spec/reports
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in ruby_flipper.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,34 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ruby_flipper (0.0.1)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ builder (3.0.0)
10
+ ci_reporter (1.6.3)
11
+ builder (>= 2.1.2)
12
+ diff-lcs (1.1.2)
13
+ mocha (0.9.10)
14
+ rake
15
+ rake (0.8.7)
16
+ rspec (2.0.1)
17
+ rspec-core (~> 2.0.1)
18
+ rspec-expectations (~> 2.0.1)
19
+ rspec-mocks (~> 2.0.1)
20
+ rspec-core (2.0.1)
21
+ rspec-expectations (2.0.1)
22
+ diff-lcs (>= 1.1.2)
23
+ rspec-mocks (2.0.1)
24
+ rspec-core (~> 2.0.1)
25
+ rspec-expectations (~> 2.0.1)
26
+
27
+ PLATFORMS
28
+ ruby
29
+
30
+ DEPENDENCIES
31
+ ci_reporter (~> 1.6.3)
32
+ mocha (~> 0.9.10)
33
+ rspec (~> 2.0.1)
34
+ ruby_flipper!
data/README.rdoc ADDED
@@ -0,0 +1,126 @@
1
+ = Ruby Flipper
2
+
3
+ Ruby Flipper makes it easy to define features and switch them on and off. It's configuration DSL provides a very flexible but still not too verbose syntax to fulfil your flipping needs.
4
+
5
+
6
+ == Usage
7
+
8
+ To use ruby flipper, you have to load a feature definition file somewhere:
9
+
10
+ RubyFlipper.load(File.expand_path '../features.rb', __FILE__)
11
+
12
+ This configuration can be reset by calling:
13
+
14
+ RubyFlipper.reset
15
+
16
+
17
+ == Feature Definitions
18
+
19
+ The file loaded by ruby flipper is written in ruby flipper's simple DSL. The most basic feature definition is just a feature with a name. Without any further conditions, the feature is active by default.
20
+
21
+ feature :always_on_feature
22
+
23
+ You can specify conditions that are used to determine whether a feature is active or not in three different ways:
24
+
25
+ feature :conditional_feature_with_parameter, false
26
+ feature :conditional_feature_with_condition_opt, :condition => false
27
+ feature :conditional_feature_with_conditions_opt, :conditions => false
28
+
29
+ In the examples above, the features are statically disabled. You can use three different types of conditions.
30
+
31
+ === Static Conditions
32
+
33
+ A static condition is a boolean that is evaluated when the feature definitions are loaded. This can be used to statically activate/deactivate features or make features dependent on variables that don't change during runtime.
34
+
35
+ feature :development_feature, Rails.env.development?
36
+
37
+ This defines a feature that is only active in Rails' development mode.
38
+
39
+ === Reference Conditions
40
+
41
+ A reference condition is a symbol that references another feature. This can be used to make a feature dependent on another feature or extract complex condition definitions and reuse them for several features.
42
+
43
+ feature :dependent_feature, :development_feature
44
+
45
+ This defines a feature that is only active when the feature :development_feature is active.
46
+
47
+ === Dynamic Conditions
48
+
49
+ A dynamic condition is a proc (or anything that responds to :call) that will be evaluated each time the state of a feature is checked. This can be used to activate/deactivate features dynamically depending on variables that change during runtime.
50
+
51
+ feature :admin_feature, condition => lambda { User.current.is_admin? }
52
+
53
+ This defines a feature that is only active when the current user is an admin (the user specific stuff must be implemented somewhere else).
54
+
55
+ Dynamic conditions can also be given to the feature method as a block:
56
+
57
+ feature :admin_feature do
58
+ User.current.is_admin?
59
+ end
60
+
61
+ Within such a proc, you can also check whether another feature is active or not to phrase complex condition dependencies:
62
+
63
+ feature :shy_feature do
64
+ active?(:development_feature) && (active?(:admin_feature) || Time.now.hour < 8)
65
+ end
66
+
67
+ This defines a feature that is only active when the feature :development_feature is active and either the feature :admin_feature is active or it is between midnight and 8:00 in the morning (however that might be useful).
68
+
69
+ === Combined Conditions
70
+
71
+ A combined condition is an array of other conditions. Any type of condition can be combined in an array. All conditions have to be met for a feature to be active.
72
+
73
+ feature :combined_feature, [:development_feature, lambda { User.current.name == 'patti' }]
74
+
75
+ This defines a feature that is only enabled when the feature :development_feature is enbled and the current user is 'patti'.
76
+
77
+ === More
78
+
79
+ For more examples, see spec/fixtures/features.rb and spec/ruby_flipper_spec.rb (section .load which is kind of an end-to-end integration test).
80
+
81
+
82
+ == Condition Definitions
83
+
84
+ If you have a lot of dependent features with shared conditions you can clean up your definition file by explicitly defining conditions that aren't used as features but just as conditions real features depend on.
85
+
86
+ condition :development, Rails.env.development?
87
+ condition :admin { User.current.is_admin? }
88
+
89
+ feature :user_administration, :development, :admin
90
+ feature :new_feature, :development
91
+
92
+ Of course, you can nest this as deep as you like (features depending on conditions depending on conditions depending on features depending on conditions...), although this might lead to completely incomprehensible rulesets.
93
+
94
+ The condition method is just an alias to the feature method, so internally, it makes absolutely no difference which method you use. Using both methods just helps you structuring your feature definitions and makes the feature definition file easier to understand.
95
+
96
+
97
+ == Usage
98
+
99
+ Finally, there are two ways to check whether a feature is active or not within your code. You can just give it a block that will only be evaluated when the feature is active:
100
+
101
+ feature_active?(:hidden_feature) do
102
+ # do something for that feature
103
+ end
104
+
105
+ or if you need the state of a feature as a boolean value or want to do something when a feature is not active:
106
+
107
+ if feature_active?(:hidden_feature)
108
+ # do something for that feature
109
+ else
110
+ # do something else
111
+ end
112
+
113
+ This method is defined on Object, so it is available anywhere you might ever need it.
114
+
115
+
116
+ == Describing Features
117
+
118
+ Last and least, you can describe your features, for example when you use ticket numbers or anything other cryptic for your feature names. The description isn't used anywhere yet, but it might make your code more readable.
119
+
120
+ feature :feature_452, :development, :description => 'Feature 452: An end user can change his data on his profile page.'
121
+
122
+ You could also just use comments for that, but giving this to ruby flipper in code enables you to evaluate your features and print lists containing the description.
123
+
124
+ RubyFlipper::Feature.all.each do |feature|
125
+ puts "#{feature.name}: #{feature.description}"
126
+ end
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ require 'ci/reporter/rake/rspec'
6
+
7
+ RSpec::Core::RakeTask.new :spec
8
+ task :default => %w(ci:setup:rspec spec)
@@ -0,0 +1 @@
1
+ Autotest.add_discovery { "rspec2" }
@@ -0,0 +1,11 @@
1
+ module RubyFlipper
2
+
3
+ class ConditionContext
4
+
5
+ def active?(*conditions)
6
+ Feature.new(nil, *conditions).active?
7
+ end
8
+
9
+ end
10
+
11
+ end
@@ -0,0 +1,12 @@
1
+ module RubyFlipper
2
+
3
+ class Dsl
4
+
5
+ def feature(name, *conditions, &block)
6
+ Feature.add(name, *conditions, &block)
7
+ end
8
+ alias :condition :feature
9
+
10
+ end
11
+
12
+ end
@@ -0,0 +1,51 @@
1
+ module RubyFlipper
2
+
3
+ class Feature
4
+
5
+ def self.all
6
+ @@features ||= {}
7
+ end
8
+
9
+ def self.reset
10
+ @@features = nil
11
+ end
12
+
13
+ def self.add(name, *conditions, &block)
14
+ all[name] = new(name, *conditions, &block)
15
+ end
16
+
17
+ def self.find(name)
18
+ all[name] || raise(NotDefinedError, "'#{name}' is not defined")
19
+ end
20
+
21
+ def self.condition_met?(condition)
22
+ if condition.is_a?(Symbol)
23
+ find(condition).active?
24
+ elsif condition.is_a?(Proc)
25
+ !!ConditionContext.new.instance_eval(&condition)
26
+ elsif condition.respond_to?(:call)
27
+ !!condition.call
28
+ else
29
+ !!condition
30
+ end
31
+ end
32
+
33
+ attr_reader :name, :description, :conditions
34
+
35
+ def initialize(name, *conditions, &block)
36
+ if conditions.last.is_a?(Hash)
37
+ opts = conditions.pop
38
+ @description = opts[:description]
39
+ conditions << opts[:condition]
40
+ conditions << opts[:conditions]
41
+ end
42
+ @name, @conditions = name, [conditions, block].flatten.compact
43
+ end
44
+
45
+ def active?
46
+ conditions.map {|condition| self.class.condition_met?(condition)}.all?
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,16 @@
1
+ module RubyFlipper
2
+
3
+ module ObjectMixin
4
+
5
+ def feature_active?(name)
6
+ active = Feature.find(name).active?
7
+ yield if active && block_given?
8
+ active
9
+ end
10
+ alias :condition_met? :feature_active?
11
+
12
+ end
13
+
14
+ end
15
+
16
+ Object.send :include, RubyFlipper::ObjectMixin
@@ -0,0 +1,19 @@
1
+ require 'ruby_flipper/object_mixin'
2
+
3
+ module RubyFlipper
4
+
5
+ class NotDefinedError < StandardError; end
6
+
7
+ autoload :ConditionContext, 'ruby_flipper/condition_context'
8
+ autoload :Dsl, 'ruby_flipper/dsl'
9
+ autoload :Feature, 'ruby_flipper/feature'
10
+
11
+ def self.load(file)
12
+ Dsl.new.instance_eval(IO.read file)
13
+ end
14
+
15
+ def self.reset
16
+ Feature.reset
17
+ end
18
+
19
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "ruby_flipper"
6
+ s.version = "0.0.2"
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Thomas Jachmann"]
9
+ s.email = ["self@thomasjachmann.com"]
10
+ s.homepage = "https://github.com/blaulabs/ruby_flipper"
11
+ s.summary = %q{Make switching features on and off easy.}
12
+ s.description = %q{Most flexible still least verbose feature flipper for ruby projects.}
13
+
14
+ s.rubyforge_project = "ruby_flipper"
15
+
16
+ s.add_development_dependency "ci_reporter", "~> 1.6.3"
17
+ s.add_development_dependency "rspec", "~> 2.0.1"
18
+ s.add_development_dependency "mocha", "~> 0.9.10"
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+ end
@@ -0,0 +1,38 @@
1
+ # statically configured condition (initially evaluated when configuration is loaded)
2
+ condition :static_is_cherry, FLIPPER_ENV[:static] == 'cherry'
3
+
4
+ # dynamically configured condition (evaluated on each access)
5
+ condition :dynamic_is_floyd do
6
+ FLIPPER_ENV[:dynamic] == 'floyd'
7
+ end
8
+
9
+ # combined condition referencing both above
10
+ condition :combined_is_cherry_and_floyd, :static_is_cherry, :dynamic_is_floyd
11
+
12
+ # combined condition with static and dynamic
13
+ condition :combined_is_lulu_first_then_gavin, FLIPPER_ENV[:changing] == 'lulu' do
14
+ FLIPPER_ENV[:changing] == 'gavin'
15
+ end
16
+
17
+
18
+ # disabled feature
19
+ feature :disabled, :description => 'disabled feature', :condition => false
20
+
21
+ # feature referencing a single predefined condition
22
+ feature :floyd, :description => 'just for floyd', :condition => :dynamic_is_floyd
23
+
24
+ # feature depending on a dynamic condition (lambda)
25
+ feature :philip, :description => 'just for philip', :condition => lambda { FLIPPER_ENV[:dynamic] == 'philip' }
26
+
27
+ # feature depending on array of a static condition, a predefined condition and a dynamic condition (lambda)
28
+ # given in :conditions (can be both :condition/:conditions)
29
+ feature :patti, :description => 'just for patti', :conditions => [
30
+ FLIPPER_ENV[:changing] == 'patti',
31
+ :dynamic_is_floyd,
32
+ lambda { FLIPPER_ENV[:changing] == 'gavin'}
33
+ ]
34
+
35
+ # feature with a complex combination of dynamic and predifined conditions given as a block
36
+ feature :sue, :description => 'just for sue' do
37
+ (active?(:static_is_cherry) || active?(:dynamic_is_floyd)) && FLIPPER_ENV[:changing] == 'sue'
38
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe RubyFlipper::ConditionContext do
4
+
5
+ describe '#active?' do
6
+
7
+ it 'should return true on an active feature' do
8
+ RubyFlipper::Feature.add(:referenced, true)
9
+ subject.active?(:referenced).should == true
10
+ end
11
+
12
+ it 'should return false on an inactive feature' do
13
+ RubyFlipper::Feature.add(:referenced, false)
14
+ subject.active?(:referenced).should == false
15
+ end
16
+
17
+ it 'should raise an error when the referenced feature is not defined' do
18
+ lambda { subject.active?(:referenced) }.should raise_error RubyFlipper::NotDefinedError, '\'referenced\' is not defined'
19
+ end
20
+
21
+ it 'should handle multiple conditions as well' do
22
+ RubyFlipper::Feature.add(:referenced1, true)
23
+ RubyFlipper::Feature.add(:referenced2, false)
24
+ RubyFlipper::Feature.add(:referenced3, true)
25
+ subject.active?(:referenced1, :referenced2).should == false
26
+ subject.active?(:referenced1, :referenced3).should == true
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe RubyFlipper::Dsl do
4
+
5
+ it 'should ensure mocked methods exist' do
6
+ RubyFlipper::Feature.should respond_to(:add)
7
+ end
8
+
9
+ [:feature, :condition].each do |method|
10
+
11
+ describe "##{method}" do
12
+
13
+ it 'should call add with only a name' do
14
+ RubyFlipper::Feature.expects(:add).with(:feature_name).returns(:feature)
15
+ subject.send(method, :feature_name).should == :feature
16
+ end
17
+
18
+ it 'should call add with a name and conditions' do
19
+ RubyFlipper::Feature.expects(:add).with(:feature_name, :one, :two).returns(:feature)
20
+ subject.send(method, :feature_name, :one, :two).should == :feature
21
+ end
22
+
23
+ it 'should call add with a name, conditions and a block' do
24
+ block = lambda {}
25
+ # cannot mock this since mocha doesn't allow to test for a proc [thomas, 2010-12-13]
26
+ feature = subject.send(method, :feature_name, :one, :two, &block)
27
+ feature.conditions.should == [:one, :two, block]
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,178 @@
1
+ require 'spec_helper'
2
+
3
+ describe RubyFlipper::Feature do
4
+
5
+ describe '.all' do
6
+
7
+ it 'should be a hash' do
8
+ RubyFlipper::Feature.all.should == {}
9
+ end
10
+
11
+ end
12
+
13
+ describe '.reset' do
14
+
15
+ it 'should reset the all hash' do
16
+ RubyFlipper::Feature.add(:inactive, false)
17
+ RubyFlipper::Feature.all.should_not be_empty
18
+ RubyFlipper::Feature.reset
19
+ RubyFlipper::Feature.all.should be_empty
20
+ end
21
+
22
+ end
23
+
24
+ describe '.add' do
25
+
26
+ it 'should call new with only a name and store the feature' do
27
+ RubyFlipper::Feature.expects(:new).with(:feature_name).returns(:feature)
28
+ RubyFlipper::Feature.add(:feature_name).should == :feature
29
+ RubyFlipper::Feature.all[:feature_name].should == :feature
30
+ end
31
+
32
+ it 'should call add with a name and conditions and store the feature' do
33
+ RubyFlipper::Feature.expects(:new).with(:feature_name, :one, :two).returns(:feature)
34
+ RubyFlipper::Feature.add(:feature_name, :one, :two).should == :feature
35
+ RubyFlipper::Feature.all[:feature_name].should == :feature
36
+ end
37
+
38
+ it 'should call add with a name, conditions and a block and store the feature' do
39
+ block = lambda {}
40
+ # cannot mock this since mocha doesn't allow to test for a proc [thomas, 2010-12-13]
41
+ feature = RubyFlipper::Feature.add(:feature_name, :one, :two, &block)
42
+ feature.conditions.should == [:one, :two, block]
43
+ RubyFlipper::Feature.all[:feature_name].should == feature
44
+ end
45
+
46
+ end
47
+
48
+ describe '.find' do
49
+
50
+ it 'should return the stored feature' do
51
+ feature = RubyFlipper::Feature.add(:feature_name)
52
+ RubyFlipper::Feature.find(:feature_name).should == feature
53
+ end
54
+
55
+ it 'should raise an error when the referenced feature is not defined' do
56
+ lambda { RubyFlipper::Feature.find(:feature_name) }.should raise_error RubyFlipper::NotDefinedError, '\'feature_name\' is not defined'
57
+ end
58
+
59
+ end
60
+
61
+ describe '.condition_met?' do
62
+
63
+ context 'with a symbol' do
64
+
65
+ it 'should return the state of the referenced feature' do
66
+ RubyFlipper::Feature.add(:referenced, true)
67
+ RubyFlipper::Feature.condition_met?(:referenced).should == true
68
+ end
69
+
70
+ it 'should raise an error when the referenced feature is not defined' do
71
+ lambda { RubyFlipper::Feature.condition_met?(:missing) }.should raise_error RubyFlipper::NotDefinedError, '\'missing\' is not defined'
72
+ end
73
+
74
+ end
75
+
76
+ {
77
+ true => true,
78
+ 'anything' => true,
79
+ false => false,
80
+ nil => false
81
+ }.each do |condition, expected|
82
+
83
+ it "should call a given proc and return #{expected} when it returns #{condition}" do
84
+ RubyFlipper::Feature.condition_met?(lambda { condition }).should == expected
85
+ end
86
+
87
+ it "should call anything callable and return #{expected} when it returns #{condition}" do
88
+ RubyFlipper::Feature.condition_met?(stub(:call => condition)).should == expected
89
+ end
90
+
91
+ it "should return #{expected} when the condition is #{condition}" do
92
+ RubyFlipper::Feature.condition_met?(condition).should == expected
93
+ end
94
+
95
+ end
96
+
97
+ context 'with a complex proc' do
98
+
99
+ it 'should return the met? of the combined referenced conditions' do
100
+ RubyFlipper::Feature.add(:true, true)
101
+ RubyFlipper::Feature.add(:false, false)
102
+ RubyFlipper::Feature.condition_met?(lambda { active?(:true) || active?(:false) }).should == true
103
+ RubyFlipper::Feature.condition_met?(lambda { active?(:true) && active?(:false) }).should == false
104
+ end
105
+
106
+ end
107
+
108
+ end
109
+
110
+ describe 'initializer' do
111
+
112
+ it 'should store the name' do
113
+ RubyFlipper::Feature.new(:feature_name).name.should == :feature_name
114
+ end
115
+
116
+ it 'should store the description' do
117
+ RubyFlipper::Feature.new(:feature_name, :description => 'desc').description.should == 'desc'
118
+ end
119
+
120
+ it 'should work with a single static condition' do
121
+ RubyFlipper::Feature.new(:feature_name, true).conditions.should == [true]
122
+ end
123
+
124
+ it 'should work with multiple static conditions' do
125
+ RubyFlipper::Feature.new(:feature_name, true, :development).conditions.should == [true, :development]
126
+ end
127
+
128
+ it 'should work with a dynamic condition as parameter' do
129
+ condition = lambda { true }
130
+ RubyFlipper::Feature.new(:feature_name, condition).conditions.should == [condition]
131
+ end
132
+
133
+ it 'should work with a dynamic condition as block' do
134
+ condition = lambda { true }
135
+ RubyFlipper::Feature.new(:feature_name, &condition).conditions.should == [condition]
136
+ end
137
+
138
+ it 'should work with a combination of static and dynamic conditions' do
139
+ condition = lambda { true }
140
+ RubyFlipper::Feature.new(:feature_name, false, :live, condition).conditions.should == [false, :live, condition]
141
+ end
142
+
143
+ it 'should work with conditions given in the opts hash as :condition' do
144
+ RubyFlipper::Feature.new(:feature_name, :condition => true).conditions.should == [true]
145
+ end
146
+
147
+ it 'should work with conditions given in the opts hash as :conditions' do
148
+ RubyFlipper::Feature.new(:feature_name, :conditions => false).conditions.should == [false]
149
+ end
150
+
151
+ it 'should work with a combination of arrays, dynamic conditions and conditions given in the opts hash and eliminate nil' do
152
+ condition = lambda { true }
153
+ RubyFlipper::Feature.new(:feature_name, [false, nil], :condition => [nil, 'c'], :conditions => ['c1', 'c2'], &condition).conditions.should == [false, 'c', 'c1', 'c2', condition]
154
+ end
155
+
156
+ end
157
+
158
+ describe '#active?' do
159
+
160
+ it 'should return true when no conditions are given (unconditionally active)' do
161
+ RubyFlipper::Feature.new(:feature_name).active?.should == true
162
+ end
163
+
164
+ it 'should return false when not all conditions are met (with dynamic)' do
165
+ RubyFlipper::Feature.new(:feature_name, true, lambda { false }).active?.should == false
166
+ end
167
+
168
+ it 'should return false when not all conditions are met (only static)' do
169
+ RubyFlipper::Feature.new(:feature_name, false, true).active?.should == false
170
+ end
171
+
172
+ it 'should return true when all conditions are met' do
173
+ RubyFlipper::Feature.new(:feature_name, true, true).active?.should == true
174
+ end
175
+
176
+ end
177
+
178
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe RubyFlipper::ObjectMixin do
4
+
5
+ it 'should be included in Object' do
6
+ Object.included_modules.should include RubyFlipper::ObjectMixin
7
+ end
8
+
9
+ describe '#feature_active?' do
10
+
11
+ subject do
12
+ # this is not really needed, since one could just call feature_active?
13
+ # but to be independent of the inclusion in Object (see above), better
14
+ # create a new subject that is sure to include RubyFlipper::ObjectMixin.
15
+ Class.new do
16
+ include RubyFlipper::ObjectMixin
17
+ end.new
18
+ end
19
+
20
+ context 'with an active feature' do
21
+
22
+ before(:each) do
23
+ RubyFlipper::Feature.add(:active, true)
24
+ end
25
+
26
+ it 'should return true when called without a block' do
27
+ subject.feature_active?(:active).should == true
28
+ end
29
+
30
+ it 'should execute the block and return true when called with a block' do
31
+ var = 'before'
32
+ subject.feature_active?(:active) do
33
+ var = 'after'
34
+ end.should == true
35
+ var.should == 'after'
36
+ end
37
+
38
+ end
39
+
40
+ context 'with an inactive feature' do
41
+
42
+ before(:each) do
43
+ RubyFlipper::Feature.add(:inactive, false)
44
+ end
45
+
46
+ it 'should return false when called without a block' do
47
+ subject.feature_active?(:inactive).should == false
48
+ end
49
+
50
+ it 'should not execute the block and return false when called with a block' do
51
+ subject.feature_active?(:inactive) do
52
+ fail 'the given block should not be called'
53
+ end.should == false
54
+ end
55
+
56
+ end
57
+
58
+ context 'with a missing feature' do
59
+
60
+ it 'should raise an error when called without a block and the referenced feature is not defined' do
61
+ lambda { subject.feature_active?(:missing) }.should raise_error RubyFlipper::NotDefinedError, '\'missing\' is not defined'
62
+ end
63
+
64
+ it 'should not execute the block and raise an error when called with a block and the referenced feature is not defined' do
65
+ lambda { subject.feature_active?(:missing) { fail 'the given block should not be called' } }.should raise_error RubyFlipper::NotDefinedError, '\'missing\' is not defined'
66
+ end
67
+
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,222 @@
1
+ require 'spec_helper'
2
+
3
+ describe RubyFlipper do
4
+
5
+ describe '.load' do
6
+
7
+ def load_features
8
+ RubyFlipper.load(File.expand_path '../fixtures/features.rb', __FILE__)
9
+ end
10
+
11
+ context 'conditions' do
12
+
13
+ context ':static_is_cherry' do
14
+
15
+ it 'should not be met when static is not cherry when loading' do
16
+ load_features
17
+ RubyFlipper::Feature.find(:static_is_cherry).should_not be_active
18
+ end
19
+
20
+ it 'should not be met when static is not cherry when loading even when it is when called' do
21
+ load_features
22
+ FLIPPER_ENV[:static] = 'cherry'
23
+ RubyFlipper::Feature.find(:static_is_cherry).should_not be_active
24
+ end
25
+
26
+ it 'should be met when static is not cherry when called' do
27
+ FLIPPER_ENV[:static] = 'cherry'
28
+ load_features
29
+ RubyFlipper::Feature.find(:static_is_cherry).should be_active
30
+ end
31
+
32
+ end
33
+
34
+ context ':dynamic_is_floyd' do
35
+
36
+ it 'should not be met when dynamic is not floyd when called' do
37
+ load_features
38
+ RubyFlipper::Feature.find(:dynamic_is_floyd).should_not be_active
39
+ end
40
+
41
+ it 'should not be met when dynamic is not floyd when called even when it was when loaded' do
42
+ FLIPPER_ENV[:dynamic] = 'floyd'
43
+ load_features
44
+ FLIPPER_ENV[:dynamic] = 'sue'
45
+ RubyFlipper::Feature.find(:dynamic_is_floyd).should_not be_active
46
+ end
47
+
48
+ it 'should be met when dynamic is floyd when called' do
49
+ load_features
50
+ FLIPPER_ENV[:dynamic] = 'floyd'
51
+ RubyFlipper::Feature.find(:dynamic_is_floyd).should be_active
52
+ end
53
+
54
+ end
55
+
56
+ context ':combined_is_cherry_and_floyd' do
57
+
58
+ it 'should not be met when static is not cherry when loaded even when it is when called and dynamic is floyd' do
59
+ load_features
60
+ FLIPPER_ENV[:dynamic] = 'floyd'
61
+ RubyFlipper::Feature.find(:combined_is_cherry_and_floyd).should_not be_active
62
+ end
63
+
64
+ it 'should not be met when static is cherry but dynamic is not floyd when called even when it was when loaded' do
65
+ FLIPPER_ENV[:static] = 'cherry'
66
+ FLIPPER_ENV[:dynamic] = 'floyd'
67
+ load_features
68
+ FLIPPER_ENV[:dynamic] = 'sue'
69
+ RubyFlipper::Feature.find(:combined_is_cherry_and_floyd).should_not be_active
70
+ end
71
+
72
+ it 'should be met when static is cherry when loaded and dynamic is floyd when called even when static is not cherry when called and dynamic was not floyd when called' do
73
+ FLIPPER_ENV[:static] = 'cherry'
74
+ load_features
75
+ FLIPPER_ENV[:static] = 'philip'
76
+ FLIPPER_ENV[:dynamic] = 'floyd'
77
+ RubyFlipper::Feature.find(:combined_is_cherry_and_floyd).should be_active
78
+ end
79
+
80
+ end
81
+
82
+ context ':combined_is_lulu_first_then_gavin' do
83
+
84
+ it 'should not be met when changing is not lulu when loaded even when it is gavin when called' do
85
+ load_features
86
+ FLIPPER_ENV[:changing] = 'gavin'
87
+ RubyFlipper::Feature.find(:combined_is_lulu_first_then_gavin).should_not be_active
88
+ end
89
+
90
+ it 'should not be met when changing is lulu when loaded but it is not gavin when called' do
91
+ FLIPPER_ENV[:changing] = 'lulu'
92
+ load_features
93
+ RubyFlipper::Feature.find(:combined_is_lulu_first_then_gavin).should_not be_active
94
+ end
95
+
96
+ it 'should be met when changing is lulu when loaded gavin when called' do
97
+ FLIPPER_ENV[:changing] = 'lulu'
98
+ load_features
99
+ FLIPPER_ENV[:changing] = 'gavin'
100
+ RubyFlipper::Feature.find(:combined_is_lulu_first_then_gavin).should be_active
101
+ end
102
+
103
+ end
104
+
105
+ end
106
+
107
+ context 'features' do
108
+
109
+ context ':disabled' do
110
+
111
+ it 'should not be active' do
112
+ load_features
113
+ RubyFlipper::Feature.find(:disabled).should_not be_active
114
+ end
115
+
116
+ end
117
+
118
+ context ':floyd' do
119
+
120
+ it 'should not be active when dynamic is not floyd' do
121
+ load_features
122
+ RubyFlipper::Feature.find(:floyd).should_not be_active
123
+ end
124
+
125
+ it 'should be active when dynamic is floyd' do
126
+ load_features
127
+ FLIPPER_ENV[:dynamic] = 'floyd'
128
+ RubyFlipper::Feature.find(:floyd).should be_active
129
+ end
130
+
131
+ end
132
+
133
+ context ':philip' do
134
+
135
+ it 'should not be active when dynamic is not philip' do
136
+ load_features
137
+ RubyFlipper::Feature.find(:philip).should_not be_active
138
+ end
139
+
140
+ it 'should be active when dynamic is philip' do
141
+ load_features
142
+ FLIPPER_ENV[:dynamic] = 'philip'
143
+ RubyFlipper::Feature.find(:philip).should be_active
144
+ end
145
+
146
+ end
147
+
148
+ context ':patti' do
149
+
150
+ it 'should not be active when changing is not patti when loaded' do
151
+ load_features
152
+ FLIPPER_ENV[:dynamic] = 'floyd'
153
+ FLIPPER_ENV[:changing] = 'gavin'
154
+ RubyFlipper::Feature.find(:patti).should_not be_active
155
+ end
156
+
157
+ it 'should not be active when dynamic is not floyd when called' do
158
+ FLIPPER_ENV[:changing] = 'patti'
159
+ FLIPPER_ENV[:dynamic] = 'floyd'
160
+ load_features
161
+ FLIPPER_ENV[:dynamic] = 'sue'
162
+ FLIPPER_ENV[:changing] = 'gavin'
163
+ RubyFlipper::Feature.find(:patti).should_not be_active
164
+ end
165
+
166
+ it 'should not be active when changing is not gavin when called' do
167
+ FLIPPER_ENV[:changing] = 'patti'
168
+ FLIPPER_ENV[:changing] = 'gavin'
169
+ load_features
170
+ FLIPPER_ENV[:dynamic] = 'floyd'
171
+ FLIPPER_ENV[:changing] = 'sue'
172
+ RubyFlipper::Feature.find(:patti).should_not be_active
173
+ end
174
+
175
+ it 'should be active when changing is patti when loaded, dynamic is floyd when called and changing is gavin when called' do
176
+ FLIPPER_ENV[:changing] = 'patti'
177
+ load_features
178
+ FLIPPER_ENV[:dynamic] = 'floyd'
179
+ FLIPPER_ENV[:changing] = 'gavin'
180
+ RubyFlipper::Feature.find(:patti).should be_active
181
+ end
182
+
183
+ end
184
+
185
+ context ':sue' do
186
+
187
+ it 'should not be active when static is not cherry when loaded and dynamic is not floyd when called' do
188
+ FLIPPER_ENV[:dynamic] = 'floyd'
189
+ load_features
190
+ FLIPPER_ENV[:dynamic] = 'gavin'
191
+ FLIPPER_ENV[:changing] = 'sue'
192
+ RubyFlipper::Feature.find(:sue).should_not be_active
193
+ end
194
+
195
+ it 'should not be active when changing is not sue when called' do
196
+ FLIPPER_ENV[:static] = 'cherry'
197
+ load_features
198
+ FLIPPER_ENV[:dynamic] = 'floyd'
199
+ RubyFlipper::Feature.find(:sue).should_not be_active
200
+ end
201
+
202
+ it 'should be active when static is cherry when loaded and changing is sue when called' do
203
+ FLIPPER_ENV[:static] = 'cherry'
204
+ load_features
205
+ FLIPPER_ENV[:changing] = 'sue'
206
+ RubyFlipper::Feature.find(:sue).should be_active
207
+ end
208
+
209
+ it 'should be active when dynamic is floyd when called and changing is sue when called' do
210
+ load_features
211
+ FLIPPER_ENV[:dynamic] = 'floyd'
212
+ FLIPPER_ENV[:changing] = 'sue'
213
+ RubyFlipper::Feature.find(:sue).should be_active
214
+ end
215
+
216
+ end
217
+
218
+ end
219
+
220
+ end
221
+
222
+ end
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ Bundler.require(:default, :development)
3
+
4
+ FLIPPER_ENV = {}
5
+
6
+ Rspec.configure do |config|
7
+ config.mock_with :mocha
8
+ config.after(:each) do
9
+ RubyFlipper.reset
10
+ FLIPPER_ENV.clear
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_flipper
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Thomas Jachmann
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-12-13 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: ci_reporter
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 9
30
+ segments:
31
+ - 1
32
+ - 6
33
+ - 3
34
+ version: 1.6.3
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 13
46
+ segments:
47
+ - 2
48
+ - 0
49
+ - 1
50
+ version: 2.0.1
51
+ type: :development
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: mocha
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 47
62
+ segments:
63
+ - 0
64
+ - 9
65
+ - 10
66
+ version: 0.9.10
67
+ type: :development
68
+ version_requirements: *id003
69
+ description: Most flexible still least verbose feature flipper for ruby projects.
70
+ email:
71
+ - self@thomasjachmann.com
72
+ executables: []
73
+
74
+ extensions: []
75
+
76
+ extra_rdoc_files: []
77
+
78
+ files:
79
+ - .gitignore
80
+ - .rspec
81
+ - Gemfile
82
+ - Gemfile.lock
83
+ - README.rdoc
84
+ - Rakefile
85
+ - autotest/discover.rb
86
+ - lib/ruby_flipper.rb
87
+ - lib/ruby_flipper/condition_context.rb
88
+ - lib/ruby_flipper/dsl.rb
89
+ - lib/ruby_flipper/feature.rb
90
+ - lib/ruby_flipper/object_mixin.rb
91
+ - ruby_flipper.gemspec
92
+ - spec/fixtures/features.rb
93
+ - spec/ruby_flipper/condition_context_spec.rb
94
+ - spec/ruby_flipper/dsl_spec.rb
95
+ - spec/ruby_flipper/feature_spec.rb
96
+ - spec/ruby_flipper/object_mixin_spec.rb
97
+ - spec/ruby_flipper_spec.rb
98
+ - spec/spec_helper.rb
99
+ has_rdoc: true
100
+ homepage: https://github.com/blaulabs/ruby_flipper
101
+ licenses: []
102
+
103
+ post_install_message:
104
+ rdoc_options: []
105
+
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ hash: 3
114
+ segments:
115
+ - 0
116
+ version: "0"
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ hash: 3
123
+ segments:
124
+ - 0
125
+ version: "0"
126
+ requirements: []
127
+
128
+ rubyforge_project: ruby_flipper
129
+ rubygems_version: 1.3.7
130
+ signing_key:
131
+ specification_version: 3
132
+ summary: Make switching features on and off easy.
133
+ test_files:
134
+ - spec/fixtures/features.rb
135
+ - spec/ruby_flipper/condition_context_spec.rb
136
+ - spec/ruby_flipper/dsl_spec.rb
137
+ - spec/ruby_flipper/feature_spec.rb
138
+ - spec/ruby_flipper/object_mixin_spec.rb
139
+ - spec/ruby_flipper_spec.rb
140
+ - spec/spec_helper.rb