flipper 0.17.1 → 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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +57 -0
  3. data/Changelog.md +114 -1
  4. data/Dockerfile +1 -1
  5. data/Gemfile +3 -6
  6. data/README.md +103 -47
  7. data/Rakefile +1 -4
  8. data/docs/Adapters.md +9 -9
  9. data/docs/Caveats.md +2 -2
  10. data/docs/DockerCompose.md +0 -1
  11. data/docs/Gates.md +74 -74
  12. data/docs/Optimization.md +70 -47
  13. data/docs/http/README.md +12 -11
  14. data/docs/images/banner.jpg +0 -0
  15. data/docs/read-only/README.md +8 -5
  16. data/examples/basic.rb +1 -12
  17. data/examples/configuring_default.rb +2 -5
  18. data/examples/dsl.rb +13 -24
  19. data/examples/enabled_for_actor.rb +8 -15
  20. data/examples/group.rb +3 -6
  21. data/examples/group_dynamic_lookup.rb +5 -19
  22. data/examples/group_with_members.rb +4 -14
  23. data/examples/importing.rb +1 -1
  24. data/examples/individual_actor.rb +2 -5
  25. data/examples/instrumentation.rb +1 -2
  26. data/examples/memoizing.rb +35 -0
  27. data/examples/percentage_of_actors.rb +6 -16
  28. data/examples/percentage_of_actors_enabled_check.rb +7 -10
  29. data/examples/percentage_of_actors_group.rb +5 -18
  30. data/examples/percentage_of_time.rb +3 -6
  31. data/flipper.gemspec +3 -4
  32. data/lib/flipper.rb +7 -3
  33. data/lib/flipper/adapters/dual_write.rb +67 -0
  34. data/lib/flipper/adapters/http.rb +32 -28
  35. data/lib/flipper/adapters/memory.rb +23 -94
  36. data/lib/flipper/adapters/operation_logger.rb +5 -0
  37. data/lib/flipper/adapters/pstore.rb +8 -1
  38. data/lib/flipper/adapters/sync.rb +7 -7
  39. data/lib/flipper/adapters/sync/interval_synchronizer.rb +1 -1
  40. data/lib/flipper/adapters/sync/synchronizer.rb +1 -0
  41. data/lib/flipper/configuration.rb +33 -7
  42. data/lib/flipper/dsl.rb +8 -0
  43. data/lib/flipper/errors.rb +2 -3
  44. data/lib/flipper/feature.rb +2 -2
  45. data/lib/flipper/identifier.rb +17 -0
  46. data/lib/flipper/middleware/memoizer.rb +30 -15
  47. data/lib/flipper/middleware/setup_env.rb +13 -3
  48. data/lib/flipper/railtie.rb +38 -0
  49. data/lib/flipper/spec/shared_adapter_specs.rb +15 -0
  50. data/lib/flipper/test/shared_adapter_test.rb +16 -1
  51. data/lib/flipper/version.rb +1 -1
  52. data/spec/flipper/adapter_spec.rb +2 -2
  53. data/spec/flipper/adapters/dual_write_spec.rb +71 -0
  54. data/spec/flipper/adapters/http_spec.rb +74 -8
  55. data/spec/flipper/adapters/memory_spec.rb +21 -1
  56. data/spec/flipper/adapters/operation_logger_spec.rb +9 -0
  57. data/spec/flipper/adapters/sync_spec.rb +4 -4
  58. data/spec/flipper/configuration_spec.rb +20 -2
  59. data/spec/flipper/feature_spec.rb +5 -5
  60. data/spec/flipper/identifier_spec.rb +14 -0
  61. data/spec/flipper/middleware/memoizer_spec.rb +95 -35
  62. data/spec/flipper/middleware/setup_env_spec.rb +23 -3
  63. data/spec/flipper/railtie_spec.rb +69 -0
  64. data/spec/{integration_spec.rb → flipper_integration_spec.rb} +0 -0
  65. data/spec/flipper_spec.rb +26 -0
  66. data/spec/helper.rb +3 -3
  67. data/spec/support/descriptions.yml +1 -0
  68. data/spec/support/spec_helpers.rb +25 -0
  69. data/test/test_helper.rb +2 -1
  70. metadata +19 -10
  71. data/.rubocop.yml +0 -52
  72. data/.rubocop_todo.yml +0 -562
  73. data/examples/example_setup.rb +0 -8
data/examples/basic.rb CHANGED
@@ -1,17 +1,6 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
- Flipper.configure do |config|
6
- config.default do
7
- # pick an adapter, this uses memory, any will do
8
- adapter = Flipper::Adapters::Memory.new
9
-
10
- # pass adapter to handy DSL instance
11
- Flipper.new(adapter)
12
- end
13
- end
14
-
15
4
  # check if search is enabled
16
5
  if Flipper.enabled?(:search)
17
6
  puts 'Search away!'
@@ -1,12 +1,9 @@
1
- require File.expand_path('../example_setup', __FILE__)
2
-
1
+ require 'bundler/setup'
3
2
  require 'flipper'
4
3
 
5
4
  # sets up default adapter so Flipper works like Flipper::DSL
