class Laboratory::Experiment
Attributes
_original_algorithm[R]
_original_id[R]
algorithm[RW]
changelog[R]
id[RW]
variants[R]
Public Class Methods
all()
click to toggle source
# File lib/laboratory/experiment.rb, line 58 def self.all Laboratory.adapter.read_all end
clear_overrides!()
click to toggle source
# File lib/laboratory/experiment.rb, line 12 def clear_overrides! Thread.current[:experiment_overrides] = {} end
create(id:, variants:, algorithm: Algorithms::Random)
click to toggle source
# File lib/laboratory/experiment.rb, line 62 def self.create(id:, variants:, algorithm: Algorithms::Random) raise ClashingExperimentIdError if find(id) experiment = Experiment.new( id: id, variants: variants, algorithm: algorithm ) experiment.save experiment end
find(id)
click to toggle source
# File lib/laboratory/experiment.rb, line 75 def self.find(id) Laboratory.adapter.read(id) end
find_or_create(id:, variants:, algorithm: Algorithms::Random)
click to toggle source
# File lib/laboratory/experiment.rb, line 79 def self.find_or_create(id:, variants:, algorithm: Algorithms::Random) find(id) || create(id: id, variants: variants, algorithm: algorithm) end
new(id:, variants:, algorithm: Algorithms::Random, changelog: [])
click to toggle source
# File lib/laboratory/experiment.rb, line 32 def initialize(id:, variants:, algorithm: Algorithms::Random, changelog: []) # rubocop:disable Metrics/MethodLength @id = id @algorithm = algorithm @changelog = changelog # We want to allow users to input Variant objects, or simple hashes. # This also helps when decoding from adapters @variants = if variants.all? { |variant| variant.instance_of?(Experiment::Variant) } variants elsif variants.all? { |variant| variant.instance_of?(Hash) } variants.map do |variant| Variant.new( id: variant[:id], percentage: variant[:percentage], participant_ids: [], events: [] ) end end @_original_id = id @_original_algorithm = algorithm end
override!(overrides)
click to toggle source
# File lib/laboratory/experiment.rb, line 8 def override!(overrides) Thread.current[:experiment_overrides] = overrides end
overrides()
click to toggle source
# File lib/laboratory/experiment.rb, line 4 def overrides Thread.current[:experiment_overrides] || {} end
Public Instance Methods
analysis_summary_for(event_id)
click to toggle source
# File lib/laboratory/experiment.rb, line 156 def analysis_summary_for(event_id) Experiment::AnalysisSummary.new(self, event_id) end
assign_to_variant(variant_id, user: Laboratory.config.current_user)
click to toggle source
# File lib/laboratory/experiment.rb, line 119 def assign_to_variant(variant_id, user: Laboratory.config.current_user) variants.each do |variant| variant.participant_ids.delete(user.id) end variant = variants.find { |s| s.id == variant_id } variant.add_participant(user) Laboratory.config.on_assignment_to_variant&.call(self, variant, user) save variant end
delete()
click to toggle source
# File lib/laboratory/experiment.rb, line 83 def delete Laboratory.adapter.delete(id) nil end
record_event!(event_id, user: Laboratory.config.current_user)
click to toggle source
# File lib/laboratory/experiment.rb, line 133 def record_event!(event_id, user: Laboratory.config.current_user) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength variant = variants.find { |s| s.participant_ids.include?(user.id) } raise UserNotInExperimentError unless variant maybe_event = variant.events.find { |event| event.id == event_id } event = if !maybe_event.nil? maybe_event else e = Event.new(id: event_id) variant.events << e e end event_recording = Event::Recording.new(user_id: user.id) event.event_recordings << event_recording Laboratory.config.on_event_recorded&.call(self, variant, user, event) save event_recording end
reset()
click to toggle source
# File lib/laboratory/experiment.rb, line 88 def reset @variants = variants.map do |variant| Variant.new( id: variant.id, percentage: variant.percentage, participant_ids: [], events: [] ) end save end
save()
click to toggle source
# File lib/laboratory/experiment.rb, line 160 def save raise errors.first unless valid? unless changeset.empty? changelog_item = Laboratory::Experiment::ChangelogItem.new( changes: changeset, timestamp: Time.now, actor: Laboratory.config.actor ) @changelog << changelog_item end Laboratory.adapter.write(self) end
valid?()
click to toggle source
# File lib/laboratory/experiment.rb, line 175 def valid? # rubocop:disable Metrics/AbcSize valid_variants = variants.all? do |variant| !variant.id.nil? && !variant.percentage.nil? end valid_percentage_amounts = variants.map(&:percentage).sum == 100 !id.nil? && !algorithm.nil? && valid_variants && valid_percentage_amounts end
variant(user: Laboratory.config.current_user)
click to toggle source
# File lib/laboratory/experiment.rb, line 100 def variant(user: Laboratory.config.current_user) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength return variant_overridden_with if overridden? selected_variant = variants.find do |variant| variant.participant_ids.include?(user.id) end return selected_variant unless selected_variant.nil? variant = algorithm.pick!(variants) variant.add_participant(user) Laboratory.config.on_assignment_to_variant&.call(self, variant, user) save variant end
Private Instance Methods
changeset()
click to toggle source
# File lib/laboratory/experiment.rb, line 197 def changeset # rubocop:disable Metrics/AbcSize, Metrics/MethodLength set = {} set[:id] = [_original_id, id] if _original_id != id if _original_algorithm != algorithm set[:algorithm] = [_original_algorithm, algorithm] end variants_changeset = variants.map do |variant| { variant.id => variant.changeset } end variants_changeset.reject! do |change| change.values.all?(&:empty?) end set[:variants] = variants_changeset unless variants_changeset.empty? set end
errors()
click to toggle source
# File lib/laboratory/experiment.rb, line 218 def errors # rubocop:disable Metrics/AbcSize, Metrics/MethodLength errors = [] missing_variant_ids = variants.any? do |variant| variant.id.nil? end missing_variant_percentages = variants.any? do |variant| variant.percentage.nil? end incorrect_percentage_total = variants.map(&:percentage).sum != 100 errors << MissingExperimentIdError if id.nil? errors << MissingExperimentAlgorithmError if algorithm.nil? errors << MissingExperimentVariantIdError if missing_variant_ids errors << MissingExperimentVariantPercentageError if missing_variant_percentages # rubocop:disable Layout/LineLength errors << IncorrectPercentageTotalError if incorrect_percentage_total errors end
overridden?()
click to toggle source
# File lib/laboratory/experiment.rb, line 189 def overridden? self.class.overrides.key?(id) && !variant_overridden_with.nil? end
variant_overridden_with()
click to toggle source
# File lib/laboratory/experiment.rb, line 193 def variant_overridden_with variants.find { |v| v.id == self.class.overrides[id] } end