binflip 0.0.2 → 0.0.3
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/Gemfile +1 -0
- data/Gemfile.lock +32 -1
- data/README.md +32 -2
- data/Rakefile +10 -1
- data/binflip.gemspec +1 -1
- data/lib/binflip.rb +94 -10
- data/lib/binflip_rails.rb +6 -0
- data/spec/javascripts/BinflipSpec.js +20 -0
- data/spec/javascripts/support/jasmine.yml +73 -0
- data/test/binflip_test.rb +2 -4
- data/vendor/assets/javascripts/binflip.js +32 -0
- metadata +15 -3
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,23 +1,53 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
binflip (0.0.
|
4
|
+
binflip (0.0.3)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: http://rubygems.org/
|
8
8
|
specs:
|
9
|
+
addressable (2.2.8)
|
10
|
+
childprocess (0.3.2)
|
11
|
+
ffi (~> 1.0.6)
|
9
12
|
coderay (1.0.5)
|
13
|
+
diff-lcs (1.1.3)
|
14
|
+
ffi (1.0.11)
|
15
|
+
jasmine (1.2.0)
|
16
|
+
jasmine-core (>= 1.2.0)
|
17
|
+
rack (~> 1.0)
|
18
|
+
rspec (>= 1.3.1)
|
19
|
+
selenium-webdriver (>= 0.1.3)
|
20
|
+
jasmine-core (1.2.0)
|
21
|
+
libwebsocket (0.1.3)
|
22
|
+
addressable
|
10
23
|
metaclass (0.0.1)
|
11
24
|
method_source (0.7.1)
|
12
25
|
mocha (0.11.0)
|
13
26
|
metaclass (~> 0.0.1)
|
27
|
+
multi_json (1.3.5)
|
14
28
|
pry (0.9.8.2)
|
15
29
|
coderay (~> 1.0.5)
|
16
30
|
method_source (~> 0.7)
|
17
31
|
slop (>= 2.4.4, < 3)
|
32
|
+
rack (1.4.1)
|
18
33
|
rake (0.9.2.2)
|
19
34
|
redis (2.2.2)
|
20
35
|
rollout (1.1.0)
|
36
|
+
rspec (2.10.0)
|
37
|
+
rspec-core (~> 2.10.0)
|
38
|
+
rspec-expectations (~> 2.10.0)
|
39
|
+
rspec-mocks (~> 2.10.0)
|
40
|
+
rspec-core (2.10.0)
|
41
|
+
rspec-expectations (2.10.0)
|
42
|
+
diff-lcs (~> 1.1.3)
|
43
|
+
rspec-mocks (2.10.1)
|
44
|
+
rubyzip (0.9.8)
|
45
|
+
selenium-webdriver (2.21.2)
|
46
|
+
childprocess (>= 0.2.5)
|
47
|
+
ffi (~> 1.0)
|
48
|
+
libwebsocket (~> 0.1.3)
|
49
|
+
multi_json (~> 1.0)
|
50
|
+
rubyzip
|
21
51
|
slop (2.4.4)
|
22
52
|
|
23
53
|
PLATFORMS
|
@@ -25,6 +55,7 @@ PLATFORMS
|
|
25
55
|
|
26
56
|
DEPENDENCIES
|
27
57
|
binflip!
|
58
|
+
jasmine
|
28
59
|
mocha
|
29
60
|
pry
|
30
61
|
rake
|
data/README.md
CHANGED
@@ -12,7 +12,7 @@ Keeping true to twelve-factor application principles <http://12factor.net>, this
|
|
12
12
|
Rollout Compatibility
|
13
13
|
======================
|
14
14
|
|
15
|
-
If Rollout <https://github.com/jamesgolick/rollout> is present, it will
|
15
|
+
If Rollout <https://github.com/jamesgolick/rollout> is present, it will pass through most rollout methods except `active?`, where a preliminary test is done to see if the environment toggle is active first.
|
16
16
|
|
17
17
|
Rationale
|
18
18
|
=========
|
@@ -64,6 +64,23 @@ Check for toggles, using just the [name]:
|
|
64
64
|
|
65
65
|
NOTE: Absence of feature toggle env variable means the feature is not active.
|
66
66
|
|
67
|
+
binflip.js
|
68
|
+
=========
|
69
|
+
|
70
|
+
To use binflip.js, you need to include `vendor/assets/javascripts/binflip.js`, and have `JSON.parse` available (use https://github.com/douglascrockford/JSON-js/blob/master/json2.js if you are using an old browser). If you are using Rails 3.1 or higher, including the Binflip gem will make it available, just require 'binflip' in your application.js manifest.
|
71
|
+
|
72
|
+
The javascript pattern is to build the feature set (for the current user) once during page load, and store the set of features as a data attribute in the `<body>` tag.
|
73
|
+
|
74
|
+
So in your `<body>` tag, do something like this:
|
75
|
+
|
76
|
+
<body data-features='<%= $binflip.active_features(current_user).to_json %>'>
|
77
|
+
|
78
|
+
Then you can test for features in your javascript with:
|
79
|
+
|
80
|
+
if (Binflip.isActive('feature')) {
|
81
|
+
alert("Feature is active");
|
82
|
+
}
|
83
|
+
|
67
84
|
Usage with Rollout
|
68
85
|
==================
|
69
86
|
|
@@ -77,7 +94,8 @@ Procfile
|
|
77
94
|
Then in application setup / initialization:
|
78
95
|
|
79
96
|
$redis = Redis.new
|
80
|
-
$
|
97
|
+
$rollout = Rollout.new($redis)
|
98
|
+
$toggle = Binflip.new($rollout)
|
81
99
|
|
82
100
|
|
83
101
|
Check for toggles:
|
@@ -87,6 +105,18 @@ Check for toggles:
|
|
87
105
|
|
88
106
|
If Rollout is present, Binflip delegates all methods to it (such as `activate_user`).
|
89
107
|
|
108
|
+
Environment Keys and Case Sensitivity
|
109
|
+
=====================================
|
110
|
+
|
111
|
+
As it is customary to set `ENVIRONMENT_VARS` in all upcase, environment feature keys are upcase'd. So you *must* set your env keys to all upcase -- e.g. `FEATURE_MY_COOL_FEATURE`. But you can test with upcase, lowercase or as a symbol:
|
112
|
+
|
113
|
+
$binflip.active?(:my_cool_feature)
|
114
|
+
$binflip.active?('my_cool_feature')
|
115
|
+
$binflip.active?('MY_COOL_FEATURE')
|
116
|
+
|
117
|
+
These are all equivalent and will test for `FEATURE_MY_COOL_FEATURE` in the `ENV` hash. NOTE: this convention is only for `ENV` and not for rollout.
|
118
|
+
|
119
|
+
|
90
120
|
License
|
91
121
|
=======
|
92
122
|
|
data/Rakefile
CHANGED
@@ -6,4 +6,13 @@ Rake::TestTask.new do |t|
|
|
6
6
|
t.verbose = true
|
7
7
|
end
|
8
8
|
|
9
|
-
task :default => :test
|
9
|
+
task :default => [ :test, "jasmine:ci" ]
|
10
|
+
|
11
|
+
begin
|
12
|
+
require 'jasmine'
|
13
|
+
load 'jasmine/tasks/jasmine.rake'
|
14
|
+
rescue LoadError
|
15
|
+
task :jasmine do
|
16
|
+
abort "Jasmine is not available. In order to run jasmine, you must: (sudo) gem install jasmine"
|
17
|
+
end
|
18
|
+
end
|
data/binflip.gemspec
CHANGED
data/lib/binflip.rb
CHANGED
@@ -1,28 +1,74 @@
|
|
1
|
-
require '
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
if defined?(Rails) && defined?(Rails::Engine)
|
4
|
+
require 'binflip_rails'
|
5
|
+
end
|
6
|
+
|
7
|
+
class FauxRedis
|
8
|
+
def smembers(*_); []; end
|
9
|
+
def sadd(*_); true; end
|
10
|
+
def srem(*_); true; end
|
11
|
+
def del(*_); 0; end
|
12
|
+
end
|
2
13
|
|
3
14
|
class Binflip
|
15
|
+
@rollout = false
|
4
16
|
|
5
|
-
def
|
6
|
-
|
17
|
+
def initialize(rollout=nil)
|
18
|
+
if rollout && rollout.is_a?(Rollout)
|
19
|
+
@rollout = rollout
|
20
|
+
end
|
7
21
|
end
|
8
22
|
|
9
|
-
def
|
10
|
-
|
11
|
-
|
23
|
+
def rollout?
|
24
|
+
!! @rollout
|
25
|
+
end
|
26
|
+
|
27
|
+
def redis
|
28
|
+
@redis ||= if rollout?
|
29
|
+
@rollout.instance_variable_get(:@redis)
|
30
|
+
else
|
31
|
+
FauxRedis.new
|
12
32
|
end
|
13
33
|
end
|
14
34
|
|
15
35
|
def active?(feature, user=nil)
|
16
|
-
if environment_active?(feature) &&
|
17
|
-
@
|
36
|
+
if environment_active?(feature) && rollout? && user
|
37
|
+
@rollout.active?(feature, user)
|
18
38
|
else
|
19
39
|
environment_active?(feature)
|
20
40
|
end
|
21
41
|
end
|
22
42
|
|
43
|
+
def active_features(user=nil)
|
44
|
+
active_features ||= {}
|
45
|
+
features.each do |feature|
|
46
|
+
active_features[feature] = active?(feature, user)
|
47
|
+
end
|
48
|
+
active_features
|
49
|
+
end
|
50
|
+
|
51
|
+
def activate_group(feature, group)
|
52
|
+
@rollout.activate_group(feature, group)
|
53
|
+
feature_set_add(feature)
|
54
|
+
end
|
55
|
+
|
56
|
+
def activate_user(feature, user)
|
57
|
+
@rollout.activate_user(feature, user)
|
58
|
+
feature_set_add(feature)
|
59
|
+
end
|
60
|
+
|
61
|
+
def cleanup_feature_set!
|
62
|
+
features_rollout.each do |feature|
|
63
|
+
unused_feature = { :percentage => 0, :groups => [], :users => [] }
|
64
|
+
feature_set_del(feature) if (@source.info(feature) == unused_feature)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
23
68
|
def method_missing(meth, *args, &block)
|
24
|
-
if
|
25
|
-
@
|
69
|
+
if rollout?
|
70
|
+
@rollout.send(meth, *args, &block)
|
71
|
+
cleanup_feature_set! if (meth =~ /(:de)?activate/)
|
26
72
|
else
|
27
73
|
super
|
28
74
|
end
|
@@ -30,6 +76,10 @@ class Binflip
|
|
30
76
|
|
31
77
|
private
|
32
78
|
|
79
|
+
def environment_key_pattern
|
80
|
+
/^FEATURE_(.*)$/
|
81
|
+
end
|
82
|
+
|
33
83
|
def environment_key(feature)
|
34
84
|
("FEATURE_%s" % feature).upcase
|
35
85
|
end
|
@@ -37,4 +87,38 @@ class Binflip
|
|
37
87
|
def environment_active?(feature)
|
38
88
|
ENV[environment_key(feature)] == '1'
|
39
89
|
end
|
90
|
+
|
91
|
+
def feature_set_purge
|
92
|
+
redis.del(feature_set_key)
|
93
|
+
end
|
94
|
+
|
95
|
+
def feature_set_del(feature)
|
96
|
+
redis.srem(feature_set_key, feature)
|
97
|
+
end
|
98
|
+
|
99
|
+
def feature_set_add(feature)
|
100
|
+
redis.sadd(feature_set_key, feature)
|
101
|
+
end
|
102
|
+
|
103
|
+
def feature_set_key
|
104
|
+
'features'
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
# Set of all feature keys
|
109
|
+
def features
|
110
|
+
features_environment + features_rollout
|
111
|
+
end
|
112
|
+
|
113
|
+
def features_rollout
|
114
|
+
redis.smembers(feature_set_key).to_set
|
115
|
+
end
|
116
|
+
|
117
|
+
def features_environment
|
118
|
+
ENV.keys.reduce(Set.new) do |s,k|
|
119
|
+
s << $1 if (k =~ environment_key_pattern)
|
120
|
+
s
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
40
124
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
describe("Binflip", function () {
|
2
|
+
|
3
|
+
beforeEach(function () {
|
4
|
+
var body = document.getElementsByTagName('body')[0];
|
5
|
+
body.setAttribute("data-features", '{"feature_on":true, "feature_off":false}');
|
6
|
+
});
|
7
|
+
|
8
|
+
it("should be true", function () {
|
9
|
+
expect(Binflip.isActive("feature_on")).toEqual(true);
|
10
|
+
});
|
11
|
+
|
12
|
+
it("should be false", function () {
|
13
|
+
expect(Binflip.isActive("feature_off")).toEqual(false);
|
14
|
+
});
|
15
|
+
|
16
|
+
it("should be false", function () {
|
17
|
+
expect(Binflip.isActive("feature_unknown")).toEqual(false);
|
18
|
+
});
|
19
|
+
|
20
|
+
});
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# src_files
|
2
|
+
#
|
3
|
+
# Return an array of filepaths relative to src_dir to include before jasmine specs.
|
4
|
+
# Default: []
|
5
|
+
#
|
6
|
+
# EXAMPLE:
|
7
|
+
#
|
8
|
+
# src_files:
|
9
|
+
# - lib/source1.js
|
10
|
+
# - lib/source2.js
|
11
|
+
# - dist/**/*.js
|
12
|
+
#
|
13
|
+
src_files:
|
14
|
+
- vendor/assets/javascripts/**/*.js
|
15
|
+
|
16
|
+
# stylesheets
|
17
|
+
#
|
18
|
+
# Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs.
|
19
|
+
# Default: []
|
20
|
+
#
|
21
|
+
# EXAMPLE:
|
22
|
+
#
|
23
|
+
# stylesheets:
|
24
|
+
# - css/style.css
|
25
|
+
# - stylesheets/*.css
|
26
|
+
#
|
27
|
+
stylesheets:
|
28
|
+
|
29
|
+
# helpers
|
30
|
+
#
|
31
|
+
# Return an array of filepaths relative to spec_dir to include before jasmine specs.
|
32
|
+
# Default: ["helpers/**/*.js"]
|
33
|
+
#
|
34
|
+
# EXAMPLE:
|
35
|
+
#
|
36
|
+
# helpers:
|
37
|
+
# - helpers/**/*.js
|
38
|
+
#
|
39
|
+
helpers:
|
40
|
+
|
41
|
+
# spec_files
|
42
|
+
#
|
43
|
+
# Return an array of filepaths relative to spec_dir to include.
|
44
|
+
# Default: ["**/*[sS]pec.js"]
|
45
|
+
#
|
46
|
+
# EXAMPLE:
|
47
|
+
#
|
48
|
+
# spec_files:
|
49
|
+
# - **/*[sS]pec.js
|
50
|
+
#
|
51
|
+
spec_files:
|
52
|
+
|
53
|
+
# src_dir
|
54
|
+
#
|
55
|
+
# Source directory path. Your src_files must be returned relative to this path. Will use root if left blank.
|
56
|
+
# Default: project root
|
57
|
+
#
|
58
|
+
# EXAMPLE:
|
59
|
+
#
|
60
|
+
# src_dir: public
|
61
|
+
#
|
62
|
+
src_dir:
|
63
|
+
|
64
|
+
# spec_dir
|
65
|
+
#
|
66
|
+
# Spec directory path. Your spec_files must be returned relative to this path.
|
67
|
+
# Default: spec/javascripts
|
68
|
+
#
|
69
|
+
# EXAMPLE:
|
70
|
+
#
|
71
|
+
# spec_dir: spec/javascripts
|
72
|
+
#
|
73
|
+
spec_dir:
|
data/test/binflip_test.rb
CHANGED
@@ -13,8 +13,6 @@ describe Binflip do
|
|
13
13
|
describe "without rollout" do
|
14
14
|
|
15
15
|
before do
|
16
|
-
Binflip.stubs(:rollout?).returns(false)
|
17
|
-
|
18
16
|
@binflip = Binflip.new
|
19
17
|
ENV['FEATURE_X'] = "1"
|
20
18
|
ENV['FEATURE_Y'] = "0"
|
@@ -34,11 +32,11 @@ describe Binflip do
|
|
34
32
|
|
35
33
|
describe "with Rollout" do
|
36
34
|
before do
|
37
|
-
Binflip.expects(:rollout?).returns(true)
|
38
35
|
Redis.new.flushdb
|
39
36
|
|
40
37
|
@redis = Redis.new
|
41
|
-
@
|
38
|
+
@rollout = Rollout.new(@redis)
|
39
|
+
@binflip = Binflip.new(@rollout)
|
42
40
|
ENV['FEATURE_X'] = "1"
|
43
41
|
ENV['FEATURE_Y'] = "0"
|
44
42
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
/**
|
2
|
+
* Usage:
|
3
|
+
*
|
4
|
+
* Set data-features in the <body> tag -- i.e.:
|
5
|
+
*
|
6
|
+
* <body data-features='{"feature_a":true}'>
|
7
|
+
*
|
8
|
+
* Then in JS, access the Binflip.isActive(feature_name) function:
|
9
|
+
*
|
10
|
+
* if (Binflip.isActive("feature_a")) {
|
11
|
+
* // feature is on
|
12
|
+
* }
|
13
|
+
*
|
14
|
+
*/
|
15
|
+
var Binflip = {
|
16
|
+
isActive: function (feature) {
|
17
|
+
var els = document.getElementsByTagName('body');
|
18
|
+
if (els.length != 1) {
|
19
|
+
throw new Error("Can't find one and onlye one <body> element!");
|
20
|
+
}
|
21
|
+
|
22
|
+
try {
|
23
|
+
if (JSON.parse(els[0].getAttribute('data-features'))[feature]) {
|
24
|
+
return true;
|
25
|
+
}
|
26
|
+
} catch(err) {
|
27
|
+
throw new Error("Could not parse attribute data-features: " + err.message);
|
28
|
+
}
|
29
|
+
|
30
|
+
return false;
|
31
|
+
}
|
32
|
+
}
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: binflip
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-05-21 00:00:00.000000000Z
|
13
13
|
dependencies: []
|
14
14
|
description: Environment-based feature flipper. Compatiable with rollout.
|
15
15
|
email:
|
@@ -27,7 +27,11 @@ files:
|
|
27
27
|
- Rakefile
|
28
28
|
- binflip.gemspec
|
29
29
|
- lib/binflip.rb
|
30
|
+
- lib/binflip_rails.rb
|
31
|
+
- spec/javascripts/BinflipSpec.js
|
32
|
+
- spec/javascripts/support/jasmine.yml
|
30
33
|
- test/binflip_test.rb
|
34
|
+
- vendor/assets/javascripts/binflip.js
|
31
35
|
homepage: ''
|
32
36
|
licenses: []
|
33
37
|
post_install_message:
|
@@ -41,17 +45,25 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
41
45
|
- - ! '>='
|
42
46
|
- !ruby/object:Gem::Version
|
43
47
|
version: '0'
|
48
|
+
segments:
|
49
|
+
- 0
|
50
|
+
hash: 2883555097750050300
|
44
51
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
52
|
none: false
|
46
53
|
requirements:
|
47
54
|
- - ! '>='
|
48
55
|
- !ruby/object:Gem::Version
|
49
56
|
version: '0'
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
hash: 2883555097750050300
|
50
60
|
requirements: []
|
51
61
|
rubyforge_project:
|
52
|
-
rubygems_version: 1.8.
|
62
|
+
rubygems_version: 1.8.10
|
53
63
|
signing_key:
|
54
64
|
specification_version: 3
|
55
65
|
summary: Kanban Flipper
|
56
66
|
test_files:
|
67
|
+
- spec/javascripts/BinflipSpec.js
|
68
|
+
- spec/javascripts/support/jasmine.yml
|
57
69
|
- test/binflip_test.rb
|