flipper 0.27.1 → 0.28.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +45 -0
  3. data/Rakefile +3 -3
  4. data/benchmark/enabled_multiple_actors_ips.rb +20 -0
  5. data/examples/dsl.rb +3 -3
  6. data/examples/enabled_for_actor.rb +4 -2
  7. data/examples/mirroring.rb +59 -0
  8. data/lib/flipper/adapters/memory.rb +33 -13
  9. data/lib/flipper/dsl.rb +4 -4
  10. data/lib/flipper/errors.rb +3 -3
  11. data/lib/flipper/feature.rb +12 -10
  12. data/lib/flipper/feature_check_context.rb +8 -4
  13. data/lib/flipper/gate.rb +12 -11
  14. data/lib/flipper/gates/actor.rb +11 -8
  15. data/lib/flipper/gates/group.rb +4 -2
  16. data/lib/flipper/gates/percentage_of_actors.rb +4 -5
  17. data/lib/flipper/identifier.rb +2 -2
  18. data/lib/flipper/instrumentation/log_subscriber.rb +24 -5
  19. data/lib/flipper/instrumentation/subscriber.rb +0 -1
  20. data/lib/flipper/poller.rb +1 -1
  21. data/lib/flipper/types/actor.rb +13 -13
  22. data/lib/flipper/types/group.rb +4 -4
  23. data/lib/flipper/version.rb +1 -1
  24. data/lib/flipper.rb +3 -3
  25. data/spec/flipper/adapters/memory_spec.rb +11 -2
  26. data/spec/flipper/dsl_spec.rb +5 -5
  27. data/spec/flipper/feature_check_context_spec.rb +5 -5
  28. data/spec/flipper/feature_spec.rb +76 -32
  29. data/spec/flipper/gates/boolean_spec.rb +1 -1
  30. data/spec/flipper/gates/group_spec.rb +2 -3
  31. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -5
  32. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
  33. data/spec/flipper/instrumentation/log_subscriber_spec.rb +5 -5
  34. data/spec/flipper/types/actor_spec.rb +45 -45
  35. data/spec/flipper/types/group_spec.rb +2 -2
  36. data/spec/flipper_integration_spec.rb +62 -50
  37. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba1d158db684bce01872e3f32c81f522d32086a78af94a78a6266422079a3473
4
- data.tar.gz: 9a7ea456415d5fd73d5adcb66bb77c4701a2a6e6b3ab94d5c89af52202907e7c
3
+ metadata.gz: 5373416d79c0f4e67654bf9945bb4b1702fae520946f127be970fa89a7f3151a
4
+ data.tar.gz: 194180daeb197f5f2323363915de63daea4458486585eb687c76d380cc48df93
5
5
  SHA512:
