flipper 0.4.0 → 0.5.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.
Files changed (59) hide show
  1. data/Guardfile +3 -8
  2. data/README.md +26 -38
  3. data/examples/percentage_of_actors.rb +17 -12
  4. data/examples/percentage_of_random.rb +3 -7
  5. data/lib/flipper.rb +8 -1
  6. data/lib/flipper/adapter.rb +2 -208
  7. data/lib/flipper/adapters/decorator.rb +9 -0
  8. data/lib/flipper/adapters/instrumented.rb +92 -0
  9. data/lib/flipper/adapters/memoizable.rb +88 -0
  10. data/lib/flipper/adapters/memory.rb +89 -7
  11. data/lib/flipper/adapters/operation_logger.rb +31 -45
  12. data/lib/flipper/decorator.rb +6 -0
  13. data/lib/flipper/dsl.rb +29 -2
  14. data/lib/flipper/feature.rb +83 -49
  15. data/lib/flipper/gate.rb +24 -41
  16. data/lib/flipper/gates/actor.rb +24 -24
  17. data/lib/flipper/gates/boolean.rb +28 -15
  18. data/lib/flipper/gates/group.rb +25 -34
  19. data/lib/flipper/gates/percentage_of_actors.rb +21 -13
  20. data/lib/flipper/gates/percentage_of_random.rb +20 -12
  21. data/lib/flipper/instrumentation/log_subscriber.rb +14 -22
  22. data/lib/flipper/middleware/memoizer.rb +23 -0
  23. data/lib/flipper/spec/shared_adapter_specs.rb +141 -92
  24. data/lib/flipper/types/boolean.rb +5 -1
  25. data/lib/flipper/version.rb +1 -1
  26. data/spec/flipper/adapters/instrumented_spec.rb +92 -0
  27. data/spec/flipper/adapters/memoizable_spec.rb +184 -0
  28. data/spec/flipper/adapters/memory_spec.rb +1 -11
  29. data/spec/flipper/adapters/operation_logger_spec.rb +93 -0
  30. data/spec/flipper/dsl_spec.rb +18 -43
  31. data/spec/flipper/feature_spec.rb +25 -9
  32. data/spec/flipper/gate_spec.rb +8 -20
  33. data/spec/flipper/gates/actor_spec.rb +6 -14
  34. data/spec/flipper/gates/boolean_spec.rb +80 -13
  35. data/spec/flipper/gates/group_spec.rb +8 -18
  36. data/spec/flipper/gates/percentage_of_actors_spec.rb +12 -28
  37. data/spec/flipper/gates/percentage_of_random_spec.rb +6 -14
  38. data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -8
  39. data/spec/flipper/instrumentation/metriks_subscriber_spec.rb +3 -6
  40. data/spec/flipper/middleware/{local_cache_spec.rb → memoizer_spec.rb} +25 -55
  41. data/spec/flipper/types/boolean_spec.rb +13 -3
  42. data/spec/flipper_spec.rb +7 -0
  43. data/spec/helper.rb +21 -3
  44. data/spec/integration_spec.rb +115 -116
  45. metadata +17 -27
  46. data/lib/flipper/adapters/memoized.rb +0 -55
  47. data/lib/flipper/key.rb +0 -38
  48. data/lib/flipper/middleware/local_cache.rb +0 -36
  49. data/lib/flipper/toggle.rb +0 -54
  50. data/lib/flipper/toggles/boolean.rb +0 -54
  51. data/lib/flipper/toggles/set.rb +0 -25
  52. data/lib/flipper/toggles/value.rb +0 -25
  53. data/spec/flipper/adapter_spec.rb +0 -463
  54. data/spec/flipper/adapters/memoized_spec.rb +0 -93
  55. data/spec/flipper/key_spec.rb +0 -23
  56. data/spec/flipper/toggle_spec.rb +0 -22
  57. data/spec/flipper/toggles/boolean_spec.rb +0 -40
  58. data/spec/flipper/toggles/set_spec.rb +0 -35
  59. data/spec/flipper/toggles/value_spec.rb +0 -55
data/lib/flipper/gate.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'forwardable'
2
- require 'flipper/key'
3
2
  require 'flipper/instrumenters/noop'
4
3
 
5
4
  module Flipper
@@ -10,16 +9,14 @@ module Flipper
10
9
  InstrumentationName = "gate_operation.#{InstrumentationNamespace}"
11
10
 
12
11
  # Private
13
- attr_reader :feature
12
+ attr_reader :feature_name
14
13
 
15
14
  # Private: What is used to instrument all the things.
16
15
  attr_reader :instrumenter
17
16
 
18
- def_delegator :@feature, :adapter
19
-
20
17
  # Public
21
- def initialize(feature, options = {})
22
- @feature = feature
18
+ def initialize(feature_name, options = {})
19
+ @feature_name = feature_name
23
20
  @instrumenter = options.fetch(:instrumenter, Flipper::Instrumenters::Noop)
24
21
  end
25
22
 
@@ -28,26 +25,29 @@ module Flipper
28
25
  raise 'Not implemented'
29
26
  end
30
27
 
31
- # Private: The piece of the adapter key that is unique to the gate class.
32
- # Implemented in subclass.
28
+ # Private: Name converted to value safe for adapter. Implemented in subclass.
33
29
  def key
34
30
  raise 'Not implemented'
35
31
  end
36
32
 
37
- # Internal: The key where details about this gate can be retrieved from the
38
- # adapter.
39
- def adapter_key
40
- @key ||= Key.new(@feature.name, key)
33
+ def data_type
34
+ raise 'Not implemented'
35
+ end
36
+
37
+ def enable(thing)
38
+ raise 'Not implemented'
39
+ end
40
+
41
+ def disable(thing)
42
+ raise 'Not implemented'
41
43
  end
42
44
 
43
- # Internal: The toggle class to use for this gate.
44
- def toggle_class
45
- Toggles::Value
45
+ def enabled?(value)
46
+ raise 'Not implemented'
46
47
  end
47
48
 
48
- # Internal: The toggle to use to enable/disable this gate.
49
- def toggle
50
- @toggle ||= toggle_class.new(self)
49
+ def description(value)
50
+ raise 'Not implemented'
51
51
  end
52
52
 
53
53
  # Internal: Check if a gate is open for a thing. Implemented in subclass.
@@ -64,33 +64,16 @@ module Flipper
64
64
  false
65
65
  end
66
66
 
67
- # Internal: Enable this gate for a thing.
68
- #
69
- # Returns the result of Flipper::Toggle#enable.
70
- def enable(thing)
71
- toggle.enable(thing)
72
- end
73
-
74
- # Internal: Disable this gate for a thing.
75
- #
76
- # Returns the result of Flipper::Toggle#disable.
77
- def disable(thing)
78
- toggle.disable(thing)
79
- end
80
-
81
- def enabled?
82
- toggle.enabled?
67
+ # Internal: Allows gate to wrap thing using one of the supported flipper
68
+ # types so adapters always get something that responds to value.
69
+ def wrap(thing)
70
+ thing
83
71
  end
84
72
 
85
73
  # Public: Pretty string version for debugging.
86
74
  def inspect
87
75
  attributes = [
88
- "feature=#{feature.name.inspect}",
89
- "description=#{description.inspect}",
90
- "adapter=#{adapter.name.inspect}",
91
- "adapter_key=#{adapter_key.inspect}",
92
- "toggle_class=#{toggle_class.inspect}",
93
- "toggle_value=#{toggle.value.inspect}",
76
+ "feature_name=#{feature_name.inspect}",
94
77
  ]
95
78
  "#<#{self.class.name}:#{object_id} #{attributes.join(', ')}>"
96
79
  end
@@ -101,7 +84,7 @@ module Flipper
101
84
  :thing => thing,
102
85
  :operation => operation,
103
86
  :gate_name => name,
104
- :feature_name => @feature.name,
87
+ :feature_name => @feature_name,
105
88
  }
106
89
 
