flipper 0.20.2 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +9 -1
  3. data/Changelog.md +59 -0
  4. data/Gemfile +1 -0
  5. data/README.md +103 -47
  6. data/docs/Adapters.md +9 -9
  7. data/docs/Caveats.md +2 -2
  8. data/docs/Gates.md +74 -74
  9. data/docs/Optimization.md +70 -47
  10. data/docs/http/README.md +12 -11
  11. data/docs/images/banner.jpg +0 -0
  12. data/docs/read-only/README.md +8 -5
  13. data/examples/basic.rb +1 -12
  14. data/examples/configuring_default.rb +2 -5
  15. data/examples/dsl.rb +13 -24
  16. data/examples/enabled_for_actor.rb +8 -15
  17. data/examples/group.rb +3 -6
  18. data/examples/group_dynamic_lookup.rb +5 -19
  19. data/examples/group_with_members.rb +4 -14
  20. data/examples/importing.rb +1 -1
  21. data/examples/individual_actor.rb +2 -5
  22. data/examples/instrumentation.rb +1 -2
  23. data/examples/memoizing.rb +3 -7
  24. data/examples/percentage_of_actors.rb +6 -16
  25. data/examples/percentage_of_actors_enabled_check.rb +7 -10
  26. data/examples/percentage_of_actors_group.rb +5 -18
  27. data/examples/percentage_of_time.rb +3 -6
  28. data/lib/flipper.rb +4 -1
  29. data/lib/flipper/adapters/memory.rb +20 -94
  30. data/lib/flipper/adapters/pstore.rb +4 -0
  31. data/lib/flipper/configuration.rb +33 -7
  32. data/lib/flipper/errors.rb +2 -3
  33. data/lib/flipper/identifier.rb +17 -0
  34. data/lib/flipper/middleware/memoizer.rb +29 -14
  35. data/lib/flipper/railtie.rb +38 -0
  36. data/lib/flipper/version.rb +1 -1
  37. data/spec/flipper/adapters/memory_spec.rb +21 -1
  38. data/spec/flipper/configuration_spec.rb +20 -2
  39. data/spec/flipper/identifier_spec.rb +14 -0
  40. data/spec/flipper/middleware/memoizer_spec.rb +95 -35
  41. data/spec/flipper/middleware/setup_env_spec.rb +0 -16
  42. data/spec/flipper/railtie_spec.rb +69 -0
  43. data/spec/flipper_spec.rb +0 -1
  44. data/spec/helper.rb +2 -2
  45. data/spec/support/spec_helpers.rb +20 -0
  46. data/test/test_helper.rb +1 -0
  47. metadata +9 -3
  48. data/examples/example_setup.rb +0 -8
@@ -1,4 +1,4 @@
1
- require File.expand_path('../example_setup', __FILE__)
1
+ require 'bundler/setup'
2
2
  require_relative 'active_record/ar_setup'
3
3
  require 'flipper'
4
4
  require 'flipper/adapters/redis'
@@ -1,10 +1,7 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
- adapter = Flipper::Adapters::Memory.new
6
- flipper = Flipper.new(adapter)
7
- stats = flipper[:stats]
4
+ stats = Flipper[:stats]
8
5
 
9
6
  # Some class that represents what will be trying to do something
10
7
  class User
@@ -1,5 +1,4 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'securerandom'
4
3
  require 'active_support/notifications'
5
4
 
@@ -1,16 +1,12 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
  require 'flipper/adapters/operation_logger'
5
4
  require 'flipper/instrumentation/log_subscriber'
6
5
 
7
6
  Flipper.configure do |config|
8
- config.default do
7
+ config.adapter do
9
8
  # pick an adapter, this uses memory, any will do
10
- adapter = Flipper::Adapters::OperationLogger.new(Flipper::Adapters::Memory.new)
11
-
12
- # pass adapter to handy DSL instance
13
- Flipper.new(adapter)
9
+ Flipper::Adapters::OperationLogger.new(Flipper::Adapters::Memory.new)
14
10
  end
15
11
  end
16
12
 
@@ -1,21 +1,11 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
- adapter = Flipper::Adapters::Memory.new
6
- flipper = Flipper.new(adapter)
7
- stats = flipper[:stats]
4
+ stats = Flipper[:stats]
8
5
 
9
6
  # Some class that represents what will be trying to do something
