Class: SplitCat::Experiment

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/split_cat/experiment.rb

Constant Summary collapse

@@cache =
HashWithIndifferentAccess.new

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.factory(name) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'app/models/split_cat/experiment.rb', line 152

def self.factory( name )
  if experiment = @@cache[ name ]
    return experiment
  end

  unless template = SplitCat.config.template( name )
    Rails.logger.error( "Experiment.factory not configured for experiment: #{name}" )
    return nil
  end

  if experiment = Experiment.includes( :goals, :hypotheses ).find_by_name( name )
    unless experiment.same_structure?( template )
      experiment = nil
      Rails.logger.error( "Experiment.factory mismatched experiment: #{name}" )
    end
  else
    if template.save
      experiment = template
    else
      Rails.logger.error( "Experiment.factory failed to save experiment: #{name}" )
    end
  end

  @@cache[ name.to_sym ] = experiment

  return experiment
end

Instance Method Details

#active?Boolean

An experiment is only valid if it matches the current configuration

Returns:

  • (Boolean)


17
18
19
20
# File 'app/models/split_cat/experiment.rb', line 17

def active?
  return false unless template = SplitCat.config.template( name )
  return !!same_structure?( template )
end

#choose_hypothesisObject

Returns a random hypothesis with weighted probability



90
91
92
93
94
95
# File 'app/models/split_cat/experiment.rb', line 90

def choose_hypothesis
  total = 0
  roll = rand( total_weight ) + 1
  hypotheses.each { |h| return h if roll <= ( total += h.weight ) }
  return hypotheses.first
end

#get_hypothesis(token) ⇒ Object

Return the winner if one has been chosen. Return nil if the token can’t be found. Return the previously assigned hypothesis (or nil on error). Choose a hypothesis randomly. Record the hypothesis assignment and return it.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'app/models/split_cat/experiment.rb', line 73

def get_hypothesis( token )
  return winner if winner.present?
  return nil unless subject = Subject.find_by_token( token )

  if join = HypothesisSubject.find_by_experiment_id_and_subject_id( id, subject.id )
    hypotheses.each { |h| return h if h.id == join.hypothesis_id }
    return nil
  end

  hypothesis = choose_hypothesis
  HypothesisSubject.create( :hypothesis_id => hypothesis.id, :subject_id => subject.id, :experiment_id => id )

  return hypothesis
end

#goal_countsObject

Returns a memoized array of goal name => hypothesis_name => subject counts



24
25
26
# File 'app/models/split_cat/experiment.rb', line 24

def goal_counts
  @goal_counts ||= GoalSubject.subject_counts( self )
end

#goal_hashObject

Return a memoized hash of goal name => goals



30
31
32
33
34
35
36
# File 'app/models/split_cat/experiment.rb', line 30

def goal_hash
 unless @goal_hash
   @goal_hash = HashWithIndifferentAccess.new
   goals.map { |goal| @goal_hash[ goal.name ] = goal }
 end
 return @goal_hash
end

#hypothesis_countsObject

Returns a memoized array of hypothesis name => subject counts



40
41
42
# File 'app/models/split_cat/experiment.rb', line 40

def hypothesis_counts
  @hypothesis_counts ||= HypothesisSubject.subject_counts( self )
end

#hypothesis_hashObject

Return a memoized hash of hypothesis name => hypotheses



46
47
48
49
50
51
52
# File 'app/models/split_cat/experiment.rb', line 46

def hypothesis_hash
  unless @hypothesis_hash
    @hypothesis_hash = HashWithIndifferentAccess.new
    hypotheses.map { |hypothesis| @hypothesis_hash[ hypothesis.name ] = hypothesis }
  end
  return @hypothesis_hash
end

#record_goal(goal, token) ⇒ Object

Return true immediately if a winner has already been chosen. Return false if the goal or token can’t be found. Return true if the user isn’t in the experiment or has already recorded this goal. Record the goal and return true.



105
106
107
108
109
110
111
112
113
114
115
116
# File 'app/models/split_cat/experiment.rb', line 105

def record_goal( goal, token )
  return true if winner_id

  return false unless goal = goal_hash[ goal ]
  return false unless subject = Subject.find_by_token( token )

  return true unless join = HypothesisSubject.find_by_experiment_id_and_subject_id( id, subject.id )
  return true if GoalSubject.find_by_goal_id_and_subject_id( goal.id, subject.id )

  GoalSubject.create( :goal_id => goal.id, :subject_id => subject.id, :experiment_id => id, :hypothesis_id => join.hypothesis_id)
  return true
end

#same_structure?(experiment) ⇒ Boolean

Returns true if the experiment has the same name, goals, and hypotheses as this one

Returns:

  • (Boolean)


120
121
122
123
124
125
# File 'app/models/split_cat/experiment.rb', line 120

def same_structure?( experiment )
  return nil if name.to_sym != experiment.name.to_sym
  return nil if goal_hash.keys != experiment.goal_hash.keys
  return nil if hypothesis_hash.keys != experiment.hypothesis_hash.keys
  return experiment
end

#to_csvObject

Generates a CSV representing the experiment results

* header row of hypothesis names
* row of total subject count per hypothesis
* goal rows of subject count per hypothesis


135
136
137
138
139
140
141
142
143
144
# File 'app/models/split_cat/experiment.rb', line 135

def to_csv
  CSV.generate do |csv|
    csv << [ nil ] + hypotheses.map { |h| h.name }
    csv << [ 'total' ] + hypotheses.map { |h| hypothesis_counts[ h.name ] || 0 }

    goals.each do |g|
      csv << [ g.name ] + hypotheses.map { |h| goal_counts[ g.name ][ h.name ] || 0 }
    end
  end
end

#total_subjectsObject



54
55
56
# File 'app/models/split_cat/experiment.rb', line 54

def total_subjects
  @total_subjects ||= hypothesis_counts.values.inject( 0 ) { |sum,count| sum + ( count || 0 ) }
end

#total_weightObject

Returns a memoized sum of hypothesis weights



60
61
62
# File 'app/models/split_cat/experiment.rb', line 60

def total_weight
  @total_weight ||= hypotheses.inject( 0 ) { |sum,h| sum + h.weight }
end