Class: SplitCat::Experiment
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- SplitCat::Experiment
- Defined in:
- app/models/split_cat/experiment.rb
Constant Summary collapse
- @@cache =
HashWithIndifferentAccess.new
Class Method Summary collapse
Instance Method Summary collapse
-
#active? ⇒ Boolean
An experiment is only valid if it matches the current configuration.
-
#choose_hypothesis ⇒ Object
Returns a random hypothesis with weighted probability.
-
#get_hypothesis(token) ⇒ Object
Return the winner if one has been chosen.
-
#goal_counts ⇒ Object
Returns a memoized array of goal name => hypothesis_name => subject counts.
-
#goal_hash ⇒ Object
Return a memoized hash of goal name => goals.
-
#hypothesis_counts ⇒ Object
Returns a memoized array of hypothesis name => subject counts.
-
#hypothesis_hash ⇒ Object
Return a memoized hash of hypothesis name => hypotheses.
-
#record_goal(goal, token) ⇒ Object
Return true immediately if a winner has already been chosen.
-
#same_structure?(experiment) ⇒ Boolean
Returns true if the experiment has the same name, goals, and hypotheses as this one.
-
#to_csv ⇒ Object
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.
- #total_subjects ⇒ Object
-
#total_weight ⇒ Object
Returns a memoized sum of hypothesis weights.
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
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_hypothesis ⇒ Object
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_counts ⇒ Object
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_hash ⇒ Object
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_counts ⇒ Object
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_hash ⇒ Object
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
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_csv ⇒ Object
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_subjects ⇒ Object
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_weight ⇒ Object
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 |