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
@@ -3,14 +3,14 @@ require 'helper'
3
3
  RSpec.describe Flipper::Gate do
4
4
  let(:feature_name) { :stats }
5
5
 
6
- subject {
6
+ subject do
7
7
  described_class.new
8
- }
8
+ end
9
9
 
10
- describe "#inspect" do
11
- context "for subclass" do
12
- let(:subclass) {
13
- Class.new(described_class) {
10
+ describe '#inspect' do
11
+ context 'for subclass' do
12
+ let(:subclass) do
13
+ Class.new(described_class) do
14
14
  def name
15
15
  :name
16
16
  end
@@ -22,14 +22,14 @@ RSpec.describe Flipper::Gate do
22
22
  def data_type
23
23
  :set
24
24
  end
25
- }
26
- }
25
+ end
26
+ end
27
27
 
28
- subject {
28
+ subject do
29
29
  subclass.new
30
- }
30
+ end
31
31
 
32
- it "includes attributes" do
32
+ it 'includes attributes' do
33
33
  string = subject.inspect
34
34
  expect(string).to include(subject.object_id.to_s)
35
35
  expect(string).to include('name=:name')
@@ -4,15 +4,15 @@ require 'flipper/gate_values'
4
4
  RSpec.describe Flipper::GateValues do
5
5
  {
6
6
  nil => false,
7
- "" => false,
7
+ '' => false,
8
8
  0 => false,
9
9
  1 => true,
10
- "0" => false,
11
- "1" => true,
10
+ '0' => false,
11
+ '1' => true,
12
12
  true => true,
13
13
  false => false,
14
- "true" => true,
15
- "false" => false,
14
+ 'true' => true,
15
+ 'false' => false,
16
16
  }.each do |value, expected|
17
17
  context "with #{value.inspect} boolean" do
18
18
  it "returns #{expected}" do
@@ -23,11 +23,11 @@ RSpec.describe Flipper::GateValues do
23
23
 
24
24
  {
25
25
  nil => 0,
26
- "" => 0,
26
+ '' => 0,
27
27
  0 => 0,
28
28
  1 => 1,
29
- "1" => 1,
30
- "99" => 99,
29
+ '1' => 1,
30
+ '99' => 99,
31
31
  }.each do |value, expected|
32
32
  context "with #{value.inspect} percentage of time" do
33
33
  it "returns #{expected}" do
@@ -38,24 +38,25 @@ RSpec.describe Flipper::GateValues do
38
38
 
39
39
  {
40
40
  nil => 0,
41
- "" => 0,
41
+ '' => 0,
42
42
  0 => 0,
43
43
  1 => 1,
44
- "1" => 1,
45
- "99" => 99,
44
+ '1' => 1,
45
+ '99' => 99,
46
46
  }.each do |value, expected|
47
47
  context "with #{value.inspect} percentage of actors" do
48
48
  it "returns #{expected}" do
49
- expect(described_class.new(percentage_of_actors: value).percentage_of_actors).to be(expected)
49
+ expect(described_class.new(percentage_of_actors: value).percentage_of_actors)
50
+ .to be(expected)
50
51
  end
51
52
  end
52
53
  end
53
54
 
54
55
  {
55
56
  nil => Set.new,
56
- "" => Set.new,
57
+ '' => Set.new,
57
58
  Set.new([1, 2]) => Set.new([1, 2]),
58
- [1, 2] => Set.new([1, 2])
59
+ [1, 2] => Set.new([1, 2]),
59
60
  }.each do |value, expected|
60
61
  context "with #{value.inspect} actors" do
61
62
  it "returns #{expected}" do
@@ -66,9 +67,9 @@ RSpec.describe Flipper::GateValues do
66
67
 
67
68
  {
68
69
  nil => Set.new,
69
- "" => Set.new,
70
+ '' => Set.new,
70
71
  Set.new([:admins, :preview_features]) => Set.new([:admins, :preview_features]),
71
- [:admins, :preview_features] => Set.new([:admins, :preview_features])
72
+ [:admins, :preview_features] => Set.new([:admins, :preview_features]),
72
73
  }.each do |value, expected|
73
74
  context "with #{value.inspect} groups" do
74
75
  it "returns #{expected}" do
@@ -77,58 +78,58 @@ RSpec.describe Flipper::GateValues do
77
78
  end
78
79
  end
79
80
 
