flipper 0.10.2 → 0.11.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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