flipper 0.27.1 → 0.28.1
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.
- 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
    
        data/lib/flipper/types/group.rb
    CHANGED
    
    | @@ -16,16 +16,16 @@ module Flipper | |
| 16 16 | 
             
                      @block = block
         | 
| 17 17 | 
             
                      @single_argument = call_with_no_context?(@block)
         | 
| 18 18 | 
             
                    else
         | 
| 19 | 
            -
                      @block = ->( | 
| 19 | 
            +
                      @block = ->(actor, context) { false }
         | 
| 20 20 | 
             
                      @single_argument = false
         | 
| 21 21 | 
             
                    end
         | 
| 22 22 | 
             
                  end
         | 
| 23 23 |  | 
| 24 | 
            -
                  def match?( | 
| 24 | 
            +
                  def match?(actor, context)
         | 
| 25 25 | 
             
                    if @single_argument
         | 
| 26 | 
            -
                      @block.call( | 
| 26 | 
            +
                      @block.call(actor)
         | 
| 27 27 | 
             
                    else
         | 
| 28 | 
            -
                      @block.call( | 
| 28 | 
            +
                      @block.call(actor, context)
         | 
| 29 29 | 
             
                    end
         | 
| 30 30 | 
             
                  end
         | 
| 31 31 |  | 
    
        data/lib/flipper/version.rb
    CHANGED
    
    
    
        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  | 
| 75 | 
            +
              #         given actor.
         | 
| 76 76 | 
             
              #
         | 
| 77 77 | 
             
              # Examples
         | 
| 78 78 | 
             
              #
         | 
| 79 | 
            -
              #   Flipper.register(:admins) { | | 
| 80 | 
            -
              #      | 
| 79 | 
            +
              #   Flipper.register(:admins) { |actor|
         | 
| 80 | 
            +
              #     actor.respond_to?(:admin?) && actor.admin?
         | 
| 81 81 | 
             
              #   }
         | 
| 82 82 | 
             
              #
         | 
| 83 83 | 
             
              # Returns a Flipper::Group.
         | 
| @@ -1,8 +1,17 @@ | |
| 1 1 | 
             
            RSpec.describe Flipper::Adapters::Memory do
         | 
| 2 2 | 
             
              let(:source) { {} }
         | 
| 3 | 
            -
              subject { described_class.new(source) }
         | 
| 4 3 |  | 
| 5 | 
            -
               | 
| 4 | 
            +
              context 'threadsafe: true' do
         | 
| 5 | 
            +
                subject { described_class.new(source, threadsafe: true) }
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                it_should_behave_like 'a flipper adapter'
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              context 'threadsafe: false' do
         | 
| 11 | 
            +
                subject { described_class.new(source, threadsafe: false) }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                it_should_behave_like 'a flipper adapter'
         | 
| 14 | 
            +
              end
         | 
| 6 15 |  | 
| 7 16 | 
             
              it "can initialize from big hash" do
         | 
| 8 17 | 
             
                flipper = Flipper.new(subject)
         | 
    
        data/spec/flipper/dsl_spec.rb
    CHANGED
    
    | @@ -149,12 +149,12 @@ RSpec.describe Flipper::DSL do | |
| 149 149 | 
             
              end
         | 
| 150 150 |  | 
| 151 151 | 
             
              describe '#actor' do
         | 
| 152 | 
            -
                context 'for  | 
| 152 | 
            +
                context 'for an actor' do
         | 
| 153 153 | 
             
                  it 'returns actor instance' do
         | 
| 154 | 
            -
                     | 
| 155 | 
            -
                     | 
| 156 | 
            -
                    expect( | 
| 157 | 
            -
                    expect( | 
| 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(: | 
| 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 | 
            -
                   | 
| 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. | 
| 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  | 
| 35 | 
            -
                options.delete(: | 
| 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)
         | 
| @@ -32,6 +32,52 @@ RSpec.describe Flipper::Feature do | |
| 32 32 | 
             
                end
         | 
| 33 33 | 
             
              end
         | 
| 34 34 |  | 
| 35 | 
            +
              describe "#enabled?" do
         | 
| 36 | 
            +
                context "for an actor" do
         | 
| 37 | 
            +
                  let(:actor) { Flipper::Actor.new("User;1") }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  it 'returns true if feature is enabled' do
         | 
| 40 | 
            +
                    subject.enable
         | 
| 41 | 
            +
                    expect(subject.enabled?(actor)).to be(true)
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  it 'returns false if feature is disabled' do
         | 
| 45 | 
            +
                    subject.disable
         | 
| 46 | 
            +
                    expect(subject.enabled?(actor)).to be(false)
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                context "for multiple actors" do
         | 
| 51 | 
            +
                  let(:actors) {
         | 
| 52 | 
            +
                    [
         | 
| 53 | 
            +
                      Flipper::Actor.new("User;1"),
         | 
| 54 | 
            +
                      Flipper::Actor.new("User;2"),
         | 
| 55 | 
            +
                      Flipper::Actor.new("User;3"),
         | 
| 56 | 
            +
                    ]
         | 
| 57 | 
            +
                  }
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  it 'returns true if feature is enabled' do
         | 
| 60 | 
            +
                    subject.enable
         | 
| 61 | 
            +
                    expect(subject.enabled?(actors)).to be(true)
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  it 'returns true if feature is enabled for any actor' do
         | 
| 65 | 
            +
                    subject.enable_actor actors.first
         | 
| 66 | 
            +
                    expect(subject.enabled?(actors)).to be(true)
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  it 'returns true if feature is enabled for any actor with multiple arguments' do
         | 
| 70 | 
            +
                    subject.enable_actor actors.last
         | 
| 71 | 
            +
                    expect(subject.enabled?(*actors)).to be(true)
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  it 'returns false if feature is disabled for all actors' do
         | 
| 75 | 
            +
                    subject.disable
         | 
| 76 | 
            +
                    expect(subject.enabled?(actors)).to be(false)
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
             | 
| 35 81 | 
             
              describe '#to_s' do
         | 
| 36 82 | 
             
                it 'returns name as string' do
         | 
| 37 83 | 
             
                  feature = described_class.new(:search, adapter)
         | 
| @@ -148,29 +194,29 @@ RSpec.describe Flipper::Feature do | |
| 148 194 | 
             
                end
         | 
| 149 195 |  | 
| 150 196 | 
             
                it 'is recorded for enable' do
         | 
| 151 | 
            -
                   | 
| 152 | 
            -
                  gate = subject.gate_for( | 
| 197 | 
            +
                  actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
         | 
| 198 | 
            +
                  gate = subject.gate_for(actor)
         | 
| 153 199 |  | 
| 154 | 
            -
                  subject.enable( | 
| 200 | 
            +
                  subject.enable(actor)
         | 
| 155 201 |  | 
| 156 202 | 
             
                  event = instrumenter.events.last
         | 
| 157 203 | 
             
                  expect(event).not_to be_nil
         | 
| 158 204 | 
             
                  expect(event.name).to eq('feature_operation.flipper')
         | 
| 159 205 | 
             
                  expect(event.payload[:feature_name]).to eq(:search)
         | 
| 160 206 | 
             
                  expect(event.payload[:operation]).to eq(:enable)
         | 
| 161 | 
            -
                  expect(event.payload[:thing]).to eq( | 
| 207 | 
            +
                  expect(event.payload[:thing]).to eq(actor)
         | 
| 162 208 | 
             
                  expect(event.payload[:result]).not_to be_nil
         | 
| 163 209 | 
             
                end
         | 
| 164 210 |  | 
| 165 211 | 
             
                it 'always instruments flipper type instance for enable' do
         | 
| 166 | 
            -
                   | 
| 167 | 
            -
                  gate = subject.gate_for( | 
| 212 | 
            +
                  actor = Flipper::Actor.new('1')
         | 
| 213 | 
            +
                  gate = subject.gate_for(actor)
         | 
| 168 214 |  | 
| 169 | 
            -
                  subject.enable( | 
| 215 | 
            +
                  subject.enable(actor)
         | 
| 170 216 |  | 
| 171 217 | 
             
                  event = instrumenter.events.last
         | 
| 172 218 | 
             
                  expect(event).not_to be_nil
         | 
| 173 | 
            -
                  expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new( | 
| 219 | 
            +
                  expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(actor))
         | 
| 174 220 | 
             
                end
         | 
| 175 221 |  | 
| 176 222 | 
             
                it 'is recorded for disable' do
         | 
| @@ -219,15 +265,15 @@ RSpec.describe Flipper::Feature do | |
| 219 265 | 
             
                end
         | 
| 220 266 |  | 
| 221 267 | 
             
                it 'always instruments flipper type instance for disable' do
         | 
| 222 | 
            -
                   | 
| 223 | 
            -
                  gate = subject.gate_for( | 
| 268 | 
            +
                  actor = Flipper::Actor.new('1')
         | 
| 269 | 
            +
                  gate = subject.gate_for(actor)
         | 
| 224 270 |  | 
| 225 | 
            -
                  subject.disable( | 
| 271 | 
            +
                  subject.disable(actor)
         | 
| 226 272 |  | 
| 227 273 | 
             
                  event = instrumenter.events.last
         | 
| 228 274 | 
             
                  expect(event).not_to be_nil
         | 
| 229 275 | 
             
                  expect(event.payload[:operation]).to eq(:disable)
         | 
| 230 | 
            -
                  expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new( | 
| 276 | 
            +
                  expect(event.payload[:thing]).to eq(Flipper::Types::Actor.new(actor))
         | 
| 231 277 | 
             
                end
         | 
| 232 278 |  | 
| 233 279 | 
             
                it 'is recorded for add' do
         | 
| @@ -275,17 +321,15 @@ RSpec.describe Flipper::Feature do | |
| 275 321 | 
             
                end
         | 
| 276 322 |  | 
| 277 323 | 
             
                it 'is recorded for enabled?' do
         | 
| 278 | 
            -
                   | 
| 279 | 
            -
                   | 
| 280 | 
            -
             | 
| 281 | 
            -
                  subject.enabled?(thing)
         | 
| 324 | 
            +
                  actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
         | 
| 325 | 
            +
                  subject.enabled?(actor)
         | 
| 282 326 |  | 
| 283 327 | 
             
                  event = instrumenter.events.last
         | 
| 284 328 | 
             
                  expect(event).not_to be_nil
         | 
| 285 329 | 
             
                  expect(event.name).to eq('feature_operation.flipper')
         | 
| 286 330 | 
             
                  expect(event.payload[:feature_name]).to eq(:search)
         | 
| 287 331 | 
             
                  expect(event.payload[:operation]).to eq(:enabled?)
         | 
| 288 | 
            -
                  expect(event.payload[: | 
| 332 | 
            +
                  expect(event.payload[:actors]).to eq([actor])
         | 
| 289 333 | 
             
                  expect(event.payload[:result]).to eq(false)
         | 
| 290 334 | 
             
                end
         | 
| 291 335 |  | 
| @@ -293,8 +337,8 @@ RSpec.describe Flipper::Feature do | |
| 293 337 | 
             
                actor = Flipper::Types::Actor.new(user)
         | 
| 294 338 | 
             
                {
         | 
| 295 339 | 
             
                  nil => nil,
         | 
| 296 | 
            -
                  user => actor,
         | 
| 297 | 
            -
                  actor => actor,
         | 
| 340 | 
            +
                  user => [actor],
         | 
| 341 | 
            +
                  actor => [actor],
         | 
| 298 342 | 
             
                }.each do |thing, wrapped_thing|
         | 
| 299 343 | 
             
                  it "always instruments #{thing.inspect} as #{wrapped_thing.class} for enabled?" do
         | 
| 300 344 | 
             
                    subject.enabled?(thing)
         | 
| @@ -302,7 +346,7 @@ RSpec.describe Flipper::Feature do | |
| 302 346 | 
             
                    event = instrumenter.events.last
         | 
| 303 347 | 
             
                    expect(event).not_to be_nil
         | 
| 304 348 | 
             
                    expect(event.payload[:operation]).to eq(:enabled?)
         | 
| 305 | 
            -
                    expect(event.payload[: | 
| 349 | 
            +
                    expect(event.payload[:actors]).to eq(wrapped_thing)
         | 
| 306 350 | 
             
                  end
         | 
| 307 351 | 
             
                end
         | 
| 308 352 | 
             
              end
         | 
| @@ -428,10 +472,10 @@ RSpec.describe Flipper::Feature do | |
| 428 472 |  | 
| 429 473 | 
             
                context 'when one or more groups enabled' do
         | 
| 430 474 | 
             
                  before do
         | 
| 431 | 
            -
                    @staff = Flipper.register(:staff) { | | 
| 432 | 
            -
                    @preview_features = Flipper.register(:preview_features) { | | 
| 433 | 
            -
                    @not_enabled = Flipper.register(:not_enabled) { | | 
| 434 | 
            -
                    @disabled = Flipper.register(:disabled) { | | 
| 475 | 
            +
                    @staff = Flipper.register(:staff) { |actor| true }
         | 
| 476 | 
            +
                    @preview_features = Flipper.register(:preview_features) { |actor| true }
         | 
| 477 | 
            +
                    @not_enabled = Flipper.register(:not_enabled) { |actor| true }
         | 
| 478 | 
            +
                    @disabled = Flipper.register(:disabled) { |actor| true }
         | 
| 435 479 | 
             
                    subject.enable @staff
         | 
| 436 480 | 
             
                    subject.enable @preview_features
         | 
| 437 481 | 
             
                    subject.disable @disabled
         | 
| @@ -467,10 +511,10 @@ RSpec.describe Flipper::Feature do | |
| 467 511 |  | 
| 468 512 | 
             
                context 'when one or more groups enabled' do
         | 
| 469 513 | 
             
                  before do
         | 
| 470 | 
            -
                    @staff = Flipper.register(:staff) { | | 
| 471 | 
            -
                    @preview_features = Flipper.register(:preview_features) { | | 
| 472 | 
            -
                    @not_enabled = Flipper.register(:not_enabled) { | | 
| 473 | 
            -
                    @disabled = Flipper.register(:disabled) { | | 
| 514 | 
            +
                    @staff = Flipper.register(:staff) { |actor| true }
         | 
| 515 | 
            +
                    @preview_features = Flipper.register(:preview_features) { |actor| true }
         | 
| 516 | 
            +
                    @not_enabled = Flipper.register(:not_enabled) { |actor| true }
         | 
| 517 | 
            +
                    @disabled = Flipper.register(:disabled) { |actor| true }
         | 
| 474 518 | 
             
                    subject.enable @staff
         | 
| 475 519 | 
             
                    subject.enable @preview_features
         | 
| 476 520 | 
             
                    subject.disable @disabled
         | 
| @@ -494,10 +538,10 @@ RSpec.describe Flipper::Feature do | |
| 494 538 |  | 
| 495 539 | 
             
                context 'when one or more groups enabled' do
         | 
| 496 540 | 
             
                  before do
         | 
| 497 | 
            -
                    @staff = Flipper.register(:staff) { | | 
| 498 | 
            -
                    @preview_features = Flipper.register(:preview_features) { | | 
| 499 | 
            -
                    @not_enabled = Flipper.register(:not_enabled) { | | 
| 500 | 
            -
                    @disabled = Flipper.register(:disabled) { | | 
| 541 | 
            +
                    @staff = Flipper.register(:staff) { |actor| true }
         | 
| 542 | 
            +
                    @preview_features = Flipper.register(:preview_features) { |actor| true }
         | 
| 543 | 
            +
                    @not_enabled = Flipper.register(:not_enabled) { |actor| true }
         | 
| 544 | 
            +
                    @disabled = Flipper.register(:disabled) { |actor| true }
         | 
| 501 545 | 
             
                    subject.enable @staff
         | 
| 502 546 | 
             
                    subject.enable @preview_features
         | 
| 503 547 | 
             
                    subject.disable @disabled
         | 
| @@ -9,7 +9,7 @@ RSpec.describe Flipper::Gates::Boolean do | |
| 9 9 | 
             
                Flipper::FeatureCheckContext.new(
         | 
| 10 10 | 
             
                  feature_name: feature_name,
         | 
| 11 11 | 
             
                  values: Flipper::GateValues.new(boolean: bool),
         | 
| 12 | 
            -
                   | 
| 12 | 
            +
                  actors: [Flipper::Types::Actor.new(Flipper::Actor.new('1'))]
         | 
| 13 13 | 
             
                )
         | 
| 14 14 | 
             
              end
         | 
| 15 15 |  | 
| @@ -9,18 +9,17 @@ RSpec.describe Flipper::Gates::Group do | |
| 9 9 | 
             
                Flipper::FeatureCheckContext.new(
         | 
| 10 10 | 
             
                  feature_name: feature_name,
         | 
| 11 11 | 
             
                  values: Flipper::GateValues.new(groups: set),
         | 
| 12 | 
            -
                   | 
| 12 | 
            +
                  actors: [Flipper::Types::Actor.new(Flipper::Actor.new('5'))]
         | 
| 13 13 | 
             
                )
         | 
| 14 14 | 
             
              end
         | 
| 15 15 |  | 
| 16 16 | 
             
              describe '#open?' do
         | 
| 17 17 | 
             
                context 'with a group in adapter, but not registered' do
         | 
| 18 18 | 
             
                  before do
         | 
| 19 | 
            -
                    Flipper.register(:staff) { | | 
| 19 | 
            +
                    Flipper.register(:staff) { |actor| true }
         | 
| 20 20 | 
             
                  end
         | 
| 21 21 |  | 
| 22 22 | 
             
                  it 'ignores group' do
         | 
| 23 | 
            -
                    thing = Flipper::Actor.new('5')
         | 
| 24 23 | 
             
                    expect(subject.open?(context(Set[:newbs, :staff]))).to be(true)
         | 
| 25 24 | 
             
                  end
         | 
| 26 25 | 
             
                end
         | 
| @@ -5,11 +5,11 @@ RSpec.describe Flipper::Gates::PercentageOfActors do | |
| 5 5 | 
             
                described_class.new
         | 
| 6 6 | 
             
              end
         | 
| 7 7 |  | 
| 8 | 
            -
              def context(percentage_of_actors_value, feature = feature_name,  | 
| 8 | 
            +
              def context(percentage_of_actors_value, feature = feature_name, actors = nil)
         | 
| 9 9 | 
             
                Flipper::FeatureCheckContext.new(
         | 
| 10 10 | 
             
                  feature_name: feature,
         | 
| 11 11 | 
             
                  values: Flipper::GateValues.new(percentage_of_actors: percentage_of_actors_value),
         | 
| 12 | 
            -
                   | 
| 12 | 
            +
                  actors: Array(actors) || [Flipper::Types::Actor.new(Flipper::Actor.new('1'))]
         | 
| 13 13 | 
             
                )
         | 
| 14 14 | 
             
              end
         | 
| 15 15 |  | 
| @@ -20,7 +20,7 @@ RSpec.describe Flipper::Gates::PercentageOfActors do | |
| 20 20 | 
             
                  let(:number_of_actors) { 10_000 }
         | 
| 21 21 |  | 
| 22 22 | 
             
                  let(:actors) do
         | 
| 23 | 
            -
                    (1..number_of_actors).map { |n| Flipper::Actor.new(n) }
         | 
| 23 | 
            +
                    (1..number_of_actors).map { |n| Flipper::Types::Actor.new(Flipper::Actor.new(n.to_s)) }
         | 
| 24 24 | 
             
                  end
         | 
| 25 25 |  | 
| 26 26 | 
             
                  let(:feature_one_enabled_actors) do
         | 
| @@ -48,13 +48,69 @@ RSpec.describe Flipper::Gates::PercentageOfActors do | |
| 48 48 | 
             
                  end
         | 
| 49 49 | 
             
                end
         | 
| 50 50 |  | 
| 51 | 
            +
                context "with an array of actors" do
         | 
| 52 | 
            +
                  let(:percentage) { 0.05 }
         | 
| 53 | 
            +
                  let(:percentage_as_integer) { percentage * 100 }
         | 
| 54 | 
            +
                  let(:number_of_actors) { 3_000 }
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  let(:user_actors) do
         | 
| 57 | 
            +
                    (1..number_of_actors).map { |n| Flipper::Types::Actor.new(Flipper::Actor.new("User;#{n}")) }
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  let(:team_actors) do
         | 
| 61 | 
            +
                    (1..number_of_actors).map { |n| Flipper::Types::Actor.new(Flipper::Actor.new("Team;#{n}")) }
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  let(:org_actors) do
         | 
| 65 | 
            +
                    (1..number_of_actors).map { |n| Flipper::Types::Actor.new(Flipper::Actor.new("Org;#{n}")) }
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  let(:actors) { user_actors + team_actors + org_actors }
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  let(:feature_one_enabled_actors) do
         | 
| 71 | 
            +
                    actors.each_slice(3).select do |group|
         | 
| 72 | 
            +
                      context = context(percentage_as_integer, :name_one, group)
         | 
| 73 | 
            +
                      subject.open?(context)
         | 
| 74 | 
            +
                    end.flatten
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                  let(:feature_two_enabled_actors) do
         | 
| 78 | 
            +
                    actors.each_slice(3).select do |group|
         | 
| 79 | 
            +
                      context = context(percentage_as_integer, :name_two, group)
         | 
| 80 | 
            +
                      subject.open?(context)
         | 
| 81 | 
            +
                    end.flatten
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  it 'does not enable both features for same set of actors' do
         | 
| 85 | 
            +
                    expect(feature_one_enabled_actors).not_to eq(feature_two_enabled_actors)
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  it 'enables feature for accurate number of actors for each feature' do
         | 
| 89 | 
            +
                    margin_of_error = 0.02 * actors.size # 2 percent margin of error
         | 
| 90 | 
            +
                    expected_enabled_size = actors.size * percentage
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                    [
         | 
| 93 | 
            +
                      feature_one_enabled_actors.size,
         | 
| 94 | 
            +
                      feature_two_enabled_actors.size,
         | 
| 95 | 
            +
                    ].each do |size|
         | 
| 96 | 
            +
                      expect(size).to be_within(margin_of_error).of(expected_enabled_size)
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  it "is consistent regardless of order of actors" do
         | 
| 101 | 
            +
                    actors = user_actors.first(10)
         | 
| 102 | 
            +
                    results = 100.times.map { |n| subject.open?(context(75, :some_feature, actors.shuffle)) }
         | 
| 103 | 
            +
                    expect(results.uniq).to eq([true])
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 51 107 | 
             
                context 'for fractional percentage' do
         | 
| 52 108 | 
             
                  let(:decimal) { 0.001 }
         | 
| 53 109 | 
             
                  let(:percentage) { decimal * 100 }
         | 
| 54 110 | 
             
                  let(:number_of_actors) { 10_000 }
         | 
| 55 111 |  | 
| 56 112 | 
             
                  let(:actors) do
         | 
| 57 | 
            -
                    (1..number_of_actors).map { |n| Flipper::Actor.new(n) }
         | 
| 113 | 
            +
                    (1..number_of_actors).map { |n| Flipper::Types::Actor.new(Flipper::Actor.new(n.to_s)) }
         | 
| 58 114 | 
             
                  end
         | 
| 59 115 |  | 
| 60 116 | 
             
                  subject { described_class.new }
         | 
| @@ -64,7 +120,7 @@ RSpec.describe Flipper::Gates::PercentageOfActors do | |
| 64 120 | 
             
                    expected_open_count = number_of_actors * decimal
         | 
| 65 121 |  | 
| 66 122 | 
             
                    open_count = actors.select do |actor|
         | 
| 67 | 
            -
                      context = context(percentage, :feature, actor)
         | 
| 123 | 
            +
                      context = context(percentage, :feature, [actor])
         | 
| 68 124 | 
             
                      subject.open?(context)
         | 
| 69 125 | 
             
                    end.size
         | 
| 70 126 |  | 
| @@ -5,11 +5,11 @@ RSpec.describe Flipper::Gates::PercentageOfTime do | |
| 5 5 | 
             
                described_class.new
         | 
| 6 6 | 
             
              end
         | 
| 7 7 |  | 
| 8 | 
            -
              def context(percentage_of_time_value, feature = feature_name,  | 
| 8 | 
            +
              def context(percentage_of_time_value, feature = feature_name, actors = nil)
         | 
| 9 9 | 
             
                Flipper::FeatureCheckContext.new(
         | 
| 10 10 | 
             
                  feature_name: feature,
         | 
| 11 11 | 
             
                  values: Flipper::GateValues.new(percentage_of_time: percentage_of_time_value),
         | 
| 12 | 
            -
                   | 
| 12 | 
            +
                  actors: Array(actors) || [Flipper::Types::Actor.new(Flipper::Actor.new('1'))]
         | 
| 13 13 | 
             
                )
         | 
| 14 14 | 
             
              end
         | 
| 15 15 |  | 
| @@ -18,8 +18,8 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do | |
| 18 18 | 
             
              end
         | 
| 19 19 |  | 
| 20 20 | 
             
              before do
         | 
| 21 | 
            -
                Flipper.register(:admins) do | | 
| 22 | 
            -
                   | 
| 21 | 
            +
                Flipper.register(:admins) do |actor|
         | 
| 22 | 
            +
                  actor.respond_to?(:admin?) && actor.admin?
         | 
| 23 23 | 
             
                end
         | 
| 24 24 |  | 
| 25 25 | 
             
                @io = StringIO.new
         | 
| @@ -46,7 +46,7 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do | |
| 46 46 |  | 
| 47 47 | 
             
                it 'logs feature calls with result after operation' do
         | 
| 48 48 | 
             
                  feature_line = find_line('Flipper feature(search) enabled? false')
         | 
| 49 | 
            -
                  expect(feature_line).to include('[  | 
| 49 | 
            +
                  expect(feature_line).to include('[ actors=nil ]')
         | 
| 50 50 | 
             
                end
         | 
| 51 51 |  | 
| 52 52 | 
             
                it 'logs adapter calls' do
         | 
| @@ -56,7 +56,7 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do | |
| 56 56 | 
             
                end
         | 
| 57 57 | 
             
              end
         | 
| 58 58 |  | 
| 59 | 
            -
              context 'feature enabled checks with  | 
| 59 | 
            +
              context 'feature enabled checks with an actor' do
         | 
| 60 60 | 
             
                let(:user) { Flipper::Types::Actor.new(Flipper::Actor.new('1')) }
         | 
| 61 61 |  | 
| 62 62 | 
             
                before do
         | 
| @@ -64,7 +64,7 @@ RSpec.describe Flipper::Instrumentation::LogSubscriber do | |
| 64 64 | 
             
                  flipper[:search].enabled?(user)
         | 
| 65 65 | 
             
                end
         | 
| 66 66 |  | 
| 67 | 
            -
                it 'logs  | 
| 67 | 
            +
                it 'logs actors for feature' do
         | 
| 68 68 | 
             
                  feature_line = find_line('Flipper feature(search) enabled?')
         | 
| 69 69 | 
             
                  expect(feature_line).to include(user.inspect)
         | 
| 70 70 | 
             
                end
         | 
| @@ -2,11 +2,11 @@ require 'flipper/types/actor' | |
| 2 2 |  | 
| 3 3 | 
             
            RSpec.describe Flipper::Types::Actor do
         | 
| 4 4 | 
             
              subject do
         | 
| 5 | 
            -
                 | 
| 6 | 
            -
                described_class.new( | 
| 5 | 
            +
                actor = actor_class.new('2')
         | 
| 6 | 
            +
                described_class.new(actor)
         | 
| 7 7 | 
             
              end
         | 
| 8 8 |  | 
| 9 | 
            -
              let(: | 
| 9 | 
            +
              let(:actor_class) do
         | 
| 10 10 | 
             
                Class.new do
         | 
| 11 11 | 
             
                  attr_reader :flipper_id
         | 
| 12 12 |  | 
| @@ -22,14 +22,14 @@ RSpec.describe Flipper::Types::Actor do | |
| 22 22 |  | 
| 23 23 | 
             
              describe '.wrappable?' do
         | 
| 24 24 | 
             
                it 'returns true if actor' do
         | 
| 25 | 
            -
                   | 
| 26 | 
            -
                   | 
| 27 | 
            -
                  expect(described_class.wrappable?( | 
| 25 | 
            +
                  actor = actor_class.new('1')
         | 
| 26 | 
            +
                  actor_type_instance = described_class.new(actor)
         | 
| 27 | 
            +
                  expect(described_class.wrappable?(actor_type_instance)).to eq(true)
         | 
| 28 28 | 
             
                end
         | 
| 29 29 |  | 
| 30 30 | 
             
                it 'returns true if responds to flipper_id' do
         | 
| 31 | 
            -
                   | 
| 32 | 
            -
                  expect(described_class.wrappable?( | 
| 31 | 
            +
                  actor = actor_class.new(10)
         | 
| 32 | 
            +
                  expect(described_class.wrappable?(actor)).to eq(true)
         | 
| 33 33 | 
             
                end
         | 
| 34 34 |  | 
| 35 35 | 
             
                it 'returns false if nil' do
         | 
| @@ -38,27 +38,27 @@ RSpec.describe Flipper::Types::Actor do | |
| 38 38 | 
             
              end
         | 
| 39 39 |  | 
| 40 40 | 
             
              describe '.wrap' do
         | 
| 41 | 
            -
                context 'for actor' do
         | 
| 42 | 
            -
                  it 'returns actor' do
         | 
| 43 | 
            -
                     | 
| 44 | 
            -
                    expect( | 
| 45 | 
            -
                    expect( | 
| 41 | 
            +
                context 'for actor type instance' do
         | 
| 42 | 
            +
                  it 'returns actor type instance' do
         | 
| 43 | 
            +
                    actor_type_instance = described_class.wrap(subject)
         | 
| 44 | 
            +
                    expect(actor_type_instance).to be_instance_of(described_class)
         | 
| 45 | 
            +
                    expect(actor_type_instance).to be(subject)
         | 
| 46 46 | 
             
                  end
         | 
| 47 47 | 
             
                end
         | 
| 48 48 |  | 
| 49 | 
            -
                context 'for other  | 
| 50 | 
            -
                  it 'returns actor' do
         | 
| 51 | 
            -
                     | 
| 52 | 
            -
                     | 
| 53 | 
            -
                    expect( | 
| 49 | 
            +
                context 'for other object' do
         | 
| 50 | 
            +
                  it 'returns actor type instance' do
         | 
| 51 | 
            +
                    actor = actor_class.new('1')
         | 
| 52 | 
            +
                    actor_type_instance = described_class.wrap(actor)
         | 
| 53 | 
            +
                    expect(actor_type_instance).to be_instance_of(described_class)
         | 
| 54 54 | 
             
                  end
         | 
| 55 55 | 
             
                end
         | 
| 56 56 | 
             
              end
         | 
| 57 57 |  | 
| 58 | 
            -
              it 'initializes with  | 
| 59 | 
            -
                 | 
| 60 | 
            -
                 | 
| 61 | 
            -
                expect( | 
| 58 | 
            +
              it 'initializes with object that responds to flipper_id' do
         | 
| 59 | 
            +
                actor = actor_class.new('1')
         | 
| 60 | 
            +
                actor_type_instance = described_class.new(actor)
         | 
| 61 | 
            +
                expect(actor_type_instance.value).to eq('1')
         | 
| 62 62 | 
             
              end
         | 
| 63 63 |  | 
| 64 64 | 
             
              it 'raises error when initialized with nil' do
         | 
| @@ -68,48 +68,48 @@ RSpec.describe Flipper::Types::Actor do | |
| 68 68 | 
             
              end
         | 
| 69 69 |  | 
| 70 70 | 
             
              it 'raises error when initalized with non-wrappable object' do
         | 
| 71 | 
            -
                 | 
| 71 | 
            +
                unwrappable_object = Struct.new(:id).new(1)
         | 
| 72 72 | 
             
                expect do
         | 
| 73 | 
            -
                  described_class.new( | 
| 73 | 
            +
                  described_class.new(unwrappable_object)
         | 
| 74 74 | 
             
                end.to raise_error(ArgumentError,
         | 
| 75 | 
            -
                                   "#{ | 
| 75 | 
            +
                                   "#{unwrappable_object.inspect} must respond to flipper_id, but does not")
         | 
| 76 76 | 
             
              end
         | 
| 77 77 |  | 
| 78 78 | 
             
              it 'converts id to string' do
         | 
| 79 | 
            -
                 | 
| 80 | 
            -
                actor = described_class.new( | 
| 79 | 
            +
                actor = actor_class.new(2)
         | 
| 80 | 
            +
                actor = described_class.new(actor)
         | 
| 81 81 | 
             
                expect(actor.value).to eq('2')
         | 
| 82 82 | 
             
              end
         | 
| 83 83 |  | 
| 84 | 
            -
              it 'proxies everything to  | 
| 85 | 
            -
                 | 
| 86 | 
            -
                actor = described_class.new( | 
| 84 | 
            +
              it 'proxies everything to actor' do
         | 
| 85 | 
            +
                actor = actor_class.new(10)
         | 
| 86 | 
            +
                actor = described_class.new(actor)
         | 
| 87 87 | 
             
                expect(actor.admin?).to eq(true)
         | 
| 88 88 | 
             
              end
         | 
| 89 89 |  | 
| 90 | 
            -
              it 'exposes  | 
| 91 | 
            -
                 | 
| 92 | 
            -
                 | 
| 93 | 
            -
                expect(actor | 
| 90 | 
            +
              it 'exposes actor' do
         | 
| 91 | 
            +
                actor = actor_class.new(10)
         | 
| 92 | 
            +
                actor_type_instance = described_class.new(actor)
         | 
| 93 | 
            +
                expect(actor_type_instance.actor).to be(actor)
         | 
| 94 94 | 
             
              end
         | 
| 95 95 |  | 
| 96 96 | 
             
              describe '#respond_to?' do
         | 
| 97 97 | 
             
                it 'returns true if responds to method' do
         | 
| 98 | 
            -
                   | 
| 99 | 
            -
                   | 
| 100 | 
            -
                  expect( | 
| 98 | 
            +
                  actor = actor_class.new('1')
         | 
| 99 | 
            +
                  actor_type_instance = described_class.new(actor)
         | 
| 100 | 
            +
                  expect(actor_type_instance.respond_to?(:value)).to eq(true)
         | 
| 101 101 | 
             
                end
         | 
| 102 102 |  | 
| 103 | 
            -
                it 'returns true if  | 
| 104 | 
            -
                   | 
| 105 | 
            -
                   | 
| 106 | 
            -
                  expect( | 
| 103 | 
            +
                it 'returns true if actor responds to method' do
         | 
| 104 | 
            +
                  actor = actor_class.new(10)
         | 
| 105 | 
            +
                  actor_type_instance = described_class.new(actor)
         | 
| 106 | 
            +
                  expect(actor_type_instance.respond_to?(:admin?)).to eq(true)
         | 
| 107 107 | 
             
                end
         | 
| 108 108 |  | 
| 109 | 
            -
                it 'returns false if does not respond to method and  | 
| 110 | 
            -
                   | 
| 111 | 
            -
                   | 
| 112 | 
            -
                  expect( | 
| 109 | 
            +
                it 'returns false if does not respond to method and actor does not respond to method' do
         | 
| 110 | 
            +
                  actor = actor_class.new(10)
         | 
| 111 | 
            +
                  actor_type_instance = described_class.new(actor)
         | 
| 112 | 
            +
                  expect(actor_type_instance.respond_to?(:frankenstein)).to eq(false)
         | 
| 113 113 | 
             
                end
         | 
| 114 114 | 
             
              end
         | 
| 115 115 | 
             
            end
         | 
| @@ -90,7 +90,7 @@ RSpec.describe Flipper::Types::Group do | |
| 90 90 | 
             
                  context = Flipper::FeatureCheckContext.new(
         | 
| 91 91 | 
             
                    feature_name: :my_feature,
         | 
| 92 92 | 
             
                    values: Flipper::GateValues.new({}),
         | 
| 93 | 
            -
                     | 
| 93 | 
            +
                    actors: [Flipper::Types::Actor.new(Flipper::Actor.new('1'))]
         | 
| 94 94 | 
             
                  )
         | 
| 95 95 | 
             
                  group = Flipper.register(:group_with_context) { |actor| actor }
         | 
| 96 96 | 
             
                  yielded_actor = group.match?(admin_actor, context)
         | 
| @@ -101,7 +101,7 @@ RSpec.describe Flipper::Types::Group do | |
| 101 101 | 
             
                  context = Flipper::FeatureCheckContext.new(
         | 
| 102 102 | 
             
                    feature_name: :my_feature,
         | 
| 103 103 | 
             
                    values: Flipper::GateValues.new({}),
         | 
| 104 | 
            -
                     | 
| 104 | 
            +
                    actors: [Flipper::Types::Actor.new(Flipper::Actor.new('1'))]
         | 
| 105 105 | 
             
                  )
         | 
| 106 106 | 
             
                  group = Flipper.register(:group_with_context) { |actor, context| [actor, context] }
         | 
| 107 107 | 
             
                  yielded_actor, yielded_context = group.match?(admin_actor, context)
         |