Class: TrailGuide::Participant

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(context, adapter: nil) ⇒ Participant

Returns a new instance of Participant.



6
7
8
9
10
# File 'lib/trail_guide/participant.rb', line 6

def initialize(context, adapter: nil)
  @context = context
  @adapter = adapter.new(context) if adapter.present?
  cleanup_inactive_experiments! if TrailGuide.configuration.cleanup_participant_experiments
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



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

def context
  @context
end

Instance Method Details

#active_experiments(include_control = true) ⇒ Object



116
117
118
119
120
121
122
123
# File 'lib/trail_guide/participant.rb', line 116

def active_experiments(include_control=true)
  return false if adapter.keys.empty?
  adapter.keys.map { |key| key.to_s.split(":").first.to_sym }.uniq.map do |key|
    experiment = TrailGuide.catalog.find(key)
    next unless experiment && !experiment.combined? && experiment.running? && participating?(experiment, include_control)
    [ experiment.experiment_name, adapter[experiment.storage_key] ]
  end.compact.to_h
end

#adapterObject



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/trail_guide/participant.rb', line 12

def adapter
  @adapter ||= begin
    config_adapter = TrailGuide.configuration.adapter
    case config_adapter
    when :cookie
      config_adapter = TrailGuide::Adapters::Participants::Cookie
    when :session
      config_adapter = TrailGuide::Adapters::Participants::Session
    when :redis
      config_adapter = TrailGuide::Adapters::Participants::Redis
    when :anonymous
      config_adapter = TrailGuide::Adapters::Participants::Anonymous
    when :multi
      config_adapter = TrailGuide::Adapters::Participants::Multi
    else
      config_adapter = config_adapter.constantize if config_adapter.is_a?(String)
    end
    config_adapter.new(context)
  rescue => e
    [TrailGuide.configuration.on_adapter_failover].flatten.compact.each do |callback|
      callback.call(config_adapter, e)
    end
    TrailGuide::Adapters::Participants::Anonymous.new(context)
  end
end

#cleanup_inactive_experiments!Object



135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/trail_guide/participant.rb', line 135

def cleanup_inactive_experiments!
  return false if adapter.keys.empty?

  adapter.keys.each do |key|
    experiment_name = key.to_s.split(":").first.to_sym
    experiment = TrailGuide.catalog.find(experiment_name)
    if !experiment || !experiment.started?
      adapter.delete(key)
    end
  end

  return true
end

#converted!(variant, checkpoint = nil, reset: false) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/trail_guide/participant.rb', line 88

def converted!(variant, checkpoint=nil, reset: false)
  checkpoint ||= :converted
  storage_key = "#{variant.experiment.storage_key}:#{checkpoint.to_s.underscore}"

  if reset
    adapter.delete(variant.experiment.storage_key)
    adapter.delete(variant.storage_key)
    adapter.delete(storage_key)
    variant.experiment.goals.each do |goal|
      goal_key = "#{variant.experiment.storage_key}:#{goal.to_s}"
      adapter.delete(goal_key)
    end
  else
    adapter[storage_key] = Time.now.to_i
  end
end

#converted?(experiment, checkpoint = nil) ⇒ Boolean

Returns:

  • (Boolean)


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
# File 'lib/trail_guide/participant.rb', line 56

def converted?(experiment, checkpoint=nil)
  return false unless experiment.started?
  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?
    storage_key = "#{experiment.storage_key}:converted"
    return false unless adapter.key?(storage_key)

    converted_at = Time.at(adapter[storage_key].to_i)
    converted_at >= experiment.started_at
  elsif !checkpoint.nil?
    raise InvalidGoalError, "Invalid goal checkpoint `#{checkpoint}` for experiment `#{experiment.experiment_name}`." unless experiment.goals.any? { |goal| goal == checkpoint.to_s.underscore.to_sym }
    storage_key = "#{experiment.storage_key}:#{checkpoint.to_s.underscore}"
    return false unless adapter.key?(storage_key)

    converted_at = Time.at(adapter[storage_key].to_i)
    converted_at >= experiment.started_at
  else
    experiment.goals.each do |goal|
      storage_key = "#{experiment.storage_key}:#{goal.to_s}"
      next unless adapter.key?(storage_key)
      converted_at = Time.at(adapter[storage_key].to_i)
      return true if converted_at >= experiment.started_at
    end
    return false
  end
end

#exit!(experiment) ⇒ Object



105
106
107
108
109
110
111
112
113
114
# File 'lib/trail_guide/participant.rb', line 105

def exit!(experiment)
  chosen = variant(experiment)
  return true if chosen.nil?
  adapter.delete(experiment.storage_key)
  adapter.delete(chosen.storage_key)
  experiment.goals.each do |goal|
    adapter.delete("#{experiment.storage_key}:#{goal.to_s}")
  end
  return true
end

#participating!(variant) ⇒ Object



83
84
85
86
# File 'lib/trail_guide/participant.rb', line 83

def participating!(variant)
  adapter[variant.experiment.storage_key] = variant.name
  adapter[variant.storage_key] = Time.now.to_i
end

#participating?(experiment, include_control = true) ⇒ Boolean

Returns:

  • (Boolean)


49
50
51
52
53
54
# File 'lib/trail_guide/participant.rb', line 49

def participating?(experiment, include_control=true)
  var = variant(experiment)
  return false if var.nil?
  return false if !include_control && var.control?
  return true
end

#participating_in_active_experiments?(include_control = true) ⇒ Boolean

Returns:

  • (Boolean)


125
126
127
128
129
130
131
132
133
# File 'lib/trail_guide/participant.rb', line 125

def participating_in_active_experiments?(include_control=true)
  return false if adapter.keys.empty?

  adapter.keys.any? do |key|
    experiment_name = key.to_s.split(":").first.to_sym
    experiment = TrailGuide.catalog.find(experiment_name)
    experiment && !experiment.combined? && experiment.running? && participating?(experiment, include_control)
  end
end

#variant(experiment) ⇒ Object



38
39
40
41
42
43
44
45
46
47
# File 'lib/trail_guide/participant.rb', line 38

def variant(experiment)
  return nil unless experiment.started?
  return nil unless adapter.key?(experiment.storage_key)
  varname = adapter[experiment.storage_key]
  variant = experiment.variants.find { |var| var == varname }
  return nil unless variant && adapter.key?(variant.storage_key)

  chosen_at = Time.at(adapter[variant.storage_key].to_i)
  return variant if chosen_at >= experiment.started_at
end