flipper 0.2.1 → 0.3.0

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.
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - ree
5
+ - 1.9.3
6
+ notifications:
7
+ email: false
8
+ bundler_args: --without guard
data/Gemfile CHANGED
@@ -7,7 +7,8 @@ group(:guard) do
7
7
  gem 'guard'
8
8
  gem 'guard-rspec'
9
9
  gem 'guard-bundler'
10
- gem 'growl'
10
+ gem 'terminal-notifier-guard'
11
+ gem 'rb-fsevent'
11
12
  end
12
13
 
13
14
  group(:test) do
@@ -1,11 +1,3 @@
1
- require 'flipper/dsl'
2
- require 'flipper/errors'
3
- require 'flipper/feature'
4
- require 'flipper/gate'
5
- require 'flipper/registry'
6
- require 'flipper/toggle'
7
- require 'flipper/type'
8
-
9
1
  module Flipper
10
2
  def self.new(*args)
11
3
  DSL.new(*args)
@@ -31,3 +23,11 @@ module Flipper
31
23
  groups.get(name)
32
24
  end
33
25
  end
26
+
27
+ require 'flipper/dsl'
28
+ require 'flipper/errors'
29
+ require 'flipper/feature'
30
+ require 'flipper/gate'
31
+ require 'flipper/registry'
32
+ require 'flipper/toggle'
33
+ require 'flipper/type'
@@ -1,5 +1,7 @@
1
1
  module Flipper
2
- # Internal: Adapter wrapper that wraps vanilla adapter instances with local caching.
2
+ # Internal: Adapter wrapper that wraps vanilla adapter instances. Adds things
3
+ # like local caching and convenience methods for adding/reading features from
4
+ # the adapter.
3
5
  #
4
6
  # So what is this local cache crap?
5
7
  #
@@ -17,6 +19,8 @@ module Flipper
17
19
  # To see an example adapter that this would wrap, checkout the [memory
18
20
  # adapter included with flipper](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/adapters/memory.rb).
19
21
  class Adapter
22
+ FeaturesKey = 'features'
23
+
20
24
  # Internal: Wraps vanilla adapter instance for use internally in flipper.
21
25
  #
22
26
  # object - Either an instance of Flipper::Adapter or a vanilla adapter instance
@@ -65,35 +69,27 @@ module Flipper
65
69
  end
66
70
 
67
71
  def read(key)
68
- if using_local_cache?
69
- cache(key) { @adapter.read(key) }
70
- else
71
- @adapter.read(key)
72
- end
72
+ perform_read(key) { @adapter.read(key) }
73
73
  end
74
74
 
75
75
  def write(key, value)
76
- @adapter.write(key, value).tap { expire_local_cache(key) }
76
+ perform_update(key) { @adapter.write(key, value) }
77
77
  end
78
78
 
79
79
  def delete(key)
80
- @adapter.delete(key).tap { expire_local_cache(key) }
80
+ perform_update(key) { @adapter.delete(key) }
81
81
  end
82
82
 
83
83
  def set_members(key)
84
- if using_local_cache?
85
- cache(key) { @adapter.set_members(key) }
86
- else
87
- @adapter.set_members(key)
88
- end
84
+ perform_read(key) { @adapter.set_members(key) }
89
85
  end
90
86
 
91
87
  def set_add(key, value)
92
- @adapter.set_add(key, value).tap { expire_local_cache(key) }
88
+ perform_update(key) { @adapter.set_add(key, value) }
93
89
  end
94
90
 
95
91
  def set_delete(key, value)
96
- @adapter.set_delete(key, value).tap { expire_local_cache(key) }
92
+ perform_update(key) { @adapter.set_delete(key, value) }
97
93
  end
98
94
 
99
95
  def eql?(other)
@@ -101,14 +97,30 @@ module Flipper
101
97
  end
102
98
  alias :== :eql?
103
99
 
