Class: TrailGuide::Experiment
- Inherits:
-
Object
- Object
- TrailGuide::Experiment
- Defined in:
- lib/trail_guide/experiment.rb
Instance Attribute Summary collapse
-
#participant ⇒ Object
readonly
Returns the value of attribute participant.
Class Method Summary collapse
- .as_json(opts = {}) ⇒ Object
- .configuration ⇒ Object
- .configure(*args, &block) ⇒ Object
- .declare_winner!(variant) ⇒ Object
- .delete! ⇒ Object
- .experiment_name ⇒ Object
- .inherited(child) ⇒ Object
- .persisted? ⇒ Boolean
- .reset! ⇒ Object
- .resettable? ⇒ Boolean
- .run_callbacks(hook, *args) ⇒ Object
- .save! ⇒ Object
- .start! ⇒ Object
- .started? ⇒ Boolean
- .started_at ⇒ Object
- .stop! ⇒ Object
- .storage_key ⇒ Object
- .variants(include_control = true) ⇒ Object
- .winner ⇒ Object
- .winner? ⇒ Boolean
Instance Method Summary collapse
- #algorithm ⇒ Object
- #choose!(override: nil, metadata: nil, **opts) ⇒ Object
- #choose_variant!(override: nil, excluded: false, metadata: nil) ⇒ Object
- #convert!(checkpoint = nil, metadata: nil) ⇒ Object
- #converted?(checkpoint = nil) ⇒ Boolean
-
#initialize(participant) ⇒ Experiment
constructor
A new instance of Experiment.
- #participating? ⇒ Boolean
- #run_callbacks(hook, *args) ⇒ Object
Constructor Details
#initialize(participant) ⇒ Experiment
Returns a new instance of Experiment.
140 141 142 |
# File 'lib/trail_guide/experiment.rb', line 140 def initialize(participant) @participant = participant end |
Instance Attribute Details
#participant ⇒ Object (readonly)
Returns the value of attribute participant.
134 135 136 |
# File 'lib/trail_guide/experiment.rb', line 134 def participant @participant end |
Class Method Details
.as_json(opts = {}) ⇒ Object
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/trail_guide/experiment.rb', line 110 def as_json(opts={}) { experiment_name => { configuration: { metric: metric, algorithm: algorithm.name, variants: variants.as_json, goals: goals.as_json, resettable: resettable?, allow_multiple_conversions: allow_multiple_conversions?, allow_multiple_goals: allow_multiple_goals? }, statistics: { # TODO expand on this for variants/goals participants: variants.sum(&:participants), converted: variants.sum(&:converted) } } } end |
.configuration ⇒ Object
14 15 16 |
# File 'lib/trail_guide/experiment.rb', line 14 def configuration @configuration ||= ExperimentConfig.new(self) end |
.configure(*args, &block) ⇒ Object
18 19 20 |
# File 'lib/trail_guide/experiment.rb', line 18 def configure(*args, &block) configuration.configure(*args, &block) end |
.declare_winner!(variant) ⇒ Object
74 75 76 77 |
# File 'lib/trail_guide/experiment.rb', line 74 def declare_winner!(variant) variant = variant.name if variant.is_a?(Variant) TrailGuide.redis.hset(storage_key, 'winner', variant.to_s.underscore) end |
.delete! ⇒ Object
97 98 99 100 101 102 |
# File 'lib/trail_guide/experiment.rb', line 97 def delete! variants.each(&:delete!) deleted = TrailGuide.redis.del(storage_key) run_callbacks(:on_delete) deleted end |
.experiment_name ⇒ Object
26 27 28 |
# File 'lib/trail_guide/experiment.rb', line 26 def experiment_name configuration.name end |
.inherited(child) ⇒ Object
10 11 12 |
# File 'lib/trail_guide/experiment.rb', line 10 def inherited(child) TrailGuide::Catalog.register(child) end |
.persisted? ⇒ Boolean
88 89 90 |
# File 'lib/trail_guide/experiment.rb', line 88 def persisted? TrailGuide.redis.exists(storage_key) end |
.reset! ⇒ Object
104 105 106 107 108 |
# File 'lib/trail_guide/experiment.rb', line 104 def reset! reset = (delete! && save!) run_callbacks(:on_reset) reset end |
.resettable? ⇒ Boolean
22 23 24 |
# File 'lib/trail_guide/experiment.rb', line 22 def resettable? !configuration.reset_manually end |
.run_callbacks(hook, *args) ⇒ Object
38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/trail_guide/experiment.rb', line 38 def run_callbacks(hook, *args) return unless callbacks[hook] args.unshift(self) callbacks[hook].each do |callback| if callback.respond_to?(:call) callback.call(*args) else send(callback, *args) end end end |
.save! ⇒ Object
92 93 94 95 |
# File 'lib/trail_guide/experiment.rb', line 92 def save! variants.each(&:save!) TrailGuide.redis.hsetnx(storage_key, 'name', experiment_name) end |
.start! ⇒ Object
50 51 52 53 54 55 56 |
# File 'lib/trail_guide/experiment.rb', line 50 def start! return false if started? save! unless persisted? started = TrailGuide.redis.hset(storage_key, 'started_at', Time.now.to_i) run_callbacks(:on_start) started end |
.started? ⇒ Boolean
70 71 72 |
# File 'lib/trail_guide/experiment.rb', line 70 def started? !!started_at end |
.started_at ⇒ Object
65 66 67 68 |
# File 'lib/trail_guide/experiment.rb', line 65 def started_at started = TrailGuide.redis.hget(storage_key, 'started_at') return Time.at(started.to_i) if started end |
.stop! ⇒ Object
58 59 60 61 62 63 |
# File 'lib/trail_guide/experiment.rb', line 58 def stop! return false unless started? stopped = TrailGuide.redis.hdel(storage_key, 'started_at') run_callbacks(:on_stop) stopped end |
.storage_key ⇒ Object
129 130 131 |
# File 'lib/trail_guide/experiment.rb', line 129 def storage_key experiment_name end |
.variants(include_control = true) ⇒ Object
30 31 32 33 34 35 36 |
# File 'lib/trail_guide/experiment.rb', line 30 def variants(include_control=true) if include_control configuration.variants else configuration.variants.select { |var| !var.control? } end end |
.winner ⇒ Object
79 80 81 82 |
# File 'lib/trail_guide/experiment.rb', line 79 def winner winner = TrailGuide.redis.hget(storage_key, 'winner') return variants.find { |var| var == winner } if winner end |
.winner? ⇒ Boolean
84 85 86 |
# File 'lib/trail_guide/experiment.rb', line 84 def winner? TrailGuide.redis.hexists(storage_key, 'winner') end |
Instance Method Details
#algorithm ⇒ Object
144 145 146 |
# File 'lib/trail_guide/experiment.rb', line 144 def algorithm @algorithm ||= self.class.algorithm.new(self) end |
#choose!(override: nil, metadata: nil, **opts) ⇒ Object
148 149 150 151 152 153 154 155 |
# File 'lib/trail_guide/experiment.rb', line 148 def choose!(override: nil, metadata: nil, **opts) return control if TrailGuide.configuration.disabled variant = choose_variant!(override: override, metadata: , **opts) participant.participating!(variant) unless override.present? && !configuration.store_override run_callbacks(:on_use, variant, ) variant end |
#choose_variant!(override: nil, excluded: false, metadata: nil) ⇒ Object
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/trail_guide/experiment.rb', line 157 def choose_variant!(override: nil, excluded: false, metadata: nil) return control if TrailGuide.configuration.disabled if override.present? variant = variants.find { |var| var == override } return variant unless configuration.track_override && started? else return winner if winner? return control if excluded return control if !started? && configuration.start_manually start! unless started? return variants.find { |var| var == participant[storage_key] } if participating? return control unless TrailGuide.configuration.allow_multiple_experiments == true || !participant.participating_in_active_experiments?(TrailGuide.configuration.allow_multiple_experiments == false) variant = algorithm.choose!(metadata: ) end variant.increment_participation! run_callbacks(:on_choose, variant, ) variant end |
#convert!(checkpoint = nil, metadata: nil) ⇒ Object
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/trail_guide/experiment.rb', line 178 def convert!(checkpoint=nil, metadata: nil) return false unless participating? raise InvalidGoalError, "Invalid goal checkpoint `#{checkpoint}` for `#{experiment_name}`." unless checkpoint.present? || funnels.empty? raise InvalidGoalError, "Invalid goal checkpoint `#{checkpoint}` for `#{experiment_name}`." unless checkpoint.nil? || funnels.any? { |funnel| funnel == checkpoint.to_s.underscore.to_sym } # TODO eventually allow progressing through funnel checkpoints towards goals if converted?(checkpoint) return false unless allow_multiple_conversions? elsif converted? return false unless allow_multiple_goals? end variant = variants.find { |var| var == participant[storage_key] } # TODO eventually only reset if we're at the final goal in a funnel participant.converted!(variant, checkpoint, reset: resettable?) variant.increment_conversion!(checkpoint) run_callbacks(:on_convert, variant, checkpoint, ) variant end |
#converted?(checkpoint = nil) ⇒ Boolean
201 202 203 |
# File 'lib/trail_guide/experiment.rb', line 201 def converted?(checkpoint=nil) participant.converted?(self, checkpoint) end |
#participating? ⇒ Boolean
197 198 199 |
# File 'lib/trail_guide/experiment.rb', line 197 def participating? participant.participating?(self) end |
#run_callbacks(hook, *args) ⇒ Object
205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/trail_guide/experiment.rb', line 205 def run_callbacks(hook, *args) return unless callbacks[hook] args.unshift(self) callbacks[hook].each do |callback| if callback.respond_to?(:call) callback.call(*args) else send(callback, *args) end end end |