Class: Growthbook::Conditions

Inherits:
Object
  • Object
show all
Defined in:
lib/growthbook/conditions.rb

Overview

internal use only Utils for condition evaluation

Class Method Summary collapse

Class Method Details

.elem_match(condition, attribute_value) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/growthbook/conditions.rb', line 94

def self.elem_match(condition, attribute_value)
  return false unless attribute_value.is_a? Array

  attribute_value.each do |item|
    if operator_object?(condition)
      return true if eval_condition_value(condition, item)
    elsif eval_condition(item, condition)
      return true
    end
  end
  false
end

.eval_and(attributes, conditions) ⇒ Object



44
45
46
47
48
49
# File 'lib/growthbook/conditions.rb', line 44

def self.eval_and(attributes, conditions)
  conditions.each do |condition|
    return false unless eval_condition(attributes, condition)
  end
  true
end

.eval_condition(attributes, condition) ⇒ Object

Evaluate a targeting conditions hash against an attributes hash Both attributes and conditions only have string keys (no symbols)



11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/growthbook/conditions.rb', line 11

def self.eval_condition(attributes, condition)
  return eval_or(attributes, condition['$or']) if condition.key?('$or')
  return !eval_or(attributes, condition['$nor']) if condition.key?('$nor')
  return eval_and(attributes, condition['$and']) if condition.key?('$and')
  return !eval_condition(attributes, condition['$not']) if condition.key?('$not')

  condition.each do |key, value|
    return false unless eval_condition_value(value, get_path(attributes, key))
  end

  true
end

.eval_condition_value(condition_value, attribute_value) ⇒ Object



84
85
86
87
88
89
90
91
92
# File 'lib/growthbook/conditions.rb', line 84

def self.eval_condition_value(condition_value, attribute_value)
  if condition_value.is_a?(Hash) && operator_object?(condition_value)
    condition_value.each do |key, value|
      return false unless eval_operator_condition(key, attribute_value, value)
    end
    return true
  end
  condition_value.to_json == attribute_value.to_json
end

.eval_operator_condition(operator, attribute_value, condition_value) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/growthbook/conditions.rb', line 107

def self.eval_operator_condition(operator, attribute_value, condition_value)
  case operator
  when '$eq'
    attribute_value == condition_value
  when '$ne'
    attribute_value != condition_value
  when '$lt'
    attribute_value < condition_value
  when '$lte'
    attribute_value <= condition_value
  when '$gt'
    attribute_value > condition_value
  when '$gte'
    attribute_value >= condition_value
  when '$regex'
    silence_warnings do
      re = Regexp.new(condition_value)
      !!attribute_value.match(re)
    rescue StandardError
      false
    end
  when '$in'
    condition_value.include? attribute_value
  when '$nin'
    !(condition_value.include? attribute_value)
  when '$elemMatch'
    elem_match(condition_value, attribute_value)
  when '$size'
    return false unless attribute_value.is_a? Array

    eval_condition_value(condition_value, attribute_value.length)
  when '$all'
    return false unless attribute_value.is_a? Array

    condition_value.each do |condition|
      passed = false
      attribute_value.each do |attr|
        passed = true if eval_condition_value(condition, attr)
      end
      return false unless passed
    end
    true
  when '$exists'
    exists = !attribute_value.nil?
    if condition_value
      exists
    else
      !exists
    end
  when '$type'
    condition_value == get_type(attribute_value)
  when '$not'
    !eval_condition_value(condition_value, attribute_value)
  else
    false
  end
end

.eval_or(attributes, conditions) ⇒ Object



35
36
37
38
39
40
41
42
# File 'lib/growthbook/conditions.rb', line 35

def self.eval_or(attributes, conditions)
  return true if conditions.length <= 0

  conditions.each do |condition|
    return true if eval_condition(attributes, condition)
  end
  false
end

.get_path(attributes, path) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/growthbook/conditions.rb', line 69

def self.get_path(attributes, path)
  path = path.to_s if path.is_a?(Symbol)

  parts = path.split('.')
  current = attributes

  parts.each do |value|
    return nil unless current.is_a?(Hash) && current&.key?(value)

    current = current[value]
  end

  current
end

.get_type(attribute_value) ⇒ Object



58
59
60
61
62
63
64
65
66
67
# File 'lib/growthbook/conditions.rb', line 58

def self.get_type(attribute_value)
  return 'string' if attribute_value.is_a? String
  return 'number' if attribute_value.is_a? Integer
  return 'number' if attribute_value.is_a? Float
  return 'boolean' if [true, false].include?(attribute_value)
  return 'array' if attribute_value.is_a? Array
  return 'null' if attribute_value.nil?

  'object'
end

.operator_object?(obj) ⇒ Boolean

Returns:

  • (Boolean)


51
52
53
54
55
56
# File 'lib/growthbook/conditions.rb', line 51

def self.operator_object?(obj)
  obj.each do |key, _value|
    return false if key[0] != '$'
  end
  true
end

.parse_condition(condition) ⇒ Object

Helper function to ensure conditions only have string keys (no symbols)



25
26
27
28
29
30
31
32
33
# File 'lib/growthbook/conditions.rb', line 25

def self.parse_condition(condition)
  case condition
  when Array
    return condition.map { |v| parse_condition(v) }
  when Hash
    return condition.to_h { |k, v| [k.to_s, parse_condition(v)] }
  end
  condition
end

.silence_warningsObject

Sets $VERBOSE for the duration of the block and back to its original value afterwards. Used for testing invalid regexes.



167
168
169
170
171
172
173
# File 'lib/growthbook/conditions.rb', line 167

def self.silence_warnings
  old_verbose = $VERBOSE
  $VERBOSE = nil
  yield
ensure
  $VERBOSE = old_verbose
end