Skip to content

Commit

Permalink
Merge pull request #847 from flippercloud/actor-limit
Browse files Browse the repository at this point in the history
Add ActorLimit adapter
  • Loading branch information
bkeepers authored Mar 15, 2024
2 parents 4d416c4 + b3c4f1f commit 60377d2
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 9 deletions.
3 changes: 2 additions & 1 deletion lib/flipper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,11 @@ def groups_registry=(registry)
require 'flipper/actor'
require 'flipper/adapter'
require 'flipper/adapters/wrapper'
require 'flipper/adapters/actor_limit'
require 'flipper/adapters/instrumented'
require 'flipper/adapters/memoizable'
require 'flipper/adapters/memory'
require 'flipper/adapters/strict'
require 'flipper/adapters/instrumented'
require 'flipper/adapter_builder'
require 'flipper/configuration'
require 'flipper/dsl'
Expand Down
28 changes: 28 additions & 0 deletions lib/flipper/adapters/actor_limit.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module Flipper
module Adapters
class ActorLimit < Wrapper
LimitExceeded = Class.new(Flipper::Error)

attr_reader :limit

def initialize(adapter, limit = 100)
super(adapter)
@limit = limit
end

def enable(feature, gate, resource)
if gate.is_a?(Flipper::Gates::Actor) && over_limit?(feature)
raise LimitExceeded, "Actor limit of #{@limit} exceeded for feature #{feature.key}. See https://www.flippercloud.io/docs/features/actors#limitations"
else
super
end
end

private

def over_limit?(feature)
feature.actors_value.size >= @limit
end
end
end
end
10 changes: 5 additions & 5 deletions lib/flipper/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def self.default_strict_value
log: ENV.fetch('FLIPPER_LOG', 'true').casecmp('true').zero?,
cloud_path: "_flipper",
strict: default_strict_value,
actor_limit: ENV["FLIPPER_ACTOR_LIMIT"]&.to_i || 100,
test_help: Flipper::Typecast.to_boolean(ENV["FLIPPER_TEST_HELP"] || Rails.env.test?),
)
end
Expand Down Expand Up @@ -65,13 +66,12 @@ def self.default_strict_value
end
end

initializer "flipper.strict", after: :load_config_initializers do |app|
initializer "flipper.adapters", after: :load_config_initializers do |app|
flipper = app.config.flipper

if flipper.strict
Flipper.configure do |config|
config.use Flipper::Adapters::Strict, flipper.strict
end
Flipper.configure do |config|
config.use Flipper::Adapters::Strict, flipper.strict if flipper.strict
config.use Flipper::Adapters::ActorLimit, flipper.actor_limit if flipper.actor_limit
end
end

Expand Down
20 changes: 20 additions & 0 deletions spec/flipper/adapters/actor_limit_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require "flipper/adapters/actor_limit"

RSpec.describe Flipper::Adapters::ActorLimit do
it_should_behave_like 'a flipper adapter' do
let(:limit) { 5 }
let(:adapter) { Flipper::Adapters::ActorLimit.new(Flipper::Adapters::Memory.new, limit) }

subject { adapter }

describe '#enable' do
it "fails when limit exceeded" do
5.times { |i| feature.enable Flipper::Actor.new("User;#{i}") }

expect {
feature.enable Flipper::Actor.new("User;6")
}.to raise_error(Flipper::Adapters::ActorLimit::LimitExceeded)
end
end
end
end
33 changes: 30 additions & 3 deletions spec/flipper/engine_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
ENV['FLIPPER_STRICT'] = 'false'
subject
expect(config.strict).to eq(false)
expect(adapter).to be_instance_of(Flipper::Adapters::Memory)
expect(adapter).not_to be_instance_of(Flipper::Adapters::Strict)
end

[true, :raise, :warn].each do |value|
Expand All @@ -69,7 +69,7 @@
initializer { config.strict = false }
subject
expect(config.strict).to eq(false)
expect(adapter).to be_instance_of(Flipper::Adapters::Memory)
expect(adapter).not_to be_instance_of(Flipper::Adapters::Strict)
end

it "defaults to strict=:warn in RAILS_ENV=development" do
Expand All @@ -85,7 +85,7 @@
expect(Rails.env).to eq(env)
subject
expect(config.strict).to eq(false)
expect(adapter).to be_instance_of(Flipper::Adapters::Memory)
expect(adapter).not_to be_instance_of(Flipper::Adapters::Strict)
end
end

Expand Down Expand Up @@ -337,6 +337,33 @@
expect(ActiveRecord::Base.ancestors).to include(Flipper::Model::ActiveRecord)
end

describe "config.actor_limit" do
let(:adapter) do
application.initialize!
Flipper.adapter.adapter.adapter
end

it "defaults to 100" do
expect(adapter).to be_instance_of(Flipper::Adapters::ActorLimit)
expect(adapter.limit).to eq(100)
end

it "can be set from FLIPPER_ACTOR_LIMIT env" do
ENV["FLIPPER_ACTOR_LIMIT"] = "500"
expect(adapter.limit).to eq(500)
end

it "can be set from an initializer" do
initializer { config.actor_limit = 99 }
expect(adapter.limit).to eq(99)
end

it "can be disabled from an initializer" do
initializer { config.actor_limit = false }
expect(adapter).not_to be_instance_of(Flipper::Adapters::ActorLimit)
end
end

# Add app initializer in the same order as config/initializers/*
def initializer(&block)
application.initializer 'spec', before: :load_config_initializers do
Expand Down
20 changes: 20 additions & 0 deletions test/adapters/actor_limit_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require "test_helper"
require "flipper/test/shared_adapter_test"
require "flipper/adapters/actor_limit"

class Flipper::Adapters::ActorLimitTest < MiniTest::Test
prepend Flipper::Test::SharedAdapterTests

def setup
@memory = Flipper::Adapters::Memory.new
@adapter = Flipper::Adapters::ActorLimit.new(@memory, 5)
end

def test_enable_fails_when_limit_exceeded
5.times { |i| @feature.enable Flipper::Actor.new("User;#{i}") }

assert_raises Flipper::Adapters::ActorLimit::LimitExceeded do
@feature.enable Flipper::Actor.new("User;6")
end
end
end

0 comments on commit 60377d2

Please sign in to comment.