100
+ def features
101
+ set_members(FeaturesKey)
102
+ end
103
+
104
+ def feature_add(name)
105
+ set_add(FeaturesKey, name.to_s)
106
+ end
107
+
104
108
  private
105
109
 
106
- def cache(key)
107
- local_cache.fetch(key) { local_cache[key] = yield }
110
+ def perform_read(key)
111
+ if using_local_cache?
112
+ local_cache.fetch(key.to_s) { local_cache[key.to_s] = yield }
113
+ else
114
+ yield
115
+ end
108
116
  end
109
117
 
110
- def expire_local_cache(key)
111
- local_cache.delete(key) if using_local_cache?
118
+ def perform_update(key)
119
+ result = yield
120
+ if using_local_cache?
121
+ local_cache.delete(key.to_s)
122
+ end
123
+ result
112
124
  end
113
125
  end
114
126
  end
@@ -8,27 +8,36 @@ module Flipper
8
8
  end
9
9
 
10
10
  def read(key)
11
- @source[key]
11
+ @source[key.to_s]
12
12
  end
13
13
 
14
14
  def write(key, value)
15
- @source[key] = value
15
+ @source[key.to_s] = value
16
16
  end
17
17
 
18
18
  def delete(key)
19
- @source.delete(key)
19
+ @source.delete(key.to_s)
20
20
  end
21
21
 
22
22
  def set_add(key, value)
23
- set_members(key).add(value)
23
+ ensure_set_initialized(key)
24
+ @source[key.to_s].add(value)
24
25
  end
25
26
 
26
27
  def set_delete(key, value)
27
- set_members(key).delete(value)
28
+ ensure_set_initialized(key)
29
+ @source[key.to_s].delete(value)
28
30
  end
29
31
 
30
32
  def set_members(key)
31
- @source[key] ||= Set.new
33
+ ensure_set_initialized(key)
34
+ @source[key.to_s]
35
+ end
36
+
37
+ private
38
+
39
+ def ensure_set_initialized(key)
40
+ @source[key.to_s] ||= Set.new
32
41
  end
33
42
  end
34
43
  end
@@ -0,0 +1,57 @@
1
+ module Flipper
2
+ module Adapters
3
+ # Public: Adapter that wraps another adapter and stores the operations.
4
+ #
5
+ # Useful in tests to verify calls and such.
6
+ class OperationLogger
7
+ attr_reader :operations
8
+
9
+ Read = Struct.new(:key)
10
+ Write = Struct.new(:key, :value)
11
+ Delete = Struct.new(:key)
12
+ SetAdd = Struct.new(:key, :value)
13
+ SetDelete = Struct.new(:key, :value)
14
+ SetMember = Struct.new(:key)
15
+
16
+ def initialize(adapter)
17
+ @operations = []
18
+ @adapter = adapter
19
+ end
20
+
21
+ def read(key)
22
+ @operations << Read.new(key.to_s)
23
+ @adapter.read key
24
+ end
25
+
26
+ def write(key, value)
27
+ @operations << Write.new(key.to_s, value)
28
+ @adapter.write key, value
29
+ end
30
+
31
+ def delete(key)
32
+ @operations << Delete.new(key.to_s, nil)
33
+ @adapter.delete key
34
+ end
35
+
36
+ def set_add(key, value)
37
+ @operations << SetAdd.new(key.to_s, value)
38
+ @adapter.set_add key, value
39
+ end
40
+
41
+ def set_delete(key, value)
42
+ @operations << SetDelete.new(key.to_s, value)
43
+ @adapter.set_delete key, value
44
+ end
45
+
46
+ def set_members(key)
47
+ @operations << SetMembers.new(key.to_s)
48
+ @adapter.set_members key
49
+ end
50
+
51
+ # Public: Clears operation log
52
+ def reset
53
+ @operations.clear
54
+ end
55
+ end
56
+ end
57
+ end
@@ -25,7 +25,7 @@ module Flipper
25
25
  end
26
26
 
27
27
  def feature(name)
