flipper 0.6.3 → 0.7.0.beta1

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -2
  3. data/Gemfile +10 -10
  4. data/Guardfile +2 -1
  5. data/README.md +54 -23
  6. data/Rakefile +1 -1
  7. data/examples/dsl.rb +2 -2
  8. data/examples/{percentage_of_random.rb → percentage_of_time.rb} +1 -1
  9. data/lib/flipper.rb +25 -12
  10. data/lib/flipper/dsl.rb +96 -8
  11. data/lib/flipper/feature.rb +216 -37
  12. data/lib/flipper/gate.rb +6 -38
  13. data/lib/flipper/gate_values.rb +42 -0
  14. data/lib/flipper/gates/actor.rb +11 -13
  15. data/lib/flipper/gates/boolean.rb +3 -12
  16. data/lib/flipper/gates/group.rb +14 -16
  17. data/lib/flipper/gates/percentage_of_actors.rb +12 -13
  18. data/lib/flipper/gates/{percentage_of_random.rb → percentage_of_time.rb} +8 -11
  19. data/lib/flipper/instrumentation/log_subscriber.rb +1 -1
  20. data/lib/flipper/instrumentation/subscriber.rb +1 -1
  21. data/lib/flipper/spec/shared_adapter_specs.rb +16 -16
  22. data/lib/flipper/type.rb +1 -1
  23. data/lib/flipper/typecast.rb +44 -0
  24. data/lib/flipper/types/actor.rb +2 -5
  25. data/lib/flipper/types/group.rb +6 -0
  26. data/lib/flipper/types/percentage.rb +7 -1
  27. data/lib/flipper/types/{percentage_of_random.rb → percentage_of_time.rb} +1 -1
  28. data/lib/flipper/version.rb +1 -1
  29. data/script/bootstrap +21 -0
  30. data/script/guard +15 -0
  31. data/script/release +15 -0
  32. data/script/test +30 -0
  33. data/spec/flipper/dsl_spec.rb +67 -8
  34. data/spec/flipper/feature_spec.rb +424 -12
  35. data/spec/flipper/gate_spec.rb +1 -20
  36. data/spec/flipper/gate_values_spec.rb +134 -0
  37. data/spec/flipper/gates/actor_spec.rb +1 -21
  38. data/spec/flipper/gates/boolean_spec.rb +3 -71
  39. data/spec/flipper/gates/group_spec.rb +3 -23
  40. data/spec/flipper/gates/percentage_of_actors_spec.rb +5 -26
  41. data/spec/flipper/gates/percentage_of_time_spec.rb +23 -0
  42. data/spec/flipper/middleware/memoizer_spec.rb +1 -2
  43. data/spec/flipper/typecast_spec.rb +63 -0
  44. data/spec/flipper/types/group_spec.rb +21 -1
  45. data/spec/flipper/types/percentage_of_time_spec.rb +6 -0
  46. data/spec/flipper/types/percentage_spec.rb +20 -0
  47. data/spec/flipper_spec.rb +31 -9
  48. data/spec/helper.rb +1 -3
  49. data/spec/integration_spec.rb +22 -22
  50. metadata +21 -11
  51. data/spec/flipper/gates/percentage_of_random_spec.rb +0 -46
  52. data/spec/flipper/types/percentage_of_random_spec.rb +0 -6
@@ -25,26 +25,24 @@ module Flipper
25
25
  end
26
26
 
27
27
  def enabled?(value)
28
- !value.nil? && !value.empty?
28
+ !Typecast.to_set(value).empty?
29
29
  end
30
30
 
31
31
  # Internal: Checks if the gate is open for a thing.
32
32
  #
33
33
  # Returns true if gate open for thing, false if not.
34
- def open?(thing, value)
35
- instrument(:open?, thing) { |payload|
36
- if thing.nil?
37
- false
34
+ def open?(thing, value, options = {})
35
+ if thing.nil?
36
+ false
37
+ else
38
+ if protects?(thing)
39
+ actor = wrap(thing)
40
+ enabled_actor_ids = value
41
+ enabled_actor_ids.include?(actor.value)
38
42
  else
39
- if protects?(thing)
40
- actor = wrap(thing)
41
- enabled_actor_ids = value
42
- enabled_actor_ids.include?(actor.value)
43
- else
44
- false
45
- end
43
+ false
46
44
  end
47
- }
45
+ end
48
46
  end