80
- it "raises argument error for percentage of time value that cannot be converted to an integer" do
81
- expect {
82
- described_class.new(percentage_of_time: ["asdf"])
83
- }.to raise_error(ArgumentError, %Q(["asdf"] cannot be converted to an integer))
81
+ it 'raises argument error for percentage of time value that cannot be converted to an integer' do
82
+ expect do
83
+ described_class.new(percentage_of_time: ['asdf'])
84
+ end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to an integer))
84
85
  end
85
86
 
86
- it "raises argument error for percentage of actors value that cannot be converted to an integer" do
87
- expect {
88
- described_class.new(percentage_of_actors: ["asdf"])
89
- }.to raise_error(ArgumentError, %Q(["asdf"] cannot be converted to an integer))
87
+ it 'raises argument error for percentage of actors value that cannot be converted to an int' do
88
+ expect do
89
+ described_class.new(percentage_of_actors: ['asdf'])
90
+ end.to raise_error(ArgumentError, %(["asdf"] cannot be converted to an integer))
90
91
  end
91
92
 
92
- it "raises argument error for actors value that cannot be converted to a set" do
93
- expect {
94
- described_class.new(actors: "asdf")
95
- }.to raise_error(ArgumentError, %Q("asdf" cannot be converted to a set))
93
+ it 'raises argument error for actors value that cannot be converted to a set' do
94
+ expect do
95
+ described_class.new(actors: 'asdf')
96
+ end.to raise_error(ArgumentError, %("asdf" cannot be converted to a set))
96
97
  end
97
98
 
98
- it "raises argument error for groups value that cannot be converted to a set" do
99
- expect {
100
- described_class.new(groups: "asdf")
101
- }.to raise_error(ArgumentError, %Q("asdf" cannot be converted to a set))
99
+ it 'raises argument error for groups value that cannot be converted to a set' do
100
+ expect do
101
+ described_class.new(groups: 'asdf')
102
+ end.to raise_error(ArgumentError, %("asdf" cannot be converted to a set))
102
103
  end
103
104
 
104
- describe "#[]" do
105
- it "can read the boolean value" do
105
+ describe '#[]' do
106
+ it 'can read the boolean value' do
106
107
  expect(described_class.new(boolean: true)[:boolean]).to be(true)
107
- expect(described_class.new(boolean: true)["boolean"]).to be(true)
108
+ expect(described_class.new(boolean: true)['boolean']).to be(true)
108
109
  end
109
110
 
110
- it "can read the actors value" do
111
+ it 'can read the actors value' do
111
112
  expect(described_class.new(actors: Set[1, 2])[:actors]).to eq(Set[1, 2])
112
- expect(described_class.new(actors: Set[1, 2])["actors"]).to eq(Set[1, 2])
113
+ expect(described_class.new(actors: Set[1, 2])['actors']).to eq(Set[1, 2])
113
114
  end
114
115
 
115
- it "can read the groups value" do
116
+ it 'can read the groups value' do
116
117
  expect(described_class.new(groups: Set[:admins])[:groups]).to eq(Set[:admins])
117
- expect(described_class.new(groups: Set[:admins])["groups"]).to eq(Set[:admins])
118
+ expect(described_class.new(groups: Set[:admins])['groups']).to eq(Set[:admins])
118
119
  end
119
120
 
120
- it "can read the percentage of time value" do
121
+ it 'can read the percentage of time value' do
121
122
  expect(described_class.new(percentage_of_time: 15)[:percentage_of_time]).to eq(15)
122
- expect(described_class.new(percentage_of_time: 15)["percentage_of_time"]).to eq(15)
123
+ expect(described_class.new(percentage_of_time: 15)['percentage_of_time']).to eq(15)
123
124
  end
124
125
 
125
- it "can read the percentage of actors value" do
126
+ it 'can read the percentage of actors value' do
126
127
  expect(described_class.new(percentage_of_actors: 15)[:percentage_of_actors]).to eq(15)
127
- expect(described_class.new(percentage_of_actors: 15)["percentage_of_actors"]).to eq(15)
128
+ expect(described_class.new(percentage_of_actors: 15)['percentage_of_actors']).to eq(15)
128
129
  end
129
130
 
130
- it "returns nil for value that is not present" do
131
- expect(described_class.new({})["not legit"]).to be(nil)
131
+ it 'returns nil for value that is not present' do
132
+ expect(described_class.new({})['not legit']).to be(nil)
132
133
  end
133
134
  end
134
135
  end
@@ -3,7 +3,7 @@ require 'helper'
3
3
  RSpec.describe Flipper::Gates::Actor do
4
4
  let(:feature_name) { :search }
5
5
 
6
- subject {
6
+ subject do
7
7
  described_class.new
8
- }
8
+ end
9
9
  end
