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 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