flipper 0.10.2 → 0.11.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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +42 -0
  3. data/.rubocop_todo.yml +188 -0
  4. data/Changelog.md +10 -0
  5. data/Gemfile +6 -3
  6. data/README.md +4 -3
  7. data/Rakefile +13 -13
  8. data/docs/Adapters.md +2 -1
  9. data/docs/DockerCompose.md +6 -3
  10. data/docs/Gates.md +25 -3
  11. data/docs/Optimization.md +27 -5
  12. data/docs/api/README.md +73 -32
  13. data/docs/http/README.md +34 -0
  14. data/docs/read-only/README.md +22 -0
  15. data/examples/percentage_of_actors_group.rb +49 -0
  16. data/flipper.gemspec +15 -15
  17. data/lib/flipper.rb +2 -5
  18. data/lib/flipper/adapter.rb +10 -0
  19. data/lib/flipper/adapters/http.rb +147 -0
  20. data/lib/flipper/adapters/http/client.rb +83 -0
  21. data/lib/flipper/adapters/http/error.rb +14 -0
  22. data/lib/flipper/adapters/instrumented.rb +36 -36
  23. data/lib/flipper/adapters/memoizable.rb +2 -6
  24. data/lib/flipper/adapters/memory.rb +10 -9
  25. data/lib/flipper/adapters/operation_logger.rb +1 -1
  26. data/lib/flipper/adapters/pstore.rb +12 -11
  27. data/lib/flipper/adapters/read_only.rb +6 -6
  28. data/lib/flipper/dsl.rb +1 -3
  29. data/lib/flipper/feature.rb +11 -16
  30. data/lib/flipper/gate.rb +3 -3
  31. data/lib/flipper/gate_values.rb +6 -6
  32. data/lib/flipper/gates/group.rb +2 -2
  33. data/lib/flipper/gates/percentage_of_actors.rb +2 -2
  34. data/lib/flipper/instrumentation/log_subscriber.rb +2 -4
  35. data/lib/flipper/instrumentation/metriks.rb +1 -1
  36. data/lib/flipper/instrumentation/statsd.rb +1 -1
  37. data/lib/flipper/instrumentation/statsd_subscriber.rb +1 -3
  38. data/lib/flipper/instrumentation/subscriber.rb +11 -10
  39. data/lib/flipper/instrumenters/memory.rb +1 -5
  40. data/lib/flipper/instrumenters/noop.rb +1 -1
  41. data/lib/flipper/middleware/memoizer.rb +11 -27
  42. data/lib/flipper/middleware/setup_env.rb +44 -0
  43. data/lib/flipper/registry.rb +8 -10
  44. data/lib/flipper/spec/shared_adapter_specs.rb +45 -67
  45. data/lib/flipper/test/shared_adapter_test.rb +25 -31
  46. data/lib/flipper/typecast.rb +2 -2
  47. data/lib/flipper/types/actor.rb +2 -4
  48. data/lib/flipper/types/group.rb +1 -1
  49. data/lib/flipper/types/percentage.rb +2 -1
  50. data/lib/flipper/version.rb +1 -1
  51. data/spec/fixtures/feature.json +31 -0
  52. data/spec/flipper/adapters/http_spec.rb +148 -0
  53. data/spec/flipper/adapters/instrumented_spec.rb +20 -20
  54. data/spec/flipper/adapters/memoizable_spec.rb +59 -59
  55. data/spec/flipper/adapters/operation_logger_spec.rb +16 -16
  56. data/spec/flipper/adapters/pstore_spec.rb +6 -6
  57. data/spec/flipper/adapters/read_only_spec.rb +28 -34
  58. data/spec/flipper/dsl_spec.rb +73 -84
  59. data/spec/flipper/feature_check_context_spec.rb +27 -27
  60. data/spec/flipper/feature_spec.rb +186 -196
  61. data/spec/flipper/gate_spec.rb +11 -11
  62. data/spec/flipper/gate_values_spec.rb +46 -45
  63. data/spec/flipper/gates/actor_spec.rb +2 -2
  64. data/spec/flipper/gates/boolean_spec.rb +24 -23
  65. data/spec/flipper/gates/group_spec.rb +19 -19
  66. data/spec/flipper/gates/percentage_of_actors_spec.rb +10 -10
  67. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
  68. data/spec/flipper/instrumentation/log_subscriber_spec.rb +20 -20
  69. data/spec/flipper/instrumentation/metriks_subscriber_spec.rb +20 -20
  70. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +11 -11
  71. data/spec/flipper/instrumenters/memory_spec.rb +5 -5
  72. data/spec/flipper/instrumenters/noop_spec.rb +6 -6
  73. data/spec/flipper/middleware/memoizer_spec.rb +83 -100
  74. data/spec/flipper/middleware/setup_env_spec.rb +76 -0
  75. data/spec/flipper/registry_spec.rb +35 -39
  76. data/spec/flipper/typecast_spec.rb +18 -18
  77. data/spec/flipper/types/actor_spec.rb +30 -29
  78. data/spec/flipper/types/boolean_spec.rb +8 -8
  79. data/spec/flipper/types/group_spec.rb +28 -28
  80. data/spec/flipper/types/percentage_spec.rb +14 -14
  81. data/spec/flipper_spec.rb +61 -54
  82. data/spec/helper.rb +26 -21
  83. data/spec/integration_spec.rb +121 -113
  84. data/spec/support/fake_udp_socket.rb +1 -1
  85. data/spec/support/spec_helpers.rb +32 -4
  86. data/test/adapters/pstore_test.rb +3 -3
  87. data/test/test_helper.rb +1 -1
  88. metadata +20 -5