@@ -3,71 +3,72 @@ require 'helper'
3
3
  RSpec.describe Flipper::Gates::Boolean do
4
4
  let(:feature_name) { :search }
5
5
 
6
- subject {
6
+ subject do
7
7
  described_class.new
8
- }
8
+ end
9
9
 
10
10
  def context(bool)
11
11
  Flipper::FeatureCheckContext.new(
12
12
  feature_name: feature_name,
13
- values: Flipper::GateValues.new({boolean: bool}),
14
- thing: Flipper::Types::Actor.new(Struct.new(:flipper_id).new(1)),
13
+ values: Flipper::GateValues.new(boolean: bool),
14
+ thing: Flipper::Types::Actor.new(Struct.new(:flipper_id).new(1))
15
15
  )
16
16
  end
17
17
 
18
- describe "#enabled?" do
19
- context "for true value" do
20
- it "returns true" do
18
+ describe '#enabled?' do
19
+ context 'for true value' do
20
+ it 'returns true' do
21
21
  expect(subject.enabled?(true)).to eq(true)
22
22
  end
23
23
  end
24
24
 
25
- context "for false value" do
26
- it "returns false" do
25
+ context 'for false value' do
26
+ it 'returns false' do
27
27
  expect(subject.enabled?(false)).to eq(false)
28
28
  end
29
29
  end
30
30
  end
31
31
 
32
- describe "#open?" do
33
- context "for true value" do
34
- it "returns true" do
32
+ describe '#open?' do
33
+ context 'for true value' do
34
+ it 'returns true' do
35
35
  expect(subject.open?(context(true))).to be(true)
36
36
  end
37
37
  end
38
38
 
39
- context "for false value" do
40
- it "returns false" do
39
+ context 'for false value' do
40
+ it 'returns false' do
41
41
  expect(subject.open?(context(false))).to be(false)
42
42
  end
43
43
  end
44
44
  end
45
45
 
46
- describe "#protects?" do
47
- it "returns true for boolean type" do
46
+ describe '#protects?' do
47
+ it 'returns true for boolean type' do
48
48
  expect(subject.protects?(Flipper::Types::Boolean.new(true))).to be(true)
49
49
  end
50
50
 
51
- it "returns true for true" do
51
+ it 'returns true for true' do
52
52
  expect(subject.protects?(true)).to be(true)
53
53
  end
54
54
 
55
- it "returns true for false" do
55
+ it 'returns true for false' do
56
56
  expect(subject.protects?(false)).to be(true)
57
57
  end
58
58
  end
59
59
 
60
- describe "#wrap" do
61
- it "returns boolean type for boolean type" do
62
- expect(subject.wrap(Flipper::Types::Boolean.new(true))).to be_instance_of(Flipper::Types::Boolean)
60
+ describe '#wrap' do
61
+ it 'returns boolean type for boolean type' do
62
+ expect(subject.wrap(Flipper::Types::Boolean.new(true)))
63
+ .to be_instance_of(Flipper::Types::Boolean)
63
64
  end
64
65
 
65
- it "returns boolean type for true" do
66
+ it 'returns boolean type for true' do
66
67
  expect(subject.wrap(true)).to be_instance_of(Flipper::Types::Boolean)
67
68
  expect(subject.wrap(true).value).to be(true)
68
69
  end
69
70
 
70
- it "returns boolean type for true" do
71
+ it 'returns boolean type for true' do
71
72
  expect(subject.wrap(false)).to be_instance_of(Flipper::Types::Boolean)
72
73
  expect(subject.wrap(false).value).to be(false)
73
74
  end
@@ -3,62 +3,62 @@ require 'helper'
3
3
  RSpec.describe Flipper::Gates::Group do
4
4
  let(:feature_name) { :search }
5
5
 
6
- subject {
6
+ subject do
7
7
  described_class.new
8
- }
8
+ end
9
9
 
10
10
  def context(set)
11
11
  Flipper::FeatureCheckContext.new(
12
12
  feature_name: feature_name,
13
- values: Flipper::GateValues.new({groups: set}),
14
- thing: Flipper::Types::Actor.new(Struct.new(:flipper_id).new("5")),
13
+ values: Flipper::GateValues.new(groups: set),
14
+ thing: Flipper::Types::Actor.new(Struct.new(:flipper_id).new('5'))
15
15
  )
16
16
  end
17
17
 
18
- describe "#open?" do
19
- context "with a group in adapter, but not registered" do
18
+ describe '#open?' do
19
+ context 'with a group in adapter, but not registered' do
20
20
  before do
