binflip 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.2@binflip --create --verbose
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem 'rake'
6
+ gem 'mocha'
7
+ gem 'pry'
8
+ gem 'redis', :require => false
9
+ gem 'rollout', :require => false
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
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs.push "lib"
5
+ t.test_files = FileList['test/*_test.rb']
6
+ t.verbose = true
7
+ end
8
+
9
+ task :default => :test
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