@@ -19,7 +19,7 @@ module Flipper
19
19
  :get_multi,
20
20
  :enable,
21
21
  :disable,
22
- ]
22
+ ].freeze
23
23
 
24
24
  # Internal: An array of the operations that have happened.
25
25
  attr_reader :operations
@@ -1,5 +1,5 @@
1
- require "pstore"
2
- require "set"
1
+ require 'pstore'
2
+ require 'set'
3
3
 
4
4
  module Flipper
5
5
  module Adapters
@@ -17,7 +17,7 @@ module Flipper
17
17
  attr_reader :path
18
18
 
19
19
  # Public
20
- def initialize(path = "flipper.pstore")
20
+ def initialize(path = 'flipper.pstore')
21
21
  @path = path
22
22
  @store = ::PStore.new(path)
23
23
  @name = :pstore
@@ -55,14 +55,15 @@ module Flipper
55
55
  result = {}
56
56
 
57
57
  feature.gates.each do |gate|
58
- result[gate.key] = case gate.data_type
59
- when :boolean, :integer
60
- read key(feature, gate)
61
- when :set
62
- set_members key(feature, gate)
63
- else
64
- raise "#{gate} is not supported by this adapter yet"
65
- end
58
+ result[gate.key] =
59
+ case gate.data_type
60
+ when :boolean, :integer
61
+ read key(feature, gate)
62
+ when :set
63
+ set_members key(feature, gate)
64
+ else
65
+ raise "#{gate} is not supported by this adapter yet"
66
+ end
66
67
  end
67
68
 
68
69
  result
@@ -6,7 +6,7 @@ module Flipper
6
6
 
7
7
  class WriteAttempted < Error
8
8
  def initialize(message = nil)
9
- super(message || "write attempted while in read only mode")
9
+ super(message || 'write attempted while in read only mode')
10
10
  end
11
11
  end
12
12
 
@@ -27,23 +27,23 @@ module Flipper
27
27
  @adapter.get(feature)
28
28
  end
29
29
 
30
- def add(feature)
30
+ def add(_feature)
31
31
  raise WriteAttempted
32
32
  end
33
33
 
34
- def remove(feature)
34
+ def remove(_feature)
35
35
  raise WriteAttempted
36
36
  end
37
37
 
38
- def clear(feature)
38
+ def clear(_feature)
39
39
  raise WriteAttempted
40
40
  end
41
41
 
42
- def enable(feature, gate, thing)
42
+ def enable(_feature, _gate, _thing)
43
43
  raise WriteAttempted
44
44
  end
45
45
 
46
- def disable(feature, gate, thing)
46
+ def disable(_feature, _gate, _thing)
47
47
  raise WriteAttempted
48
48
  end
49
49
  end
@@ -158,9 +158,7 @@ module Flipper
158
158
  raise ArgumentError, "#{name} must be a String or Symbol"
159
159
  end
160
160
 
161
- @memoized_features[name.to_sym] ||= Feature.new(name, @adapter, {
162
- :instrumenter => instrumenter,
163
- })
161
+ @memoized_features[name.to_sym] ||= Feature.new(name, @adapter, instrumenter: instrumenter)
164
162
  end
165
163
 
166
164
  # Public: Preload the features with the given names.
