• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Unleash / unleash-client-ruby / 4944881348

pending completion
4944881348

push

github

GitHub
fix: forcefully disabling metrics when client is disabled (#139) (#140)

0 of 21 new or added lines in 2 files covered. (0.0%)

2237 existing lines in 58 files now uncovered.

0 of 2339 relevant lines covered (0.0%)

0.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/lib/unleash/feature_toggle.rb
UNCOV
1
require 'unleash/activation_strategy'
×
UNCOV
2
require 'unleash/constraint'
×
UNCOV
3
require 'unleash/variant_definition'
×
UNCOV
4
require 'unleash/variant'
×
UNCOV
5
require 'unleash/strategy/util'
×
UNCOV
6
require 'securerandom'
×
7

UNCOV
8
module Unleash
×
UNCOV
9
  class FeatureToggle
×
UNCOV
10
    attr_accessor :name, :enabled, :strategies, :variant_definitions
×
11

UNCOV
12
    def initialize(params = {}, segment_map = {})
×
UNCOV
13
      params = {} if params.nil?
×
14

UNCOV
15
      self.name       = params.fetch('name', nil)
×
UNCOV
16
      self.enabled    = params.fetch('enabled', false)
×
17

UNCOV
18
      self.strategies = initialize_strategies(params, segment_map)
×
UNCOV
19
      self.variant_definitions = initialize_variant_definitions(params)
×
20
    end
21

UNCOV
22
    def to_s
×
23
      "<FeatureToggle: name=#{name},enabled=#{enabled},strategies=#{strategies},variant_definitions=#{variant_definitions}>"
×
24
    end
25

UNCOV
26
    def is_enabled?(context)
×
UNCOV
27
      result = am_enabled?(context)
×
28

UNCOV
29
      choice = result ? :yes : :no
×
UNCOV
30
      Unleash.toggle_metrics.increment(name, choice) unless Unleash.configuration.disable_metrics
×
31

UNCOV
32
      result
×
33
    end
34

UNCOV
35
    def get_variant(context, fallback_variant = Unleash::FeatureToggle.disabled_variant)
×
UNCOV
36
      raise ArgumentError, "Provided fallback_variant is not of type Unleash::Variant" if fallback_variant.class.name != 'Unleash::Variant'
×
37

UNCOV
38
      context = ensure_valid_context(context)
×
39

UNCOV
40
      toggle_enabled = am_enabled?(context)
×
UNCOV
41
      variant = resolve_variant(context, toggle_enabled)
×
42

UNCOV
43
      choice = toggle_enabled ? :yes : :no
×
UNCOV
44
      Unleash.toggle_metrics.increment_variant(self.name, choice, variant.name) unless Unleash.configuration.disable_metrics
×
UNCOV
45
      variant
×
46
    end
47

UNCOV
48
    def self.disabled_variant
×
UNCOV
49
      Unleash::Variant.new(name: 'disabled', enabled: false)
×
50
    end
51

UNCOV
52
    private
×
53

UNCOV
54
    def resolve_variant(context, toggle_enabled)
×
UNCOV
55
      return Unleash::FeatureToggle.disabled_variant unless toggle_enabled
×
UNCOV
56
      return Unleash::FeatureToggle.disabled_variant if sum_variant_defs_weights <= 0
×
57

UNCOV
58
      variant_from_override_match(context) || variant_from_weights(context, resolve_stickiness)
×
59
    end
60

UNCOV
61
    def resolve_stickiness
×
UNCOV
62
      self.variant_definitions&.map(&:stickiness)&.compact&.first || "default"
×
63
    end
64

65
    # only check if it is enabled, do not do metrics
UNCOV
66
    def am_enabled?(context)
×
UNCOV
67
      result =
×
68
        if self.enabled
UNCOV
69
          self.strategies.empty? ||
×
70
            self.strategies.any? do |s|
UNCOV
71
              strategy_enabled?(s, context) && strategy_constraint_matches?(s, context)
×
72
            end
73
        else
UNCOV
74
          false
×
75
        end
76

UNCOV
77
      Unleash.logger.debug "Unleash::FeatureToggle (enabled:#{self.enabled} " \
×
UNCOV
78
        "and Strategies combined with contraints returned #{result})"
×
79

UNCOV
80
      result
×
81
    end
82

UNCOV
83
    def strategy_enabled?(strategy, context)
×
UNCOV
84
      r = Unleash.strategies.fetch(strategy.name).is_enabled?(strategy.params, context)
×
UNCOV
85
      Unleash.logger.debug "Unleash::FeatureToggle.strategy_enabled? Strategy #{strategy.name} returned #{r} with context: #{context}"
×
UNCOV
86
      r
×
87
    end
88

UNCOV
89
    def strategy_constraint_matches?(strategy, context)
×
UNCOV
90
      return false if strategy.disabled
×
91

UNCOV
92
      strategy.constraints.empty? || strategy.constraints.all?{ |c| c.matches_context?(context) }
×
93
    end
94

UNCOV
95
    def sum_variant_defs_weights
×
UNCOV
96
      self.variant_definitions.map(&:weight).reduce(0, :+)
×
97
    end
98

UNCOV
99
    def variant_salt(context, stickiness = "default")
×
UNCOV
100
      return context.get_by_name(stickiness) unless stickiness == "default"
×
UNCOV
101
      return context.user_id unless context.user_id.to_s.empty?
×
UNCOV
102
      return context.session_id unless context.session_id.to_s.empty?
×
UNCOV
103
      return context.remote_address unless context.remote_address.to_s.empty?
×
104

UNCOV
105
      SecureRandom.random_number
×
106
    end
107

UNCOV
108
    def variant_from_override_match(context)
×
UNCOV
109
      variant = self.variant_definitions.find{ |vd| vd.override_matches_context?(context) }
×
UNCOV
110
      return nil if variant.nil?
×
111

UNCOV
112
      Unleash::Variant.new(name: variant.name, enabled: true, payload: variant.payload)
×
113
    end
114

UNCOV
115
    def variant_from_weights(context, stickiness)
×
UNCOV
116
      variant_weight = Unleash::Strategy::Util.get_normalized_number(variant_salt(context, stickiness), self.name, sum_variant_defs_weights)
×
UNCOV
117
      prev_weights = 0
×
118

UNCOV
119
      variant_definition = self.variant_definitions
×
120
        .find do |v|
UNCOV
121
          res = (prev_weights + v.weight >= variant_weight)
×
UNCOV
122
          prev_weights += v.weight
×
UNCOV
123
          res
×
124
        end
UNCOV
125
      return self.disabled_variant if variant_definition.nil?
×
126

UNCOV
127
      Unleash::Variant.new(name: variant_definition.name, enabled: true, payload: variant_definition.payload)
×
128
    end
129

UNCOV
130
    def ensure_valid_context(context)
×
UNCOV
131
      unless ['NilClass', 'Unleash::Context'].include? context.class.name
×
132
        Unleash.logger.error "Provided context is not of the correct type #{context.class.name}, " \
×
133
          "please use Unleash::Context. Context set to nil."
134
        context = nil
×
135
      end
UNCOV
136
      context
×
137
    end
138

UNCOV
139
    def initialize_strategies(params, segment_map)
×
UNCOV
140
      params.fetch('strategies', [])
×
UNCOV
141
        .select{ |s| s.has_key?('name') && Unleash.strategies.includes?(s['name']) }
×
142
        .map do |s|
UNCOV
143
          ActivationStrategy.new(
×
144
            s['name'],
145
            s['parameters'],
146
            resolve_constraints(s, segment_map)
147
          )
148
        end || []
149
    end
150

UNCOV
151
    def resolve_constraints(strategy, segment_map)
×
UNCOV
152
      segment_constraints = (strategy["segments"] || []).map do |segment_id|
×
UNCOV
153
        segment_map[segment_id]&.fetch("constraints")
×
154
      end
UNCOV
155
      (strategy.fetch("constraints", []) + segment_constraints).flatten.map do |constraint|
×
UNCOV
156
        return nil if constraint.nil?
×
157

UNCOV
158
        Constraint.new(
×
159
          constraint.fetch('contextName'),
160
          constraint.fetch('operator'),
161
          constraint.fetch('value', nil) || constraint.fetch('values', nil),
162
          inverted: constraint.fetch('inverted', false),
163
          case_insensitive: constraint.fetch('caseInsensitive', false)
164
        )
165
      end
166
    end
167

UNCOV
168
    def initialize_variant_definitions(params)
×
UNCOV
169
      (params.fetch('variants', []) || [])
×
UNCOV
170
        .select{ |v| v.is_a?(Hash) && v.has_key?('name') }
×
171
        .map do |v|
UNCOV
172
          VariantDefinition.new(
×
173
            v.fetch('name', ''),
174
            v.fetch('weight', 0),
175
            v.fetch('payload', nil),
176
            v.fetch('stickiness', nil),
177
            v.fetch('overrides', [])
178
          )
179
        end || []
180
    end
181
  end
182
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc