class Unleash::FeatureToggle

Attributes

enabled[RW]
name[RW]
strategies[RW]
variant_definitions[RW]

Public Class Methods

new(params = {}) click to toggle source
# File lib/unleash/feature_toggle.rb, line 12
def initialize(params = {})
  params = {} if params.nil?

  self.name       = params.fetch('name', nil)
  self.enabled    = params.fetch('enabled', false)

  self.strategies = initialize_strategies(params)
  self.variant_definitions = initialize_variant_definitions(params)
end

Public Instance Methods

get_variant(context, fallback_variant = disabled_variant) click to toggle source
# File lib/unleash/feature_toggle.rb, line 35
def get_variant(context, fallback_variant = disabled_variant)
  raise ArgumentError, "Provided fallback_variant is not of type Unleash::Variant" if fallback_variant.class.name != 'Unleash::Variant'

  context = ensure_valid_context(context)

  return disabled_variant unless self.enabled && am_enabled?(context, true)
  return disabled_variant if sum_variant_defs_weights <= 0

  variant = variant_from_override_match(context)
  variant = variant_from_weights(context) if variant.nil?

  Unleash.toggle_metrics.increment_variant(self.name, variant.name) unless Unleash.configuration.disable_metrics
  variant
end
is_enabled?(context, default_result) click to toggle source
# File lib/unleash/feature_toggle.rb, line 26
def is_enabled?(context, default_result)
  result = am_enabled?(context, default_result)

  choice = result ? :yes : :no
  Unleash.toggle_metrics.increment(name, choice) unless Unleash.configuration.disable_metrics

  result
end
to_s() click to toggle source
# File lib/unleash/feature_toggle.rb, line 22
def to_s
  "<FeatureToggle: name=#{name},enabled=#{enabled},strategies=#{strategies},variant_definitions=#{variant_definitions}>"
end

Private Instance Methods

am_enabled?(context, default_result) click to toggle source

only check if it is enabled, do not do metrics

# File lib/unleash/feature_toggle.rb, line 53
def am_enabled?(context, default_result)
  result =
    if self.enabled
      self.strategies.empty? ||
        self.strategies.any? do |s|
          strategy_enabled?(s, context) && strategy_constraint_matches?(s, context)
        end
    else
      default_result
    end

  Unleash.logger.debug "Unleash::FeatureToggle (enabled:#{self.enabled} default_result:#{default_result} " \
    "and Strategies combined with contraints returned #{result})"

  result
end
disabled_variant() click to toggle source
# File lib/unleash/feature_toggle.rb, line 80
def disabled_variant
  Unleash::Variant.new(name: 'disabled', enabled: false)
end
ensure_valid_context(context) click to toggle source
# File lib/unleash/feature_toggle.rb, line 118
def ensure_valid_context(context)
  unless ['NilClass', 'Unleash::Context'].include? context.class.name
    Unleash.logger.error "Provided context is not of the correct type #{context.class.name}, " \
      "please use Unleash::Context. Context set to nil."
    context = nil
  end
  context
end
initialize_strategies(params) click to toggle source
# File lib/unleash/feature_toggle.rb, line 127
def initialize_strategies(params)
  params.fetch('strategies', [])
    .select{ |s| s.has_key?('name') && Unleash::STRATEGIES.has_key?(s['name'].to_sym) }
    .map do |s|
      ActivationStrategy.new(
        s['name'],
        s['parameters'],
        (s['constraints'] || []).map do |c|
          Constraint.new(
            c.fetch('contextName'),
            c.fetch('operator'),
            c.fetch('values')
          )
        end
      )
    end || []
end
initialize_variant_definitions(params) click to toggle source
# File lib/unleash/feature_toggle.rb, line 145
def initialize_variant_definitions(params)
  (params.fetch('variants', []) || [])
    .select{ |v| v.is_a?(Hash) && v.has_key?('name') }
    .map do |v|
      VariantDefinition.new(
        v.fetch('name', ''),
        v.fetch('weight', 0),
        v.fetch('payload', nil),
        v.fetch('overrides', [])
      )
    end || []
end
strategy_constraint_matches?(strategy, context) click to toggle source
# File lib/unleash/feature_toggle.rb, line 76
def strategy_constraint_matches?(strategy, context)
  strategy.constraints.empty? || strategy.constraints.all?{ |c| c.matches_context?(context) }
end
strategy_enabled?(strategy, context) click to toggle source
# File lib/unleash/feature_toggle.rb, line 70
def strategy_enabled?(strategy, context)
  r = Unleash::STRATEGIES.fetch(strategy.name.to_sym, :unknown).is_enabled?(strategy.params, context)
  Unleash.logger.debug "Unleash::FeatureToggle.strategy_enabled? Strategy #{strategy.name} returned #{r} with context: #{context}"
  r
end
sum_variant_defs_weights() click to toggle source
# File lib/unleash/feature_toggle.rb, line 84
def sum_variant_defs_weights
  self.variant_definitions.map(&:weight).reduce(0, :+)
end
variant_from_override_match(context) click to toggle source
# File lib/unleash/feature_toggle.rb, line 96
def variant_from_override_match(context)
  variant = self.variant_definitions.find{ |vd| vd.override_matches_context?(context) }
  return nil if variant.nil?

  Unleash::Variant.new(name: variant.name, enabled: true, payload: variant.payload)
end
variant_from_weights(context) click to toggle source
# File lib/unleash/feature_toggle.rb, line 103
def variant_from_weights(context)
  variant_weight = Unleash::Strategy::Util.get_normalized_number(variant_salt(context), self.name, sum_variant_defs_weights)
  prev_weights = 0

  variant_definition = self.variant_definitions
    .find do |v|
      res = (prev_weights + v.weight >= variant_weight)
      prev_weights += v.weight
      res
    end
  return disabled_variant if variant_definition.nil?

  Unleash::Variant.new(name: variant_definition.name, enabled: true, payload: variant_definition.payload)
end
variant_salt(context) click to toggle source
# File lib/unleash/feature_toggle.rb, line 88
def variant_salt(context)
  return context.user_id unless context.user_id.to_s.empty?
  return context.session_id unless context.session_id.to_s.empty?
  return context.remote_address unless context.remote_address.to_s.empty?

  SecureRandom.random_number
end