Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature and Gate Tweaks #45

Merged
merged 21 commits into from Apr 4, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
bd6c479
Move feature name to parameter on open? from Gate#initialize
jnunemaker Apr 3, 2015
929da17
Move gate instrumentation to feature
jnunemaker Apr 3, 2015
eaf1a86
Use gate value typecasting instead of just to_i for open? check in pe…
jnunemaker Apr 3, 2015
161f635
Move typecasting to Typecast module from GateValues
jnunemaker Apr 3, 2015
295ed56
Alias Feature#groups as #enabled_groups
jnunemaker Apr 3, 2015
9309a00
Flipper group and group_names should be Set like everything else
jnunemaker Apr 3, 2015
e63c51d
Add Feature#disabled_groups
jnunemaker Apr 3, 2015
5b634a4
Make enabled_groups the method and alias groups to be consistent with
jnunemaker Apr 3, 2015
06328e0
Merge branch 'master' into gate-tweaks
jnunemaker Apr 4, 2015
0dadd50
Stop passing instrumenter to gate
jnunemaker Apr 4, 2015
21876ba
Check gate instance type instead of using gates
jnunemaker Apr 4, 2015
c205f6a
Denote features as public
jnunemaker Apr 4, 2015
d40e4ee
Denote key as public
jnunemaker Apr 4, 2015
02f4257
Remove memoization from to_s and to_param
jnunemaker Apr 4, 2015
052dbe5
Update docs for feature
jnunemaker Apr 4, 2015
c34ea39
Remove gate enable/disable
jnunemaker Apr 4, 2015
a98742d
Add name, key and data type to gate inspect
jnunemaker Apr 4, 2015
a2e45a9
Remove source from specs
jnunemaker Apr 4, 2015
482deff
Update spec/docs for integration spec
jnunemaker Apr 4, 2015
fc975f7
Ensure that GateValues#[] returns nil for bad ivar
jnunemaker Apr 4, 2015
c6a3211
Document legit ivars constant
jnunemaker Apr 4, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 5 additions & 4 deletions lib/flipper.rb
Expand Up @@ -30,16 +30,16 @@ def self.register(name, &block)
raise DuplicateGroup, %Q{Group #{name.inspect} has already been registered}
end

# Public: Returns an array of registered Types::Group instances.
# Public: Returns a Set of registered Types::Group instances.
def self.groups
groups_registry.values
groups_registry.values.to_set
end

# Public: Returns an array of symbols where each symbol is a registered
# Public: Returns a Set of symbols where each symbol is a registered
# group name. If you just want the names, this is more efficient than doing
# `Flipper.groups.map(&:name)`.
def self.group_names
groups_registry.keys
groups_registry.keys.to_set
end

# Public: Clears the group registry.
Expand Down Expand Up @@ -90,3 +90,4 @@ def self.groups_registry=(registry)
require 'flipper/gate'
require 'flipper/registry'
require 'flipper/type'
require 'flipper/typecast'
2 changes: 1 addition & 1 deletion lib/flipper/dsl.rb
Expand Up @@ -223,7 +223,7 @@ def actors(number)
end
alias_method :percentage_of_actors, :actors

# Internal: Returns a Set of the known features for this adapter.
# Public: Returns a Set of the known features for this adapter.
#
# Returns Set of Flipper::Feature instances.
def features
Expand Down
95 changes: 68 additions & 27 deletions lib/flipper/feature.rb
Expand Up @@ -6,13 +6,16 @@

module Flipper
class Feature
# Private: The name of instrumentation events.
# Private: The name of feature instrumentation events.
InstrumentationName = "feature_operation.#{InstrumentationNamespace}"

# Private: The name of gate instrumentation events.
GateInstrumentationName = "gate_operation.#{InstrumentationNamespace}"

# Public: The name of the feature.
attr_reader :name

# Internal: Name converted to value safe for adapter.
# Public: Name converted to value safe for adapter.
attr_reader :key

# Private: The adapter this feature should use.
Expand Down Expand Up @@ -75,14 +78,16 @@ def enabled?(thing = nil)
instrument(:enabled?, thing) { |payload|
values = gate_values

gate = gates.detect { |gate|
gate.open?(thing, values[gate.key])
open_gate = gates.detect { |gate|
instrument_gate(gate, :open?, thing) { |gate_payload|
gate.open?(thing, values[gate.key], feature_name: @name)
}
}

if gate.nil?
if open_gate.nil?
false
else
payload[:gate_name] = gate.name
payload[:gate_name] = open_gate.name
true
end
}
Expand Down Expand Up @@ -219,44 +224,64 @@ def gate_values
GateValues.new(adapter.get(self))
end

# Public: Returns the Set of Flipper::Types::Group instances enabled.
def groups
# Public: Get groups enabled for this feature.
#
# Returns Set of Flipper::Types::Group instances.
def enabled_groups
groups_value.map { |name| Flipper.group(name) }.to_set
end
alias_method :groups, :enabled_groups

# Public: Get groups not enabled for this feature.
#
# Returns Set of Flipper::Types::Group instances.
def disabled_groups
Flipper.groups - enabled_groups
end

# Public: Returns the Set of group Symbol names enabled.
# Public: Get the adapter value for the groups gate.
#
# Returns Set of String group names.
def groups_value
gate_values.groups
end

# Public: Returns the Set of actor flipper ids enabled.
# Public: Get the adapter value for the actors gate.
#
# Returns Set of String flipper_id's.
def actors_value
gate_values.actors
end

# Public: Returns the adapter value for the boolean gate.
# Public: Get the adapter value for the boolean gate.
#
# Returns true or false.
def boolean_value
gate_values.boolean
end

# Public: Returns the adapter value for the percentage of actors gate.
# Public: Get the adapter value for the percentage of actors gate.
#
# Returns Integer greater than or equal to 0 and less than or equal to 100.
def percentage_of_actors_value
gate_values.percentage_of_actors
end

# Public: Returns the adapter value for the percentage of time gate.
# Public: Get the adapter value for the percentage of time gate.
#
# Returns Integer greater than or equal to 0 and less than or equal to 100.
def percentage_of_time_value
gate_values.percentage_of_time
end

# Public: Returns the string representation of the feature.
def to_s
@to_s ||= name.to_s
name.to_s
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

end

# Public: Identifier to be used in the url (a rails-ism).
def to_param
@to_param ||= name.to_s
to_s
end

# Public: Pretty string version for debugging.
Expand All @@ -270,27 +295,27 @@ def inspect
"#<#{self.class.name}:#{object_id} #{attributes.join(', ')}>"
end

# Internal: Gates to check to see if feature is enabled/disabled
# Public: Get all the gates used to determine enabled/disabled for the feature.
#
# Returns an array of gates
def gates
@gates ||= [
Gates::Boolean.new(@name, :instrumenter => @instrumenter),
Gates::Group.new(@name, :instrumenter => @instrumenter),
Gates::Actor.new(@name, :instrumenter => @instrumenter),
Gates::PercentageOfActors.new(@name, :instrumenter => @instrumenter),
Gates::PercentageOfTime.new(@name, :instrumenter => @instrumenter),
Gates::Boolean.new,
Gates::Group.new,
Gates::Actor.new,
Gates::PercentageOfActors.new,
Gates::PercentageOfTime.new,
]
end

# Internal: Finds a gate by name.
# Public: Find a gate by name.
#
# Returns a Flipper::Gate if found, nil if not.
def gate(name)
gates.detect { |gate| gate.name == name.to_sym }
end

# Internal: Find the gate that protects a thing.
# Public: Find the gate that protects a thing.
#
# thing - The object for which you would like to find a gate
#
Expand All @@ -301,25 +326,27 @@ def gate_for(thing)
raise(GateNotFound.new(thing))
end

# Private
private

# Private: Get the boolean gate.
def boolean_gate
@boolean_gate ||= gate(:boolean)
end

# Private
# Private: Get all gates except the boolean gate.
def non_boolean_gates
@non_boolean_gates ||= gates - [boolean_gate]
end

# Private
# Private: Get all non boolean gates that are enabled in some way.
def conditional_gates(gate_values)
non_boolean_gates.select { |gate|
value = gate_values[gate.key]
gate.enabled?(value)
}
end

# Private
# Private: Instrument a feature operation.
def instrument(operation, thing)
payload = {
:feature_name => name,
Expand All @@ -331,5 +358,19 @@ def instrument(operation, thing)
payload[:result] = yield(payload) if block_given?
}
end

# Private: Intrument a gate operation.
def instrument_gate(gate, operation, thing)
payload = {
:feature_name => @name,
:gate_name => gate.name,
:operation => operation,
:thing => thing,
}

@instrumenter.instrument(GateInstrumentationName, payload) {
payload[:result] = yield(payload) if block_given?
}
end
end
end
42 changes: 5 additions & 37 deletions lib/flipper/gate.rb
@@ -1,23 +1,11 @@
require 'forwardable'
require 'flipper/instrumenters/noop'

module Flipper
class Gate
extend Forwardable

# Private: The name of instrumentation events.
InstrumentationName = "gate_operation.#{InstrumentationNamespace}"

# Private
attr_reader :feature_name

# Private: What is used to instrument all the things.
attr_reader :instrumenter

# Public
def initialize(feature_name, options = {})
@feature_name = feature_name
@instrumenter = options.fetch(:instrumenter, Flipper::Instrumenters::Noop)
def initialize(options = {})
end

# Public: The name of the gate. Implemented in subclass.
Expand All @@ -34,14 +22,6 @@ def data_type
raise 'Not implemented'
end

def enable(thing)
raise 'Not implemented'
end

def disable(thing)
raise 'Not implemented'
end

def enabled?(value)
raise 'Not implemented'
end
Expand All @@ -53,7 +33,7 @@ def description(value)
# Internal: Check if a gate is open for a thing. Implemented in subclass.
#
# Returns true if gate open for thing, false if not.
def open?(thing)
def open?(thing, value, options = {})
false
end

Expand All @@ -73,24 +53,12 @@ def wrap(thing)
# Public: Pretty string version for debugging.
def inspect
attributes = [
"feature_name=#{feature_name.inspect}",
"name=#{@name.inspect}",
"key=#{@key.inspect}",
"data_type=#{@data_type.inspect}",
]
"#<#{self.class.name}:#{object_id} #{attributes.join(', ')}>"
end

# Private
def instrument(operation, thing)
payload = {
:thing => thing,
:operation => operation,
:gate_name => name,
:feature_name => @feature_name,
}

@instrumenter.instrument(InstrumentationName, payload) {
payload[:result] = yield(payload) if block_given?
}
end
end
end

Expand Down
60 changes: 15 additions & 45 deletions lib/flipper/gate_values.rb
@@ -1,45 +1,14 @@
module Flipper
class GateValues
TruthMap = {
true => true,
1 => true,
"true" => true,
"1" => true,
}

# Internal: Convert value to a boolean.
#
# Returns true or false.
def self.to_boolean(value)
!!TruthMap[value]
end

# Internal: Convert value to an integer.
#
# Returns an Integer representation of the value.
# Raises ArgumentError if conversion is not possible.
def self.to_integer(value)
if value.respond_to?(:to_i)
value.to_i
else
raise ArgumentError, "#{value.inspect} cannot be converted to an integer"
end
end

# Internal: Convert value to a set.
#
# Returns a Set representation of the value.
# Raises ArgumentError if conversion is not possible.
def self.to_set(value)
return value if value.is_a?(Set)
return Set.new if value.nil? || value.empty?

if value.respond_to?(:to_set)
value.to_set
else
raise ArgumentError, "#{value.inspect} cannot be converted to a set"
end
end
# Private: Array of instance variables that are readable through the []
# instance method.
LegitIvars = [
"boolean",
"actors",
"groups",
"percentage_of_time",
"percentage_of_actors",
]

attr_reader :boolean
attr_reader :actors
Expand All @@ -48,14 +17,15 @@ def self.to_set(value)
attr_reader :percentage_of_time

def initialize(adapter_values)
@boolean = self.class.to_boolean(adapter_values[:boolean])
@actors = self.class.to_set(adapter_values[:actors])
@groups = self.class.to_set(adapter_values[:groups])
@percentage_of_actors = self.class.to_integer(adapter_values[:percentage_of_actors])
@percentage_of_time = self.class.to_integer(adapter_values[:percentage_of_time])
@boolean = Typecast.to_boolean(adapter_values[:boolean])
@actors = Typecast.to_set(adapter_values[:actors])
@groups = Typecast.to_set(adapter_values[:groups])
@percentage_of_actors = Typecast.to_integer(adapter_values[:percentage_of_actors])
@percentage_of_time = Typecast.to_integer(adapter_values[:percentage_of_time])
end

def [](key)
return nil unless LegitIvars.include?(key.to_s)
instance_variable_get("@#{key}")
end

Expand Down