lib/field_test/experiment.rb in field_test-0.1.1 vs lib/field_test/experiment.rb in field_test-0.1.2
- old
+ new
@@ -1,30 +1,32 @@
module FieldTest
class Experiment
- attr_reader :id, :name, :variants, :winner, :started_at, :ended_at
+ attr_reader :id, :name, :variants, :weights, :winner, :started_at, :ended_at
def initialize(attributes)
attributes = attributes.symbolize_keys
@id = attributes[:id]
@name = attributes[:name] || @id.to_s.titleize
@variants = attributes[:variants]
+ @weights = @variants.size.times.map { |i| attributes[:weights].to_a[i] || 1 }
@winner = attributes[:winner]
@started_at = Time.zone.parse(attributes[:started_at].to_s) if attributes[:started_at]
@ended_at = Time.zone.parse(attributes[:ended_at].to_s) if attributes[:ended_at]
end
def variant(participants, options = {})
return winner if winner
return variants.first if options[:exclude]
participants = FieldTest::Participant.standardize(participants)
+ check_participants(participants)
membership = membership_for(participants) || FieldTest::Membership.new(experiment: id)
if options[:variant] && variants.include?(options[:variant])
membership.variant = options[:variant]
else
- membership.variant ||= variants.sample
+ membership.variant ||= weighted_variant
end
# upgrade to preferred participant
membership.participant = participants.first
@@ -39,10 +41,11 @@
membership.try(:variant) || variants.first
end
def convert(participants)
participants = FieldTest::Participant.standardize(participants)
+ check_participants(participants)
membership = membership_for(participants)
if membership
membership.converted = true
membership.save! if membership.changed?
@@ -115,12 +118,27 @@
end
end
private
+ def check_participants(participants)
+ raise FieldTest::UnknownParticipant, "Use the :participant option to specify a participant" if participants.empty?
+ end
+
def membership_for(participants)
memberships = self.memberships.where(participant: participants).index_by(&:participant)
participants.map { |part| memberships[part] }.compact.first
+ end
+
+ def weighted_variant
+ total = weights.sum.to_f
+ pick = rand
+ n = 0
+ weights.map { |w| w / total }.each_with_index do |w, i|
+ n += w
+ return variants[i] if n >= pick
+ end
+ variants.last
end
# formula from
# http://www.evanmiller.org/bayesian-ab-testing.html
def prob_b_beats_a(alpha_a, beta_a, alpha_b, beta_b)