Class: TrailGuide::Variant

Inherits:
Object
  • Object
show all
Defined in:
lib/trail_guide/variant.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(experiment, name, metadata: {}, weight: 1, control: false) ⇒ Variant

Returns a new instance of Variant.



9
10
11
12
13
14
15
# File 'lib/trail_guide/variant.rb', line 9

def initialize(experiment, name, metadata: {}, weight: 1, control: false)
  @experiment = experiment
  @name = name.to_s.underscore.to_sym
   = 
  @weight = weight
  @control = control
end

Instance Attribute Details

#experimentObject (readonly)

Returns the value of attribute experiment.



3
4
5
# File 'lib/trail_guide/variant.rb', line 3

def experiment
  @experiment
end

#metadataObject (readonly)

Returns the value of attribute metadata.



3
4
5
# File 'lib/trail_guide/variant.rb', line 3

def 
  
end

#nameObject (readonly)

Returns the value of attribute name.



3
4
5
# File 'lib/trail_guide/variant.rb', line 3

def name
  @name
end

#weightObject (readonly)

Returns the value of attribute weight.



3
4
5
# File 'lib/trail_guide/variant.rb', line 3

def weight
  @weight
end

Instance Method Details

#==(other) ⇒ Object



17
18
19
20
21
22
23
24
25
# File 'lib/trail_guide/variant.rb', line 17

def ==(other)
  if other.is_a?(self.class)
    # TODO eventually remove the experiment requirement here once we start
    # taking advantage of === below
    return name == other.name && experiment == other.experiment
  elsif other.is_a?(String) || other.is_a?(Symbol)
    return name == other.to_s.underscore.to_sym
  end
end

#===(other) ⇒ Object



27
28
29
30
# File 'lib/trail_guide/variant.rb', line 27

def ===(other)
  return false unless other.is_a?(self.class)
  return name == other.name && experiment == other.experiment
end

#as_json(opts = {}) ⇒ Object

export the variant state (not config) as json



111
112
113
114
115
116
117
118
119
120
# File 'lib/trail_guide/variant.rb', line 111

def as_json(opts={})
  if experiment.goals.empty?
    conversions = converted
  else
    conversions = experiment.goals.map { |g| [g.name, converted(g)] }.to_h
  end

  { name => { participants: participants,
              converted: conversions } }
end

#control!Object

mark this variant as the control



36
37
38
# File 'lib/trail_guide/variant.rb', line 36

def control!
  @control = true
end

#control?Boolean

check if this variant is the control

Returns:

  • (Boolean)


41
42
43
# File 'lib/trail_guide/variant.rb', line 41

def control?
  !!@control
end

#converted(checkpoint = nil) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/trail_guide/variant.rb', line 71

def converted(checkpoint=nil)
  if experiment.goals.empty?
    raise InvalidGoalError, "You provided the checkpoint `#{checkpoint}` but the experiment `#{experiment.experiment_name}` does not have any goals defined." unless checkpoint.nil?
    (TrailGuide.redis.hget(storage_key, 'converted') || 0).to_i
  elsif !checkpoint.nil?
    goal = experiment.goals.find { |g| g == checkpoint }
    raise InvalidGoalError, "Invalid goal checkpoint `#{checkpoint}` for experiment `#{experiment.experiment_name}`." if goal.nil?
    (TrailGuide.redis.hget(storage_key, goal.to_s) || 0).to_i
  else
    experiment.goals.sum do |goal|
      (TrailGuide.redis.hget(storage_key, goal.to_s) || 0).to_i
    end
  end
end

#delete!Object



58
59
60
# File 'lib/trail_guide/variant.rb', line 58

def delete!
  TrailGuide.redis.del(storage_key)
end

#dup(experiment) ⇒ Object



5
6
7
# File 'lib/trail_guide/variant.rb', line 5

def dup(experiment)
  self.class.new(experiment, name, metadata: , weight: weight, control: control?)
end

#increment_conversion!(checkpoint = nil) ⇒ Object



101
102
103
104
105
106
107
108
# File 'lib/trail_guide/variant.rb', line 101

def increment_conversion!(checkpoint=nil)
  if checkpoint.nil?
    checkpoint = 'converted'
  else
    checkpoint = experiment.goals.find { |g| g == checkpoint }.to_s
  end
  TrailGuide.redis.hincrby(storage_key, checkpoint, 1)
end

#increment_participation!Object



97
98
99
# File 'lib/trail_guide/variant.rb', line 97

def increment_participation!
  TrailGuide.redis.hincrby(storage_key, 'participants', 1)
end

#measure(goal = nil, against = nil) ⇒ Object



90
91
92
93
94
95
# File 'lib/trail_guide/variant.rb', line 90

def measure(goal=nil, against=nil)
  superset = against ? converted(against) : participants
  converts = converted(goal)
  return 0 if superset.zero? || converts.zero?
  converts.to_f / superset.to_f
end

#participantsObject



67
68
69
# File 'lib/trail_guide/variant.rb', line 67

def participants
  (TrailGuide.redis.hget(storage_key, 'participants') || 0).to_i
end

#persisted?Boolean

Returns:

  • (Boolean)


50
51
52
# File 'lib/trail_guide/variant.rb', line 50

def persisted?
  TrailGuide.redis.exists(storage_key)
end

#reset!Object



62
63
64
65
# File 'lib/trail_guide/variant.rb', line 62

def reset!
  delete!
  save!
end

#save!Object



54
55
56
# File 'lib/trail_guide/variant.rb', line 54

def save!
  TrailGuide.redis.hsetnx(storage_key, 'name', name)
end

#storage_keyObject



126
127
128
# File 'lib/trail_guide/variant.rb', line 126

def storage_key
  "#{experiment.experiment_name}:#{name}"
end

#to_sObject



122
123
124
# File 'lib/trail_guide/variant.rb', line 122

def to_s
  name.to_s
end

#unconvertedObject



86
87
88
# File 'lib/trail_guide/variant.rb', line 86

def unconverted
  participants - converted
end

#variant!Object

unmark this variant as the control



46
47
48
# File 'lib/trail_guide/variant.rb', line 46

def variant!
  @control = false
end