binflip 0.0.1
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/.rvmrc +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +32 -0
- data/README.md +95 -0
- data/Rakefile +9 -0
- data/binflip.gemspec +17 -0
- data/lib/binflip/cucumber.rb +56 -0
- data/lib/binflip/rails/active_support.rb +53 -0
- data/lib/binflip/rails/tasks/db.rake +29 -0
- data/lib/binflip/rails.rb +1 -0
- data/lib/binflip.rb +40 -0
- data/lib/core_ext/rake.rb +14 -0
- data/test/binflip_test.rb +64 -0
- metadata +61 -0
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 1.9.2@binflip --create --verbose
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
binflip (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
coderay (1.0.5)
|
10
|
+
metaclass (0.0.1)
|
11
|
+
method_source (0.7.1)
|
12
|
+
mocha (0.11.0)
|
13
|
+
metaclass (~> 0.0.1)
|
14
|
+
pry (0.9.8.2)
|
15
|
+
coderay (~> 1.0.5)
|
16
|
+
method_source (~> 0.7)
|
17
|
+
slop (>= 2.4.4, < 3)
|
18
|
+
rake (0.9.2.2)
|
19
|
+
redis (2.2.2)
|
20
|
+
rollout (1.1.0)
|
21
|
+
slop (2.4.4)
|
22
|
+
|
23
|
+
PLATFORMS
|
24
|
+
ruby
|
25
|
+
|
26
|
+
DEPENDENCIES
|
27
|
+
binflip!
|
28
|
+
mocha
|
29
|
+
pry
|
30
|
+
rake
|
31
|
+
redis
|
32
|
+
rollout
|
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
Binflip
|
2
|
+
=======
|
3
|
+
|
4
|
+
Simple environmental feature toggling (that works well with Rollout)
|
5
|
+
|
6
|
+
|
7
|
+
Description
|
8
|
+
===========
|
9
|
+
|
10
|
+
Keeping true to twelve-factor application principles <http://12factor.net>, this simple library uses environment variables, with a straightforward naming convention, to specify feature toggles.
|
11
|
+
|
12
|
+
Rollout Compatibility
|
13
|
+
======================
|
14
|
+
|
15
|
+
If Rollout <https://github.com/jamesgolick/rollout> is present, it will delegate all methods, adding to `active?` a preliminary test to see if the environment toggle is on before checking rollout.
|
16
|
+
|
17
|
+
Rationale
|
18
|
+
=========
|
19
|
+
|
20
|
+
When using a continuous delivery process, it is important to try to get all code integrated into the mainline as soon as possible. Feature toggles are favored over feature branches (<http://www.infoq.com/interviews/jez-humble-martin-fowler-cd>, <http://martinfowler.com/bliki/FeatureToggle.html>, <http://fournines.wordpress.com/2011/11/20/feature-branches-vs-feature-toggles/>, and <http://blog.jayfields.com/2010/10/experience-report-feature-toggle-over.html>) during the dev process (to guard against incomplete features being deployed to production).
|
21
|
+
|
22
|
+
Twelve-factor tells us to keep app configuration setting in environment variables. Hence this libaray adds a tiny bit of convention on how feature toggle env vars are spelled.
|
23
|
+
|
24
|
+
As the ultimate acceptance of a feature is the market response, this library has been designed to work with rollout.
|
25
|
+
|
26
|
+
Installation
|
27
|
+
============
|
28
|
+
|
29
|
+
Add this line to your application's Gemfile:
|
30
|
+
|
31
|
+
gem 'binflip'
|
32
|
+
|
33
|
+
And then execute:
|
34
|
+
|
35
|
+
$ bundle
|
36
|
+
|
37
|
+
Or install it yourself as:
|
38
|
+
|
39
|
+
$ gem install binflip
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
Usage
|
44
|
+
=====
|
45
|
+
|
46
|
+
Set an environment variable following a FEATURE_[name] pattern:
|
47
|
+
|
48
|
+
Procfile
|
49
|
+
|
50
|
+
FEATURE_CHAT = 1
|
51
|
+
FEATURE_UPLOAD_VIDEO = 1
|
52
|
+
|
53
|
+
(Or set the ENV vars manually)
|
54
|
+
|
55
|
+
Then in application setup / initialization:
|
56
|
+
|
57
|
+
$toggle = Binflip.new
|
58
|
+
|
59
|
+
|
60
|
+
Check for toggles, using just the [name]:
|
61
|
+
|
62
|
+
$toggle.active?(:chat)
|
63
|
+
$toggle.active?(:upload_video)
|
64
|
+
|
65
|
+
NOTE: Absence of feature toggle env variable means the feature is not active.
|
66
|
+
|
67
|
+
Usage with Rollout
|
68
|
+
==================
|
69
|
+
|
70
|
+
Procfile
|
71
|
+
|
72
|
+
FEATURE_CHAT = 1
|
73
|
+
FEATURE_UPLOAD_VIDEO = 1
|
74
|
+
|
75
|
+
(Or set the ENV vars manually)
|
76
|
+
|
77
|
+
Then in application setup / initialization:
|
78
|
+
|
79
|
+
$redis = Redis.new
|
80
|
+
$toggle = Binflip.new($redis)
|
81
|
+
|
82
|
+
|
83
|
+
Check for toggles:
|
84
|
+
|
85
|
+
$toggle.active?(:chat, @user)
|
86
|
+
$toggle.active?(:upload_video, @user)
|
87
|
+
|
88
|
+
If Rollout is present, Binflip delegates all methods to it (such as `activate_user`).
|
89
|
+
|
90
|
+
License
|
91
|
+
=======
|
92
|
+
|
93
|
+
Copyright (c) 2012 Brian Kaney
|
94
|
+
|
95
|
+
MIT License
|
data/Rakefile
ADDED
data/binflip.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "binflip"
|
5
|
+
s.version = '0.0.1'
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.authors = ["Brian Kaney"]
|
8
|
+
s.email = ["brian@vermonster.com"]
|
9
|
+
s.homepage = ""
|
10
|
+
s.summary = %q{Kanban Flipper}
|
11
|
+
s.description = %q{Environment-based feature flipper. Compatiable with rollout.}
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
s.require_paths = ["lib", "tools"]
|
17
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'binflip'
|
2
|
+
|
3
|
+
module Binflip
|
4
|
+
module Cucumber
|
5
|
+
|
6
|
+
def self.toggle_bin!(current_bin, scenario)
|
7
|
+
# Toggle bin
|
8
|
+
Binflip.module_eval <<-RUBY
|
9
|
+
def self.current_bin
|
10
|
+
return '#{current_bin}'
|
11
|
+
end
|
12
|
+
RUBY
|
13
|
+
|
14
|
+
# Toggle RAILS_ENV
|
15
|
+
ENV["RAILS_ENV"] = Rails.env = "cucumber_#{current_bin}"
|
16
|
+
ActiveRecord::Base.configurations = Rails.application.config.database_configuration
|
17
|
+
ActiveRecord::Base.establish_connection
|
18
|
+
|
19
|
+
|
20
|
+
# Reload routes
|
21
|
+
reload_routes_if_new_bin(scenario)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.current_tags(scenario)
|
25
|
+
[ scenario.instance_variable_get(:@tags).tag_names +
|
26
|
+
scenario.instance_variable_get(:@feature).instance_variable_get(:@tags).tag_names
|
27
|
+
].flatten
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.rails_app
|
31
|
+
raise NotImplementedError
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.reload_routes_if_new_bin(scenario)
|
35
|
+
if current_tags(scenario) != $tags
|
36
|
+
rails_app.reload_routes!
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
Before do |scenario|
|
44
|
+
tags = Binflip::Cucumber.current_tags(scenario)
|
45
|
+
|
46
|
+
if tags.size <= 1
|
47
|
+
bin = tags.first.try(:gsub, '@', '')
|
48
|
+
bin = bin.nil? ? Binflip::DEPLOYED_BIN : bin
|
49
|
+
Binflip::Cucumber.toggle_bin!(bin, scenario)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
After do |scenario|
|
55
|
+
$tags = Binflip::Cucumber.current_tags(scenario)
|
56
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
# This patch all adds a #skip? method on the migration classes. It is
|
3
|
+
# mainly intended to be used with feature toggles.
|
4
|
+
#
|
5
|
+
# class FauxMigration < ActiveRecord::Migration
|
6
|
+
#
|
7
|
+
# def skip?
|
8
|
+
# Feature.active?('178_feature_x')
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# def up
|
12
|
+
# add_column :patients, :foob, :string
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# def down
|
16
|
+
# remove_column :patients, :foob
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
class Migration
|
21
|
+
|
22
|
+
def skip?
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
def migrate_with_skip(direction)
|
27
|
+
if skip?
|
28
|
+
announce "Skipping Migration, skip? returned true."
|
29
|
+
else
|
30
|
+
migrate_without_skip(direction)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
alias_method :migrate_without_skip, :migrate
|
34
|
+
alias_method :migrate, :migrate_with_skip
|
35
|
+
end
|
36
|
+
|
37
|
+
class MigrationProxy
|
38
|
+
delegate :skip?, :to => :migration
|
39
|
+
end
|
40
|
+
|
41
|
+
class Migrator
|
42
|
+
def record_version_state_after_migrating_with_skip(version)
|
43
|
+
current = migrations.detect { |m| m.version == version } # This is actually the proxy for the migration
|
44
|
+
unless current.skip?
|
45
|
+
record_version_state_after_migrating_without_skip(version)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
alias_method :record_version_state_after_migrating_without_skip, :record_version_state_after_migrating
|
50
|
+
alias_method :record_version_state_after_migrating, :record_version_state_after_migrating_with_skip
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'binflip'
|
2
|
+
|
3
|
+
namespace :db do
|
4
|
+
namespace :test do
|
5
|
+
task :prepare_with_kanban do
|
6
|
+
Binflip::BINS.each do |bin|
|
7
|
+
# Toggle bin
|
8
|
+
|
9
|
+
ENV['RAILS_ENV'] = Rails.env = "cucumber_#{bin}"
|
10
|
+
puts "=== prepare for #{Rails.env}"
|
11
|
+
|
12
|
+
Binflip.module_eval <<-RUBY
|
13
|
+
def self.current_bin
|
14
|
+
return '#{bin}'
|
15
|
+
end
|
16
|
+
RUBY
|
17
|
+
|
18
|
+
Rake::Task['db:drop'].execute
|
19
|
+
Rake::Task['db:create'].execute
|
20
|
+
Rake::Task['db:migrate'].execute
|
21
|
+
Rake::Task['db:schema:dump'].execute
|
22
|
+
Rake::Task['db:test:prepare_without_kanban'].execute
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
alias_task('db:test:prepare_without_kanban', 'db:test:prepare')
|
29
|
+
alias_task('db:test:prepare', 'db:test:prepare_with_kanban')
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'binflip/rails/active_support' #if defined?(ActiveSupport::Base)
|
data/lib/binflip.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
class Binflip
|
4
|
+
|
5
|
+
def self.rollout?
|
6
|
+
defined?(Rollout) == 'constant'
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(redis=nil)
|
10
|
+
if Binflip.rollout?
|
11
|
+
@source = SimpleDelegator.new(Rollout.new(redis))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def active?(feature, user=nil)
|
16
|
+
if environment_active?(feature) && @source
|
17
|
+
@source.active?(feature, user)
|
18
|
+
else
|
19
|
+
environment_active?(feature)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(meth, *args, &block)
|
24
|
+
if @source
|
25
|
+
@source.send(meth, *args, &block)
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def environment_key(feature)
|
34
|
+
("FEATURE_%s" % feature).upcase
|
35
|
+
end
|
36
|
+
|
37
|
+
def environment_active?(feature)
|
38
|
+
ENV[environment_key(feature)] == '1'
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# This is an alias method, e.g.:
|
2
|
+
#
|
3
|
+
# alias_task(:alias_task, :original_task)
|
4
|
+
#
|
5
|
+
def alias_task(name, old_name)
|
6
|
+
t = Rake::Task[old_name]
|
7
|
+
desc t.full_comment if t.full_comment
|
8
|
+
task name, *t.arg_names do |_, args|
|
9
|
+
# values_at is broken on Rake::TaskArguments
|
10
|
+
args = t.arg_names.map { |a| args[a] }
|
11
|
+
t.invoke(args)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'minitest/spec'
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'mocha'
|
6
|
+
require 'pry'
|
7
|
+
require 'rollout'
|
8
|
+
require 'redis'
|
9
|
+
require 'binflip'
|
10
|
+
|
11
|
+
describe Binflip do
|
12
|
+
|
13
|
+
describe "without rollout" do
|
14
|
+
|
15
|
+
before do
|
16
|
+
Binflip.stubs(:rollout?).returns(false)
|
17
|
+
|
18
|
+
@binflip = Binflip.new
|
19
|
+
ENV['FEATURE_X'] = "1"
|
20
|
+
ENV['FEATURE_Y'] = "0"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should be active" do
|
24
|
+
@binflip.active?(:x).must_equal true
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should not be active" do
|
28
|
+
@binflip.active?(:y).must_equal false
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should not be active for unspecified" do
|
32
|
+
@binflip.active?(:z).must_equal false
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "with Rollout" do
|
36
|
+
before do
|
37
|
+
Binflip.expects(:rollout?).returns(true)
|
38
|
+
Redis.new.flushdb
|
39
|
+
|
40
|
+
@redis = Redis.new
|
41
|
+
@binflip = Binflip.new(@redis)
|
42
|
+
ENV['FEATURE_X'] = "1"
|
43
|
+
ENV['FEATURE_Y'] = "0"
|
44
|
+
end
|
45
|
+
|
46
|
+
it "must be true if the environment is true and rollout is true" do
|
47
|
+
@binflip.activate_user(:x, stub(:id => 51))
|
48
|
+
@binflip.active?(:x, stub(:id => 51)).must_equal true
|
49
|
+
end
|
50
|
+
|
51
|
+
it "must be true if the environment is true and rollout is false" do
|
52
|
+
@binflip.active?(:x, stub(:id => 51)).must_equal false
|
53
|
+
end
|
54
|
+
|
55
|
+
it "must be false if the environment is false" do
|
56
|
+
@binflip.activate_user(:x, stub(:id => 51))
|
57
|
+
@binflip.active?(:y, stub(:id => 51)).must_equal false
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: binflip
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Brian Kaney
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-20 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Environment-based feature flipper. Compatiable with rollout.
|
15
|
+
email:
|
16
|
+
- brian@vermonster.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .rvmrc
|
22
|
+
- .travis.yml
|
23
|
+
- Gemfile
|
24
|
+
- Gemfile.lock
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- binflip.gemspec
|
28
|
+
- lib/binflip.rb
|
29
|
+
- lib/binflip/cucumber.rb
|
30
|
+
- lib/binflip/rails.rb
|
31
|
+
- lib/binflip/rails/active_support.rb
|
32
|
+
- lib/binflip/rails/tasks/db.rake
|
33
|
+
- lib/core_ext/rake.rb
|
34
|
+
- test/binflip_test.rb
|
35
|
+
homepage: ''
|
36
|
+
licenses: []
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
- tools
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
requirements: []
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 1.8.15
|
57
|
+
signing_key:
|
58
|
+
specification_version: 3
|
59
|
+
summary: Kanban Flipper
|
60
|
+
test_files:
|
61
|
+
- test/binflip_test.rb
|