# frozen_string_literal: true module Split class Trial attr_accessor :goals attr_accessor :experiment attr_writer :metadata def initialize(attrs = {}) self.experiment = attrs.delete(:experiment) self.alternative = attrs.delete(:alternative) self.metadata = attrs.delete(:metadata) self.goals = attrs.delete(:goals) || [] @user = attrs.delete(:user) @options = attrs @alternative_chosen = false end def metadata @metadata ||= experiment.metadata[alternative.name] if experiment.metadata end def alternative @alternative ||= if @experiment.has_winner? @experiment.winner end end def alternative=(alternative) @alternative = if alternative.kind_of?(Split::Alternative) alternative else @experiment.alternatives.find{|a| a.name == alternative } end end def complete!(context = nil) if alternative if Array(goals).empty? alternative.increment_completion else Array(goals).each {|g| alternative.increment_completion(g) } end run_callback context, Split.configuration.on_trial_complete end end # Choose an alternative, add a participant, and save the alternative choice on the user. This # method is guaranteed to only run once, and will skip the alternative choosing process if run # a second time. def choose!(context = nil) @user.cleanup_old_experiments! # Only run the process once return alternative if @alternative_chosen new_participant = @user[@experiment.key].nil? if override_is_alternative? self.alternative = @options[:override] if should_store_alternative? && !@user[@experiment.key] self.alternative.increment_participation end elsif @options[:disabled] || Split.configuration.disabled? self.alternative = @experiment.control elsif @experiment.has_winner? self.alternative = @experiment.winner else cleanup_old_versions if exclude_user? self.alternative = @experiment.control else self.alternative = @user[@experiment.key] if alternative.nil? if @experiment.cohorting_disabled? self.alternative = @experiment.control else self.alternative = @experiment.next_alternative # Increment the number of participants since we are actually choosing a new alternative self.alternative.increment_participation run_callback context, Split.configuration.on_trial_choose end end end end new_participant_and_cohorting_disabled = new_participant && @experiment.cohorting_disabled? @user[@experiment.key] = alternative.name unless @experiment.has_winner? || !should_store_alternative? || new_participant_and_cohorting_disabled @alternative_chosen = true run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled? || new_participant_and_cohorting_disabled alternative end private def run_callback(context, callback_name) context.send(callback_name, self) if callback_name && context.respond_to?(callback_name, true) end def override_is_alternative? @experiment.alternatives.map(&:name).include?(@options[:override]) end def should_store_alternative? if @options[:override] || @options[:disabled] Split.configuration.store_override else !exclude_user? end end def cleanup_old_versions if @experiment.version > 0 @user.cleanup_old_versions!(@experiment) end end def exclude_user? @options[:exclude] || @experiment.start_time.nil? || @user.max_experiments_reached?(@experiment.key) end end end