49
47
 
50
48
  def wrap(thing)
@@ -1,13 +1,6 @@
1
1
  module Flipper
2
2
  module Gates
3
3
  class Boolean < Gate
4
- TruthMap = {
5
- true => true,
6
- false => false,
7
- 'true' => true,
8
- 'false' => false,
9
- }
10
-
11
4
  # Internal: The name of the gate. Used for instrumentation, etc.
12
5
  def name
13
6
  :boolean
@@ -31,17 +24,15 @@ module Flipper
31
24
  end
32
25
 
33
26
  def enabled?(value)
34
- !!TruthMap[value]
27
+ Typecast.to_boolean(value)
35
28
  end
36
29
 
37
30
  # Internal: Checks if the gate is open for a thing.
38
31
  #
39
32
  # Returns true if explicitly set to true, false if explicitly set to false
40
33
  # or nil if not explicitly set.
41
- def open?(thing, value)
42
- instrument(:open?, thing) { |payload|
43
- !!TruthMap[value]
44
- }
34
+ def open?(thing, value, options = {})
35
+ value
45
36
  end
46
37
 
47
38
  def protects?(thing)
@@ -25,27 +25,25 @@ module Flipper
25
25
  end
26
26
 
27
27
  def enabled?(value)
28
- !value.nil? && !value.empty?
28
+ !Typecast.to_set(value).empty?
29
29
  end
30
30
 
31
31
  # Internal: Checks if the gate is open for a thing.
32
32
  #
33
33
  # Returns true if gate open for thing, false if not.
34
- def open?(thing, value)
35
- instrument(:open?, thing) { |payload|
36
- if thing.nil?
37
- false
38
- else
39
- value.any? { |name|
40
- begin
41
- group = Flipper.group(name)
42
- group.match?(thing)
43
- rescue GroupNotRegistered
44
- false
45
- end
46
- }
47
- end
48
- }
34
+ def open?(thing, value, options = {})
35
+ if thing.nil?
36
+ false
37
+ else
38
+ value.any? { |name|
39
+ begin
40
+ group = Flipper.group(name)
41
+ group.match?(thing)
42
+ rescue GroupNotRegistered
43
+ false
44
+ end
45
+ }
46
+ end
49
47
  end
50
48
 
51
49
  def protects?(thing)
@@ -26,24 +26,23 @@ module Flipper
26
26
  end
27
27
 
28
28
  def enabled?(value)
29
- !value.nil? && value.to_i > 0
29
+ Typecast.to_integer(value) > 0
30
30
  end
31
31
 
32
32
  # Internal: Checks if the gate is open for a thing.
33
33
  #
34
34
  # Returns true if gate open for thing, false if not.
35
- def open?(thing, value)
36
- instrument(:open?, thing) { |payload|
37
- percentage = value.to_i
38
-
39
- if Types::Actor.wrappable?(thing)
40
- actor = Types::Actor.wrap(thing)
41
- key = "#{@feature_name}#{actor.value}"
42
- Zlib.crc32(key) % 100 < percentage
43
- else
44
- false
45
- end
46
- }
35
+ def open?(thing, value, options = {})
36
+ feature_name = options.fetch(:feature_name)
37
+ percentage = Typecast.to_integer(value)
38
+
39
+ if Types::Actor.wrappable?(thing)
40
+ actor = Types::Actor.wrap(thing)
41
+ key = "#{feature_name}#{actor.value}"
42
+ Zlib.crc32(key) % 100 < percentage
43
+ else
44
+ false
45
+ end
47
46
  end
48
47
 
49
48
  def protects?(thing)
@@ -1,14 +1,14 @@
1
1
  module Flipper
2
2
  module Gates
3
- class PercentageOfRandom < Gate
3
+ class PercentageOfTime < Gate
4
4
  # Internal: The name of the gate. Used for instrumentation, etc.