@@ -41,7 +41,7 @@ module Flipper
41
41
  #
42
42
  # Returns the result of Adapter#enable.
43
43
  def enable(thing = true)
44
- instrument(:enable) { |payload|
44
+ instrument(:enable) do |payload|
45
45
  adapter.add self
46
46
 
47
47
  gate = gate_for(thing)
@@ -50,14 +50,14 @@ module Flipper
50
50
  payload[:thing] = wrapped_thing
51
51
 
52
52
  adapter.enable self, gate, wrapped_thing
53
- }
53
+ end
54
54
  end
55
55
 
56
56
  # Public: Disable this feature for something.
57
57
  #
58
58
  # Returns the result of Adapter#disable.
59
59
  def disable(thing = false)
60
- instrument(:disable) { |payload|
60
+ instrument(:disable) do |payload|
61
61
  adapter.add self
62
62
 
63
63
  gate = gate_for(thing)
@@ -65,12 +65,8 @@ module Flipper
65
65
  payload[:gate_name] = gate.name
66
66
  payload[:thing] = wrapped_thing
67
67
 
68
- if gate.is_a?(Gates::Boolean)
69
- adapter.clear self
70
- else
71
- adapter.disable self, gate, wrapped_thing
72
- end
73
- }
68
+ adapter.disable self, gate, wrapped_thing
69
+ end
74
70
  end
75
71
 
76
72
  # Public: Removes this feature.
@@ -84,14 +80,14 @@ module Flipper
84
80
  #
85
81
  # Returns true if enabled, false if not.
86
82
  def enabled?(thing = nil)
87
- instrument(:enabled?) { |payload|
83
+ instrument(:enabled?) do |payload|
88
84
  values = gate_values
89
85
  thing = gate(:actor).wrap(thing) unless thing.nil?
90
86
  payload[:thing] = thing
91
87
  context = FeatureCheckContext.new(
92
88
  feature_name: @name,
93
89
  values: values,
94
- thing: thing,
90
+ thing: thing
95
91
  )
96
92
 
97
93
  if open_gate = gates.detect { |gate| gate.open?(context) }
@@ -100,7 +96,7 @@ module Flipper
100
96
  else
101
97
  false
102
98
  end
103
- }
99
+ end
104
100
  end
105
101
 
106
102
  # Public: Enables a feature for an actor.
@@ -346,19 +342,18 @@ module Flipper
346
342
  # Returns a Flipper::Gate.
347
343
  # Raises Flipper::GateNotFound if no gate found for thing
348
344
  def gate_for(thing)
349
- gates.detect { |gate| gate.protects?(thing) } ||
350
- raise(GateNotFound.new(thing))
345
+ gates.detect { |gate| gate.protects?(thing) } || raise(GateNotFound, thing)
351
346
  end
352
347
 
353
348
  private
354
349
 
355
350
  # Private: Instrument a feature operation.
356
351
  def instrument(operation)
357
- @instrumenter.instrument(InstrumentationName) { |payload|
352
+ @instrumenter.instrument(InstrumentationName) do |payload|
358
353
  payload[:feature_name] = name
359
354
  payload[:operation] = operation
360
355
  payload[:result] = yield(payload) if block_given?
361
- }
356
+ end
362
357
  end
363
358
  end
364
359
  end
@@ -18,21 +18,21 @@ module Flipper
18
18
  raise 'Not implemented'
19
19
  end
20
20
 
21
- def enabled?(value)
21
+ def enabled?(_value)
22
22
  raise 'Not implemented'
23
23
  end
24
24
 
25
25
  # Internal: Check if a gate is open for a thing. Implemented in subclass.
26
26
  #
27
27
  # Returns true if gate open for thing, false if not.
28
- def open?(thing, value, options = {})
28
+ def open?(_thing, _value, _options = {})
29
29
  false
30
30
  end
31
31
 
32
32
  # Internal: Check if a gate is protects a thing. Implemented in subclass.
33
33
  #
34
34
  # Returns true if gate protects thing, false if not.
35
- def protects?(thing)
35
+ def protects?(_thing)
36
36
  false
37
37
  end
38
38
 
@@ -1,15 +1,15 @@
1
- require "set"
1
+ require 'set'
2
2
 
3
3
  module Flipper
4
4
  class GateValues
5
5
  # Private: Array of instance variables that are readable through the []
6
6
  # instance method.
