flipper 0.27.1 → 0.28.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba1d158db684bce01872e3f32c81f522d32086a78af94a78a6266422079a3473
4
- data.tar.gz: 9a7ea456415d5fd73d5adcb66bb77c4701a2a6e6b3ab94d5c89af52202907e7c
3
+ metadata.gz: a581a01f7e6dade34fb261d0b6215f262125e4041a0095081dd54719db303656
4
+ data.tar.gz: 1d3df8718d8a46f88cb33e97e4ef2d193e0eae2dd2cf4eecd23fc87dd977d11a
5
5
  SHA512:
6
- metadata.gz: f708342f07ebe1bfbf3c9d0e0f1db184ccd686bdd995752b6459f96ab119fe589573e0077d156afad1ac8f73b45140a54e301ff23ef423e92b3c109e89285992
7
- data.tar.gz: e9ca8640f9c9b655ae464651adda6cb6998355b967b58119fc42c2a87d3c3f848538dd05ff36585e75527384aca05ab6be1996558679a0090ef24bb8a6a7a682
6
+ metadata.gz: e56bd86ca42668104197ad8b64596f7639bbf25c04ac48e3da6cff8f3d71736ab62282d9e01a13f2eb85fa7c058a11e2360e1a260fe249972e16f911d83ca2f5
7
+ data.tar.gz: a11b459234c76ac9ab928b26c9e12009736af655f602a04557d776dc72579877cf1b7ab38f4faecd54d49b765186ff0bf754eef174a96ee1f5a584daf2dacd16
data/Changelog.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 0.28.0
6
+
7
+ ### Additions/Changes
8
+
9
+ * Allow multiple actors for Flipper.enabled?. Improves performance of feature flags for multiple actors and simplifies code for users of flipper. This likely breaks things for anyone using Flipper internal classes related to actors, but that isn't likely you so you should be fine.
10
+ ```diff
11
+ - [user, user.team, user.org].any? { |actor| Flipper.enabled?(:my_feature, actor) }
12
+ + Flipper.enabled?(:my_feature, user, user.team, user.org)
13
+ ```
14
+
15
+ ### Deprecations
16
+
17
+ * `:thing` in `enabled?` instrumentation payload. Use `:actors` instead.
18
+ ```diff
19
+ ActiveSupport::Notifications.subscribe('enabled?.feature_operation.flipper') do |name, start, finish, id, payload|
20
+ - payload[:thing]
21
+ + payload[:actors]
22
+ end
23
+ ```
24
+
5
25
  ## 0.27.1
6
26
 
7
27
  * Quick fix for missing require of "flipper/version" that was causing issues with some flipper-ui people.
data/Rakefile CHANGED
@@ -17,9 +17,9 @@ end
17
17
 
18
18
  desc 'Tags version, pushes to remote, and pushes gem'
19
19
  task release: :build do
20
- # sh 'git', 'tag', "v#{Flipper::VERSION}"
21
- # sh 'git push origin main'
22
- # sh "git push origin v#{Flipper::VERSION}"
20
+ sh 'git', 'tag', "v#{Flipper::VERSION}"
21
+ sh 'git push origin main'
22
+ sh "git push origin v#{Flipper::VERSION}"
23
23
  puts "\nWhat OTP code should be used?"
24
24
  otp_code = STDIN.gets.chomp
25
25
  sh "ls pkg/*.gem | xargs -n 1 gem push --otp #{otp_code}"
@@ -0,0 +1,20 @@
1
+ require 'bundler/setup'
2
+ require 'flipper'
3
+ require 'benchmark/ips'
4
+
5
+ actor1 = Flipper::Actor.new("User;1")
6
+ actor2 = Flipper::Actor.new("User;2")
7
+ actor3 = Flipper::Actor.new("User;3")
8
+ actor4 = Flipper::Actor.new("User;4")
9
+ actor5 = Flipper::Actor.new("User;5")
10
+ actor6 = Flipper::Actor.new("User;6")
11
+ actor7 = Flipper::Actor.new("User;7")
12
+ actor8 = Flipper::Actor.new("User;8")
13
+
14
+ actors = [actor1, actor2, actor3, actor4, actor5, actor6, actor7, actor8]
15
+
16
+ Benchmark.ips do |x|
17
+ x.report("with array of actors") { Flipper.enabled?(:foo, actors) }
18
+ x.report("with multiple enabled? checks") { actors.each { |actor| Flipper.enabled?(:foo, actor) } }
19
+ x.compare!
20
+ end
data/examples/dsl.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'bundler/setup'
2
2
  require 'flipper'