5
5
  def name
6
- :percentage_of_random
6
+ :percentage_of_time
7
7
  end
8
8
 
9
9
  # Internal: Name converted to value safe for adapter.
10
10
  def key
11
- :percentage_of_random
11
+ :percentage_of_time
12
12
  end
13
13
 
14
14
  def data_type
@@ -24,22 +24,19 @@ module Flipper
24
24
  end
25
25
 
26
26
  def enabled?(value)
27
- !value.nil? && value.to_i > 0
27
+ Typecast.to_integer(value) > 0
28
28
  end
29
29
 
30
30
  # Internal: Checks if the gate is open for a thing.
31
31
  #
32
32
  # Returns true if gate open for thing, false if not.
33
- def open?(thing, value)
34
- instrument(:open?, thing) { |payload|
35
- percentage = value.to_i
36
-
37
- rand < (percentage / 100.0)
38
- }
33
+ def open?(thing, value, options = {})
34
+ percentage = Typecast.to_integer(value)
35
+ rand < (percentage / 100.0)
39
36
  end
40
37
 
41
38
  def protects?(thing)
42
- thing.is_a?(Flipper::Types::PercentageOfRandom)
39
+ thing.is_a?(Flipper::Types::PercentageOfTime)
43
40
  end
44
41
  end
45
42
  end
@@ -74,7 +74,7 @@ module Flipper
74
74
  # # Flipper feature(search) gate(group) open false (0.1ms) [ thing=... ]
75
75
  # # Flipper feature(search) gate(actor) open false (0.1ms) [ thing=... ]
76
76
  # # Flipper feature(search) gate(percentage_of_actors) open false (0.1ms) [ thing=... ]
77
- # # Flipper feature(search) gate(percentage_of_random) open false (0.1ms) [ thing=... ]
77
+ # # Flipper feature(search) gate(percentage_of_time) open false (0.1ms) [ thing=... ]
78
78
  #
79
79
  # Returns nothing.
80
80
  def gate_operation(event)
@@ -95,7 +95,7 @@ module Flipper
95
95
 
96
96
  # Private
97
97
  def strip_trailing_question_mark(operation)
98
- operation.to_s.gsub(/\?$/, '')
98
+ operation.to_s.chomp('?')
99
99
  end
100
100
  end
101
101
  end
@@ -10,7 +10,7 @@ shared_examples_for 'a flipper adapter' do
10
10
  let(:group_gate) { feature.gate(:group) }
11
11
  let(:actor_gate) { feature.gate(:actor) }
12
12
  let(:actors_gate) { feature.gate(:percentage_of_actors) }
13
- let(:random_gate) { feature.gate(:percentage_of_random) }
13
+ let(:time_gate) { feature.gate(:percentage_of_time) }
14
14
 
15
15
  before do
16
16
  Flipper.register(:admins) { |actor|
@@ -41,7 +41,7 @@ shared_examples_for 'a flipper adapter' do
41
41
  :groups => Set.new,
42
42
  :actors => Set.new,
43
43
  :percentage_of_actors => nil,
44
- :percentage_of_random => nil,
44
+ :percentage_of_time => nil,
45
45
  })
46
46
  end
47
47
 
@@ -63,7 +63,7 @@ shared_examples_for 'a flipper adapter' do
63
63
  subject.enable(feature, group_gate, flipper.group(:admins)).should eq(true)
64
64
  subject.enable(feature, actor_gate, flipper.actor(actor_22)).should eq(true)
65
65
  subject.enable(feature, actors_gate, flipper.actors(25)).should eq(true)
66
- subject.enable(feature, random_gate, flipper.random(45)).should eq(true)
66
+ subject.enable(feature, time_gate, flipper.time(45)).should eq(true)
67
67
 
68
68
  subject.disable(feature, boolean_gate, flipper.boolean).should eq(true)
69
69
 
@@ -72,7 +72,7 @@ shared_examples_for 'a flipper adapter' do
72
72
  :groups => Set.new,
73
73
  :actors => Set.new,