10
- class User
11
- attr_reader :id
12
-
13
- def initialize(id)
14
- @id = id
15
- end
16
-
17
- # Must respond to flipper_id
18
- alias_method :flipper_id, :id
7
+ class User < Struct.new(:id)
8
+ include Flipper::Identifier
19
9
  end
20
10
 
21
11
  total = 100_000
@@ -24,10 +14,10 @@ total = 100_000
24
14
  users = (1..total).map { |n| User.new(n) }
25
15
 
26
16
  perform_test = lambda { |number|
27
- flipper[:stats].enable flipper.actors(number)
17
+ Flipper.enable_percentage_of_actors :stats, number
28
18
 
29
19
  enabled = users.map { |user|
30
- flipper[:stats].enabled?(user) ? true : nil
20
+ Flipper.enabled?(:stats, user) ? true : nil
31
21
  }.compact
32
22
 
33
23
  actual = (enabled.size / total.to_f * 100).round(3)
@@ -1,10 +1,6 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
- adapter = Flipper::Adapters::Memory.new
6
- flipper = Flipper.new(adapter)
7
-
8
4
  # Some class that represents what will be trying to do something
9
5
  class User
10
6
  attr_reader :id
@@ -18,15 +14,16 @@ class User
18
14
  end
19
15
 
20
16
  # checking a bunch
21
- gate = Flipper::Gates::PercentageOfActors.new
22
- feature_name = "data_migration"
23
- percentage_enabled = 10
24
17
  total = 20_000
25
18
  enabled = []
19
+ percentage_enabled = 10
20
+
21
+ feature = Flipper[:data_migration]
22
+ feature.enable_percentage_of_actors 10
26
23
 
27
24
  (1..total).each do |id|
28
25
  user = User.new(id)
29
- if gate.open?(user, percentage_enabled, feature_name: feature_name)
26
+ if feature.enabled? user
30
27
  enabled << user
31
28
  end
32
29
  end
@@ -35,4 +32,4 @@ p actual: enabled.size, expected: total * (percentage_enabled * 0.01)
35
32
 
36
33
  # checking one
37
34
  user = User.new(1)
38
- p user_1_enabled: Flipper::Gates::PercentageOfActors.new.open?(user, percentage_enabled, feature_name: feature_name)
35
+ p user_1_enabled: feature.enabled?(user)
@@ -3,25 +3,12 @@
3
3
  # feature for actors in a particular location or on a particular plan, but only
4
4
  # for a percentage of them. The percentage is a constant, but could easily be
5
5
  # plucked from memcached, redis, mysql or whatever.
6
- require File.expand_path('../example_setup', __FILE__)
6
+ require 'bundler/setup'
7
7
  require 'flipper'
8
8
 
9
- adapter = Flipper::Adapters::Memory.new
10
- flipper = Flipper.new(adapter)
11
- stats = flipper[:stats]
12
-
13
9
  # Some class that represents what will be trying to do something
14
- class User
15
- attr_reader :id
16
-
17
- def initialize(id)
18
- @id = id
19
- end
20
-
21
- # Must respond to flipper_id
22
- def flipper_id
23
- "User;#{@id}"
24
- end
10
+ class User < Struct.new(:id)
11
+ include Flipper::Identifier
25
12
  end
26
13
 
27
14
  PERCENTAGE = 50
@@ -34,13 +21,13 @@ Flipper.register(:experimental) do |actor|
34
21
  end
35
22
 
36
23
  # enable the experimental group
37
- flipper[:stats].enable_group :experimental
24
+ Flipper.enable_group :stats, :experimental
38
25
 
39
26
  # create a bunch of fake users and see how many are enabled
40
27
  total = 10_000
41
28
  users = (1..total).map { |n| User.new(n) }
42
29
  enabled = users.map { |user|
43
- flipper[:stats].enabled?(user) ? true : nil
30
+ Flipper.enabled?(:stats, user) ? true : nil
44
31
  }.compact
45
32
 
46
33
  # show the results
@@ -1,13 +1,10 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
- adapter = Flipper::Adapters::Memory.new
6
- flipper = Flipper.new(adapter)
7
- logging = flipper[:logging]
4
+ logging = Flipper[:logging]
8
5
 
9
6
  perform_test = lambda do |number|
10
- logging.enable flipper.time(number)
7
+ logging.enable_percentage_of_time number
11
8
 
12
9
  total = 100_000
13
10
  enabled = []
data/lib/flipper.rb CHANGED
@@ -16,7 +16,7 @@ module Flipper
16
16
  # Public: Configure flipper.
17
17
  #
18
18
  # Flipper.configure do |config|
19
- # config.default { ... }
19
+ # config.adapter { ... }
20
20
  # end
21
21
  #
22
22
  # Yields Flipper::Configuration instance.
@@ -152,6 +152,7 @@ require 'flipper/feature'
152
152
  require 'flipper/gate'
153
153
  require 'flipper/instrumenters/memory'
154
154
  require 'flipper/instrumenters/noop'
155
+ require 'flipper/identifier'
155
156
  require 'flipper/middleware/memoizer'
156
157
  require 'flipper/middleware/setup_env'
157
158
  require 'flipper/registry'
@@ -163,3 +164,5 @@ require 'flipper/types/percentage'
163
164
  require 'flipper/types/percentage_of_actors'
164
165
  require 'flipper/types/percentage_of_time'
165
166
  require 'flipper/typecast'
167
+
168
+ require "flipper/railtie" if defined?(Rails::Railtie)
@@ -20,58 +20,57 @@ module Flipper
20
20
 
21
21
  # Public: The set of known features.
22
22
  def features
23
- read_feature_keys
23
+ @source.keys.to_set
24
24
  end
25
25
 
26
26
  # Public: Adds a feature to the set of known features.
27
27
  def add(feature)
28
- features.add(feature.key)
28
+ @source[feature.key] ||= default_config
29
29
  true
30
30
  end
31
31
 
32
32
  # Public: Removes a feature from the set of known features and clears
33
33
  # all the values for the feature.
34
34
  def remove(feature)
35
- features.delete(feature.name.to_s)
36
- clear(feature)
35
+ @source.delete(feature.key)
37
36
  true
38
37
  end
39
38
 
40
39
  # Public: Clears all the gate values for a feature.
41
40
  def clear(feature)
42
- feature.gates.each do |gate|
43
- delete key(feature, gate)
44
- end
41
+ @source[feature.key] = default_config
45
42
  true
46
43
  end
47
44
 
48
45
  # Public
49
46
  def get(feature)
50
- read_feature(feature)
47
+ @source[feature.key] || default_config
51
48
  end
52
49
 
53
50
  def get_multi(features)
54
- read_many_features(features)
51
+ result = {}
52
+ features.each do |feature|
53
+ result[feature.key] = @source[feature.key] || default_config
54
+ end
55
+ result
55
56
  end
56
57
 
57
58
  def get_all
58
- features = read_feature_keys.map do |key|
59
- Flipper::Feature.new(key, self)
60
- end
61
-
62
- read_many_features(features)
59
+ @source
63
60
  end
64
61
 
65
62
  # Public
66
63
  def enable(feature, gate, thing)
64
+ @source[feature.key] ||= default_config
65
+
67
66
  case gate.data_type
68
67
  when :boolean
69
68
  clear(feature)
70
- write key(feature, gate), thing.value.to_s
69
+ @source[feature.key][gate.key] = thing.value.to_s
71
70
  when :integer
72
- write key(feature, gate), thing.value.to_s
71
+ @source[feature.key][gate.key] = thing.value.to_s
73
72
  when :set
74
- set_add key(feature, gate), thing.value.to_s
73
+ @source[feature.key][gate.key] << thing.value.to_s
75
74
  else
76
75
  raise "#{gate} is not supported by this adapter yet"
77
76
  end
@@ -81,13 +80,15 @@ module Flipper
81
80
 
82
81
  # Public
83
82
  def disable(feature, gate, thing)
83
+ @source[feature.key] ||= default_config
84
+
84
85
  case gate.data_type
85
86
  when :boolean
86
87
  clear(feature)
87
88
  when :integer
88
- write key(feature, gate), thing.value.to_s
89
+ @source[feature.key][gate.key] = thing.value.to_s
89
90
  when :set
90
- set_delete key(feature, gate), thing.value.to_s
91
+ @source[feature.key][gate.key].delete thing.value.to_s
91
92
  else
92
93
  raise "#{gate} is not supported by this adapter yet"
93
94
  end
@@ -103,81 +104,6 @@ module Flipper
103
104
  ]