3
3
 
4
- # create a thing with an identifier
4
+ # create an actor with an identifier
5
5
  class Person < Struct.new(:id)
6
6
  include Flipper::Identifier
7
7
  end
@@ -58,8 +58,8 @@ responds_to_flipper_id = Struct.new(:flipper_id).new(10)
58
58
  puts Flipper.actor(responds_to_flipper_id).inspect
59
59
 
60
60
  # get an instance of an actor using an object
61
- thing = Struct.new(:flipper_id).new(22)
62
- puts Flipper.actor(thing).inspect
61
+ actor = Struct.new(:flipper_id).new(22)
62
+ puts Flipper.actor(actor).inspect
63
63
 
64
64
  # register a top level group
65
65
  admins = Flipper.register(:admins) { |actor|
@@ -31,5 +31,7 @@ Flipper.enable_percentage_of_actors :pro_stats, 50
31
31
  Flipper.enable_group :tweets, :admins
32
32
  Flipper.enable_actor :posts, user2
33
33
 
34
- pp Flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name)
35
- pp Flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name)
34
+ pp Flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name).sort
35
+ pp Flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name).sort
36
+ pp Flipper.features.select { |feature| feature.enabled?(user1, user2) }.map(&:name).sort
37
+ pp Flipper.features.select { |feature| feature.enabled?([user2, user1]) }.map(&:name).sort
data/lib/flipper/dsl.rb CHANGED
@@ -237,12 +237,12 @@ module Flipper
237
237
 
238
238
  # Public: Wraps an object as a flipper actor.
239
239
  #
240
- # thing - The object that you would like to wrap.
240
+ # actor - The object that you would like to wrap.
241
241
  #
242
242
  # Returns an instance of Flipper::Types::Actor.
243
- # Raises ArgumentError if thing does not respond to `flipper_id`.
244
- def actor(thing)
245
- Types::Actor.new(thing)
243
+ # Raises ArgumentError if actor does not respond to `flipper_id`.
244
+ def actor(actor)
245
+ Types::Actor.new(actor)
246
246
  end
247
247
 
248
248
  # Public: Shortcut for getting a percentage of time instance.
@@ -2,10 +2,10 @@ module Flipper
2
2
  # Top level error that all other errors inherit from.
3
3
  class Error < StandardError; end
4
4
 
5
- # Raised when gate can not be found for a thing.
5
+ # Raised when gate can not be found for an actor.
6
6
  class GateNotFound < Error
7
- def initialize(thing)
8
- super "Could not find gate for #{thing.inspect}"
7
+ def initialize(actor)
8
+ super "Could not find gate for #{actor.inspect}"
9
9
  end
10
10
  end
11
11
 
@@ -96,17 +96,19 @@ module Flipper
96
96
  instrument(:clear) { adapter.clear(self) }
97
97
  end
98
98
 
99
- # Public: Check if a feature is enabled for a thing.
99
+ # Public: Check if a feature is enabled for zero or more actors.
100
100
  #
101
101
  # Returns true if enabled, false if not.
102
- def enabled?(thing = nil)
103
- thing = Types::Actor.wrap(thing) unless thing.nil?
102
+ def enabled?(*actors)
103
+ actors = actors.flatten.compact.map { |actor| Types::Actor.wrap(actor) }
104
+ actors = nil if actors.empty?
104
105
 
105
- instrument(:enabled?, thing: thing) do |payload|
106
+ # thing is left for backwards compatibility
107
+ instrument(:enabled?, thing: actors&.first, actors: actors) do |payload|
106
108
  context = FeatureCheckContext.new(
107
109
  feature_name: @name,
108
110
  values: gate_values,
109
- thing: thing
111
+ actors: actors
110
112
  )
111
113
 
112
114
  if open_gate = gates.detect { |gate| gate.open?(context) }
@@ -359,14 +361,14 @@ module Flipper
359
361
  gates_hash[name.to_sym]
360
362
  end
361
363
 
362
- # Public: Find the gate that protects a thing.
364
+ # Public: Find the gate that protects an actor.
363
365
  #