74
74
  :percentage_of_actors => nil,
75
- :percentage_of_random => nil,
75
+ :percentage_of_time => nil,
76
76
  })
77
77
  end
78
78
 
@@ -121,14 +121,14 @@ shared_examples_for 'a flipper adapter' do
121
121
  result[:percentage_of_actors].should eq('0')
122
122
  end
123
123
 
124
- it "can enable, disable and get value for percentage of random gate" do
125
- subject.enable(feature, random_gate, flipper.random(10)).should eq(true)
124
+ it "can enable, disable and get value for percentage of time gate" do
125
+ subject.enable(feature, time_gate, flipper.time(10)).should eq(true)
126
126
  result = subject.get(feature)
127
- result[:percentage_of_random].should eq('10')
127
+ result[:percentage_of_time].should eq('10')
128
128
 
129
- subject.disable(feature, random_gate, flipper.random(0)).should eq(true)
129
+ subject.disable(feature, time_gate, flipper.time(0)).should eq(true)
130
130
  result = subject.get(feature)
131
- result[:percentage_of_random].should eq('0')
131
+ result[:percentage_of_time].should eq('0')
132
132
  end
133
133
 
134
134
  it "converts boolean value to a string" do
@@ -149,10 +149,10 @@ shared_examples_for 'a flipper adapter' do
149
149
  result[:groups].should eq(Set['admins'])
150
150
  end
151
151
 
152
- it "converts percentage of random integer value to a string" do
153
- subject.enable(feature, random_gate, flipper.random(10)).should eq(true)
152
+ it "converts percentage of time integer value to a string" do
153
+ subject.enable(feature, time_gate, flipper.time(10)).should eq(true)
154
154
  result = subject.get(feature)
155
- result[:percentage_of_random].should eq('10')
155
+ result[:percentage_of_time].should eq('10')
156
156
  end
157
157
 
158
158
  it "converts percentage of actors integer value to a string" do
@@ -183,7 +183,7 @@ shared_examples_for 'a flipper adapter' do
183
183
  subject.enable(feature, group_gate, flipper.group(:admins)).should eq(true)
184
184
  subject.enable(feature, actor_gate, flipper.actor(actor_22)).should eq(true)
185
185
  subject.enable(feature, actors_gate, flipper.actors(25)).should eq(true)
186
- subject.enable(feature, random_gate, flipper.random(45)).should eq(true)
186
+ subject.enable(feature, time_gate, flipper.time(45)).should eq(true)
187
187
 
188
188
  subject.remove(feature).should eq(true)
189
189
 
@@ -192,7 +192,7 @@ shared_examples_for 'a flipper adapter' do
192
192
  :groups => Set.new,
193
193
  :actors => Set.new,
194
194
  :percentage_of_actors => nil,
195
- :percentage_of_random => nil,
195
+ :percentage_of_time => nil,
196
196
  })
197
197
  end
198
198
 
@@ -202,7 +202,7 @@ shared_examples_for 'a flipper adapter' do
202
202
  subject.enable(feature, group_gate, flipper.group(:admins)).should eq(true)
203
203
  subject.enable(feature, actor_gate, flipper.actor(actor_22)).should eq(true)
204
204
  subject.enable(feature, actors_gate, flipper.actors(25)).should eq(true)
205
- subject.enable(feature, random_gate, flipper.random(45)).should eq(true)
205
+ subject.enable(feature, time_gate, flipper.time(45)).should eq(true)
206
206
 
207
207
  subject.clear(feature).should eq(true)
208
208
 
@@ -211,7 +211,7 @@ shared_examples_for 'a flipper adapter' do
211
211
  :groups => Set.new,
212
212
  :actors => Set.new,
213
213
  :percentage_of_actors => nil,
214
- :percentage_of_random => nil,
214
+ :percentage_of_time => nil,
215
215
  })
216
216
  end
217
217
 
data/lib/flipper/type.rb CHANGED
@@ -12,4 +12,4 @@ require 'flipper/types/boolean'
12
12
  require 'flipper/types/group'
13
13
  require 'flipper/types/percentage'