104
105
  "#<#{self.class.name}:#{object_id} #{attributes.join(', ')}>"
105
106
  end
106
-
107
- private
108
-
109
- def read_feature_keys
110
- set_members(FeaturesKey)
111
- end
112
-
113
- # Private
114
- def key(feature, gate)
115
- "feature/#{feature.key}/#{gate.key}"
116
- end
117
-
118
- def read_many_features(features)
119
- result = {}
120
- features.each do |feature|
121
- result[feature.key] = read_feature(feature)
122
- end
123
- result
124
- end
125
-
126
- def read_feature(feature)
127
- result = {}
128
-
129
- feature.gates.each do |gate|
130
- result[gate.key] =
131
- case gate.data_type
132
- when :boolean, :integer
133
- read key(feature, gate)
134
- when :set
135
- set_members key(feature, gate)
136
- else
137
- raise "#{gate} is not supported by this adapter yet"
138
- end
139
- end
140
-
141
- result
142
- end
143
-
144
- # Private
145
- def read(key)
146
- @source[key.to_s]
147
- end
148
-
149
- # Private
150
- def write(key, value)
151
- @source[key.to_s] = value.to_s
152
- end
153
-
154
- # Private
155
- def delete(key)
156
- @source.delete(key.to_s)
157
- end
158
-
159
- # Private
160
- def set_add(key, value)
161
- ensure_set_initialized(key)
162
- @source[key.to_s].add(value.to_s)
163
- end
164
-
165
- # Private
166
- def set_delete(key, value)
167
- ensure_set_initialized(key)
168
- @source[key.to_s].delete(value.to_s)
169
- end
170
-
171
- # Private
172
- def set_members(key)
173
- ensure_set_initialized(key)
174
- @source[key.to_s]
175
- end
176
-
177
- # Private
178
- def ensure_set_initialized(key)
179
- @source[key.to_s] ||= Set.new
180
- end
181
107
  end