364
- # thing - The object for which you would like to find a gate
366
+ # actor - The object for which you would like to find a gate
365
367
  #
366
368
  # Returns a Flipper::Gate.
367
- # Raises Flipper::GateNotFound if no gate found for thing
368
- def gate_for(thing)
369
- gates.detect { |gate| gate.protects?(thing) } || raise(GateNotFound, thing)
369
+ # Raises Flipper::GateNotFound if no gate found for actor
370
+ def gate_for(actor)
371
+ gates.detect { |gate| gate.protects?(actor) } || raise(GateNotFound, actor)
370
372
  end
371
373
 
372
374
  private
@@ -7,13 +7,17 @@ module Flipper
7
7
  # gates for the feature.
8
8
  attr_reader :values
9
9
 
10
- # Public: The thing we want to know if a feature is enabled for.
11
- attr_reader :thing
10
+ # Public: The actors we want to know if a feature is enabled for.
11
+ attr_reader :actors
12
12
 
13
- def initialize(feature_name:, values:, thing:)
13
+ def initialize(feature_name:, values:, actors:)
14
14
  @feature_name = feature_name
15
15
  @values = values
16
- @thing = thing
16
+ @actors = actors
17
+ end
18
+
19
+ def actors?
20
+ !@actors.nil? && !@actors.empty?
17
21
  end
18
22
 
19
23
  # Public: Convenience method for groups value like Feature has.
data/lib/flipper/gate.rb CHANGED
@@ -18,28 +18,29 @@ module Flipper
18
18
  raise 'Not implemented'
19
19
  end
20
20
 
21
- def enabled?(_value)
21
+ def enabled?(value)
22
22
  raise 'Not implemented'
23
23
  end
24
24
 
25
- # Internal: Check if a gate is open for a thing. Implemented in subclass.
25
+ # Internal: Check if a gate is open for one or more actors. Implemented
26
+ # in subclass.
26
27
  #
27
- # Returns true if gate open for thing, false if not.
28
- def open?(_thing, _value, _options = {})
28
+ # Returns true if gate open for any actor, false if not.
29
+ def open?(actors, value, options = {})
29
30
  false
30
31
  end
31
32
 
32
- # Internal: Check if a gate is protects a thing. Implemented in subclass.
33
+ # Internal: Check if a gate is protects an actor. Implemented in subclass.
33
34
  #
34
- # Returns true if gate protects thing, false if not.
35
- def protects?(_thing)
35
+ # Returns true if gate protects actor, false if not.
36
+ def protects?(actor)
36
37
  false
37
38
  end
38
39
 
39
- # Internal: Allows gate to wrap thing using one of the supported flipper
40
- # types so adapters always get something that responds to value.
41
- def wrap(thing)
42
- thing
40
+ # Internal: Allows gate to wrap actor using one of the supported flipper
41
+ # types so adapters always get someactor that responds to value.
42
+ def wrap(actor)
43
+ actor
43
44
  end
44
45
 
45
46
  # Public: Pretty string version for debugging.
@@ -19,20 +19,23 @@ module Flipper
19
19
  !value.empty?
20
20
  end
21
21
 
22
- # Internal: Checks if the gate is open for a thing.
22
+ # Internal: Checks if the gate is open for an actor.
23
23
  #
24
- # Returns true if gate open for thing, false if not.
24
+ # Returns true if gate open for actor, false if not.
25
25
  def open?(context)
26
- return false if context.thing.nil?
27
- context.values.actors.include?(context.thing.value)
26
+ return false unless context.actors?
27
+
28
+ context.actors.any? do |actor|
29
+ context.values.actors.include?(actor.value)
30
+ end
28
31
  end
29
32
 
30
- def wrap(thing)
31
- Types::Actor.wrap(thing)
33
+ def wrap(actor)
34
+ Types::Actor.wrap(actor)
32
35
  end
33
36
 
34
- def protects?(thing)
35
- Types::Actor.wrappable?(thing)
37
+ def protects?(actor)
38
+ Types::Actor.wrappable?(actor)
36
39
  end
37
40
  end
38
41
  end
@@ -23,10 +23,12 @@ module Flipper
23
23
  #
24
24
  # Returns true if gate open for thing, false if not.
25
25
  def open?(context)