7
7
  LegitIvars = {
8
- "boolean" => "@boolean",
9
- "actors" => "@actors",
10
- "groups" => "@groups",
11
- "percentage_of_time" => "@percentage_of_time",
12
- "percentage_of_actors" => "@percentage_of_actors",
8
+ 'boolean' => '@boolean',
9
+ 'actors' => '@actors',
10
+ 'groups' => '@groups',
11
+ 'percentage_of_time' => '@percentage_of_time',
12
+ 'percentage_of_actors' => '@percentage_of_actors',
13
13
  }.freeze
14
14
 
15
15
  attr_reader :boolean
@@ -27,14 +27,14 @@ module Flipper
27
27
  if context.thing.nil?
28
28
  false
29
29
  else
30
- value.any? { |name|
30
+ value.any? do |name|
31
31
  begin
32
32
  group = Flipper.group(name)
33
33
  group.match?(context.thing, context)
34
34
  rescue GroupNotRegistered
35
35
  false
36
36
  end
37
- }
37
+ end
38
38
  end
39
39
  end
40
40
 
@@ -29,8 +29,8 @@ module Flipper
29
29
 
30
30
  if Types::Actor.wrappable?(context.thing)
31
31
  actor = Types::Actor.wrap(context.thing)
32
- key = "#{context.feature_name}#{actor.value}"
33
- Zlib.crc32(key) % 100 < percentage
32
+ id = "#{context.feature_name}#{actor.value}"
33
+ Zlib.crc32(id) % 100 < percentage
34
34
  else
35
35
  false
36
36
  end
@@ -25,9 +25,7 @@ module Flipper
25
25
  description = "Flipper feature(#{feature_name}) #{operation} #{result.inspect}"
26
26
  details = "thing=#{thing.inspect}"
27
27
 
28
- unless gate_name.nil?
29
- details += " gate_name=#{gate_name}"
30
- end
28
+ details += " gate_name=#{gate_name}" unless gate_name.nil?
31
29
 
32
30
  name = '%s (%.1fms)' % [description, event.duration]
33
31
  debug " #{color(name, CYAN, true)} [ #{details} ]"
@@ -54,7 +52,7 @@ module Flipper
54
52
  operation = event.payload[:operation]
55
53
  result = event.payload[:result]
56
54
 
57
- description = "Flipper "
55
+ description = 'Flipper '
58
56
  description << "feature(#{feature_name}) " unless feature_name.nil?
59
57
  description << "adapter(#{adapter_name}) "
60
58
  description << "#{operation} "
@@ -3,4 +3,4 @@ require 'active_support/notifications'
3
3
  require 'flipper/instrumentation/metriks_subscriber'
4
4
 
5
5
  ActiveSupport::Notifications.subscribe /\.flipper$/,
6
- Flipper::Instrumentation::MetriksSubscriber
6
+ Flipper::Instrumentation::MetriksSubscriber
@@ -3,4 +3,4 @@ require 'active_support/notifications'
3
3
  require 'flipper/instrumentation/statsd_subscriber'
4
4
 
5
5
  ActiveSupport::Notifications.subscribe /\.flipper$/,
6
- Flipper::Instrumentation::StatsdSubscriber
6
+ Flipper::Instrumentation::StatsdSubscriber
@@ -18,9 +18,7 @@ module Flipper
18
18
  end
19
19
 
20
20
  def update_counter(metric)
21
- if self.class.client
22
- self.class.client.increment metric
23
- end
21
+ self.class.client.increment metric if self.class.client
24
22
  end
25
23
  end
26
24
  end
@@ -17,12 +17,12 @@ module Flipper
17
17
  end
18
18
 
19
19
  # Internal: Override in subclass.
20
- def update_timer(metric)
20
+ def update_timer(_metric)
21
21
  raise 'not implemented'
22
22
  end
23
23
 
24
24
  # Internal: Override in subclass.
25
- def update_counter(metric)
25
+ def update_counter(_metric)
26
26
  raise 'not implemented'
27
27
  end
28
28
 
@@ -34,7 +34,8 @@ module Flipper
34
34
  if respond_to?(method_name)
35
35
  send(method_name)
36
36
  else
37
- puts "Could not update #{operation_type} metrics as #{self.class} did not respond to `#{method_name}`"
37
+ puts "Could not update #{operation_type} metrics as #{self.class} " \
38
+ "did not respond to `#{method_name}`"
38
39
  end
39
40
  end
40
41
 
@@ -49,11 +50,12 @@ module Flipper
49
50
  update_timer "flipper.feature_operation.#{operation}"
50
51
 