21
- Flipper.register(:staff) { |thing| true }
21
+ Flipper.register(:staff) { |_thing| true }
22
22
  end
23
23
 
24
- it "ignores group" do
24
+ it 'ignores group' do
25
25
  thing = Struct.new(:flipper_id).new('5')
26
26
  expect(subject.open?(context(Set[:newbs, :staff]))).to be(true)
27
27
  end
28
28
  end
29
29
 
30
- context "thing that does not respond to method in group block" do
30
+ context 'thing that does not respond to method in group block' do
31
31
  before do
32
- Flipper.register(:stinkers) { |thing| thing.stinker? }
32
+ Flipper.register(:stinkers, &:stinker?)
33
33
  end
34
34
 
35
- it "raises error" do
36
- expect {
35
+ it 'raises error' do
36
+ expect do
37
37
  subject.open?(context(Set[:stinkers]))
38
- }.to raise_error(NoMethodError)
38
+ end.to raise_error(NoMethodError)
39
39
  end
40
40
  end
41
41
  end
42
42
 
43
- describe "#wrap" do
44
- it "returns group instance for symbol" do
43
+ describe '#wrap' do
44
+ it 'returns group instance for symbol' do
45
45
  group = Flipper.register(:admins) {}
46
46
  expect(subject.wrap(:admins)).to eq(group)
47
47
  end
48
48
 
49
- it "returns group instance for group instance" do
49
+ it 'returns group instance for group instance' do
50
50
  group = Flipper.register(:admins) {}
51
51
  expect(subject.wrap(group)).to eq(group)
52
52
  end
53
53
  end
54
54
 
55
- describe "#protects?" do
56
- it "returns true for group" do
55
+ describe '#protects?' do
56
+ it 'returns true for group' do
57
57
  group = Flipper.register(:admins) {}
58
58
  expect(subject.protects?(group)).to be(true)
59
59
  end
60
60
 
61
- it "returns true for symbol" do
61
+ it 'returns true for symbol' do
62
62
  expect(subject.protects?(:admins)).to be(true)
63
63
  end
64
64
  end
@@ -3,27 +3,27 @@ require 'helper'
3
3
  RSpec.describe Flipper::Gates::PercentageOfActors do
4
4
  let(:feature_name) { :search }
5
5
 
6
- subject {
6
+ subject do
7
7
  described_class.new
8
- }
8
+ end
9
9
 
10
10
  def context(integer, feature = feature_name, thing = nil)
11
11
  Flipper::FeatureCheckContext.new(
12
12
  feature_name: feature,
13
- values: Flipper::GateValues.new({percentage_of_actors: integer}),
14
- thing: thing || Flipper::Types::Actor.new(Struct.new(:flipper_id).new(1)),
13
+ values: Flipper::GateValues.new(percentage_of_actors: integer),
14
+ thing: thing || Flipper::Types::Actor.new(Struct.new(:flipper_id).new(1))
15
15
  )
16
16
  end
17
17
 
18
- describe "#open?" do
19
- context "when compared against two features" do
18
+ describe '#open?' do
19
+ context 'when compared against two features' do
20
20
  let(:percentage) { 0.05 }
21
21
  let(:percentage_as_integer) { percentage * 100 }
22
22
  let(:number_of_actors) { 100 }
23
23
 
24
- let(:actors) {
24
+ let(:actors) do
25
25
  (1..number_of_actors).map { |n| Struct.new(:flipper_id).new(n) }
26
- }
26
+ end
27
27
 
28
28
  let(:feature_one_enabled_actors) do
29
29
  gate = described_class.new
@@ -35,11 +35,11 @@ RSpec.describe Flipper::Gates::PercentageOfActors do
35
35
  actors.select { |actor| gate.open? context(percentage_as_integer, :name_two, actor) }
36
36
  end
37
37
 
38
- it "does not enable both features for same set of actors" do
38
+ it 'does not enable both features for same set of actors' do
39
39
  expect(feature_one_enabled_actors).not_to eq(feature_two_enabled_actors)
40
40
  end
41
41
 
42
- it "enables feature for accurate number of actors for each feature" do
42
+ it 'enables feature for accurate number of actors for each feature' do
43
43
  margin_of_error = 0.02 * number_of_actors # 2 percent margin of error
44
44
  expected_enabled_size = number_of_actors * percentage
45
45
 
@@ -3,7 +3,7 @@ require 'helper'
3
3
  RSpec.describe Flipper::Gates::PercentageOfTime do
4
4
  let(:feature_name) { :search }
5
5
 
6
- subject {
6
+ subject do
7
7
  described_class.new
8
- }
8
+ end
9
9
  end