26
- return false if context.thing.nil?
26
+ return false unless context.actors?
27
27
 
28
28
  context.values.groups.any? do |name|
29
- Flipper.group(name).match?(context.thing, context)
29
+ context.actors.any? do |actor|
30
+ Flipper.group(name).match?(actor, context)
31
+ end
30
32
  end
31
33
  end
32
34
 
@@ -26,13 +26,12 @@ module Flipper
26
26
  SCALING_FACTOR = 1_000
27
27
  private_constant :SCALING_FACTOR
28
28
 
29
- # Internal: Checks if the gate is open for a thing.
29
+ # Internal: Checks if the gate is open for one or more actors.
30
30
  #
31
- # Returns true if gate open for thing, false if not.
31
+ # Returns true if gate open for any actors, false if not.
32
32
  def open?(context)
33
- return false if context.thing.nil?
34
-
35
- id = "#{context.feature_name}#{context.thing.value}"
33
+ return false unless context.actors?
34
+ id = "#{context.feature_name}#{context.actors.map(&:value).sort.join}"
36
35
  Zlib.crc32(id) % (100 * SCALING_FACTOR) < context.values.percentage_of_actors * SCALING_FACTOR
37
36
  end
38
37
 
@@ -6,8 +6,8 @@ module Flipper
6
6
  # end
7
7
  #
8
8
  # user = User.new(99)
9
- # Flipper.enable :thing, user
10
- # Flipper.enabled? :thing, user #=> true
9
+ # Flipper.enable :some_feature, user
10
+ # Flipper.enabled? :some_feature, user #=> true
11
11
  #
12
12
  module Identifier
13
13
  def flipper_id
@@ -10,7 +10,7 @@ module Flipper
10
10
  # Example Output
11
11
  #
12
12
  # flipper[:search].enabled?(user)
13
- # # Flipper feature(search) enabled? false (1.2ms) [ thing=... ]
13
+ # # Flipper feature(search) enabled? false (1.2ms) [ actors=... ]
14
14
  #
15
15
  # Returns nothing.
16
16
  def feature_operation(event)
@@ -20,10 +20,14 @@ module Flipper
20
20
  gate_name = event.payload[:gate_name]
21
21
  operation = event.payload[:operation]
22
22
  result = event.payload[:result]
23
- thing = event.payload[:thing]
24
23
 
25
24
  description = "Flipper feature(#{feature_name}) #{operation} #{result.inspect}"
26
- details = "thing=#{thing.inspect}"
25
+
26
+ details = if event.payload.key?(:actors)
27
+ "actors=#{event.payload[:actors].inspect}"
28
+ else
29
+ "thing=#{event.payload[:thing].inspect}"
30
+ end
27
31
 
28
32
  details += " gate_name=#{gate_name}" unless gate_name.nil?
29
33
 
@@ -45,7 +45,6 @@ module Flipper
45
45
  gate_name = @payload[:gate_name]
46
46
  operation = strip_trailing_question_mark(@payload[:operation])
47
47
  result = @payload[:result]
48
- thing = @payload[:thing]
49
48
 
50
49
  update_timer "flipper.feature_operation.#{operation}"
51
50
 
@@ -1,35 +1,35 @@
1
1
  module Flipper
2
2
  module Types
3
3
  class Actor < Type
4
- def self.wrappable?(thing)
5
- return false if thing.nil?
6
- thing.respond_to?(:flipper_id)
4
+ def self.wrappable?(actor)
5
+ return false if actor.nil?
6
+ actor.respond_to?(:flipper_id)
7
7
  end
8
8
 
9
- attr_reader :thing
9
+ attr_reader :actor
10
10
 
11
- def initialize(thing)
12
- raise ArgumentError, 'thing cannot be nil' if thing.nil?
11
+ def initialize(actor)
12
+ raise ArgumentError, 'actor cannot be nil' if actor.nil?
13
13
 
14
- unless thing.respond_to?(:flipper_id)
15
- raise ArgumentError, "#{thing.inspect} must respond to flipper_id, but does not"
14
+ unless actor.respond_to?(:flipper_id)
15
+ raise ArgumentError, "#{actor.inspect} must respond to flipper_id, but does not"
16
16
  end
17
17
 
18
- @thing = thing
19
- @value = thing.flipper_id.to_s
18
+ @actor = actor
19
+ @value = actor.flipper_id.to_s
20
20
  end
