lib/field_test/experiment.rb in field_test-0.2.4 vs lib/field_test/experiment.rb in field_test-0.3.0

- old
+ new

@@ -11,10 +11,11 @@ @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] @goals = attributes[:goals] || ["conversion"] + @goals_defined = !attributes[:goals].nil? @use_events = attributes[:use_events] end def variant(participants, options = {}) return winner if winner @@ -28,29 +29,22 @@ membership.variant = options[:variant] else membership.variant ||= weighted_variant end + participant = participants.first + # upgrade to preferred participant - membership.participant = participants.first + membership.participant = participant.participant if membership.respond_to?(:participant=) + membership.participant_type = participant.type if membership.respond_to?(:participant_type=) + membership.participant_id = participant.id if membership.respond_to?(:participant_id=) if membership.changed? begin membership.save! - - # log it! - info = { - experiment: id, - variant: membership.variant, - participant: membership.participant - }.merge(options.slice(:ip, :user_agent)) - - # sorta logfmt :) - info = info.map { |k, v| v = "\"#{v}\"" if k == :user_agent; "#{k}=#{v}" }.join(" ") - Rails.logger.info "[field test] #{info}" rescue ActiveRecord::RecordNotUnique - membership = memberships.find_by(participant: participants.first) + membership = memberships.find_by(participant.where_values) end end membership.try(:variant) || variants.first end @@ -98,20 +92,33 @@ relation = memberships.group(:variant) relation = relation.where("field_test_memberships.created_at >= ?", started_at) if started_at relation = relation.where("field_test_memberships.created_at <= ?", ended_at) if ended_at - if use_events? + if use_events? && @goals_defined data = {} - sql = - relation.joins("LEFT JOIN field_test_events ON field_test_events.field_test_membership_id = field_test_memberships.id") - .select("variant, COUNT(DISTINCT participant) AS participated, COUNT(DISTINCT field_test_membership_id) AS converted") - .where(field_test_events: {name: [goal, nil]}) - FieldTest::Membership.connection.select_all(sql).each do |row| - data[[row["variant"], true]] = row["converted"].to_i - data[[row["variant"], false]] = row["participated"].to_i - row["converted"].to_i + participated = relation.count + + adapter_name = relation.connection.adapter_name + column = + if FieldTest.legacy_participants + :participant + elsif adapter_name =~ /postg/i # postgres + "(participant_type, participant_id)" + elsif adapter_name =~ /mysql/i + "participant_type, participant_id" + else + # not perfect, but it'll do + "COALESCE(participant_type, '') || ':' || participant_id" + end + + converted = events.merge(relation).where(field_test_events: {name: goal}).distinct.count(column) + + (participated.keys + converted.keys).uniq.each do |variant| + data[[variant, true]] = converted[variant].to_i + data[[variant, false]] = participated[variant].to_i - converted[variant].to_i end else data = relation.group(:converted).count end @@ -123,10 +130,11 @@ participated: participated, converted: converted, conversion_rate: participated > 0 ? converted.to_f / participated : nil } end + case variants.size when 1, 2, 3 total = 0.0 (variants.size - 1).times do |i| @@ -187,34 +195,39 @@ end end private - def check_participants(participants) - raise FieldTest::UnknownParticipant, "Use the :participant option to specify a participant" if participants.empty? - end + 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 + # TODO fetch in single query + def membership_for(participants) + membership = nil + participants.each do |participant| + membership = self.memberships.find_by(participant.where_values) + break if membership end + membership + 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 + 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 - def cache_fetch(key) - if FieldTest.cache - Rails.cache.fetch(key.join("/")) { yield } - else - yield - end + def cache_fetch(key) + if FieldTest.cache + Rails.cache.fetch(key.join("/")) { yield } + else + yield end + end end end