107
90
  @instrumenter.instrument(InstrumentationName, payload) {
@@ -6,27 +6,39 @@ module Flipper
6
6
  :actor
7
7
  end
8
8
 
9
- # Internal: The piece of the adapter key that is unique to the gate class.
9
+ # Internal: Name converted to value safe for adapter.
10
10
  def key
11
11
  :actors
12
12
  end
13
13
 
14
- # Internal: The toggle class used to enable/disable the gate for a thing.
15
- def toggle_class
16
- Toggles::Set
14
+ def data_type
15
+ :set
16
+ end
17
+
18
+ def description(value)
19
+ if enabled?(value)
20
+ actor_ids = value.to_a.sort.map { |id| id.inspect }
21
+ "actors (#{actor_ids.join(', ')})"
22
+ else
23
+ 'disabled'
24
+ end
25
+ end
26
+
27
+ def enabled?(value)
28
+ !value.nil? && !value.empty?
17
29
  end
18
30
 
19
31
  # Internal: Checks if the gate is open for a thing.
20
32
  #
21
33
  # Returns true if gate open for thing, false if not.
22
- def open?(thing)
34
+ def open?(thing, value)
23
35
  instrument(:open?, thing) { |payload|
24
36
  if thing.nil?
25
37
  false
26
38
  else
27
- if Types::Actor.wrappable?(thing)
28
- actor = Types::Actor.wrap(thing)
29
- enabled_actor_ids = toggle.value
39
+ if protects?(thing)
40
+ actor = wrap(thing)
41
+ enabled_actor_ids = value
30
42
  enabled_actor_ids.include?(actor.value)
31
43
  else
32
44
  false
@@ -35,24 +47,12 @@ module Flipper
35
47
  }
36
48
  end
37
49
 
38
- def protects?(thing)
39
- Types::Actor.wrappable?(thing)
40
- end
41
-
42
- def enable(thing)
43
- super Types::Actor.wrap(thing)
50
+ def wrap(thing)
51
+ Types::Actor.wrap(thing)
44
52
  end
45
53
 
46
- def disable(thing)
47
- super Types::Actor.wrap(thing)
48
- end
49
-
50
- def description
51
- if enabled?
52
- "actors (#{toggle.value.to_a.sort.join(', ')})"
53
- else
54
- 'disabled'
55
- end
54
+ def protects?(thing)
55
+ Types::Actor.wrappable?(thing)
56
56
  end
57
57
  end
58
58
  end
@@ -1,39 +1,52 @@
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
+
4
11
  # Internal: The name of the gate. Used for instrumentation, etc.
5
12
  def name
6
13
  :boolean
7
14
  end
8
15
 
9
- # Internal: The piece of the adapter key that is unique to the gate class.
16
+ # Internal: Name converted to value safe for adapter.
10
17
  def key
11
18
  :boolean
12
19
  end
13
20
 
14
- # Internal: The toggle class used to enable/disable the gate for a thing.
15
- def toggle_class
16
- Toggles::Boolean
21
+ def data_type
22
+ :boolean
23
+ end
24
+
25
+ def description(value)
26
+ if enabled?(value)
27
+ 'Enabled'
28
+ else
29
+ 'Disabled'
30
+ end
31
+ end
32
+
33
+ def enabled?(value)
34
+ !!TruthMap[value]
17
35
  end
18
36
 
19
37
  # Internal: Checks if the gate is open for a thing.
20
38
  #
21
- # Returns true if gate open for thing, false if not.
22
- def open?(thing)
23
- instrument(:open?, thing) { |payload| toggle.value }
39
+ # Returns true if explicitly set to true, false if explicitly set to false
40
+ # or nil if not explicitly set.
41
+ def open?(thing, value)
42
+ instrument(:open?, thing) { |payload|
43
+ !!TruthMap[value]
44
+ }
24
45
  end
25
46
 
26
47
  def protects?(thing)
27
48
  thing.is_a?(Flipper::Types::Boolean)
28
49
  end
29
-
30
- def description
31
- if enabled?
32
- 'Enabled'
33
- else
34
- 'Disabled'
35
- end
36
- end
37
50
  end
38
51
  end
39
52
  end
@@ -6,25 +6,44 @@ module Flipper
6
6
  :group
7
7
  end
8
8
 
9
- # Internal: The piece of the adapter key that is unique to the gate class.
9
+ # Internal: Name converted to value safe for adapter.
10
10
  def key
11
11
  :groups
12
12
  end
13
13
 
14
- # Internal: The toggle class used to enable/disable the gate for a thing.
15
- def toggle_class
16
- Toggles::Set
14
+ def data_type
15
+ :set
16
+ end
17
+
18
+ def description(value)
19
+ if enabled?(value)
20
+ group_names = value.to_a.sort.map { |name| name.to_sym.inspect }
21
+ "groups (#{group_names.join(', ')})"
22
+ else
23
+ 'disabled'
24
+ end
25
+ end
26
+
27
+ def enabled?(value)
28
+ !value.nil? && !value.empty?
17
29
  end
18
30
 
19
31
  # Internal: Checks if the gate is open for a thing.
20
32
  #
21
33
  # Returns true if gate open for thing, false if not.
22
- def open?(thing)
34
+ def open?(thing, value)
23
35
  instrument(:open?, thing) { |payload|
24
36
  if thing.nil?
25
37
  false
26
38
  else
27
- enabled_groups.any? { |group| group.match?(thing) }
39
+ value.any? { |name|
40
+ begin
41
+ group = Flipper.group(name)
42
+ group.match?(thing)
43
+ rescue GroupNotRegistered
44
+ false
45
+ end
46
+ }
28
47
  end
29
48
  }
30
49
  end
@@ -32,34 +51,6 @@ module Flipper
32
51
  def protects?(thing)
33
52
  thing.is_a?(Flipper::Types::Group)
34
53
  end
35
-
36
- def description
37
- if enabled?
38
- "groups (#{toggle.value.to_a.sort.join(', ')})"
39
- else
40
- 'disabled'
41
- end
42
- end
43
-
44
- # Private: Get all the enabled groups for this gate.
45
- #
46
- # Returns an Array of Flipper::Types::Group instances.
47
- def enabled_groups
48
- enabled_group_names.map { |name|
49
- begin
50
- Flipper.group(name)
51
- rescue GroupNotRegistered
52
- nil
53
- end
54
- }.compact
55
- end
56
-
57
- # Private: Get all the names of enabled groups.
58
- #
59
- # Returns a Set of the enabled group names.
60
- def enabled_group_names
61
- toggle.value
62
- end
63
54
  end
64
55
  end
65
56
  end
@@ -8,21 +8,37 @@ module Flipper
8
8
  :percentage_of_actors
9
9
  end
10
10
 
11
- # Internal: The piece of the adapter key that is unique to the gate class.
11
+ # Internal: Name converted to value safe for adapter.
12
12
  def key
13
- :perc_actors
13
+ :percentage_of_actors
14
+ end
15
+
16
+ def data_type
17
+ :integer
18
+ end
19
+
20
+ def description(value)
21
+ if enabled?(value)
22
+ "#{value}% of actors"
23
+ else
24
+ 'disabled'
25
+ end
26
+ end
27
+
28
+ def enabled?(value)
29
+ !value.nil? && value.to_i > 0
14
30
  end
15
31
 
16
32
  # Internal: Checks if the gate is open for a thing.
17
33
  #
18
34
  # Returns true if gate open for thing, false if not.
19
- def open?(thing)
35
+ def open?(thing, value)
20
36
  instrument(:open?, thing) { |payload|
21
- percentage = toggle.value.to_i
37
+ percentage = value.to_i
22
38
 
23
39
  if Types::Actor.wrappable?(thing)
24
40
  actor = Types::Actor.wrap(thing)
25
- key = "#{@feature.name}#{actor.value}"
41
+ key = "#{@feature_name}#{actor.value}"
26
42
  Zlib.crc32(key) % 100 < percentage
27
43
  else
28
44
  false
@@ -33,14 +49,6 @@ module Flipper
33
49
  def protects?(thing)
34
50
  thing.is_a?(Flipper::Types::PercentageOfActors)
35
51
  end
36
-
37
- def description
38
- if enabled?
39
- "#{toggle.value}% of actors"
40
- else
41
- 'disabled'
42
- end
43
- end
44
52
  end
45
53
  end
46
54
  end
@@ -6,17 +6,33 @@ module Flipper
6
6
  :percentage_of_random
7
7
  end
8
8
 
9
- # Internal: The piece of the adapter key that is unique to the gate class.
9
+ # Internal: Name converted to value safe for adapter.
10
10
  def key
11
- :perc_time
11
+ :percentage_of_random
12
+ end
13
+
14
+ def data_type
15
+ :integer
16
+ end
17
+
18
+ def description(value)
19
+ if enabled?(value)
20
+ "#{value}% of the time"
21
+ else
22
+ 'disabled'
23
+ end
24
+ end
25
+
26
+ def enabled?(value)
27
+ !value.nil? && value.to_i > 0
12
28
  end
13
29
 
14
30
  # Internal: Checks if the gate is open for a thing.
15
31
  #
16
32
  # Returns true if gate open for thing, false if not.
17
- def open?(thing)
33
+ def open?(thing, value)
18
34
  instrument(:open?, thing) { |payload|
19
- percentage = toggle.value.to_i
35
+ percentage = value.to_i
20
36
 
21
37
  rand < (percentage / 100.0)
22
38
  }
@@ -25,14 +41,6 @@ module Flipper
25
41
  def protects?(thing)
26
42
  thing.is_a?(Flipper::Types::PercentageOfRandom)
27
43
  end
28
-
29
- def description
30
- if enabled?
31
- "#{toggle.value}% of the time"
32
- else
33
- 'disabled'
34
- end
35
- end
36
44
  end
37
45
  end
38
46
  end