21
21
 
22
22
  def respond_to?(*args)
23
- super || @thing.respond_to?(*args)
23
+ super || @actor.respond_to?(*args)
24
24
  end
25
25
 
26
26
  if RUBY_VERSION >= '3.0'
27
27
  def method_missing(name, *args, **kwargs, &block)
28
- @thing.send name, *args, **kwargs, &block
28
+ @actor.send name, *args, **kwargs, &block
29
29
  end
30
30
  else
31
31
  def method_missing(name, *args, &block)
32
- @thing.send name, *args, &block
32
+ @actor.send name, *args, &block
33
33
  end
34
34
  end
35
35
  end
@@ -16,16 +16,16 @@ module Flipper
16
16
  @block = block
17
17
  @single_argument = call_with_no_context?(@block)
18
18
  else
19
- @block = ->(_thing, _context) { false }
19
+ @block = ->(actor, context) { false }
20
20
  @single_argument = false
21
21
  end
22
22
  end
23
23
 
24
- def match?(thing, context)
24
+ def match?(actor, context)
25
25
  if @single_argument
26
- @block.call(thing)
26
+ @block.call(actor)
27
27
  else
28
- @block.call(thing, context)
28
+ @block.call(actor, context)
29
29
  end
30
30
  end
31
31
 
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.27.1'.freeze
2
+ VERSION = '0.28.0'.freeze
3
3
  end
data/lib/flipper.rb CHANGED
@@ -72,12 +72,12 @@ module Flipper
72
72
  #
73
73
  # name - The Symbol name of the group.
74
74
  # block - The block that should be used to determine if the group matches a
75
- # given thing.
75
+ # given actor.
76
76
  #
77
77
  # Examples
78
78
  #
79
- # Flipper.register(:admins) { |thing|
80
- # thing.respond_to?(:admin?) && thing.admin?
79
+ # Flipper.register(:admins) { |actor|
80
+ # actor.respond_to?(:admin?) && actor.admin?
81
81
  # }
82
82
  #
83
83
  # Returns a Flipper::Group.
@@ -149,12 +149,12 @@ RSpec.describe Flipper::DSL do
149
149
  end
150
150
 
151
151
  describe '#actor' do
152
- context 'for a thing' do
152
+ context 'for an actor' do
153
153
  it 'returns actor instance' do
154
- thing = Flipper::Actor.new(33)
155
- actor = subject.actor(thing)
156
- expect(actor).to be_instance_of(Flipper::Types::Actor)
157
- expect(actor.value).to eq('33')
154
+ actor = Flipper::Actor.new(33)
155
+ flipper_actor = subject.actor(actor)
156
+ expect(flipper_actor).to be_instance_of(Flipper::Types::Actor)
157
+ expect(flipper_actor.value).to eq('33')
158
158
  end
159
159
  end
160
160
 
@@ -1,12 +1,12 @@
1
1
  RSpec.describe Flipper::FeatureCheckContext do
2
2
  let(:feature_name) { :new_profiles }
3
3
  let(:values) { Flipper::GateValues.new({}) }
4
- let(:thing) { Flipper::Actor.new('5') }
4
+ let(:actor) { Flipper::Actor.new('5') }
5
5
  let(:options) do
6
6
  {
7
7
  feature_name: feature_name,
8
8
  values: values,
9
- thing: thing,
9
+ actors: [actor],
10
10
  }
11
11
  end
12
12
 
@@ -14,7 +14,7 @@ RSpec.describe Flipper::FeatureCheckContext do
14
14
  instance = described_class.new(**options)
15
15
  expect(instance.feature_name).to eq(feature_name)
16
16
  expect(instance.values).to eq(values)
17
- expect(instance.thing).to eq(thing)
17
+ expect(instance.actors).to eq([actor])
18
18
  end
19
19
 
20
20
  it 'requires feature_name' do
@@ -31,8 +31,8 @@ RSpec.describe Flipper::FeatureCheckContext do
31
31
  end.to raise_error(ArgumentError)
32
32
  end
33
33
 
34
- it 'requires thing' do
35
- options.delete(:thing)
34
+ it 'requires actors' do
35
+ options.delete(:actors)
36
36
  expect do
37
37
  described_class.new(**options)
38
38
  end.to raise_error(ArgumentError)