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

Class Method Details

.build_bucket_key(attribute_keys, context, type, feature_key) ⇒ Array

Build bucket key array from attribute keys and context

Parameters:

  • attribute_keys (Array<String>)

    Array of attribute keys

  • context (Hash)

    User context

  • type (String)

    Bucketing type (“plain”, “and”, “or”)

  • feature_key (String)

    Feature key to append

Returns:

  • (Array)

    Array of bucket key components



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?

    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
end

.get_bucket_key(options) ⇒ String

Get bucket key from feature configuration and context

Parameters:

  • options (Hash)

    Options hash containing:

    • feature_key [String] The feature key

    • bucket_by [String, Array<String>, Hash] Bucketing strategy

    • context [Hash] User context

    • logger [Logger] Logger instance

Returns:

  • (String)

    The bucket key

Raises:

  • (StandardError)

    If bucket_by is invalid



36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/featurevisor/bucketer.rb', line 36

def self.get_bucket_key(options)
  feature_key = options[:feature_key]
  bucket_by = options[:bucket_by]
  context = options[:context]
  logger = options[:logger]

  type, attribute_keys = parse_bucket_by(bucket_by, logger, 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

Parameters:

  • bucket_key (String)

    The bucket key to hash

Returns:

  • (Integer)

    Bucket value between 0 and 100000



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

.parse_bucket_by(bucket_by, logger, feature_key) ⇒ Array

Parse bucket_by configuration to determine type and attribute keys

Parameters:

  • bucket_by (String, Array<String>, Hash)

    Bucketing strategy

  • logger (Logger)

    Logger instance

  • feature_key (String)

    Feature key for error logging

Returns:

  • (Array)

    Tuple of [type, 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, logger, 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
    logger.error("invalid bucketBy", { feature_key: feature_key, bucket_by: bucket_by })
    raise StandardError, "invalid bucketBy"
  end
end