flip 0.0.1.alpha

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/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ Flip — flip your features
2
+ ================
3
+
4
+ [Learnable](https://learnable.com) uses feature flippers ([so does Flickr](http://code.flickr.com/blog/2009/12/02/flipping-out/)) as a tool to help achieve [continuous deployment](http://timothyfitz.wordpress.com/2009/02/10/continuous-deployment-at-imvu-doing-the-impossible-fifty-times-a-day/).
5
+
6
+ **Flip** gives us a declarative, layered mechanism to enable and disable features. There's a configurable system-wide default (`default: !Rails.env.production?` works nicely), plus three layers of strategies to determine status per-feature:
7
+
8
+ * The declared default, e.g. `feature :world_domination, default: true`,
9
+ * A database-backed strategy, for flipping features site-wide for all users.
10
+ * A cookie-backed strategy, for privately previewing features in your own browser only.
11
+
12
+ (Hint: that last one is a a killer feature..)
13
+
14
+ Install
15
+ -------
16
+
17
+ Note: the alpha version number indicates Flip is currently being extracted from its host application. **The process described here is currently fictional.** But it does have a happy ending.
18
+
19
+ **Rails 3.0 and 3.1+**
20
+
21
+ # Gemfile
22
+ gem "flip"
23
+
24
+ # Generate the model and migration
25
+ > rails g flip:install
26
+
27
+ # Run the migration
28
+ > rake db:migrate
29
+
30
+ # They lived happily ever after.
31
+
32
+
33
+ Declaring Features
34
+ ------------------
35
+
36
+ # This is the model class generated by rails g flip:install
37
+ class Feature < ActiveRecord::Base
38
+ include Flip::Declarable
39
+
40
+ # The recommended Flip strategy stack.
41
+ strategy Flip::CookieStrategy
42
+ strategy Flip::DatabaseStrategy
43
+ strategy Flip::DefaultStrategy
44
+ default false
45
+
46
+ # A basic feature declaration.
47
+ feature :shiny_things
48
+
49
+ # Override the system-wide default.
50
+ feature :world_domination, default: true
51
+
52
+ # Enabled half the time..? Sure, we can do that.
53
+ feature :flakey,
54
+ default: proc { rand(2).zero? }
55
+
56
+ # Provide a description, normally derived from the feature name.
57
+ feature :something,
58
+ default: true,
59
+ description: "Ability to purchase enrollments in courses",
60
+
61
+ end
62
+
63
+
64
+ Checking Features
65
+ -----------------
66
+
67
+ Feature status can be checked by any code using `on?` or using the dynamic predicate methods:
68
+
69
+ Flip.on? :world_domination # true
70
+ Flip.world_domination? # true
71
+
72
+ Flip.on? :shiny_things # false
73
+ Flip.shiny_things? # false
74
+
75
+ Within view and controller methods, the `FlipHelper` module provides a `feature?(key)` method:
76
+
77
+ <div>
78
+ <% if feature? :world_domination %>
79
+ <%= link_to "Dominate World", world_dominations_path %>
80
+ <% end %>
81
+ </div>
82
+
83
+
84
+ Feature Flipping Controllers
85
+ ----------------------------
86
+
87
+ The `Flip::ControllerFilters` module is mixed into the base `ApplicationController` class. The following controller will respond with 404 Page Not Found to all but the `index` action unless the :new_stuff feature is enabled:
88
+
89
+ class SampleController < ApplicationController
90
+
91
+ require_feature :something, :except => :index
92
+
93
+ def show
94
+ end
95
+
96
+ def index
97
+ end
98
+
99
+ end
100
+
101
+ Note that conditionally declared routes require a server restart to notice changes to feature flags, so they're not a good idea; database/cookie feature flipping will be ignored.
102
+
103
+
104
+ Command Center
105
+ --------------
106
+
107
+ A dashboard allows you to view the current state of the feature set, and flip any switchable strategies (database, cookie). *Screenshot coming&hellip;*
108
+
109
+
110
+ ----
111
+ Copyright © 2011 Paul Annesley and Learnable Pty Ltd, [MIT Licence](http://www.opensource.org/licenses/mit-license.php).
112
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ desc "Run all tests"
5
+ task :default => :spec
6
+
7
+ desc "Run specs"
8
+ task :spec do
9
+ system 'bundle exec rspec --color --format documentation spec/*_spec.rb'
10
+ end
data/flip.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "flip/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "flip"
7
+ s.version = Flip::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Paul Annesley"]
10
+ s.email = ["paul@annesley.cc"]
11
+ s.homepage = "https://github.com/pda/flip"
12
+ s.summary = %q{A feature flipper for Rails web applications.}
13
+ s.description = %q{Declarative API for specifying features, switchable in declaration, database and cookies.}
14
+
15
+ s.rubyforge_project = "flip"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency("activesupport", "~> 3.0")
23
+ s.add_dependency("i18n")
24
+
25
+ s.add_development_dependency("rspec", "~> 2.5")
26
+ s.add_development_dependency("rake")
27
+ end
data/lib/flip.rb ADDED
@@ -0,0 +1,24 @@
1
+ # ActiveSupport dependencies.
2
+ %w{
3
+ concern
4
+ inflector
5
+ core_ext/hash/reverse_merge
6
+ core_ext/object/blank
7
+ }.each { |name| require "active_support/#{name}" }
8
+
9
+ # Flip files.
10
+ %w{
11
+ abstract_strategy
12
+ controller_filters
13
+ cookie_strategy
14
+ database_strategy
15
+ declarable
16
+ declaration_strategy
17
+ definition
18
+ facade
19
+ feature_set
20
+ }.each { |name| require "flip/#{name}" }
21
+
22
+ module Flip
23
+ extend Facade
24
+ end
@@ -0,0 +1,26 @@
1
+ module Flip
2
+ class AbstractStrategy
3
+
4
+ def name
5
+ self.class.name.split("::").last.gsub(/Strategy$/, "").underscore
6
+ end
7
+
8
+ def description; ""; end
9
+
10
+ # Whether the strategy knows the on/off state of the switch.
11
+ def knows? definition; raise; end
12
+
13
+ # Given the state is known, whether it is on or off.
14
+ def on? definition; raise; end
15
+
16
+ # Whether the feature can be switched on and off at runtime.
17
+ # If true, the strategy must also respond to switch! and delete!
18
+ def switchable?
19
+ false
20
+ end
21
+
22
+ def switch! key, on; raise; end
23
+ def delete! key; raise; end
24
+
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ module Flip
2
+ module ControllerFilters
3
+
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+
8
+ def require_feature key, options = {}
9
+ before_filter options do
10
+ flip_feature_disabled key unless Flip.on? key
11
+ end
12
+ end
13
+
14
+ end
15
+
16
+ def flip_feature_disabled key
17
+ # TODO: handle this with a 404
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,56 @@
1
+ # Uses cookie to determine feature state.
2
+ module Flip
3
+ class CookieStrategy < AbstractStrategy
4
+
5
+ def description
6
+ "Uses cookies to apply only to your session."
7
+ end
8
+
9
+ def knows? definition
10
+ cookies.key? cookie_name(definition)
11
+ end
12
+
13
+ def on? definition
14
+ cookies[cookie_name(definition)] === "true"
15
+ end
16
+
17
+ def switchable?
18
+ true
19
+ end
20
+
21
+ def switch! key, on
22
+ cookies[cookie_name(key)] = on ? "true" : "false"
23
+ end
24
+
25
+ def delete! key
26
+ cookies.delete cookie_name(key)
27
+ end
28
+
29
+ def self.cookies= cookies
30
+ @@cookies = cookies
31
+ end
32
+
33
+ def cookie_name(definition)
34
+ definition = definition.key unless definition.is_a? Symbol
35
+ "flip_#{definition}"
36
+ end
37
+
38
+ private
39
+
40
+ def cookies
41
+ @@cookies || raise("Cookies not loaded")
42
+ end
43
+
44
+ # Include in ApplicationController to push cookies into CookieStrategy.
45
+ module Loader
46
+ extend ActiveSupport::Concern
47
+ included { around_filter :cookie_feature_strategy }
48
+ def cookie_feature_strategy
49
+ CookieStrategy.cookies = cookies
50
+ yield
51
+ CookieStrategy.cookies = nil
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,40 @@
1
+ # Database backed system-wide
2
+ module Flip
3
+ class DatabaseStrategy < AbstractStrategy
4
+
5
+ def initialize(model_klass)
6
+ @klass = model_klass
7
+ end
8
+
9
+ def description
10
+ "Database backed, applies to all users."
11
+ end
12
+
13
+ def knows? definition
14
+ !!feature(definition)
15
+ end
16
+
17
+ def on? definition
18
+ feature(definition).on?
19
+ end
20
+
21
+ def switchable?
22
+ true
23
+ end
24
+
25
+ def switch! key, on
26
+ @klass.find_or_initialize_by_key(key).update_attributes! on: on
27
+ end
28
+
29
+ def delete! key
30
+ @klass.find_by_key(key).try(:destroy)
31
+ end
32
+
33
+ private
34
+
35
+ def feature(definition)
36
+ @klass.find_by_key definition.key
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,20 @@
1
+ module Flip
2
+ module Declarable
3
+
4
+ # Adds a new feature definition, creates predicate method.
5
+ def feature(key, options = {})
6
+ FeatureSet.instance << Flip::Definition.new(key, options)
7
+ end
8
+
9
+ # Adds a strategy for determining feature status.
10
+ def strategy(strategy)
11
+ FeatureSet.instance.add_strategy strategy
12
+ end
13
+
14
+ # The default response, boolean or a Proc to be called.
15
+ def default(default)
16
+ FeatureSet.instance.default = default
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # Uses :default option passed to feature declaration.
2
+ # May be boolean or a Proc to be passed the definition.
3
+ module Flip
4
+ class DeclarationStrategy < AbstractStrategy
5
+
6
+ def description
7
+ "The default status declared with the feature."
8
+ end
9
+
10
+ def knows? definition
11
+ definition.options.key? :default
12
+ end
13
+
14
+ def on? definition
15
+ default = definition.options[:default]
16
+ default.is_a?(Proc) ? default.call(definition) : default
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ module Flip
2
+ class Definition
3
+
4
+ attr_accessor :key
5
+ attr_accessor :options
6
+
7
+ def initialize(key, options = {})
8
+ @key = key
9
+ @options = options.reverse_merge \
10
+ description: key.to_s.humanize + "."
11
+ end
12
+
13
+ alias :name :key
14
+ alias :to_s :key
15
+
16
+ def description
17
+ options[:description]
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ module Flip
2
+ module Facade
3
+
4
+ def on?(feature)
5
+ FeatureSet.instance.on? feature
6
+ end
7
+
8
+ def reset
9
+ FeatureSet.reset
10
+ end
11
+
12
+ def method_missing(method, *parameters)
13
+ super unless method =~ %r{^(.*)\?$}
14
+ FeatureSet.instance.on? $1.to_sym
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,57 @@
1
+ module Flip
2
+ class FeatureSet
3
+
4
+ def self.instance
5
+ @instance ||= self.new
6
+ end
7
+
8
+ def self.reset
9
+ remove_instance_variable :@instance
10
+ end
11
+
12
+ # Sets the default for definitions which fall through the strategies.
13
+ # Accepts boolean or a Proc to be called.
14
+ attr_writer :default
15
+
16
+ def initialize
17
+ @definitions = Hash.new { |_, k| raise "No feature declared with key #{k.inspect}" }
18
+ @strategies = Hash.new { |_, k| raise "No strategy named #{k}" }
19
+ @default = false
20
+ end
21
+
22
+ # Whether the given feature is switched on.
23
+ def on? key
24
+ d = @definitions[key]
25
+ @strategies.each_value { |s| return s.on?(d) if s.knows?(d) }
26
+ default_for d
27
+ end
28
+
29
+ # Adds a feature definition to the set.
30
+ def << definition
31
+ @definitions[definition.key] = definition
32
+ end
33
+
34
+ # Adds a strategy for determing feature status.
35
+ def add_strategy(strategy)
36
+ strategy = strategy.new if strategy.is_a? Class
37
+ @strategies[strategy.name] = strategy
38
+ end
39
+
40
+ def strategy(klass)
41
+ @strategies[klass]
42
+ end
43
+
44
+ def default_for(definition)
45
+ @default.is_a?(Proc) ? @default.call(definition) : @default
46
+ end
47
+
48
+ def definitions
49
+ @definitions.values
50
+ end
51
+
52
+ def strategies
53
+ @strategies.values
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,3 @@
1
+ module Flip
2
+ VERSION = "0.0.1.alpha"
3
+ end
@@ -0,0 +1,11 @@
1
+ require "spec_helper"
2
+
3
+ # Perhaps this is silly, but it provides some
4
+ # coverage to an important base class.
5
+ describe Flip::AbstractStrategy do
6
+
7
+ its(:name) { should == "abstract" }
8
+ its(:description) { should == "" }
9
+ it { should_not be_switchable }
10
+
11
+ end
@@ -0,0 +1,27 @@
1
+ require "spec_helper"
2
+
3
+ class ControllerWithFlipFilters
4
+ include Flip::ControllerFilters
5
+ end
6
+
7
+ describe ControllerWithFlipFilters do
8
+
9
+ describe ".require_feature" do
10
+
11
+ it "adds before_filter without options" do
12
+ ControllerWithFlipFilters.tap do |klass|
13
+ klass.should_receive(:before_filter).with({})
14
+ klass.send(:require_feature, :testable)
15
+ end
16
+ end
17
+
18
+ it "adds before_filter with options" do
19
+ ControllerWithFlipFilters.tap do |klass|
20
+ klass.should_receive(:before_filter).with({ only: [ :show ] })
21
+ klass.send(:require_feature, :testable, only: [ :show ])
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,105 @@
1
+ require "spec_helper"
2
+
3
+ class ControllerWithoutCookieStrategy; end
4
+ class ControllerWithCookieStrategy
5
+ def self.around_filter(_); end
6
+ def cookies; []; end
7
+ include Flip::CookieStrategy::Loader
8
+ end
9
+
10
+ describe Flip::CookieStrategy do
11
+
12
+ let(:cookies) do
13
+ { strategy.cookie_name(:one) => "true",
14
+ strategy.cookie_name(:two) => "false" }
15
+ end
16
+ let(:strategy) do
17
+ Flip::CookieStrategy.new.tap do |s|
18
+ s.stub(:cookies) { cookies }
19
+ end
20
+ end
21
+
22
+ its(:description) { should be_present }
23
+ it { should be_switchable }
24
+
25
+ describe "cookie interrogration" do
26
+ context "enabled feature" do
27
+ specify "#knows? is true" do
28
+ strategy.knows?(:one).should be_true
29
+ end
30
+ specify "#on? is true" do
31
+ strategy.on?(:one).should be_true
32
+ end
33
+ end
34
+ context "disabled feature" do
35
+ specify "#knows? is true" do
36
+ strategy.knows?(:two).should be_true
37
+ end
38
+ specify "#on? is false" do
39
+ strategy.on?(:two).should be_false
40
+ end
41
+ end
42
+ context "feature with no cookie present" do
43
+ specify "#knows? is false" do
44
+ strategy.knows?(:three).should be_false
45
+ end
46
+ specify "#on? is false" do
47
+ strategy.on?(:three).should be_false
48
+ end
49
+ end
50
+ end
51
+
52
+ describe "cookie manipulation" do
53
+ it "can switch known features on" do
54
+ strategy.switch! :one, true
55
+ strategy.on?(:one).should be_true
56
+ end
57
+ it "can switch unknown features on" do
58
+ strategy.switch! :three, true
59
+ strategy.on?(:three).should be_true
60
+ end
61
+ it "can switch features off" do
62
+ strategy.switch! :two, false
63
+ strategy.on?(:two).should be_false
64
+ end
65
+ it "can delete knowledge of a feature" do
66
+ strategy.delete! :one
67
+ strategy.on?(:one).should be_false
68
+ strategy.knows?(:one).should be_false
69
+ end
70
+ end
71
+
72
+ end
73
+
74
+ describe Flip::CookieStrategy::Loader do
75
+
76
+ it "adds around_filter when included in controller" do
77
+ ControllerWithoutCookieStrategy.tap do |klass|
78
+ klass.should_receive(:around_filter).with(:cookie_feature_strategy)
79
+ klass.send :include, Flip::CookieStrategy::Loader
80
+ end
81
+ end
82
+
83
+ describe "#cookie_feature_strategy as around_filter" do
84
+
85
+ let(:strategy) { Flip::CookieStrategy.new }
86
+ let(:controller) { ControllerWithCookieStrategy.new }
87
+
88
+ it "yields to block" do
89
+ run = false
90
+ ControllerWithCookieStrategy.new.cookie_feature_strategy { run = true }
91
+ run.should be_true
92
+ end
93
+
94
+ it "passes controller cookies to Flip::CookieStrategy" do
95
+ controller.should_receive(:cookies).and_return(strategy.cookie_name(:test) => "true")
96
+ results = []
97
+ controller.cookie_feature_strategy {
98
+ results << strategy.on?(:test)
99
+ results << strategy.on?(:different)
100
+ }
101
+ results.should == [ true, false ]
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,66 @@
1
+ require "spec_helper"
2
+
3
+ describe Flip::DatabaseStrategy do
4
+
5
+ let(:definition) { double("definition").tap{ |d| d.stub(:key) { :one } } }
6
+ let(:strategy) { Flip::DatabaseStrategy.new(model_klass) }
7
+ let(:model_klass) do
8
+ Class.new do
9
+ extend Flip::Declarable
10
+ feature :one
11
+ feature :two, description: "Second one."
12
+ feature :three, default: true
13
+ end
14
+ end
15
+ let(:enabled_record) { model_klass.new.tap { |m| m.stub(:on?) { true } } }
16
+ let(:disabled_record) { model_klass.new.tap { |m| m.stub(:on?) { false } } }
17
+
18
+ subject { strategy }
19
+
20
+ its(:switchable?) { should be_true }
21
+ its(:description) { should be_present }
22
+
23
+ describe "#knows?" do
24
+ it "does not know features that cannot be found" do
25
+ model_klass.stub(:find_by_key) { nil }
26
+ strategy.knows?(definition).should be_false
27
+ end
28
+ it "knows features that can be found" do
29
+ model_klass.stub(:find_by_key) { disabled_record }
30
+ strategy.knows?(definition).should be_true
31
+ end
32
+ end
33
+
34
+ describe "#on?" do
35
+ it "is true for an enabled record from the database" do
36
+ model_klass.stub(:find_by_key) { enabled_record }
37
+ strategy.on?(definition).should be_true
38
+ end
39
+ it "is false for a disabled record from the database" do
40
+ model_klass.stub(:find_by_key) { disabled_record }
41
+ strategy.on?(definition).should be_false
42
+ end
43
+ end
44
+
45
+ describe "#switch!" do
46
+ it "can switch a feature on" do
47
+ model_klass.should_receive(:find_or_initialize_by_key).with(:one).and_return(disabled_record)
48
+ disabled_record.should_receive(:update_attributes!).with(on: true)
49
+ strategy.switch! :one, true
50
+ end
51
+ it "can switch a feature off" do
52
+ model_klass.should_receive(:find_or_initialize_by_key).with(:one).and_return(enabled_record)
53
+ enabled_record.should_receive(:update_attributes!).with(on: false)
54
+ strategy.switch! :one, false
55
+ end
56
+ end
57
+
58
+ describe "#delete!" do
59
+ it "can delete a feature record" do
60
+ model_klass.should_receive(:find_by_key).with(:one).and_return(enabled_record)
61
+ enabled_record.should_receive(:try).with(:destroy)
62
+ strategy.delete! :one
63
+ end
64
+ end
65
+
66
+ end
@@ -0,0 +1,31 @@
1
+ require "spec_helper"
2
+
3
+ class TestableFlipModel
4
+ extend Flip::Declarable
5
+
6
+ strategy Flip::DeclarationStrategy
7
+ default false
8
+
9
+ feature :one
10
+ feature :two, description: "Second one."
11
+ feature :three, default: true
12
+ end
13
+
14
+ describe Flip::Declarable do
15
+
16
+ let(:model_class) { TestableFlipModel }
17
+ subject { Flip::FeatureSet.instance }
18
+
19
+ describe "the .on? class method" do
20
+ context "with default set to false" do
21
+ it { should_not be_on(:one) }
22
+ it { should be_on(:three) }
23
+ end
24
+ context "with default set to true" do
25
+ before(:all) { model_class.send(:default, true) }
26
+ it { should be_on(:one) }
27
+ it { should be_on(:three) }
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+
3
+ describe Flip::DeclarationStrategy do
4
+
5
+ def definition(default)
6
+ Flip::Definition.new :feature, default: default
7
+ end
8
+
9
+ describe "#knows?" do
10
+ specify "definition without default should be false" do
11
+ subject.knows?(Flip::Definition.new :feature).should be_false
12
+ end
13
+ specify " should be true" do
14
+ subject.knows?(definition(true)).should be_true
15
+ end
16
+ end
17
+
18
+ describe "#on? for Flip::Definition with default of" do
19
+ specify "true" do
20
+ subject.on?(definition(true)).should be_true
21
+ end
22
+ specify "false" do
23
+ subject.on?(definition(false)).should be_false
24
+ end
25
+ specify "proc returning true" do
26
+ subject.on?(definition(proc { true })).should be_true
27
+ end
28
+ specify "proc returning false" do
29
+ subject.on?(definition(proc { false })).should be_false
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,19 @@
1
+ require "spec_helper"
2
+
3
+ describe Flip::Definition do
4
+
5
+ subject { Flip::Definition.new :the_key, description: "The description" }
6
+
7
+ [:key, :name, :to_s].each do |method|
8
+ its(method) { should == :the_key }
9
+ end
10
+
11
+ its(:description) { should == "The description" }
12
+ its(:options) { should == { description: "The description" } }
13
+
14
+ context "without description specified" do
15
+ subject { Flip::Definition.new :the_key }
16
+ its(:description) { should == "The key." }
17
+ end
18
+
19
+ end
@@ -0,0 +1,64 @@
1
+ require "spec_helper"
2
+
3
+ class NullStrategy < Flip::AbstractStrategy
4
+ def knows?(d); false; end
5
+ end
6
+
7
+ class TrueStrategy < Flip::AbstractStrategy
8
+ def knows?(d); true; end
9
+ def on?(d); true; end
10
+ end
11
+
12
+ describe Flip::FeatureSet do
13
+
14
+ let :feature_set_with_null_strategy do
15
+ Flip::FeatureSet.new.tap do |s|
16
+ s << Flip::Definition.new(:feature)
17
+ s.add_strategy NullStrategy
18
+ end
19
+ end
20
+
21
+ let :feature_set_with_null_then_true_strategies do
22
+ feature_set_with_null_strategy.tap do |s|
23
+ s.add_strategy TrueStrategy
24
+ end
25
+ end
26
+
27
+ describe ".instance" do
28
+ it "returns a singleton instance" do
29
+ Flip::FeatureSet.instance.should equal(Flip::FeatureSet.instance)
30
+ end
31
+ it "can be reset" do
32
+ instance_before_reset = Flip::FeatureSet.instance
33
+ Flip::FeatureSet.reset
34
+ Flip::FeatureSet.instance.should_not equal(instance_before_reset)
35
+ end
36
+ end
37
+
38
+ describe "#default= and #on? with null strategy" do
39
+ subject { feature_set_with_null_strategy }
40
+ it "defaults to false" do
41
+ subject.on?(:feature).should be_false
42
+ end
43
+ it "can default to true" do
44
+ subject.default = true
45
+ subject.on?(:feature).should be_true
46
+ end
47
+ it "accepts a proc returning true" do
48
+ subject.default = proc { true }
49
+ subject.on?(:feature).should be_true
50
+ end
51
+ it "accepts a proc returning false" do
52
+ subject.default = proc { false }
53
+ subject.on?(:feature).should be_false
54
+ end
55
+ end
56
+
57
+ describe "feature set with null strategy then always-true strategy" do
58
+ subject { feature_set_with_null_then_true_strategies }
59
+ it "returns true due to second strategy" do
60
+ subject.on?(:feature).should be_true
61
+ end
62
+ end
63
+
64
+ end
data/spec/flip_spec.rb ADDED
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+
3
+ describe Flip do
4
+
5
+ before(:all) do
6
+ Class.new do
7
+ extend Flip::Declarable
8
+ strategy Flip::DeclarationStrategy
9
+ default false
10
+ feature :one, default: true
11
+ feature :two, default: false
12
+ end
13
+ end
14
+
15
+ after(:all) do
16
+ Flip.reset
17
+ end
18
+
19
+ describe ".on?" do
20
+ it "returns true for enabled features" do
21
+ Flip.on?(:one).should be_true
22
+ end
23
+ it "returns false for disabled features" do
24
+ Flip.on?(:two).should be_false
25
+ end
26
+ end
27
+
28
+ describe "dynamic predicate methods" do
29
+ its(:one?) { should be_true }
30
+ its(:two?) { should be_false }
31
+ end
32
+
33
+ end
@@ -0,0 +1 @@
1
+ require "flip"
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flip
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.alpha
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Paul Annesley
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-05-24 00:00:00.000000000 +10:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ requirement: &2153418240 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *2153418240
26
+ - !ruby/object:Gem::Dependency
27
+ name: i18n
28
+ requirement: &2153417820 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *2153417820
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec
39
+ requirement: &2153417280 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: '2.5'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *2153417280
48
+ - !ruby/object:Gem::Dependency
49
+ name: rake
50
+ requirement: &2153416860 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *2153416860
59
+ description: Declarative API for specifying features, switchable in declaration, database
60
+ and cookies.
61
+ email:
62
+ - paul@annesley.cc
63
+ executables: []
64
+ extensions: []
65
+ extra_rdoc_files: []
66
+ files:
67
+ - .gitignore
68
+ - Gemfile
69
+ - README.md
70
+ - Rakefile
71
+ - flip.gemspec
72
+ - lib/flip.rb
73
+ - lib/flip/abstract_strategy.rb
74
+ - lib/flip/controller_filters.rb
75
+ - lib/flip/cookie_strategy.rb
76
+ - lib/flip/database_strategy.rb
77
+ - lib/flip/declarable.rb
78
+ - lib/flip/declaration_strategy.rb
79
+ - lib/flip/definition.rb
80
+ - lib/flip/facade.rb
81
+ - lib/flip/feature_set.rb
82
+ - lib/flip/version.rb
83
+ - spec/abstract_strategy_spec.rb
84
+ - spec/controller_filters_spec.rb
85
+ - spec/cookie_strategy_spec.rb
86
+ - spec/database_strategy_spec.rb
87
+ - spec/declarable_spec.rb
88
+ - spec/declaration_strategy_spec.rb
89
+ - spec/definition_spec.rb
90
+ - spec/feature_set_spec.rb
91
+ - spec/flip_spec.rb
92
+ - spec/spec_helper.rb
93
+ has_rdoc: true
94
+ homepage: https://github.com/pda/flip
95
+ licenses: []
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ! '>'
110
+ - !ruby/object:Gem::Version
111
+ version: 1.3.1
112
+ requirements: []
113
+ rubyforge_project: flip
114
+ rubygems_version: 1.5.2
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: A feature flipper for Rails web applications.
118
+ test_files:
119
+ - spec/abstract_strategy_spec.rb
120
+ - spec/controller_filters_spec.rb
121
+ - spec/cookie_strategy_spec.rb
122
+ - spec/database_strategy_spec.rb
123
+ - spec/declarable_spec.rb
124
+ - spec/declaration_strategy_spec.rb
125
+ - spec/definition_spec.rb
126
+ - spec/feature_set_spec.rb
127
+ - spec/flip_spec.rb
128
+ - spec/spec_helper.rb