flipper 0.20.3 → 0.22.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +18 -9
  3. data/Changelog.md +62 -0
  4. data/Gemfile +2 -0
  5. data/README.md +104 -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/api/README.md +5 -5
  11. data/docs/http/README.md +12 -11
  12. data/docs/images/banner.jpg +0 -0
  13. data/docs/read-only/README.md +8 -5
  14. data/examples/api/basic.ru +19 -0
  15. data/examples/api/custom_memoized.ru +37 -0
  16. data/examples/api/memoized.ru +43 -0
  17. data/examples/basic.rb +1 -12
  18. data/examples/configuring_default.rb +2 -5
  19. data/examples/dsl.rb +13 -24
  20. data/examples/enabled_for_actor.rb +8 -15
  21. data/examples/group.rb +3 -6
  22. data/examples/group_dynamic_lookup.rb +5 -19
  23. data/examples/group_with_members.rb +4 -14
  24. data/examples/importing.rb +1 -1
  25. data/examples/individual_actor.rb +2 -5
  26. data/examples/instrumentation.rb +1 -2
  27. data/examples/memoizing.rb +3 -7
  28. data/examples/percentage_of_actors.rb +6 -16
  29. data/examples/percentage_of_actors_enabled_check.rb +7 -10
  30. data/examples/percentage_of_actors_group.rb +5 -18
  31. data/examples/percentage_of_time.rb +3 -6
  32. data/lib/flipper.rb +4 -1
  33. data/lib/flipper/adapters/pstore.rb +4 -0
  34. data/lib/flipper/configuration.rb +33 -7
  35. data/lib/flipper/errors.rb +2 -3
  36. data/lib/flipper/identifier.rb +17 -0
  37. data/lib/flipper/middleware/memoizer.rb +30 -15
  38. data/lib/flipper/railtie.rb +46 -0
  39. data/lib/flipper/version.rb +1 -1
  40. data/spec/flipper/configuration_spec.rb +20 -2
  41. data/spec/flipper/identifier_spec.rb +14 -0
  42. data/spec/flipper/middleware/memoizer_spec.rb +95 -35
  43. data/spec/flipper/middleware/setup_env_spec.rb +0 -16
  44. data/spec/flipper/railtie_spec.rb +69 -0
  45. data/spec/flipper_spec.rb +0 -1
  46. data/spec/helper.rb +2 -2
  47. data/spec/support/spec_helpers.rb +20 -0
  48. data/test/test_helper.rb +1 -0
  49. metadata +12 -3
  50. data/examples/example_setup.rb +0 -8
@@ -1,5 +1,4 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
4
  # Some class that represents what will be trying to do something
@@ -22,21 +21,15 @@ end
22
21
  user1 = User.new(1, true)
23
22
  user2 = User.new(2, false)
24
23
 
25
- # pick an adapter
26
- adapter = Flipper::Adapters::Memory.new
27
-
28
- # get a handy dsl instance
29
- flipper = Flipper.new(adapter)
30
-
31
24
  Flipper.register :admins do |actor|
32
25
  actor.admin?
33
26
  end
34
27
 
35
- flipper[:search].enable
36
- flipper[:stats].enable_actor user1
37
- flipper[:pro_stats].enable_percentage_of_actors 50
38
- flipper[:tweets].enable_group :admins
39
- flipper[:posts].enable_actor user2
28
+ Flipper.enable :search
29
+ Flipper.enable_actor :stats, user1
30
+ Flipper.enable_percentage_of_actors :pro_stats, 50
31
+ Flipper.enable_group :tweets, :admins
32
+ Flipper.enable_actor :posts, user2
40
33
 
41
- pp flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name)
42
- pp flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name)
34
+ pp Flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name)
35
+ pp Flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name)
data/examples/group.rb CHANGED
@@ -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
  # Register group
10
7
  Flipper.register(:admins) do |actor|
@@ -35,7 +32,7 @@ puts "Stats for admin: #{stats.enabled?(admin)}"
35
32
  puts "Stats for non_admin: #{stats.enabled?(non_admin)}"
36
33
 
37
34
  puts "\nEnabling Stats for admins...\n\n"
38
- stats.enable(flipper.group(:admins))
35
+ stats.enable_group :admins
39
36
 
40
37
  puts "Stats for admin: #{stats.enabled?(admin)}"
41
38
  puts "Stats for non_admin: #{stats.enabled?(non_admin)}"
@@ -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
  # Register group
10
7
  Flipper.register(:enabled_team_member) do |actor, context|
@@ -15,19 +12,12 @@ Flipper.register(:enabled_team_member) do |actor, context|
15
12
  end
16
13
 
17
14
  # Some class that represents actor that will be trying to do something
18
- class User
19
- attr_reader :id
20
-
21
- def initialize(id)
22
- @id = id
23
- end
24
-
25
- def flipper_id
26
- "User;#{@id}"
27
- end
15
+ class User < Struct.new(:id)
16
+ include Flipper::Identifier
28
17
  end
29
18
 
30
19
  class Team
20
+ include Flipper::Identifier
31
21
  attr_reader :name
32
22
 
33
23
  def self.all
@@ -51,10 +41,6 @@ class Team
51
41
  def member?(actor)
52
42
  @members.map(&:id).include?(actor.id)
53
43
  end
54
-
55
- def flipper_id
56
- "Team:#{@name}"
57
- end
58
44
  end
59
45
 
60
46
  jnunemaker = User.new("jnunemaker")
@@ -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
  # Register group
10
7
  Flipper.register(:team_actor) do |actor|
@@ -12,15 +9,8 @@ Flipper.register(:team_actor) do |actor|
12
9
  end
13
10
 
14
11
  # Some class that represents actor that will be trying to do something
15
- class User
16
- attr_reader :id
17
-
18
- def initialize(id)
19
- @id = id
20
- end
21
-
22
- # Must respond to flipper_id
23
- alias_method :flipper_id, :id
12
+ class User < Struct.new(:id)
13
+ include Flipper::Identifier
24
14
  end
25
15
 
26
16
  class Team
@@ -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)
@@ -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.
@@ -16,9 +16,8 @@ module Flipper
16
16
  # use it.
17
17
  class DefaultNotSet < Flipper::Error
18
18
  def initialize(message = nil)
19
- default = "Default flipper instance not configured. See " \
20
- "Flipper.configure for how to configure the default instance."
21
- super(message || default)
19
+ warn "Flipper::DefaultNotSet is deprecated and will be removed in 1.0"
20
+ super
22
21
  end
23
22
  end
24
23
 
@@ -0,0 +1,17 @@
1
+ module Flipper
2
+ # A default implementation of `#flipper_id` for actors.
3
+ #
4
+ # class User < Struct.new(:id)
5
+ # include Flipper::Identifier
6
+ # end
7
+ #
8
+ # user = User.new(99)
9
+ # Flipper.enable :thing, user
10
+ # Flipper.enabled? :thing, user #=> true
11
+ #
12
+ module Identifier
13
+ def flipper_id
14
+ "#{self.class.name};#{id}"
15
+ end
16
+ end
17
+ end