flipper 0.27.1 → 0.28.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +45 -0
- data/Rakefile +3 -3
- data/benchmark/enabled_multiple_actors_ips.rb +20 -0
- data/examples/dsl.rb +3 -3
- data/examples/enabled_for_actor.rb +4 -2
- data/examples/mirroring.rb +59 -0
- data/lib/flipper/adapters/memory.rb +33 -13
- data/lib/flipper/dsl.rb +4 -4
- data/lib/flipper/errors.rb +3 -3
- data/lib/flipper/feature.rb +12 -10
- data/lib/flipper/feature_check_context.rb +8 -4
- data/lib/flipper/gate.rb +12 -11
- data/lib/flipper/gates/actor.rb +11 -8
- data/lib/flipper/gates/group.rb +4 -2
- data/lib/flipper/gates/percentage_of_actors.rb +4 -5
- data/lib/flipper/identifier.rb +2 -2
- data/lib/flipper/instrumentation/log_subscriber.rb +24 -5
- data/lib/flipper/instrumentation/subscriber.rb +0 -1
- data/lib/flipper/poller.rb +1 -1
- data/lib/flipper/types/actor.rb +13 -13
- data/lib/flipper/types/group.rb +4 -4
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +3 -3
- data/spec/flipper/adapters/memory_spec.rb +11 -2
- data/spec/flipper/dsl_spec.rb +5 -5
- data/spec/flipper/feature_check_context_spec.rb +5 -5
- data/spec/flipper/feature_spec.rb +76 -32
- data/spec/flipper/gates/boolean_spec.rb +1 -1
- data/spec/flipper/gates/group_spec.rb +2 -3
- data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -5
- data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +5 -5
- data/spec/flipper/types/actor_spec.rb +45 -45
- data/spec/flipper/types/group_spec.rb +2 -2
- data/spec/flipper_integration_spec.rb +62 -50
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5373416d79c0f4e67654bf9945bb4b1702fae520946f127be970fa89a7f3151a
|
4
|
+
data.tar.gz: 194180daeb197f5f2323363915de63daea4458486585eb687c76d380cc48df93
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
21
|
-
|
22
|
-
|
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
|
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
|
-
|
62
|
-
puts Flipper.actor(
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
44
|
+
synchronize { @source[feature.key] = default_config }
|
45
45
|
true
|
46
46
|
end
|
47
47
|
|
48
48
|
# Public
|
49
49
|
def get(feature)
|
50
|
-
|
50
|
+
synchronize { @source[feature.key] } || default_config
|
51
51
|
end
|
52
52
|
|
53
53
|
def get_multi(features)
|
54
|
-
|
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
|
-
|
64
|
+
synchronize { Typecast.features_hash(@source) }
|
65
65
|
end
|
66
66
|
|
67
67
|
# Public
|
68
68
|
def enable(feature, gate, thing)
|
69
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
244
|
-
def actor(
|
245
|
-
Types::Actor.new(
|
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.
|
data/lib/flipper/errors.rb
CHANGED
@@ -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
|
5
|
+
# Raised when gate can not be found for an actor.
|
6
6
|
class GateNotFound < Error
|
7
|
-
def initialize(
|
8
|
-
super "Could not find gate for #{
|
7
|
+
def initialize(actor)
|
8
|
+
super "Could not find gate for #{actor.inspect}"
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
data/lib/flipper/feature.rb
CHANGED
@@ -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
|
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?(
|
103
|
-
|
102
|
+
def enabled?(*actors)
|
103
|
+
actors = actors.flatten.compact.map { |actor| Types::Actor.wrap(actor) }
|
104
|
+
actors = nil if actors.empty?
|
104
105
|
|
105
|
-
|
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
|
-
|
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
|
364
|
+
# Public: Find the gate that protects an actor.
|
363
365
|
#
|
364
|
-
#
|
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
|
368
|
-
def gate_for(
|
369
|
-
gates.detect { |gate| gate.protects?(
|
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
|
11
|
-
attr_reader :
|
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:,
|
13
|
+
def initialize(feature_name:, values:, actors:)
|
14
14
|
@feature_name = feature_name
|
15
15
|
@values = values
|
16
|
-
@
|
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?(
|
21
|
+
def enabled?(value)
|
22
22
|
raise 'Not implemented'
|
23
23
|
end
|
24
24
|
|
25
|
-
# Internal: Check if a gate is open for
|
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
|
28
|
-
def open?(
|
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
|
33
|
+
# Internal: Check if a gate is protects an actor. Implemented in subclass.
|
33
34
|
#
|
34
|
-
# Returns true if gate protects
|
35
|
-
def protects?(
|
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
|
40
|
-
# types so adapters always get
|
41
|
-
def wrap(
|
42
|
-
|
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.
|
data/lib/flipper/gates/actor.rb
CHANGED
@@ -19,20 +19,23 @@ module Flipper
|
|
19
19
|
!value.empty?
|
20
20
|
end
|
21
21
|
|
22
|
-
# Internal: Checks if the gate is open for
|
22
|
+
# Internal: Checks if the gate is open for an actor.
|
23
23
|
#
|
24
|
-
# Returns true if gate open for
|
24
|
+
# Returns true if gate open for actor, false if not.
|
25
25
|
def open?(context)
|
26
|
-
return false
|
27
|
-
|
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(
|
31
|
-
Types::Actor.wrap(
|
33
|
+
def wrap(actor)
|
34
|
+
Types::Actor.wrap(actor)
|
32
35
|
end
|
33
36
|
|
34
|
-
def protects?(
|
35
|
-
Types::Actor.wrappable?(
|
37
|
+
def protects?(actor)
|
38
|
+
Types::Actor.wrappable?(actor)
|
36
39
|
end
|
37
40
|
end
|
38
41
|
end
|
data/lib/flipper/gates/group.rb
CHANGED
@@ -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
|
26
|
+
return false unless context.actors?
|
27
27
|
|
28
28
|
context.values.groups.any? do |name|
|
29
|
-
|
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
|
29
|
+
# Internal: Checks if the gate is open for one or more actors.
|
30
30
|
#
|
31
|
-
# Returns true if gate open for
|
31
|
+
# Returns true if gate open for any actors, false if not.
|
32
32
|
def open?(context)
|
33
|
-
return false
|
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
|
|
data/lib/flipper/identifier.rb
CHANGED
@@ -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) [
|
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
|
-
|
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 " #{
|
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 " #{
|
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
|
|
data/lib/flipper/poller.rb
CHANGED
@@ -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."
|
data/lib/flipper/types/actor.rb
CHANGED
@@ -1,35 +1,35 @@
|
|
1
1
|
module Flipper
|
2
2
|
module Types
|
3
3
|
class Actor < Type
|
4
|
-
def self.wrappable?(
|
5
|
-
return false if
|
6
|
-
|
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 :
|
9
|
+
attr_reader :actor
|
10
10
|
|
11
|
-
def initialize(
|
12
|
-
raise ArgumentError, '
|
11
|
+
def initialize(actor)
|
12
|
+
raise ArgumentError, 'actor cannot be nil' if actor.nil?
|
13
13
|
|
14
|
-
unless
|
15
|
-
raise ArgumentError, "#{
|
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
|
-
@
|
19
|
-
@value =
|
18
|
+
@actor = actor
|
19
|
+
@value = actor.flipper_id.to_s
|
20
20
|
end
|
21
21
|
|
22
22
|
def respond_to?(*args)
|
23
|
-
super || @
|
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
|
-
@
|
28
|
+
@actor.send name, *args, **kwargs, &block
|
29
29
|
end
|
30
30
|
else
|
31
31
|
def method_missing(name, *args, &block)
|
32
|
-
@
|
32
|
+
@actor.send name, *args, &block
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|