Class: Split::Experiment
- Inherits:
-
Object
- Object
- Split::Experiment
- Defined in:
- lib/split/experiment.rb
Instance Attribute Summary collapse
- #algorithm ⇒ Object
-
#alternatives ⇒ Object
Returns the value of attribute alternatives.
-
#goals ⇒ Object
Returns the value of attribute goals.
-
#name ⇒ Object
Returns the value of attribute name.
-
#resettable ⇒ Object
Returns the value of attribute resettable.
Class Method Summary collapse
- .all ⇒ Object
-
.all_active_first ⇒ Object
Return experiments without a winner (considered “active”) first.
- .find(name) ⇒ Object
- .find_or_create(label, *alternatives) ⇒ Object
Instance Method Summary collapse
- #==(obj) ⇒ Object
- #[](name) ⇒ Object
- #control ⇒ Object
- #delete ⇒ Object
- #delete_goals ⇒ Object
- #finished_key ⇒ Object
- #goals_key ⇒ Object
- #increment_version ⇒ Object
-
#initialize(name, options = {}) ⇒ Experiment
constructor
A new instance of Experiment.
- #key ⇒ Object
- #load_from_redis ⇒ Object
- #new_record? ⇒ Boolean
- #next_alternative ⇒ Object
- #participant_count ⇒ Object
- #random_alternative ⇒ Object
- #reset ⇒ Object
- #reset_winner ⇒ Object
- #resettable? ⇒ Boolean
- #save ⇒ Object
- #start ⇒ Object
- #start_time ⇒ Object
- #validate! ⇒ Object
- #version ⇒ Object
- #winner ⇒ Object
- #winner=(winner_name) ⇒ Object
Constructor Details
#initialize(name, options = {}) ⇒ Experiment
Returns a new instance of Experiment.
9 10 11 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 37 38 |
# File 'lib/split/experiment.rb', line 9 def initialize(name, = {}) = { :resettable => true, }.merge() @name = name.to_s alts = [:alternatives] || [] if alts.length == 1 if alts[0].is_a? Hash alts = alts[0].map{|k,v| {k => v} } end end if alts.empty? exp_config = Split.configuration.experiment_for(name) if exp_config alts = load_alternatives_from_configuration [:goals] = load_goals_from_configuration [:resettable] = exp_config[:resettable] [:algorithm] = exp_config[:algorithm] end end self.alternatives = alts self.goals = [:goals] self.algorithm = [:algorithm] self.resettable = [:resettable] end |
Instance Attribute Details
#algorithm ⇒ Object
117 118 119 |
# File 'lib/split/experiment.rb', line 117 def algorithm @algorithm ||= Split.configuration.algorithm end |
#alternatives ⇒ Object
Returns the value of attribute alternatives.
7 8 9 |
# File 'lib/split/experiment.rb', line 7 def alternatives @alternatives end |
#goals ⇒ Object
Returns the value of attribute goals.
6 7 8 |
# File 'lib/split/experiment.rb', line 6 def goals @goals end |
#name ⇒ Object
Returns the value of attribute name.
3 4 5 |
# File 'lib/split/experiment.rb', line 3 def name @name end |
#resettable ⇒ Object
Returns the value of attribute resettable.
5 6 7 |
# File 'lib/split/experiment.rb', line 5 def resettable @resettable end |
Class Method Details
.all ⇒ Object
40 41 42 |
# File 'lib/split/experiment.rb', line 40 def self.all Split.redis.smembers(:experiments).map {|e| find(e)} end |
.all_active_first ⇒ Object
Return experiments without a winner (considered “active”) first
45 46 47 |
# File 'lib/split/experiment.rb', line 45 def self.all_active_first all.sort_by{|e| e.winner ? 1 : 0} # sort_by hack since true/false isn't sortable end |
.find(name) ⇒ Object
49 50 51 52 53 54 55 56 57 |
# File 'lib/split/experiment.rb', line 49 def self.find(name) if Split.redis.exists(name) obj = self.new name obj.load_from_redis else obj = nil end obj end |
.find_or_create(label, *alternatives) ⇒ Object
59 60 61 62 63 64 65 66 |
# File 'lib/split/experiment.rb', line 59 def self.find_or_create(label, *alternatives) experiment_name_with_version, goals = normalize_experiment(label) name = experiment_name_with_version.to_s.split(':')[0] exp = self.new name, :alternatives => alternatives, :goals => goals exp.save exp end |
Instance Method Details
#==(obj) ⇒ Object
109 110 111 |
# File 'lib/split/experiment.rb', line 109 def ==(obj) self.name == obj.name end |
#[](name) ⇒ Object
113 114 115 |
# File 'lib/split/experiment.rb', line 113 def [](name) alternatives.find{|a| a.name == name} end |
#control ⇒ Object
155 156 157 |
# File 'lib/split/experiment.rb', line 155 def control alternatives.first end |
#delete ⇒ Object
226 227 228 229 230 231 232 233 234 |
# File 'lib/split/experiment.rb', line 226 def delete alternatives.each(&:delete) reset_winner Split.redis.srem(:experiments, name) Split.redis.del(name) delete_goals Split.configuration.on_experiment_delete.call(self) increment_version end |
#delete_goals ⇒ Object
236 237 238 |
# File 'lib/split/experiment.rb', line 236 def delete_goals Split.redis.del(goals_key) end |
#finished_key ⇒ Object
211 212 213 |
# File 'lib/split/experiment.rb', line 211 def finished_key "#{key}:finished" end |
#goals_key ⇒ Object
207 208 209 |
# File 'lib/split/experiment.rb', line 207 def goals_key "#{name}:goals" end |
#increment_version ⇒ Object
195 196 197 |
# File 'lib/split/experiment.rb', line 195 def increment_version @version = Split.redis.incr("#{name}:version") end |
#key ⇒ Object
199 200 201 202 203 204 205 |
# File 'lib/split/experiment.rb', line 199 def key if version.to_i > 0 "#{name}:#{version}" else name end end |
#load_from_redis ⇒ Object
240 241 242 243 244 245 246 |
# File 'lib/split/experiment.rb', line 240 def load_from_redis exp_config = Split.redis.hgetall(experiment_config_key) self.resettable = exp_config['resettable'] self.algorithm = exp_config['algorithm'] self.alternatives = load_alternatives_from_redis self.goals = load_goals_from_redis end |
#new_record? ⇒ Boolean
105 106 107 |
# File 'lib/split/experiment.rb', line 105 def new_record? !Split.redis.exists(name) end |
#next_alternative ⇒ Object
179 180 181 |
# File 'lib/split/experiment.rb', line 179 def next_alternative winner || random_alternative end |
#participant_count ⇒ Object
151 152 153 |
# File 'lib/split/experiment.rb', line 151 def participant_count alternatives.inject(0){|sum,a| sum + a.participant_count} end |
#random_alternative ⇒ Object
183 184 185 186 187 188 189 |
# File 'lib/split/experiment.rb', line 183 def random_alternative if alternatives.length > 1 algorithm.choose_alternative(self) else alternatives.first end end |
#reset ⇒ Object
219 220 221 222 223 224 |
# File 'lib/split/experiment.rb', line 219 def reset alternatives.each(&:reset) reset_winner Split.configuration.on_experiment_reset.call(self) increment_version end |
#reset_winner ⇒ Object
159 160 161 |
# File 'lib/split/experiment.rb', line 159 def reset_winner Split.redis.hdel(:experiment_winner, name) end |
#resettable? ⇒ Boolean
215 216 217 |
# File 'lib/split/experiment.rb', line 215 def resettable? resettable end |
#save ⇒ Object
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 |
# File 'lib/split/experiment.rb', line 68 def save validate! if new_record? Split.redis.sadd(:experiments, name) start unless Split.configuration.start_manually @alternatives.reverse.each {|a| Split.redis.lpush(name, a.name)} @goals.reverse.each {|a| Split.redis.lpush(goals_key, a)} unless @goals.nil? else existing_alternatives = load_alternatives_from_redis existing_goals = load_goals_from_redis unless existing_alternatives == @alternatives.map(&:name) && existing_goals == @goals reset @alternatives.each(&:delete) delete_goals Split.redis.del(@name) @alternatives.reverse.each {|a| Split.redis.lpush(name, a.name)} @goals.reverse.each {|a| Split.redis.lpush(goals_key, a)} unless @goals.nil? end end Split.redis.hset(experiment_config_key, :resettable, resettable) Split.redis.hset(experiment_config_key, :algorithm, algorithm.to_s) self end |
#start ⇒ Object
163 164 165 |
# File 'lib/split/experiment.rb', line 163 def start Split.redis.hset(:experiment_start_times, @name, Time.now.to_i) end |
#start_time ⇒ Object
167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/split/experiment.rb', line 167 def start_time t = Split.redis.hget(:experiment_start_times, @name) if t # Check if stored time is an integer if t =~ /^[-+]?[0-9]+$/ t = Time.at(t.to_i) else t = Time.parse(t) end end end |
#validate! ⇒ Object
95 96 97 98 99 100 101 102 103 |
# File 'lib/split/experiment.rb', line 95 def validate! if @alternatives.empty? && Split.configuration.experiment_for(@name).nil? raise ExperimentNotFound.new("Experiment #{@name} not found") end @alternatives.each {|a| a.validate! } unless @goals.nil? || goals.kind_of?(Array) raise ArgumentError, 'Goals must be an array' end end |
#version ⇒ Object
191 192 193 |
# File 'lib/split/experiment.rb', line 191 def version @version ||= (Split.redis.get("#{name.to_s}:version").to_i || 0) end |
#winner ⇒ Object
139 140 141 142 143 144 145 |
# File 'lib/split/experiment.rb', line 139 def winner if w = Split.redis.hget(:experiment_winner, name) Split::Alternative.new(w, name) else nil end end |
#winner=(winner_name) ⇒ Object
147 148 149 |
# File 'lib/split/experiment.rb', line 147 def winner=(winner_name) Split.redis.hset(:experiment_winner, name, winner_name.to_s) end |