6
- metadata.gz: f708342f07ebe1bfbf3c9d0e0f1db184ccd686bdd995752b6459f96ab119fe589573e0077d156afad1ac8f73b45140a54e301ff23ef423e92b3c109e89285992
7
- data.tar.gz: e9ca8640f9c9b655ae464651adda6cb6998355b967b58119fc42c2a87d3c3f848538dd05ff36585e75527384aca05ab6be1996558679a0090ef24bb8a6a7a682
6
+ metadata.gz: 8e6652132da82dfd9ee46419d3f4fcc9836b6464fe2a89d670e17315f1664db6f88784ab3b3006b2aa43b150461a1645791d9334d96b93dd685b31cf98909a1d
7
+ data.tar.gz: 161e2acea4928753b8ebb8d83e9fa05e33dcee6bab15cc2323c3acc3beb8d5e95f96aace11ffceddcc2dc035ee3d9aa3918f61e956a9c4eeb243387c8eacf87a
data/Changelog.md CHANGED
@@ -2,6 +2,51 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 0.28.1
6
+
7
+ ### Additions/Changes
8
+
9
+ * Use new method of making logs bold for rails (https://github.com/jnunemaker/flipper/pull/726)
10
+ * Bundle bootstrap, jquery and poppler with the library. (https://github.com/jnunemaker/flipper/pull/731)
11
+
12
+ ## 0.28.0
13
+
14
+ ### Additions/Changes
15
+
16
+ * 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.
17
+ ```diff
18
+ - [user, user.team, user.org].any? { |actor| Flipper.enabled?(:my_feature, actor) }
19
+ + Flipper.enabled?(:my_feature, user, user.team, user.org)
20
+ ```
21
+ * If you currently use `actor.thing` in a group, you'll need to change it to `actor.actor`.
22
+ ```diff
23
+ - Flipper.register(:our_group) do |actor|
24
+ - actor.thing.is_a?(OurClassName)
25
+ - end
26
+ + Flipper.register(:our_group) do |actor|
27
+ + actor.actor.is_a?(OurClassName)
28
+ + end
29
+ ```
30
+ * If you currently use `context.thing` in a group or elsewhere, you'll need to change it to `context.actors`.
31
+ ```diff
32
+ - Flipper.register(:our_group) do |actor, context|
33
+ - context.thing.is_a?(OurClassName)
34
+ - end
35
+ + Flipper.register(:our_group) do |actor, context|
36
+ + context.actors.any? { |actor| actor.is_a?(OurClassName) }
37
+ + end
38
+ ```
39
+
40
+ ### Deprecations
41
+
42
+ * `:thing` in `enabled?` instrumentation payload. Use `:actors` instead.
43
+ ```diff
44
+ ActiveSupport::Notifications.subscribe('enabled?.feature_operation.flipper') do |name, start, finish, id, payload|
45
+ - payload[:thing]
46
+ + payload[:actors]
47
+ end
48
+ ```
49
+
5
50
  ## 0.27.1
6
51
 
7
52
  * 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
@@ -0,0 +1,59 @@
1
+ require 'bundler/setup'
2
+ require_relative 'active_record/ar_setup'
3
+ require 'flipper'
4
+ require 'flipper/adapters/redis'
5
+ require 'flipper/adapters/active_record'
6
+
7
+ # Say you have production...
8
+ production_adapter = Flipper::Adapters::Memory.new
9
+ production = Flipper.new(production_adapter)
10
+
11
+ # And production has some stuff enabled...
12
+ production.enable(:search)
13
+ production.enable_percentage_of_time(:verbose_logging, 5)
14
+ production.enable_percentage_of_actors(:new_feature, 5)
15
+ production.enable_actor(:issues, Flipper::Actor.new('1'))
16
+ production.enable_actor(:issues, Flipper::Actor.new('2'))
17
+ production.enable_group(:request_tracing, :staff)
18
+
19
+ # And you would like to mirror production to staging...
20
+ staging_adapter = Flipper::Adapters::Memory.new
21
+ staging = Flipper.new(staging_adapter)
22
+ staging_export = staging.export
23
+
24
+ puts "Here is the state of the world for staging and production..."
25
+ puts "Staging"
26
+ staging.features.each do |feature|
27
+ pp feature: feature.key, values: feature.gate_values
28
+ end
29
+ puts "Production"
30
+ production.features.each do |feature|
31
+ pp feature: feature.key, values: feature.gate_values
32
+ end
33
+
34
+ # NOTE: This wipes active record clean and copies features/gates from redis into active record.
35
+ puts "Mirroring production to staging..."
36
+ staging.import(production.export)
37
+ puts "Staging is now identical to Production."
38
+
39
+ puts "Staging"
40
+ staging.features.each do |feature|
41
+ pp feature: feature.key, values: feature.gate_values
42
+ end
43
+ puts "Production"
44
+ production.features.each do |feature|
45
+ pp feature: feature.key, values: feature.gate_values
46
+ end
47
+
48
+ puts "Restoring staging to original state..."
49
+ staging.import(staging_export)
50
+ puts "Staging restored."
51
+
52
+ puts "Staging"
53
+ staging.features.each do |feature|
54
+ pp feature: feature.key, values: feature.gate_values
55
+ end
56
+ puts "Production"
57
+ production.features.each do |feature|
58
+ pp feature: feature.key, values: feature.gate_values
59
+ end
@@ -1,6 +1,5 @@
1
1
  require "flipper/adapter"
2
2
  require "flipper/typecast"
3
- require 'concurrent/atomic/read_write_lock'
4
3
 
5
4
  module Flipper
6
5
  module Adapters
@@ -15,43 +14,44 @@ module Flipper
15
14
  attr_reader :name
16
15
 
17
16
  # Public
18
- def initialize(source = nil)
17
+ def initialize(source = nil, threadsafe: true)
19
18
  @source = Typecast.features_hash(source)
20
19
  @name = :memory
21
- @lock = Concurrent::ReadWriteLock.new
20
+ @lock = Mutex.new if threadsafe
21
+ reset
22
22
  end
23
23
 
24
24
  # Public: The set of known features.
25
25
  def features
26
- @lock.with_read_lock { @source.keys }.to_set
26
+ synchronize { @source.keys }.to_set
27
27
  end
28
28
 
29
29
  # Public: Adds a feature to the set of known features.
30
30
  def add(feature)
31
- @lock.with_write_lock { @source[feature.key] ||= default_config }
31
+ synchronize { @source[feature.key] ||= default_config }
32
32
  true
33
33
  end
34
34
 
35
35
  # Public: Removes a feature from the set of known features and clears
36
36
  # all the values for the feature.
37
37
  def remove(feature)
38
- @lock.with_write_lock { @source.delete(feature.key) }
38
+ synchronize { @source.delete(feature.key) }
39
39
  true
40
40
  end
41
41
 
42
42
  # Public: Clears all the gate values for a feature.
43
43
  def clear(feature)
44
- @lock.with_write_lock { @source[feature.key] = default_config }
44
+ synchronize { @source[feature.key] = default_config }
45
45
  true
46
46
  end
47
47
 
48
48
  # Public
49
49
  def get(feature)
50
- @lock.with_read_lock { @source[feature.key] } || default_config
50
+ synchronize { @source[feature.key] } || default_config
51
51
  end
52
52
 
53
53
  def get_multi(features)
54
- @lock.with_read_lock do
54
+ synchronize do
55
55
  result = {}
56
56
  features.each do |feature|
57
57
  result[feature.key] = @source[feature.key] || default_config
@@ -61,12 +61,12 @@ module Flipper
61
61
  end
62
62
 
63
63
  def get_all
64
- @lock.with_read_lock { Typecast.features_hash(@source) }
64
+ synchronize { Typecast.features_hash(@source) }
65
65
  end
66
66
 
67
67
  # Public
68
68
  def enable(feature, gate, thing)
69
- @lock.with_write_lock do
69
+ synchronize do
70
70
  @source[feature.key] ||= default_config
71
71
 
72
72
  case gate.data_type
@@ -87,7 +87,7 @@ module Flipper
87
87
 
88
88
  # Public
89
89
  def disable(feature, gate, thing)
90
- @lock.with_write_lock do
90
+ synchronize do
91
91
  @source[feature.key] ||= default_config
92
92
 
93
93
  case gate.data_type
@@ -118,9 +118,29 @@ module Flipper
118
118
  def import(source)
119
119
  adapter = self.class.from(source)
120
120
  get_all = Typecast.features_hash(adapter.get_all)
121
- @lock.with_write_lock { @source.replace(get_all) }
121
+ synchronize { @source.replace(get_all) }
122
122
  true
123
123
  end
124
+
125
+ private
126
+
127
+ def reset
128
+ @pid = Process.pid
129
+ @lock&.unlock if @lock&.locked?
130
+ end
131
+
132
+ def forked?
133
+ @pid != Process.pid
134
+ end
135
+
136
+ def synchronize(&block)
137
+ if @lock
138
+ reset if forked?
139
+ @lock.synchronize(&block)
140
+ else
141
+ block.call
142
+ end
143
+ end
124
144
  end
125
145
  end
126
146
  end
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,15 +20,19 @@ 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
 
30
34
  name = '%s (%.1fms)' % [description, event.duration]
31
- debug " #{color(name, CYAN, true)} [ #{details} ]"
35
+ debug " #{color_name(name)} [ #{details} ]"
32
36
  end
33
37
 
34
38
  # Logs an adapter operation. If operation is for a feature, then that
@@ -60,12 +64,27 @@ module Flipper
60
64
  details = "result=#{result.inspect}"
61
65
 
62
66
  name = '%s (%.1fms)' % [description, event.duration]
63
- debug " #{color(name, CYAN, true)} [ #{details} ]"
67
+ debug " #{color_name(name)} [ #{details} ]"
64
68
  end
65
69
 
66
70
  def logger
67
71
  self.class.logger
68
72
  end
73
+
74
+ private
75
+
76
+ # Rails 7.1 changed the signature of this function.
77
+ # Checking if > 7.0.99 rather than >= 7.1 so that 7.1 pre-release versions are included.
78
+ COLOR_OPTIONS = if Rails.gem_version > Gem::Version.new('7.0.99')
79
+ { bold: true }.freeze
80
+ else
81
+ true
82
+ end
83
+ private_constant :COLOR_OPTIONS
84
+
85
+ def color_name(name)
86
+ color(name, CYAN, COLOR_OPTIONS)
87
+ end
69
88
  end
70
89
  end
71
90
 
@@ -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
 
@@ -28,7 +28,7 @@ module Flipper
28
28
  @remote_adapter = options.fetch(:remote_adapter)
29
29
  @interval = options.fetch(:interval, 10).to_f
30
30
  @last_synced_at = Concurrent::AtomicFixnum.new(0)
31
- @adapter = Adapters::Memory.new
31
+ @adapter = Adapters::Memory.new(nil, threadsafe: true)
32
32
 
33
33
  if @interval < 1
34
34
  warn "Flipper::Cloud poll interval must be greater than or equal to 1 but was #{@interval}. Setting @interval to 1."
@@ -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