lib/vanity/experiment/ab_test.rb in vanity-1.8.1 vs lib/vanity/experiment/ab_test.rb in vanity-1.8.2
- old
+ new
@@ -13,11 +13,11 @@
@value = value
end
# Alternative id, only unique for this experiment.
attr_reader :id
-
+
# Alternative name (option A, option B, etc).
attr_reader :name
# Alternative value.
attr_reader :value
@@ -46,11 +46,11 @@
# Z-score for this alternative, related to 2nd-best performing alternative. Populated by AbTest#score.
attr_accessor :z_score
# Probability derived from z-score. Populated by AbTest#score.
attr_accessor :probability
-
+
# Difference from least performing alternative. Populated by AbTest#score.
attr_accessor :difference
# Conversion rate calculated as converted/participants
def conversion_rate
@@ -62,11 +62,11 @@
def measure
conversion_rate
end
def <=>(other)
- measure <=> other.measure
+ measure <=> other.measure
end
def ==(other)
other && id == other.id && experiment == other.experiment
end
@@ -99,23 +99,23 @@
probability = AbTest::Z_TO_PROBABILITY.find { |z,p| score >= z }
probability ? probability.last : 0
end
def friendly_name
- "A/B Test"
+ "A/B Test"
end
end
def initialize(*args)
super
end
# -- Metric --
-
- # Tells A/B test which metric we're measuring, or returns metric in use.
+
+ # Tells A/B test which metric we're measuring, or returns metric in use.
#
# @example Define A/B test against coolness metric
# ab_test "Background color" do
# metrics :coolness
# alternatives "red", "blue", "orange"
@@ -125,11 +125,10 @@
def metrics(*args)
@metrics = args.map { |id| @playground.metric(id) } unless args.empty?
@metrics
end
-
# -- Alternatives --
# Call this method once to set alternative values for this experiment
# (requires at least two values). Call without arguments to obtain
# current list of alternatives.
@@ -137,11 +136,11 @@
# @example Define A/B test with three alternatives
# ab_test "Background color" do
# metrics :coolness
# alternatives "red", "blue", "orange"
# end
- #
+ #
# @example Find out which alternatives this test uses
# alts = experiment(:background_color).alternatives
# puts "#{alts.count} alternatives, with the colors: #{alts.map(&:value).join(", ")}"
def alternatives(*args)
@alternatives = args.empty? ? [true, false] : args.clone
@@ -173,11 +172,11 @@
# default pair of alternatives, so just syntactic sugar for those who love
# being explicit.
#
# @example
# ab_test "More bacon" do
- # metrics :yummyness
+ # metrics :yummyness
# false_true
# end
#
def false_true
alternatives false, true
@@ -196,17 +195,24 @@
# color = experiment(:which_blue).choose
def choose
if @playground.collecting?
if active?
identity = identity()
- index = connection.ab_showing(@id, identity)
- unless index
- index = alternative_for(identity)
- if !@playground.using_js?
- connection.ab_add_participant @id, index, identity
- check_completion!
- end
+ index = connection.ab_showing(@id, identity)
+ unless index
+ index = alternative_for(identity)
+ if !@playground.using_js?
+ # if we have an on_assignment block, call it on new assignments
+ if @on_assignment_block
+ assignment = alternatives[index.to_i]
+ if !connection.ab_seen @id, identity, assignment
+ @on_assignment_block.call(Vanity.context, identity, assignment, self)
+ end
+ end
+ connection.ab_add_participant @id, index, identity
+ check_completion!
+ end
end
else
index = connection.ab_get_outcome(@id) || alternative_for(identity)
end
else
@@ -223,13 +229,13 @@
# (e.g. choosing alternative from HTTP query parameter).
def fingerprint(alternative)
Digest::MD5.hexdigest("#{id}:#{alternative.id}")[-10,10]
end
-
+
# -- Testing --
-
+
# Forces this experiment to use a particular alternative. You'll want to
# use this from your test cases to test for the different alternatives.
#
# @example Setup test to red button
# setup do
@@ -254,12 +260,12 @@
unless index == connection.ab_showing(@id, identity)
connection.ab_add_participant @id, index, identity
check_completion!
end
raise ArgumentError, "No alternative #{value.inspect} for #{name}" unless index
- if (connection.ab_showing(@id, identity) && connection.ab_showing(@id, identity) != index) ||
- alternative_for(identity) != index
+ if (connection.ab_showing(@id, identity) && connection.ab_showing(@id, identity) != index) ||
+ alternative_for(identity) != index
connection.ab_show @id, identity, index
end
end
else
@showing ||= {}
@@ -292,11 +298,11 @@
# Alternatives returned by this method are populated with the following
# attributes:
# [:z_score] Z-score (relative to the base alternative).
# [:probability] Probability (z-score mapped to 0, 90, 95, 99 or 99.9%).
# [:difference] Difference from the least performant altenative.
- #
+ #
# The choice alternative is set only if its probability is higher or
# equal to the specified probability (default is 90%).
def score(probability = 90)
alts = alternatives
# sort by conversion rate to find second best and 2nd best
@@ -403,21 +409,21 @@
super
if @outcome_is
begin
result = @outcome_is.call
outcome = result.id if Alternative === result && result.experiment == self
- rescue
+ rescue
warn "Error in AbTest#complete!: #{$!}"
end
else
best = score.best
outcome = best.id if best
end
# TODO: logging
connection.ab_set_outcome @id, outcome || 0
end
-
+
# -- Store/validate --
def destroy
connection.destroy_experiment @id
super