Class: TrailGuide::Experiments::Config

Inherits:
Canfig::Config
  • Object
show all
Defined in:
lib/trail_guide/experiments/config.rb

Direct Known Subclasses

CombinedConfig

Constant Summary collapse

DEFAULT_KEYS =
[
  :name, :summary, :preview_url, :algorithm, :groups, :variants, :goals,
  :start_manually, :reset_manually, :sticky_assignment, :store_override,
  :track_override, :combined, :allow_multiple_conversions,
  :allow_multiple_goals, :track_winner_conversions, :skip_request_filter,
  :target_sample_size, :can_resume, :enable_calibration
].freeze
CALLBACK_KEYS =
[
  :on_start, :on_schedule, :on_stop, :on_pause, :on_resume, :on_winner,
  :on_reset, :on_delete, :on_choose, :on_use, :on_convert,
  :on_redis_failover, :allow_participation, :allow_conversion,
  :track_participation, :rollout_winner
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(experiment, *args, **opts, &block) ⇒ Config

Returns a new instance of Config.



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/trail_guide/experiments/config.rb', line 33

def initialize(experiment, *args, **opts, &block)
  @experiment = experiment
  opts = default_config.merge(opts)
  ancestor = opts.delete(:inherit)
  if ancestor.present?
    keys = opts.keys.dup.concat(args).concat(DEFAULT_KEYS).concat(CALLBACK_KEYS).uniq
    opts = opts.merge(ancestor.to_h.slice(*keys))
    opts[:name] = nil
    opts[:goals] = ancestor.goals.dup
    opts[:combined] = ancestor.combined.dup
    opts[:variants] = ancestor.variants.dup(experiment)
    opts = opts.merge(ancestor.callbacks.map { |k,v| [k,[v].flatten.compact] }.to_h)
  end
  super(*args, **opts, &block)
end

Instance Attribute Details

#experimentObject (readonly)

Returns the value of attribute experiment.



31
32
33
# File 'lib/trail_guide/experiments/config.rb', line 31

def experiment
  @experiment
end

Instance Method Details

#algorithmObject



89
90
91
92
93
94
95
# File 'lib/trail_guide/experiments/config.rb', line 89

def algorithm
  if self[:algorithm].is_a?(Array) && self[:algorithm].last.respond_to?(:call)
    @algorithm ||= TrailGuide::Algorithms.algorithm(self[:algorithm].first).new(&self[:algorithm].last)
  else
    @algorithm ||= TrailGuide::Algorithms.algorithm(self[:algorithm])
  end
end

#allow_conversion(meth = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


294
295
296
297
298
# File 'lib/trail_guide/experiments/config.rb', line 294

def allow_conversion(meth=nil, &block)
  raise ArgumentError if meth.nil? && !block_given?
  self[:allow_conversion] ||= []
  self[:allow_conversion] << (meth || block)
end

#allow_multiple_conversions?Boolean

Returns:

  • (Boolean)


61
62
63
# File 'lib/trail_guide/experiments/config.rb', line 61

def allow_multiple_conversions?
  !!allow_multiple_conversions
end

#allow_multiple_goals?Boolean

Returns:

  • (Boolean)


65
66
67
# File 'lib/trail_guide/experiments/config.rb', line 65

def allow_multiple_goals?
  !!allow_multiple_goals
end

#allow_participation(meth = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


288
289
290
291
292
# File 'lib/trail_guide/experiments/config.rb', line 288

def allow_participation(meth=nil, &block)
  raise ArgumentError if meth.nil? && !block_given?
  self[:allow_participation] ||= []
  self[:allow_participation] << (meth || block)
end

#callback_configObject



25
26
27
28
29
# File 'lib/trail_guide/experiments/config.rb', line 25

def callback_config
  CALLBACK_KEYS.map do |key|
    [key, []]
  end.to_h
end

#callbacksObject



212
213
214
# File 'lib/trail_guide/experiments/config.rb', line 212

def callbacks
  to_h.slice(*CALLBACK_KEYS).map { |k,v| [k, [v].flatten.compact] }.to_h
end

#can_resume?Boolean

Returns:

  • (Boolean)


77
78
79
# File 'lib/trail_guide/experiments/config.rb', line 77

def can_resume?
  !!can_resume
end

#combinedObject



200
201
202
# File 'lib/trail_guide/experiments/config.rb', line 200

def combined
  self[:combined] ||= []
end

#combined?Boolean

Returns:

  • (Boolean)


204
205
206
# File 'lib/trail_guide/experiments/config.rb', line 204

def combined?
  !combined.empty?
end

#controlObject



110
111
112
# File 'lib/trail_guide/experiments/config.rb', line 110

def control
  return variants.find { |var| var.control? } || variants.first
end

#control=(name) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/trail_guide/experiments/config.rb', line 114

def control=(name)
  variants.each(&:variant!)
  var_idx = variants.index { |var| var == name }

  if var_idx.nil?
    variant = Variant.new(experiment, name, control: true)
    variants.push(variant)
  else
    variant = variants[var_idx]
    variant.control!
  end

  variant
end

#default_configObject



19
20
21
22
23
# File 'lib/trail_guide/experiments/config.rb', line 19

def default_config
  DEFAULT_KEYS.map do |key|
    [key, nil]
  end.to_h.merge(callback_config)
end

#enable_calibration?Boolean

Returns:

  • (Boolean)


81
82
83
# File 'lib/trail_guide/experiments/config.rb', line 81

def enable_calibration?
  !!enable_calibration
end

#goal(name, **config, &block) ⇒ Object Also known as: funnel



155
156
157
# File 'lib/trail_guide/experiments/config.rb', line 155

def goal(name, **config, &block)
  goals << Metrics::Goal.new(experiment, name, **config, &block)
end

#goal=(name) ⇒ Object Also known as: funnel=



160
161
162
# File 'lib/trail_guide/experiments/config.rb', line 160

def goal=(name)
  goals << Metrics::Goal.new(experiment, name)
end

#goals(*names, **config, &block) ⇒ Object Also known as: funnels



170
171
172
173
174
175
176
177
# File 'lib/trail_guide/experiments/config.rb', line 170

def goals(*names, **config, &block)
  self[:goals] ||= []
  self[:goals] = self[:goals].map { |g| g.is_a?(Metrics::Goal) ? g : Metrics::Goal.new(experiment, g) }
  unless names.empty?
    self[:goals] = self[:goals].concat([names].flatten.map { |g| Metrics::Goal.new(experiment, g, **config, &block) })
  end
  self[:goals]
end

#goals=(*names) ⇒ Object Also known as: funnels=



165
166
167
# File 'lib/trail_guide/experiments/config.rb', line 165

def goals=(*names)
  self[:goals] = [names].flatten.map { |g| Metrics::Goal.new(experiment, g) }
end

#group(grp = nil) ⇒ Object



142
143
144
145
146
147
148
# File 'lib/trail_guide/experiments/config.rb', line 142

def group(grp=nil)
  unless grp.nil?
    groups << grp.to_s.underscore.to_sym
    return groups.last
  end
  groups.first
end

#group=(grp) ⇒ Object



150
151
152
153
# File 'lib/trail_guide/experiments/config.rb', line 150

def group=(grp)
  groups.unshift(grp.to_s.underscore.to_sym)
  groups.first
end

#groups(*grps) ⇒ Object



129
130
131
132
133
134
135
136
# File 'lib/trail_guide/experiments/config.rb', line 129

def groups(*grps)
  self[:groups] ||= []
  self[:groups] = self[:groups].map { |g| g.to_s.underscore.to_sym }
  unless grps.empty?
    self[:groups] = self[:groups].concat([grps].flatten.map { |g| g.to_s.underscore.to_sym })
  end
  self[:groups]
end

#groups=(*grps) ⇒ Object



138
139
140
# File 'lib/trail_guide/experiments/config.rb', line 138

def groups=(*grps)
  self[:groups] = [grps].flatten.map { |g| g.to_s.underscore.to_sym }
end

#metric(name, **config, &block) ⇒ Object



180
181
182
183
# File 'lib/trail_guide/experiments/config.rb', line 180

def metric(name, **config, &block)
  group(name)
  goal(name, **config, &block)
end

#metric=(name) ⇒ Object



185
186
187
188
# File 'lib/trail_guide/experiments/config.rb', line 185

def metric=(name)
  self.group = name
  self.goal = name
end

#metrics(*names, **config, &block) ⇒ Object



190
191
192
193
# File 'lib/trail_guide/experiments/config.rb', line 190

def metrics(*names, **config, &block)
  groups(*names)
  goals(*names, **config, &block)
end

#metrics=(*names) ⇒ Object



195
196
197
198
# File 'lib/trail_guide/experiments/config.rb', line 195

def metrics=(*names)
  self.send(:groups=, *names.flatten)
  self.send(:goals=, *names.flatten)
end

#nameObject



85
86
87
# File 'lib/trail_guide/experiments/config.rb', line 85

def name
  @name ||= (self[:name] || experiment.name).try(:to_s).try(:underscore).try(:to_sym)
end

#on_choose(meth = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


216
217
218
219
220
# File 'lib/trail_guide/experiments/config.rb', line 216

def on_choose(meth=nil, &block)
  raise ArgumentError if meth.nil? && !block_given?
  self[:on_choose] ||= []
  self[:on_choose] << (meth || block)
end

#on_convert(meth = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


228
229
230
231
232
# File 'lib/trail_guide/experiments/config.rb', line 228

def on_convert(meth=nil, &block)
  raise ArgumentError if meth.nil? && !block_given?
  self[:on_convert] ||= []
  self[:on_convert] << (meth || block)
end

#on_delete(meth = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


276
277
278
279
280
# File 'lib/trail_guide/experiments/config.rb', line 276

def on_delete(meth=nil, &block)
  raise ArgumentError if meth.nil? && !block_given?
  self[:on_delete] ||= []
  self[:on_delete] << (meth || block)
end

#on_pause(meth = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


252
253
254
255
256
# File 'lib/trail_guide/experiments/config.rb', line 252

def on_pause(meth=nil, &block)
  raise ArgumentError if meth.nil? && !block_given?
  self[:on_pause] ||= []
  self[:on_pause] << (meth || block)
end

#on_redis_failover(meth = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


282
283
284
285
286
# File 'lib/trail_guide/experiments/config.rb', line 282

def on_redis_failover(meth=nil, &block)
  raise ArgumentError if meth.nil? && !block_given?
  self[:on_redis_failover] ||= []
  self[:on_redis_failover] << (meth || block)
end

#on_reset(meth = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


270
271
272
273
274
# File 'lib/trail_guide/experiments/config.rb', line 270

def on_reset(meth=nil, &block)
  raise ArgumentError if meth.nil? && !block_given?
  self[:on_reset] ||= []
  self[:on_reset] << (meth || block)
end

#on_resume(meth = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


258
259
260
261
262
# File 'lib/trail_guide/experiments/config.rb', line 258

def on_resume(meth=nil, &block)
  raise ArgumentError if meth.nil? && !block_given?
  self[:on_resume] ||= []
  self[:on_resume] << (meth || block)
end

#on_schedule(meth = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


240
241
242
243
244
# File 'lib/trail_guide/experiments/config.rb', line 240

def on_schedule(meth=nil, &block)
  raise ArgumentError if meth.nil? && !block_given?
  self[:on_schedule] ||= []
  self[:on_schedule] << (meth || block)
end

#on_start(meth = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


234
235
236
237
238
# File 'lib/trail_guide/experiments/config.rb', line 234

def on_start(meth=nil, &block)
  raise ArgumentError if meth.nil? && !block_given?
  self[:on_start] ||= []
  self[:on_start] << (meth || block)
end

#on_stop(meth = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


246
247
248
249
250
# File 'lib/trail_guide/experiments/config.rb', line 246

def on_stop(meth=nil, &block)
  raise ArgumentError if meth.nil? && !block_given?
  self[:on_stop] ||= []
  self[:on_stop] << (meth || block)
end

#on_use(meth = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


222
223
224
225
226
# File 'lib/trail_guide/experiments/config.rb', line 222

def on_use(meth=nil, &block)
  raise ArgumentError if meth.nil? && !block_given?
  self[:on_use] ||= []
  self[:on_use] << (meth || block)
end

#on_winner(meth = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


264
265
266
267
268
# File 'lib/trail_guide/experiments/config.rb', line 264

def on_winner(meth=nil, &block)
  raise ArgumentError if meth.nil? && !block_given?
  self[:on_winner] ||= []
  self[:on_winner] << (meth || block)
end

#preview_url?Boolean

Returns:

  • (Boolean)


208
209
210
# File 'lib/trail_guide/experiments/config.rb', line 208

def preview_url?
  !!preview_url
end

#reset_manually?Boolean

Returns:

  • (Boolean)


53
54
55
# File 'lib/trail_guide/experiments/config.rb', line 53

def reset_manually?
  !!reset_manually
end

#rollout_winner(meth = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


306
307
308
309
310
# File 'lib/trail_guide/experiments/config.rb', line 306

def rollout_winner(meth=nil, &block)
  raise ArgumentError if meth.nil? && !block_given?
  self[:rollout_winner] ||= []
  self[:rollout_winner] << (meth || block)
end

#skip_request_filter?Boolean

Returns:

  • (Boolean)


73
74
75
# File 'lib/trail_guide/experiments/config.rb', line 73

def skip_request_filter?
  !!skip_request_filter
end

#start_manually?Boolean

Returns:

  • (Boolean)


49
50
51
# File 'lib/trail_guide/experiments/config.rb', line 49

def start_manually?
  !!start_manually
end

#sticky_assignment?Boolean

Returns:

  • (Boolean)


57
58
59
# File 'lib/trail_guide/experiments/config.rb', line 57

def sticky_assignment?
  !!sticky_assignment
end

#track_participation(meth = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


300
301
302
303
304
# File 'lib/trail_guide/experiments/config.rb', line 300

def track_participation(meth=nil, &block)
  raise ArgumentError if meth.nil? && !block_given?
  self[:track_participation] ||= []
  self[:track_participation] << (meth || block)
end

#track_winner_conversions?Boolean

Returns:

  • (Boolean)


69
70
71
# File 'lib/trail_guide/experiments/config.rb', line 69

def track_winner_conversions?
  !!track_winner_conversions
end

#variant(varname, metadata: {}, weight: 1, control: false) ⇒ Object

Raises:

  • (ArgumentError)


101
102
103
104
105
106
107
108
# File 'lib/trail_guide/experiments/config.rb', line 101

def variant(varname, metadata: {}, weight: 1, control: false)
  raise ArgumentError, "The variant `#{varname}` already exists in the experiment `#{name}`" if variants.any? { |var| var == varname }
  control = true if variants.empty?
  variants.each(&:variant!) if control
  variant = Variant.new(experiment, varname, metadata: , weight: weight, control: control)
  variants << variant
  variant
end

#variantsObject



97
98
99
# File 'lib/trail_guide/experiments/config.rb', line 97

def variants
  self[:variants] ||= TrailGuide::Variants.new
end