Module: Featurevisor::Bucketer
- Defined in:
- lib/featurevisor/bucketer.rb
Overview
Bucketer module for handling feature flag bucketing
Constant Summary collapse
- MAX_BUCKETED_NUMBER =
Maximum bucketed number (100% * 1000 to include three decimal places)
100_000- HASH_SEED =
Hash seed for consistent bucketing
1- MAX_HASH_VALUE =
Maximum hash value for 32-bit integers
2**32
- DEFAULT_BUCKET_KEY_SEPARATOR =
Default separator for bucket keys
"."
Class Method Summary collapse
-
.build_bucket_key(attribute_keys, context, type, feature_key) ⇒ Array
Build bucket key array from attribute keys and context.
- .context_path_exists?(context, path) ⇒ Boolean
-
.get_bucket_key(options) ⇒ String
Get bucket key from feature configuration and context.
-
.get_bucketed_number(bucket_key) ⇒ Integer
Get bucketed number from a bucket key.
- .javascript_string(value) ⇒ Object
-
.parse_bucket_by(bucket_by, diagnostics, feature_key) ⇒ Array
Parse bucket_by configuration to determine type and attribute keys.
Class Method Details
.build_bucket_key(attribute_keys, context, type, feature_key) ⇒ Array
Build bucket key array from attribute keys and context
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/featurevisor/bucketer.rb', line 75 def self.build_bucket_key(attribute_keys, context, type, feature_key) bucket_key = [] attribute_keys.each do |attribute_key| attribute_value = Featurevisor::Conditions.get_value_from_context(context, attribute_key) next if attribute_value.nil? && !context_path_exists?(context, attribute_key) if type == "plain" || type == "and" bucket_key << attribute_value elsif type == "or" && bucket_key.empty? # For "or" type, only take the first available value bucket_key << attribute_value end end bucket_key << feature_key bucket_key.map { |value| javascript_string(value) } end |
.context_path_exists?(context, path) ⇒ Boolean
95 96 97 98 99 100 101 102 103 |
# File 'lib/featurevisor/bucketer.rb', line 95 def self.context_path_exists?(context, path) path.split(".").reduce(context) do |current, key| return false unless current.is_a?(Hash) return false unless current.key?(key.to_sym) || current.key?(key) current.key?(key.to_sym) ? current[key.to_sym] : current[key] end true end |
.get_bucket_key(options) ⇒ String
Get bucket key from feature configuration and context
36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/featurevisor/bucketer.rb', line 36 def self.get_bucket_key() feature_key = [:feature_key] bucket_by = [:bucket_by] context = [:context] diagnostics = [:diagnostics] type, attribute_keys = parse_bucket_by(bucket_by, diagnostics, feature_key) bucket_key = build_bucket_key(attribute_keys, context, type, feature_key) bucket_key.join(DEFAULT_BUCKET_KEY_SEPARATOR) end |
.get_bucketed_number(bucket_key) ⇒ Integer
Get bucketed number from a bucket key
21 22 23 24 25 26 |
# File 'lib/featurevisor/bucketer.rb', line 21 def self.get_bucketed_number(bucket_key) hash_value = Featurevisor.murmur_hash_v3(bucket_key, HASH_SEED) ratio = hash_value.to_f / MAX_HASH_VALUE (ratio * MAX_BUCKETED_NUMBER).floor end |
.javascript_string(value) ⇒ Object
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/featurevisor/bucketer.rb', line 105 def self.javascript_string(value) return "" if value.nil? return value ? "true" : "false" if value == true || value == false if value.is_a?(Float) return "NaN" if value.nan? return value.positive? ? "Infinity" : "-Infinity" if value.infinite? return "0" if value.zero? return value.to_i.to_s if value == value.to_i && value.abs < 1e21 if value.abs >= 1e-6 && value.abs < 1e21 return scientific_to_fixed(value.to_s) end coefficient, exponent = value.to_s.downcase.split("e") return "#{coefficient.delete_suffix('.0')}e#{Integer(exponent) >= 0 ? '+' : ''}#{Integer(exponent)}" end return value.map { |item| javascript_string(item) }.join(",") if value.is_a?(Array) return "[object Object]" if value.is_a?(Hash) value.to_s end |
.parse_bucket_by(bucket_by, diagnostics, feature_key) ⇒ Array
Parse bucket_by configuration to determine type and attribute keys
56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/featurevisor/bucketer.rb', line 56 def self.parse_bucket_by(bucket_by, diagnostics, feature_key) if bucket_by.is_a?(String) ["plain", [bucket_by]] elsif bucket_by.is_a?(Array) ["and", bucket_by] elsif bucket_by.is_a?(Hash) && bucket_by[:or].is_a?(Array) ["or", bucket_by[:or]] else diagnostics.error("invalid bucketBy", { feature_key: feature_key, bucket_by: bucket_by }) raise StandardError, "invalid bucketBy" end end |