6
5
  Flipper.configure do |config|
7
- config.default do
8
- Flipper.new Flipper::Adapters::Memory.new
9
- end
6
+ config.adapter { Flipper::Adapters::Memory.new }
10
7
  end
11
8
 
12
9
  puts Flipper.enabled?(:search) # => false
data/examples/dsl.rb CHANGED
@@ -1,20 +1,9 @@
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
  # create a thing with an identifier
9
- class Person
10
- attr_reader :id
11
-
12
- def initialize(id)
13
- @id = id
14
- end
15
-
16
- # Must respond to flipper_id
17
- alias_method :flipper_id, :id
5
+ class Person < Struct.new(:id)
6
+ include Flipper::Identifier
18
7
  end
19
8
 
20
9
  person = Person.new(1)
@@ -22,14 +11,14 @@ person = Person.new(1)
22
11
  puts "Stats are disabled by default\n\n"
23
12
 
24
13
  # is a feature enabled
25
- puts "flipper.enabled? :stats: #{flipper.enabled? :stats}"
14
+ puts "flipper.enabled? :stats: #{Flipper.enabled? :stats}"
26
15
 
27
16
  # is a feature on or off for a particular person
28
- puts "flipper.enabled? :stats, person: #{flipper.enabled? :stats, person}"
17
+ puts "Flipper.enabled? :stats, person: #{Flipper.enabled? :stats, person}"
29
18
 
30
19
  # get at a feature
31
- puts "\nYou can also get an individual feature like this:\nstats = flipper[:stats]\n\n"
32
- stats = flipper[:stats]
20
+ puts "\nYou can also get an individual feature like this:\nstats = Flipper[:stats]\n\n"
21
+ stats = Flipper[:stats]
33
22
 
34
23
  # is that feature enabled
35
24
  puts "stats.enabled?: #{stats.enabled?}"
@@ -39,7 +28,7 @@ puts "stats.enabled? person: #{stats.enabled? person}"
39
28
 
40
29
  # enable a feature by name
41
30
  puts "\nEnabling stats\n\n"
42
- flipper.enable :stats
31
+ Flipper.enable :stats
43
32
 
44
33
  # or, you can use the feature to enable
45
34
  stats.enable
@@ -49,7 +38,7 @@ puts "stats.enabled? person: #{stats.enabled? person}"
49
38
 
50
39
  # oh, no, let's turn this baby off
51
40
  puts "\nDisabling stats\n\n"
52
- flipper.disable :stats
41
+ Flipper.disable :stats
53
42
 
54
43
  # or we can disable using feature obviously
55
44
  stats.disable
@@ -59,18 +48,18 @@ puts "stats.enabled? person: #{stats.enabled? person}"
59
48
  puts
60
49
 
61
50
  # get an instance of the percentage of time type set to 5
62
- puts flipper.time(5).inspect
51
+ puts Flipper.time(5).inspect
63
52
 
64
53
  # get an instance of the percentage of actors type set to 15
65
- puts flipper.actors(15).inspect
54
+ puts Flipper.actors(15).inspect
66
55
 
67
56
  # get an instance of an actor using an object that responds to flipper_id
68
57
  responds_to_flipper_id = Struct.new(:flipper_id).new(10)
69
- puts flipper.actor(responds_to_flipper_id).inspect
58
+ puts Flipper.actor(responds_to_flipper_id).inspect
70
59
 
71
60
  # get an instance of an actor using an object
72
61
  thing = Struct.new(:flipper_id).new(22)
73
- puts flipper.actor(thing).inspect
62
+ puts Flipper.actor(thing).inspect
74
63
 
75
64
  # register a top level group
76
65
  admins = Flipper.register(:admins) { |actor|
@@ -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
 
@@ -0,0 +1,35 @@
1
+ require 'bundler/setup'
2
+ require 'flipper'
3
+ require 'flipper/adapters/operation_logger'
4
+ require 'flipper/instrumentation/log_subscriber'
5
+
6
+ Flipper.configure do |config|
7
+ config.adapter do
8
+ # pick an adapter, this uses memory, any will do
9
+ Flipper::Adapters::OperationLogger.new(Flipper::Adapters::Memory.new)
10
+ end
11
+ end
12
+
13
+ Flipper.enable(:foo)
14
+ Flipper.enable(:bar)
15
+ Flipper.disable(:baz)
16
+ Flipper.disable(:wick)
17
+ # reset the operation logging adapter to empty for clarity
18
+ Flipper.adapter.reset
19
+
20
+ # Turn on memoization (the memoizing middleware does this per request).
21
+ Flipper.memoize = true
22
+
23
+ # Preload all the features.
24
+ Flipper.preload_all
25
+
26
+ # Do as many feature checks as your heart desires.
27
+ %w[foo bar baz wick].each do |name|
28
+ Flipper.enabled?(name)
29
+ end
30
+
31
+ # See that only one operation exists, a get_all (which is the preload_all).
32
+ pp Flipper.adapter.operations
33
+ # [#<Flipper::Adapters::OperationLogger::Operation:0x00007fdcfe1100e8
34
+ # @args=[],
35
+ # @type=:get_all>]
@@ -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 = []