flipper 0.20.0 → 0.21.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +57 -0
  3. data/Changelog.md +37 -1
  4. data/Gemfile +1 -0
  5. data/README.md +103 -47
  6. data/docs/Adapters.md +1 -0
  7. data/docs/Gates.md +74 -74
  8. data/docs/Optimization.md +7 -7
  9. data/docs/images/banner.jpg +0 -0
  10. data/examples/basic.rb +1 -12
  11. data/examples/configuring_default.rb +1 -2
  12. data/examples/dsl.rb +13 -24
  13. data/examples/enabled_for_actor.rb +8 -15
  14. data/examples/group.rb +3 -6
  15. data/examples/group_dynamic_lookup.rb +5 -19
  16. data/examples/group_with_members.rb +4 -14
  17. data/examples/importing.rb +1 -1
  18. data/examples/individual_actor.rb +2 -5
  19. data/examples/instrumentation.rb +1 -2
  20. data/examples/memoizing.rb +1 -2
  21. data/examples/percentage_of_actors.rb +6 -16
  22. data/examples/percentage_of_actors_enabled_check.rb +7 -10
  23. data/examples/percentage_of_actors_group.rb +5 -18
  24. data/examples/percentage_of_time.rb +3 -6
  25. data/lib/flipper.rb +1 -0
  26. data/lib/flipper/adapters/http.rb +32 -28
  27. data/lib/flipper/adapters/memory.rb +20 -94
  28. data/lib/flipper/adapters/sync/interval_synchronizer.rb +1 -1
  29. data/lib/flipper/configuration.rb +6 -6
  30. data/lib/flipper/errors.rb +2 -3
  31. data/lib/flipper/identifier.rb +17 -0
  32. data/lib/flipper/version.rb +1 -1
  33. data/spec/flipper/adapters/http_spec.rb +74 -8
  34. data/spec/flipper/adapters/memory_spec.rb +21 -1
  35. data/spec/flipper/configuration_spec.rb +5 -2
  36. data/spec/flipper/identifier_spec.rb +14 -0
  37. data/spec/flipper/middleware/memoizer_spec.rb +1 -1
  38. data/spec/flipper/middleware/setup_env_spec.rb +0 -16
  39. data/spec/flipper_spec.rb +0 -1
  40. data/spec/support/spec_helpers.rb +3 -0
  41. data/test/test_helper.rb +1 -0
  42. metadata +9 -5
  43. data/examples/example_setup.rb +0 -8
data/docs/Optimization.md CHANGED
@@ -7,7 +7,7 @@ One optimization that flipper provides is a memoizing middleware. The memoizing
7
7
  You can use the middleware like so for Rails:
8
8
 
9
9
  ```ruby
10
- # setup default instance (perhaps in config/initializer/flipper.rb)
10
+ # setup default instance (perhaps in config/initializers/flipper.rb)
11
11
  Flipper.configure do |config|
12
12
  config.default do
13
13
  Flipper.new(...)
@@ -15,7 +15,7 @@ Flipper.configure do |config|
15
15
  end
16
16
 
17
17
  # This assumes you setup a default flipper instance using configure.
18
- config.middleware.use Flipper::Middleware::Memoizer
18
+ Rails.configuration.middleware.use Flipper::Middleware::Memoizer
19
19
  ```
20
20
 
21
21
  **Note**: Be sure that the middleware is high enough up in your stack that all feature checks are wrapped.
@@ -23,8 +23,8 @@ config.middleware.use Flipper::Middleware::Memoizer
23
23
  **Also Note**: If you haven't setup a default instance, you can pass the instance to `SetupEnv` as `Memoizer` uses whatever is setup in the `env`:
24
24
 
25
25
  ```ruby
26
- config.middleware.use Flipper::Middleware::SetupEnv, -> { Flipper.new(...) }
27
- config.middleware.use Flipper::Middleware::Memoizer
26
+ Rails.configuration.middleware.use Flipper::Middleware::SetupEnv, -> { Flipper.new(...) }
27
+ Rails.configuration.middleware.use Flipper::Middleware::Memoizer
28
28
  ```
29
29
 
30
30
  ### Options
@@ -33,18 +33,18 @@ The Memoizer middleware also supports a few options. Use either `preload` or `pr
33
33
 
34
34
  * **`:preload`** - An `Array` of feature names (`Symbol`) to preload for every request. Useful if you have features that are used on every endpoint. `preload` uses `Adapter#get_multi` to attempt to load the features in one network call instead of N+1 network calls.
35
35
  ```ruby
36
- config.middleware.use Flipper::Middleware::Memoizer,
36
+ Rails.configuration.middleware.use Flipper::Middleware::Memoizer,
37
37
  preload: [:stats, :search, :some_feature]
38
38
  ```
39
39
  * **`:preload_all`** - A Boolean value (default: false) of whether or not all features should be preloaded. Using this results in a `preload_all` call with the result of `Adapter#get_all`. Any subsequent feature checks will be memoized and perform no network calls. I wouldn't recommend using this unless you have few features (< 100?) and nearly all of them are used on every request.
40
40
  ```ruby
41
- config.middleware.use Flipper::Middleware::Memoizer,
41
+ Rails.configuration.middleware.use Flipper::Middleware::Memoizer,
42
42
  preload_all: true
43
43
  ```
44
44
  * **`:unless`** - A block that prevents preloading and memoization if it evaluates to true.
45
45
  ```ruby
46
46
  # skip preloading and memoizing if path starts with /assets
47
- config.middleware.use Flipper::Middleware::Memoizer,
47
+ Rails.configuration.middleware.use Flipper::Middleware::Memoizer,
48
48
  unless: ->(request) { request.path.start_with?("/assets") }
49
49
  ```
50
50
 
Binary file
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,5 +1,4 @@
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
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
 
@@ -1,5 +1,4 @@
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'
@@ -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