51
52
  if @payload[:operation] == :enabled?
52
- metric_name = if result
53
- "flipper.feature.#{feature_name}.enabled"
54
- else
55
- "flipper.feature.#{feature_name}.disabled"
56
- end
53
+ metric_name =
54
+ if result
55
+ "flipper.feature.#{feature_name}.enabled"
56
+ else
57
+ "flipper.feature.#{feature_name}.disabled"
58
+ end
57
59
 
58
60
  update_counter metric_name
59
61
  end
@@ -67,11 +69,10 @@ module Flipper
67
69
  value = @payload[:value]
68
70
  key = @payload[:key]
69
71
 
70
-
71
72
  update_timer "flipper.adapter.#{adapter_name}.#{operation}"
72
73
  end
73
74
 
74
- QUESTION_MARK = "?".freeze
75
+ QUESTION_MARK = '?'.freeze
75
76
 
76
77
  # Private
77
78
  def strip_trailing_question_mark(operation)
@@ -17,11 +17,7 @@ module Flipper
17
17
  # block rather than the one passed to #instrument.
18
18
  payload = payload.dup
19
19
 
20
- result = if block_given?
21
- yield payload
22
- else
23
- nil
24
- end
20
+ result = (yield payload if block_given?)
25
21
  @events << Event.new(name, payload, result)
26
22
  result
27
23
  end
@@ -1,7 +1,7 @@
1
1
  module Flipper
2
2
  module Instrumenters
3
3
  class Noop
4
- def self.instrument(name, payload = {})
4
+ def self.instrument(_name, payload = {})
5
5
  yield payload if block_given?
6
6
  end
7
7
  end
@@ -3,43 +3,29 @@ require 'rack/body_proxy'
3
3
  module Flipper
4
4
  module Middleware
5
5
  class Memoizer
6
- # Public: Initializes an instance of the Memoizer middleware.
6
+ # Public: Initializes an instance of the Memoizer middleware. The flipper
7
+ # instance must be setup in the env of the request. You can do this by
8
+ # using the Flipper::Middleware::SetupEnv middleware.
7
9
  #
8
10
  # app - The app this middleware is included in.
9
- # flipper_or_block - The Flipper::DSL instance or a block that yields a
10
- # Flipper::DSL instance to use for all operations.
11
11
  #
12
12
  # Examples
13
13
  #
14
- # # using with a normal flipper instance
15
- # flipper = Flipper.new(...)
16
- # use Flipper::Middleware::Memoizer, flipper
17
- #
18
- # # using with a block that yields a flipper instance
19
- # use Flipper::Middleware::Memoizer, lambda { Flipper.new(...) }
14
+ # use Flipper::Middleware::Memoizer
20
15
  #
21
16
  # # using with preload_all features
22
- # use Flipper::Middleware::Memoizer, flipper, preload_all: true
17
+ # use Flipper::Middleware::Memoizer, preload_all: true
23
18
  #
24
19
  # # using with preload specific features
25
- # use Flipper::Middleware::Memoizer, flipper, preload: [:stats, :search, :some_feature]
20
+ # use Flipper::Middleware::Memoizer, preload: [:stats, :search, :some_feature]
26
21
  #
27
- def initialize(app, flipper_or_block, opts = {})
22
+ def initialize(app, opts = {})
28
23
  @app = app
29
24
  @opts = opts
30
-
31
- if flipper_or_block.respond_to?(:call)
32
- @flipper_block = flipper_or_block
33
- else
34
- @flipper = flipper_or_block
35
- end
36
- end
37
-
38
- def flipper
39
- @flipper ||= @flipper_block.call
40
25
  end
41
26
 
42
27
  def call(env)
28
+ flipper = env.fetch('flipper')
43
29
  original = flipper.adapter.memoizing?
44
30
  flipper.adapter.memoize = true
45
31
 
@@ -48,14 +34,12 @@ module Flipper
48
34
  flipper.preload(names)
49
35
  end
50
36
 
51
- if @opts[:preload]
52
- flipper.preload(@opts[:preload])
53
- end
37
+ flipper.preload(@opts[:preload]) if @opts[:preload]
54
38
 
55
39
  response = @app.call(env)
56
- response[2] = Rack::BodyProxy.new(response[2]) {
40
+ response[2] = Rack::BodyProxy.new(response[2]) do
57
41
  flipper.adapter.memoize = original
58
- }
42
+ end
59
43
  response
60
44
  rescue
61
45
  flipper.adapter.memoize = original