flipper 0.20.2 → 0.21.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 (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.