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