182
108
  end
183
109
  end
@@ -216,3 +216,7 @@ module Flipper
216
216
  end
217
217
  end
218
218
  end
219
+
220
+ Flipper.configure do |config|
221
+ config.adapter { Flipper::Adapters::PStore.new }
222
+ end
@@ -1,7 +1,33 @@
1
1
  module Flipper
2
2
  class Configuration
3
- def initialize
4
- @default = -> { raise DefaultNotSet }
3
+ def initialize(options = {})
4
+ @default = -> { Flipper.new(adapter) }
5
+ @adapter = -> { Flipper::Adapters::Memory.new }
6
+ end
7
+
8
+ # The default adapter to use.
9
+ #
10
+ # Pass a block to assign the adapter, and invoke without a block to
11
+ # return the configured adapter instance.
12
+ #
13
+ # Flipper.configure do |config|
14
+ # config.adapter # => instance of default Memory adapter
15
+ #
16
+ # # Configure it to use the ActiveRecord adapter
17
+ # config.adapter do
18
+ # require "flipper/adapters/active_record"
19
+ # Flipper::Adapters::ActiveRecord.new
20
+ # end
21
+ #
22
+ # config.adapter # => instance of ActiveRecord adapter
23
+ # end
24
+ #
25
+ def adapter(&block)
26
+ if block_given?
27
+ @adapter = block
28
+ else
29
+ @adapter.call
30
+ end
5
31
  end
6
32
 
7
33
  # Controls the default instance for flipper. When used with a block it
@@ -9,15 +35,15 @@ module Flipper
9
35
  # without a block, it performs a block invocation and returns the result.
10
36
  #
11
37
  # configuration = Flipper::Configuration.new
12
- # configuration.default # => raises DefaultNotSet error.
38
+ # configuration.default # => Flipper::DSL instance using Memory adapter
13
39
  #
14
- # # sets the default block to generate a new instance using Memory adapter
40
+ # # sets the default block to generate a new instance using ActiveRecord adapter
15
41
  # configuration.default do
16
- # require "flipper/adapters/memory"
17
- # Flipper.new(Flipper::Adapters::Memory.new)
42
+ # require "flipper/adapters/active_record"
43
+ # Flipper.new(Flipper::Adapters::ActiveRecord.new)
18
44
  # end
19
45
  #
20
- # configuration.default # => Flipper::DSL instance using Memory adapter
46
+ # configuration.default # => Flipper::DSL instance using ActiveRecord adapter
21
47
  #
22
48
  # Returns result of default block invocation if called without block. If
23
49
  # called with block, assigns the default block.