diff --git a/lib/flipper.rb b/lib/flipper.rb index 8d5452f54..88bb3d557 100644 --- a/lib/flipper.rb +++ b/lib/flipper.rb @@ -59,8 +59,8 @@ def instance=(flipper) :enabled?, :enable, :disable, :enable_expression, :disable_expression, :expression, :add_expression, :remove_expression, - :enable_actor, :disable_actor, - :enable_group, :disable_group, + :enable_actor, :disable_actor, :block_actor, :unblock_actor, + :enable_group, :disable_group, :block_group, :unblock_group, :enable_percentage_of_actors, :disable_percentage_of_actors, :enable_percentage_of_time, :disable_percentage_of_time, :features, :feature, :[], :preload, :preload_all, diff --git a/lib/flipper/dsl.rb b/lib/flipper/dsl.rb index 6f7c3d11a..07f76068c 100644 --- a/lib/flipper/dsl.rb +++ b/lib/flipper/dsl.rb @@ -181,6 +181,22 @@ def disable_percentage_of_actors(name) feature(name).disable_percentage_of_actors end + def block_actor(name, actor) + feature(name).block_actor(actor) + end + + def block_group(name, group) + feature(name).block_group(group) + end + + def unblock_actor(name, actor) + feature(name).unblock_actor(actor) + end + + def unblock_group(name, group) + feature(name).unblock_group(group) + end + # Public: Add a feature. # # name - The String or Symbol name of the feature. diff --git a/lib/flipper/feature.rb b/lib/flipper/feature.rb index a4dcc63ec..0e29a3612 100644 --- a/lib/flipper/feature.rb +++ b/lib/flipper/feature.rb @@ -68,6 +68,32 @@ def disable(thing = false) end end + def block(thing) + instrument(:block) do |payload| + adapter.add self + + blocking_gate = gate_for(thing, blocking: true) + wrapped_thing = blocking_gate.wrap(thing) + payload[:gate_name] = blocking_gate.name + payload[:thing] = wrapped_thing + + adapter.enable self, blocking_gate, wrapped_thing + end + end + + def unblock(thing) + instrument(:unblock) do |payload| + adapter.add self + + blocking_gate = gate_for(thing, blocking: true) + wrapped_thing = blocking_gate.wrap(thing) + payload[:gate_name] = blocking_gate.name + payload[:thing] = wrapped_thing + + adapter.disable self, blocking_gate, wrapped_thing + end + end + # Public: Adds this feature. # # Returns the result of Adapter#add. @@ -118,7 +144,10 @@ def enabled?(*actors) actors: actors ) - if open_gate = gates.detect { |gate| gate.open?(context) } + if blocking_gate = gates.detect { |gate| gate.blocks?(context) } + payload[:gate_name] = blocking_gate.name + false + elsif open_gate = gates.detect { |gate| gate.open?(context) } payload[:gate_name] = open_gate.name true else @@ -250,6 +279,22 @@ def disable_percentage_of_actors disable Types::PercentageOfActors.new(0) end + def block_actor(actor) + block Types::Actor.wrap(actor) + end + + def block_group(group) + block Types::Group.wrap(group) + end + + def unblock_actor(actor) + unblock Types::Actor.wrap(actor) + end + + def unblock_group(group) + unblock Types::Group.wrap(group) + end + # Public: Returns state for feature (:on, :off, or :conditional). def state values = gate_values @@ -413,6 +458,8 @@ def gates_hash percentage_of_actors: Gates::PercentageOfActors.new, percentage_of_time: Gates::PercentageOfTime.new, group: Gates::Group.new, + blocking_actor: Gates::Actor.new({ blocking: true }), + blocking_group: Gates::Group.new({ blocking: true }), }.freeze end @@ -429,8 +476,8 @@ def gate(name) # # Returns a Flipper::Gate. # Raises Flipper::GateNotFound if no gate found for actor - def gate_for(actor) - gates.detect { |gate| gate.protects?(actor) } || raise(GateNotFound, actor) + def gate_for(actor, blocking: false) + gates.detect { |gate| gate.protects?(actor) && gate.blocking == blocking } || raise(GateNotFound, actor) end private diff --git a/lib/flipper/gate.rb b/lib/flipper/gate.rb index 3b55dc5a2..0ddbc8349 100644 --- a/lib/flipper/gate.rb +++ b/lib/flipper/gate.rb @@ -1,7 +1,10 @@ module Flipper class Gate + attr_reader :blocking + # Public def initialize(options = {}) + @blocking = options.fetch(:blocking, false) end # Public: The name of the gate. Implemented in subclass. @@ -30,6 +33,10 @@ def open?(context) false end + def blocks?(context) + false + end + # Internal: Check if a gate is protects an actor. Implemented in subclass. # # Returns true if gate protects actor, false if not. diff --git a/lib/flipper/gate_values.rb b/lib/flipper/gate_values.rb index 1d9df3b16..25a7c3ce8 100644 --- a/lib/flipper/gate_values.rb +++ b/lib/flipper/gate_values.rb @@ -9,6 +9,8 @@ class GateValues attr_reader :expression attr_reader :percentage_of_actors attr_reader :percentage_of_time + attr_reader :blocking_actors + attr_reader :blocking_groups def initialize(adapter_values) @boolean = Typecast.to_boolean(adapter_values[:boolean]) @@ -17,6 +19,8 @@ def initialize(adapter_values) @expression = adapter_values[:expression] @percentage_of_actors = Typecast.to_number(adapter_values[:percentage_of_actors]) @percentage_of_time = Typecast.to_number(adapter_values[:percentage_of_time]) + @blocking_actors = Typecast.to_set(adapter_values[:blocking_actors]) + @blocking_groups = Typecast.to_set(adapter_values[:blocking_groups]) end def eql?(other) @@ -26,7 +30,9 @@ def eql?(other) groups == other.groups && expression == other.expression && percentage_of_actors == other.percentage_of_actors && - percentage_of_time == other.percentage_of_time + percentage_of_time == other.percentage_of_time && + blocking_actors == other.blocking_actors && + blocking_groups == other.blocking_groups end alias_method :==, :eql? end diff --git a/lib/flipper/gates/actor.rb b/lib/flipper/gates/actor.rb index ed3d84150..d7a139565 100644 --- a/lib/flipper/gates/actor.rb +++ b/lib/flipper/gates/actor.rb @@ -3,11 +3,15 @@ module Gates class Actor < Gate # Internal: The name of the gate. Used for instrumentation, etc. def name + return :blocking_actor if blocking + :actor end # Internal: Name converted to value safe for adapter. def key + return :blocking_actors if blocking + :actors end @@ -30,6 +34,14 @@ def open?(context) end end + def blocks?(context) + return false unless context.actors? + + context.actors.any? do |actor| + context.values.blocking_actors.include?(actor.value) + end + end + def wrap(actor) Types::Actor.wrap(actor) end diff --git a/lib/flipper/gates/group.rb b/lib/flipper/gates/group.rb index 0f966b2ef..971368a8a 100644 --- a/lib/flipper/gates/group.rb +++ b/lib/flipper/gates/group.rb @@ -3,11 +3,15 @@ module Gates class Group < Gate # Internal: The name of the gate. Used for instrumentation, etc. def name + return :blocking_group if blocking + :group end # Internal: Name converted to value safe for adapter. def key + return :blocking_groups if blocking + :groups end @@ -32,6 +36,16 @@ def open?(context) end end + def blocks?(context) + return false unless context.actors? + + context.values.blocking_groups.any? do |name| + context.actors.any? do |actor| + Flipper.group(name).match?(actor, context) + end + end + end + def wrap(thing) Types::Group.wrap(thing) end