14
14
  require 'flipper/types/percentage_of_actors'
15
- require 'flipper/types/percentage_of_random'
15
+ require 'flipper/types/percentage_of_time'
@@ -0,0 +1,44 @@
1
+ module Flipper
2
+ module Typecast
3
+ TruthMap = {
4
+ true => true,
5
+ 1 => true,
6
+ "true" => true,
7
+ "1" => true,
8
+ }
9
+
10
+ # Internal: Convert value to a boolean.
11
+ #
12
+ # Returns true or false.
13
+ def self.to_boolean(value)
14
+ !!TruthMap[value]
15
+ end
16
+
17
+ # Internal: Convert value to an integer.
18
+ #
19
+ # Returns an Integer representation of the value.
20
+ # Raises ArgumentError if conversion is not possible.
21
+ def self.to_integer(value)
22
+ if value.respond_to?(:to_i)
23
+ value.to_i
24
+ else
25
+ raise ArgumentError, "#{value.inspect} cannot be converted to an integer"
26
+ end
27
+ end
28
+
29
+ # Internal: Convert value to a set.
30
+ #
31
+ # Returns a Set representation of the value.
32
+ # Raises ArgumentError if conversion is not possible.
33
+ def self.to_set(value)
34
+ return value if value.is_a?(Set)
35
+ return Set.new if value.nil? || value.empty?
36
+
37
+ if value.respond_to?(:to_set)
38
+ value.to_set
39
+ else
40
+ raise ArgumentError, "#{value.inspect} cannot be converted to a set"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -8,11 +8,8 @@ module Flipper
8
8
  end
9
9
 
10
10
  def self.wrap(thing)
11
- if thing.is_a?(Flipper::Types::Actor)
12
- thing
13
- else
14
- new(thing)
15
- end
11
+ return thing if thing.is_a?(self)
12
+ new(thing)
16
13
  end
17
14
 
18
15
  attr_reader :value
@@ -1,6 +1,12 @@
1
1
  module Flipper
2
2
  module Types
3
3
  class Group < Type
4
+
5
+ def self.wrap(group_or_name)
6
+ return group_or_name if group_or_name.is_a?(self)
7
+ Flipper.group(group_or_name)
8
+ end
9
+
4
10
  attr_reader :name
5
11
 
6
12
  def initialize(name, &block)
@@ -1,10 +1,16 @@
1
1
  module Flipper
2
2
  module Types
3
3
  class Percentage < Type
4
+
5
+ def self.wrap(value)
6
+ return value if value.is_a?(self)
7
+ new(value)
8
+ end
9
+
4
10
  attr_reader :value
5
11
 
6
12
  def initialize(value)
7
- value = value.to_i
13
+ value = Typecast.to_integer(value)
8
14
 
9
15
  if value < 0 || value > 100
10
16
  raise ArgumentError, "value must be a positive number less than or equal to 100, but was #{value}"
@@ -1,6 +1,6 @@
1
1
  module Flipper
2
2
  module Types
3
- class PercentageOfRandom < Percentage
3
+ class PercentageOfTime < Percentage
4
4
  end
5
5
  end
6
6
  end
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = "0.6.3"
2
+ VERSION = "0.7.0.beta1"
3
3
  end
data/script/bootstrap ADDED
@@ -0,0 +1,21 @@
1
+ #!/bin/sh
2
+ #/ Usage: bootstrap [bundle options]
3
+ #/
4
+ #/ Bundle install the dependencies.
5
+ #/
6
+ #/ Examples:
7
+ #/
8
+ #/ bootstrap
9
+ #/ bootstrap --local
10
+ #/
11
+
12
+ set -e
13
+ cd $(dirname "$0")/..
14
+
15
+ [ "$1" = "--help" -o "$1" = "-h" -o "$1" = "help" ] && {
16
+ grep '^#/' <"$0"| cut -c4-
17
+ exit 0
18
+ }
19
+
20
+ rm -rf .bundle/{binstubs,config}
21
+ bundle install --binstubs .bundle/binstubs --path .bundle --quiet "$@"