binflip 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -5,5 +5,6 @@ gemspec
5
5
  gem 'rake'
6
6
  gem 'mocha'
7
7
  gem 'pry'
8
+ gem 'jasmine'
8
9
  gem 'redis', :require => false
9
10
  gem 'rollout', :require => false
data/Gemfile.lock CHANGED
@@ -1,23 +1,53 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- binflip (0.0.1)
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 delegate all methods, adding to `active?` a preliminary test to see if the environment toggle is on before checking rollout.
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
- $toggle = Binflip.new($redis)
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "binflip"
5
- s.version = '0.0.2'
5
+ s.version = '0.0.3'
6
6
  s.platform = Gem::Platform::RUBY
7
7
  s.authors = ["Brian Kaney"]
8
8
  s.email = ["brian@vermonster.com"]
data/lib/binflip.rb CHANGED
@@ -1,28 +1,74 @@
1
- require 'delegate'
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 self.rollout?
6
- defined?(Rollout) == 'constant'
17
+ def initialize(rollout=nil)
18
+ if rollout && rollout.is_a?(Rollout)
19
+ @rollout = rollout
20
+ end
7
21
  end
8
22
 
9
- def initialize(redis=nil)
10
- if Binflip.rollout?
11
- @source = SimpleDelegator.new(Rollout.new(redis))
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) && @source
17
- @source.active?(feature, user)
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 @source
25
- @source.send(meth, *args, &block)
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,6 @@
1
+ module BinflipRails
2
+ if defined?(Rails) && defined?(Rails::Engine)
3
+ class Engine < Rails::Engine
4
+ end
5
+ end
6
+ 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
- @binflip = Binflip.new(@redis)
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.2
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-04-20 00:00:00.000000000 Z
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.15
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