Class: Optimizely::Bucketer

Inherits:
Object
  • Object
show all
Defined in:
lib/optimizely/bucketer.rb

Constant Summary collapse

BUCKETING_ID_TEMPLATE =

Optimizely bucketing algorithm that evenly distributes visitors.

'%{user_id}%{entity_id}'
HASH_SEED =
1
MAX_HASH_VALUE =
2**32
MAX_TRAFFIC_VALUE =
10_000
UNSIGNED_MAX_32_BIT_VALUE =
0xFFFFFFFF

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Bucketer

Returns a new instance of Bucketer.



29
30
31
32
33
34
35
36
# File 'lib/optimizely/bucketer.rb', line 29

def initialize(config)
  # Bucketer init method to set bucketing seed and project config data.
  #
  # config - ProjectConfig data to be used in making bucketing decisions.

  @bucket_seed = HASH_SEED
  @config = config
end

Instance Method Details

#bucket(experiment_key, user_id) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/optimizely/bucketer.rb', line 38

def bucket(experiment_key, user_id)
  # Determines ID of variation to be shown for a given experiment key and user ID.
  #
  # experiment_key - String Key representing experiment for which visitor is to be bucketed.
  # user_id - String ID for user.
  #
  # Returns String variation ID in which visitor with ID user_id has been placed. Nil if no variation.

  # check if experiment is in a group; if so, check if user is bucketed into specified experiment
  experiment_id = @config.get_experiment_id(experiment_key)
  group_id = @config.get_experiment_group_id(experiment_key)
  if group_id
    group = @config.group_key_map.fetch(group_id)
    if Helpers::Group.random_policy?(group)
      bucketing_id = sprintf(BUCKETING_ID_TEMPLATE, user_id: user_id, entity_id: group_id)
      traffic_allocations = group.fetch('trafficAllocation')
      bucket_value = generate_bucket_value(bucketing_id)
      @config.logger.log(Logger::DEBUG, "Assigned experiment bucket #{bucket_value} to user '#{user_id}'.")
      bucketed_experiment_id = find_bucket(bucket_value, traffic_allocations)

      # return if the user is not bucketed into any experiment
      unless bucketed_experiment_id
        @config.logger.log(Logger::INFO, "User '#{user_id}' is in no experiment.")
        return nil
      end

      # return if the user is bucketed into a different experiment than the one specified
      if bucketed_experiment_id != experiment_id
        @config.logger.log(
          Logger::INFO,
          "User '#{user_id}' is not in experiment '#{experiment_key}' of group #{group_id}."
        )
        return nil
      end

      # continue bucketing if the user is bucketed into the experiment specified
      @config.logger.log(
        Logger::INFO,
        "User '#{user_id}' is in experiment '#{experiment_key}' of group #{group_id}."
      )
    end
  end

  bucketing_id = sprintf(BUCKETING_ID_TEMPLATE, user_id: user_id, entity_id: experiment_id)
  bucket_value = generate_bucket_value(bucketing_id)
  @config.logger.log(Logger::DEBUG, "Assigned variation bucket #{bucket_value} to user '#{user_id}'.")
  traffic_allocations = @config.get_traffic_allocation(experiment_key)
  variation_id = find_bucket(bucket_value, traffic_allocations)
  if variation_id && variation_id != ''
    variation_key = @config.get_variation_key_from_id(experiment_key, variation_id)
    @config.logger.log(
      Logger::INFO,
      "User '#{user_id}' is in variation '#{variation_key}' of experiment '#{experiment_key}'."
    )
    return variation_id
  end

  # Handle the case when the traffic range is empty due to sticky bucketing
  if variation_id == ''
    @config.logger.log(Logger::DEBUG, 'Bucketed into an empty traffic range. Returning nil.')
  end

  @config.logger.log(Logger::INFO, "User '#{user_id}' is in no variation.")
  nil
end

#get_forced_variation_id(experiment_key, user_id) ⇒ Object



104
105
106
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
# File 'lib/optimizely/bucketer.rb', line 104

def get_forced_variation_id(experiment_key, user_id)
  # Determine if a user is forced into a variation for the given experiment and return the id of that variation.
  #
  # experiment_key - Key representing the experiment for which user is to be bucketed.
  # user_id - ID for the user.
  #
  # Returns variation ID in which the user with ID user_id is forced into. Nil if no variation.

  forced_variations = @config.get_forced_variations(experiment_key)

  return nil unless forced_variations

  forced_variation_key = forced_variations[user_id]

  return nil unless forced_variation_key

  forced_variation_id = @config.get_variation_id_from_key(experiment_key, forced_variation_key)

  unless forced_variation_id
    @config.logger.log(
      Logger::INFO,
      "Variation key '#{forced_variation_key}' is not in datafile. Not activating user '#{user_id}'."
    )
    return nil
  end

  @config.logger.log(Logger::INFO, "User '#{user_id}' is forced in variation '#{forced_variation_key}'.")
  forced_variation_id
end