Module: ConvertSdk::Comparisons Private
- Defined in:
- lib/convert_sdk/comparisons.rb
Overview
This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.
The 13 rule comparison operators — the cross-SDK audience-targeting predicate
set, ported BYTE-FOR-BYTE from the JS SDK
packages/utils/src/comparisons.ts.
JS is the ONLY truth here. The PHP reference is QUARANTINED for this surface:
it ships zero +exists+/+not_exists+ handling (a disk-verified gap) and folds
case on the isIn values side (a second divergence), so it must not influence
the Ruby contract. Every operator below mirrors its JS body at the cited
comparisons.ts line; the goldens in the cross-SDK vector suite are the CI
proof of parity.
Two-worlds dispatch (operators): the platform sends operator names as
camelCase WIRE strings inside the rule JSON (+equalsNumber+, startsWith, …).
Those strings are config-world identifiers and stay byte-identical; the Ruby
methods underneath are snake_case. Comparisons.dispatch is the map from the wire name to
the Ruby method symbol, consumed by RuleManager.
The undefined/nil distinction (the subtle one): JS distinguishes undefined
(a data key is ABSENT) from null (the key is present with a null value).
Ruby hashes collapse both to nil, so absence is modeled EXPLICITLY with the
frozen private UNDEFINED marker — RuleManager passes UNDEFINED for an
absent key so the existence operators (and JS-parity need-more-data
propagation) behave exactly as JS does with undefined. For the existence
operators themselves UNDEFINED, nil, and "" are all "does not exist",
matching value !== undefined && value !== null && value !== ''
(+comparisons.ts:159+).
Pure and stateless (NFR1): every method is a singleton (+self.+) method with no I/O and no instance state (the same module form as MurmurHash3).
Constant Summary collapse
- UNDEFINED =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Sentinel for an ABSENT data key, distinct from
nil(a present null value). Frozen so it is a stable, comparable singleton. Mirrors JSundefinedon the rule-evaluation path. Object.new.freeze
- NUMERIC_REGEXP =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
JS
isNumericregex (+string-utils.ts:69+): optional leading minus, then either grouped thousands (+1,234+) or plain digits, with an optional fractional part, or a bare fraction (+.5+). /\A-?(?:(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?|\.\d+)\z/- DISPATCH =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Maps each wire comparison operator name to its implementing method symbol.
{ "equals" => :equals, "equalsNumber" => :equals_number, "matches" => :matches, "less" => :less, "lessEqual" => :less_equal, "contains" => :contains, "isIn" => :is_in, "startsWith" => :starts_with, "endsWith" => :ends_with, "regexMatches" => :regex_matches, "exists" => :exists, "not_exists" => :not_exists, "doesNotExist" => :does_not_exist }.freeze
Class Method Summary collapse
-
.contains(value, test_against, negation = false) ⇒ Boolean
private
Case-insensitive substring test (+comparisons.ts:71-87+).
-
.dispatch ⇒ Hash{String=>Symbol}
private
The wire-name -> Ruby-method dispatch map (the two-worlds rule for operators).
-
.does_not_exist(value, test_against = nil, negation = false) ⇒ Object
private
Alias of Comparisons.not_exists (+comparisons.ts:172+ —
doesNotExist = this.not_exists). -
.ends_with(value, test_against, negation = false) ⇒ Boolean
private
Case-insensitive suffix test (+comparisons.ts:130-141+).
-
.equals(value, test_against, negation = false) ⇒ Boolean
private
Case-insensitive equality.
-
.equals_number(value, test_against, negation = false) ⇒ Object
private
Alias of Comparisons.equals (+comparisons.ts:42+ —
equalsNumber = this.equals). -
.exists(value, _test_against = nil, negation = false) ⇒ Boolean
private
Presence test (+comparisons.ts:154-161+): true unless the value is
UNDEFINED(JSundefined),nil(JSnull), or the empty string. -
.is_in(values, test_against, negation = false, splitter = "|") ⇒ Boolean
private
Pipe-split membership (+comparisons.ts:89-115+).
-
.less(value, test_against, negation = false) ⇒ Boolean
private
Strict less-than over numerically-normalized inputs (+comparisons.ts:45-56+).
-
.less_equal(value, test_against, negation = false) ⇒ Boolean
private
Less-than-or-equal counterpart of Comparisons.less (+comparisons.ts:58-69+).
-
.matches(value, test_against, negation = false) ⇒ Object
private
Alias of Comparisons.equals (+comparisons.ts:43+ —
matches = this.equals). -
.not_exists(value, _test_against = nil, negation = false) ⇒ Boolean
private
Absence test — the logical inverse of Comparisons.exists (+comparisons.ts:163-170+).
-
.numeric?(value) ⇒ Boolean
private
JS
isNumeric(+string-utils.ts:68-74+): numbers are numeric when finite; strings are numeric only when they match NUMERIC_REGEXP and parse finite. -
.regex_matches(value, test_against, negation = false) ⇒ Boolean
private
Case-insensitive regex test (+comparisons.ts:143-152+ —
new RegExp(t, 'i')). -
.starts_with(value, test_against, negation = false) ⇒ Boolean
private
Case-insensitive prefix test (+comparisons.ts:117-128+ —
indexOf === 0). -
.to_number(value) ⇒ Object
private
JS
toNumber(+string-utils.ts:81-91+): numbers pass through; strings with a leading"0"thousands segment treat commas as decimal points (all commas replaced with dots viatr, matching JS's globalreplace(/,/g, '.')), otherwise commas are stripped.
Class Method Details
.contains(value, test_against, negation = false) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Case-insensitive substring test (+comparisons.ts:71-87+). PRESERVED JS
quirk: an empty or whitespace-only test_against returns true
(ts:80-81) — do not "fix" it.
93 94 95 96 97 98 99 |
# File 'lib/convert_sdk/comparisons.rb', line 93 def self.contains(value, test_against, negation = false) value = value.to_s.downcase test_against = test_against.to_s.downcase return negation_check(true, negation) if test_against.gsub(/\A\s*|\s*\z/, "").empty? negation_check(value.include?(test_against), negation) end |
.dispatch ⇒ Hash{String=>Symbol}
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
The wire-name -> Ruby-method dispatch map (the two-worlds rule for
operators). Keys are byte-identical to the rule JSON match_type strings;
RuleManager looks an operator up here and invokes the mapped method.
195 196 197 |
# File 'lib/convert_sdk/comparisons.rb', line 195 def self.dispatch DISPATCH end |
.does_not_exist(value, test_against = nil, negation = false) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Alias of not_exists (+comparisons.ts:172+ — doesNotExist = this.not_exists).
169 170 171 |
# File 'lib/convert_sdk/comparisons.rb', line 169 def self.does_not_exist(value, test_against = nil, negation = false) not_exists(value, test_against, negation) end |
.ends_with(value, test_against, negation = false) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Case-insensitive suffix test (+comparisons.ts:130-141+).
134 135 136 137 138 |
# File 'lib/convert_sdk/comparisons.rb', line 134 def self.ends_with(value, test_against, negation = false) value = value.to_s.downcase test_against = test_against.to_s.downcase negation_check(value.end_with?(test_against), negation) end |
.equals(value, test_against, negation = false) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Case-insensitive equality. Mirrors comparisons.ts:15-40:
Array value -> membership of test_against; non-empty Hash value -> key
membership; otherwise both sides are stringified, lowercased, and compared.
54 55 56 57 58 59 |
# File 'lib/convert_sdk/comparisons.rb', line 54 def self.equals(value, test_against, negation = false) return negation_check(value.include?(test_against), negation) if value.is_a?(Array) return negation_check(value.key?(test_against.to_s), negation) if value.is_a?(Hash) && !value.empty? negation_check(value.to_s.downcase == test_against.to_s.downcase, negation) end |
.equals_number(value, test_against, negation = false) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Alias of equals (+comparisons.ts:42+ — equalsNumber = this.equals).
62 63 64 |
# File 'lib/convert_sdk/comparisons.rb', line 62 def self.equals_number(value, test_against, negation = false) equals(value, test_against, negation) end |
.exists(value, _test_against = nil, negation = false) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Presence test (+comparisons.ts:154-161+): true unless the value is
UNDEFINED (JS undefined), nil (JS null), or the empty string.
155 156 157 158 |
# File 'lib/convert_sdk/comparisons.rb', line 155 def self.exists(value, _test_against = nil, negation = false) value_exists = !value.equal?(UNDEFINED) && !value.nil? && value != "" negation_check(value_exists, negation) end |
.is_in(values, test_against, negation = false, splitter = "|") ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Pipe-split membership (+comparisons.ts:89-115+). BOTH values and a string
test_against are split on the splitter. Only the test_against items
are lowercased after splitting; the values items are compared AS-IS
against the lowercased list (so an uppercased value does not match a
lowercased entry — exact JS semantics at ts:106-110).
112 113 114 115 116 117 118 119 120 |
# File 'lib/convert_sdk/comparisons.rb', line 112 def self.is_in(values, test_against, negation = false, splitter = "|") matched_values = values.to_s.split(splitter, -1).map(&:to_s) test_against = test_against.split(splitter, -1) if test_against.is_a?(String) unless test_against.is_a?(Array) test_against = [] #: Array[untyped] end test_against = test_against.map { |item| item.to_s.downcase } negation_check(matched_values.any? { |item| test_against.include?(item) }, negation) end |
.less(value, test_against, negation = false) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Strict less-than over numerically-normalized inputs (+comparisons.ts:45-56+).
Numeric-looking strings/numbers normalize to a number; anything else keeps
its type. When the normalized types differ the result is false (JS
typeof value !== typeof testAgainst, ts:52-54).
77 78 79 |
# File 'lib/convert_sdk/comparisons.rb', line 77 def self.less(value, test_against, negation = false) compare_numeric(value, test_against, negation) { |a, b| a < b } end |
.less_equal(value, test_against, negation = false) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Less-than-or-equal counterpart of less (+comparisons.ts:58-69+).
84 85 86 |
# File 'lib/convert_sdk/comparisons.rb', line 84 def self.less_equal(value, test_against, negation = false) compare_numeric(value, test_against, negation) { |a, b| a <= b } end |
.matches(value, test_against, negation = false) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Alias of equals (+comparisons.ts:43+ — matches = this.equals).
67 68 69 |
# File 'lib/convert_sdk/comparisons.rb', line 67 def self.matches(value, test_against, negation = false) equals(value, test_against, negation) end |
.not_exists(value, _test_against = nil, negation = false) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Absence test — the logical inverse of exists (+comparisons.ts:163-170+).
163 164 165 166 |
# File 'lib/convert_sdk/comparisons.rb', line 163 def self.not_exists(value, _test_against = nil, negation = false) value_not_exists = value.equal?(UNDEFINED) || value.nil? || value == "" negation_check(value_not_exists, negation) end |
.numeric?(value) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
JS isNumeric (+string-utils.ts:68-74+): numbers are numeric when finite;
strings are numeric only when they match NUMERIC_REGEXP and parse finite.
202 203 204 205 206 207 208 209 |
# File 'lib/convert_sdk/comparisons.rb', line 202 def self.numeric?(value) return value.finite? if value.is_a?(Numeric) return false unless value.is_a?(String) && NUMERIC_REGEXP.match?(value) Float(value.delete(",")).finite? rescue ArgumentError, TypeError false end |
.regex_matches(value, test_against, negation = false) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Case-insensitive regex test (+comparisons.ts:143-152+ — new RegExp(t, 'i')).
value is lowercased; the pattern keeps its case but matches
case-insensitively via the i flag.
145 146 147 148 149 |
# File 'lib/convert_sdk/comparisons.rb', line 145 def self.regex_matches(value, test_against, negation = false) value = value.to_s.downcase pattern = Regexp.new(test_against.to_s, Regexp::IGNORECASE) negation_check(!pattern.match(value).nil?, negation) end |
.starts_with(value, test_against, negation = false) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Case-insensitive prefix test (+comparisons.ts:117-128+ — indexOf === 0).
125 126 127 128 129 |
# File 'lib/convert_sdk/comparisons.rb', line 125 def self.starts_with(value, test_against, negation = false) value = value.to_s.downcase test_against = test_against.to_s.downcase negation_check(value.start_with?(test_against), negation) end |
.to_number(value) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
JS toNumber (+string-utils.ts:81-91+): numbers pass through; strings with
a leading "0" thousands segment treat commas as decimal points (all commas
replaced with dots via tr, matching JS's global replace(/,/g, '.')),
otherwise commas are stripped. The result is parsed with to_f (lenient,
never raises) — matching JS parseFloat(): the leading numeric portion is
extracted and a trailing-garbage input like "0.123.456" returns 0.123
rather than raising. This is a verified parity fix: Ruby Float() (strict)
raises on that input while JS parseFloat and Ruby to_f do not.
220 221 222 223 224 225 226 227 |
# File 'lib/convert_sdk/comparisons.rb', line 220 def self.to_number(value) return value if value.is_a?(Numeric) str = value.to_s parts = str.split(",") normalized = parts[0] == "0" ? str.tr(",", ".") : str.delete(",") normalized.to_f end |