28
- features[name.to_sym] ||= Feature.new(name, @adapter)
28
+ memoized_features[name.to_sym] ||= Feature.new(name, @adapter)
29
29
  end
30
30
 
31
31
  alias :[] :feature
@@ -35,21 +35,27 @@ module Flipper
35
35
  end
36
36
 
37
37
  def actor(thing)
38
- Flipper::Types::Actor.new(thing)
38
+ Types::Actor.new(thing)
39
39
  end
40
40
 
41
41
  def random(number)
42
- Flipper::Types::PercentageOfRandom.new(number)
42
+ Types::PercentageOfRandom.new(number)
43
43
  end
44
+ alias :percentage_of_random :random
44
45
 
45
46
  def actors(number)
46
- Flipper::Types::PercentageOfActors.new(number)
47
+ Types::PercentageOfActors.new(number)
48
+ end
49
+ alias :percentage_of_actors :actors
50
+
51
+ def features
52
+ adapter.features.map { |name| feature(name) }.to_set
47
53
  end
48
54
 
49
55
  private
50
56
 
51
- def features
52
- @features ||= {}
57
+ def memoized_features
58
+ @memoized_features ||= {}
53
59
  end
54
60
  end
55
61
  end
@@ -30,13 +30,9 @@ module Flipper
30
30
  !enabled?(actor)
31
31
  end
32
32
 
33
- private
34
-
35
- def gate_for(thing)
36
- gates.detect { |gate| gate.protects?(thing) } ||
37
- raise(GateNotFound.new(thing))
38
- end
39
-
33
+ # Internal: Gates to check to see if feature is enabled/disabled
34
+ #
35
+ # Returns an array of gates
40
36
  def gates
41
37
  @gates ||= [
42
38
  Gates::Boolean.new(self),
@@ -46,5 +42,20 @@ module Flipper
46
42
  Gates::PercentageOfRandom.new(self),
47
43
  ]
48
44
  end
45
+
46
+ # Internal: Returns gate that protects thing
47
+ #
48
+ # thing - The object for which you would like to find a gate
49
+ #
50
+ # Raises Flipper::GateNotFound if no gate found for thing
51
+ def gate_for(thing)
52
+ find_gate(thing) || raise(GateNotFound.new(thing))
53
+ end
54
+
55
+ private
56
+
57
+ def find_gate(thing)
58
+ gates.detect { |gate| gate.protects?(thing) }
59
+ end
49
60
  end
50
61
  end
@@ -1,11 +1,10 @@
1
1
  require 'forwardable'
2
+ require 'flipper/key'
2
3
 
3
4
  module Flipper
4
5
  class Gate
5
6
  extend Forwardable
6
7
 
7
- Separator = '/'
8
-
9
8
  attr_reader :feature
10
9
 
11
10
  def_delegator :@feature, :adapter
@@ -14,12 +13,8 @@ module Flipper
14
13
  @feature = feature
15
14
  end
16
15
 
17
- def key_prefix
18
- @feature.name
19
- end
20
-
21
16
  def key
22
- "#{key_prefix}#{Separator}#{type_key}"
17
+ @key ||= Key.new(@feature.name, type_key)
23
18
  end
24
19
 
25
20
  def toggle_class
@@ -1,3 +1,5 @@
1
+ require 'zlib'
2
+
1
3
  module Flipper
2
4
  module Gates
3
5
  class PercentageOfActors < Gate
@@ -13,7 +15,7 @@ module Flipper
13
15
  if percentage.nil?
14
16
  false
15
17
  else
16
- actor.identifier % 100 < percentage
18
+ Zlib.crc32(actor.identifier.to_s) % 100 < percentage
17
19
  end
18
20
  end
19
21
 
@@ -0,0 +1,19 @@
1
+ module Flipper
2
+ class Key
3
+ Separator = '/'
4
+
5
+ attr_reader :prefix, :suffix
6
+
7
+ def initialize(prefix, suffix)
8
+ @prefix, @suffix = prefix, suffix
9
+ end
10
+
11
+ def separator
12
+ Separator.dup
13
+ end
14
+
15
+ def to_s
16
+ "#{prefix}#{separator}#{suffix}"
17
+ end
18
+ end
19
+ end
@@ -1,3 +1,5 @@
1
+ require 'thread'
2
+
1
3
  module Flipper
2
4
  class Registry
3
5
  include Enumerable
@@ -5,74 +5,69 @@ require 'set'
5
5
  # read_key(key)
6
6
  # write_key(key, value)
7
7
  shared_examples_for 'a flipper adapter' do
8
- describe "#write" do
9
- let(:separator) { Flipper::Gate::Separator }
8
+ let(:key) { Flipper::Key.new(:foo, :bar) }
10
9
 
10
+ describe "#write" do
11
11
  it "sets key to value in store" do
12
- subject.write('foo', true)
13
- read_key('foo').should be_true
14
- end
15
-
16
- it "works with separator" do
17
- subject.write("foo#{separator}bar", true)
18
- read_key("foo#{separator}bar").should be_true
12
+ subject.write(key, true)
13
+ read_key(key).should be_true
19
14
  end
20
15
  end
21
16
 
22
17
  describe "#read" do
23
18
  it "returns nil if key not in store" do
24
- subject.read('foo').should be_nil
19
+ subject.read(key).should be_nil
25
20
  end
26
21
 
27
22
  it "returns value if key in store" do
28
- write_key 'foo', 'bar'
29
- subject.read('foo').should eq('bar')
23
+ write_key key, 'bar'
24
+ subject.read(key).should eq('bar')
30
25
  end
31
26
  end
32
27
 
33
28
  describe "#delete" do
34
29
  it "deletes key" do
35
- write_key 'foo', 'bar'
36
- subject.delete('foo')
37
- read_key('foo').should be_nil
30
+ write_key key, 'bar'
31
+ subject.delete(key)
32
+ read_key(key).should be_nil
38
33
  end
39
34
  end
40
35
 
41
36
  describe "#set_add" do
42
37
  it "adds value to store" do
43
- subject.set_add('foo', 1)
44
- read_key('foo').should eq(Set[1])
38
+ subject.set_add(key, 1)
39
+ read_key(key).should eq(Set[1])
45
40
  end
46
41
 
47
42
  it "does not add same value more than once" do
48
- subject.set_add('foo', 1)
49
- subject.set_add('foo', 1)
50
- subject.set_add('foo', 1)
51
- subject.set_add('foo', 2)
52
- read_key('foo').should eq(Set[1, 2])
43
+ subject.set_add(key, 1)
44
+ subject.set_add(key, 1)
45
+ subject.set_add(key, 1)
46
+ subject.set_add(key, 2)
47
+ read_key(key).should eq(Set[1, 2])
53
48
  end
54
49
  end
55
50
 
56
51
  describe "#set_delete" do
57
52
  it "removes value from set if key in store" do
58
- write_key 'foo', Set[1, 2]
59
- subject.set_delete('foo', 1)
60
- read_key('foo').should eq(Set[2])
53
+ write_key key, Set[1, 2]
54
+ subject.set_delete(key, 1)
55
+ read_key(key).should eq(Set[2])
61
56
  end
62
57
 
63
58
  it "works fine if key not in store" do
64
- subject.set_delete('foo', 'bar')
59
+ subject.set_delete(key, 'bar')
65
60
  end
66
61
  end
67
62
 
68
63
  describe "#set_members" do
69
64
  it "defaults to empty set" do
70
- subject.set_members('foo').should eq(Set.new)
65
+ subject.set_members(key).should eq(Set.new)
71
66
  end
72
67
 
73
68
  it "returns set if in store" do
74
- write_key 'foo', Set[1, 2]
75
- subject.set_members('foo').should eq(Set[1, 2])
69
+ write_key key, Set[1, 2]
70
+ subject.set_members(key).should eq(Set[1, 2])
76
71
  end
77
72
  end
78
73