Module: MgCore::Controller

Included in:
ActionController::Base, ActionMailer::Base, ActiveRecord::Base
Defined in:
lib/mountain-goat/mg_core.rb

Instance Method Summary collapse

Instance Method Details

#bandit_reward(goal_type, reward, options = {}) ⇒ Object

allows bandit_reward(goal, options)



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/mountain-goat/mg_core.rb', line 163

def bandit_reward(goal_type, reward, options = {})
  
  if reward.is_a?(Hash) #allow arguments bandit_reward(test, options)
    options = reward
    reward = 0
  end
  
  tests = {} #for user-defined metrics
  options = options.with_indifferent_access
  
  MountainGoat.get_meta_options.each do |k, v|
    if options.include?(k) && options[k]
      options.delete(k)
      res = v.call(self)
      options.merge!( res ) if !res.nil? && res.instance_of?(Hash)
    end
  end
  
  options.each do |k, v|
    if k.to_s =~ /^test_(\w+)$/i
      options.delete k
      tests.merge!({ $1 => v })
    end
  end
  
  logger.warn "Recording goal #{goal_type.to_s} with options #{options.inspect}"
  
  goal = Mg::Goal.first( :conditions => { :goal_type => goal_type.to_s } )
  
  # Now, we just create the goal if we don't have one
  goal = Mg::Goal.create!( :goal_type => goal_type.to_s, :name => goal_type.to_s, :rewards_total => reward, :rewards_given => 1 ) if goal.nil?
  
  # First, let's tally for the goal itself
  goal.tally_reward_given( reward )
  
  # We need to see what meta information we should fill based on the goal type
  Mg::Record.create!( { :mg_goal_id => goal.id, :reward => reward } ).(options)
  
  # User-defined test tallies
  tests.each do |test_type, choice_id|
    t = Mg::Test.find_by_test_type(test_type)
    if t.nil?
      logger.warn "Missing user-defined test #{test_type}"
      next
    end
    
    c = t.choices.first( :conditions => { :id => choice_id } ) #make sure everything matches up
    
    if c.nil?
      logger.warn "Choice #{choice_id} not in choices for #{t.title}"
      next
    end
    
    logger.warn "Tallying goal #{goal.name} for #{t.title} - #{c.name} (#{c.value} - #{c.id})"
    c.tally_goal(goal, reward)
  end
  
  if !mg_storage.nil?
    #we just converted, let's tally each of our metrics (from cookies or session)
    Mg::Test.all.each do |test|
      test_sym = "test_#{test.test_type}".to_sym
      choice_sym = "test_#{test.test_type}_choice".to_sym
      
      value = mg_storage[test_sym]
      choice_id = mg_storage[choice_sym]
      
      #logger.warn "Value: #{metric_sym} - #{value}"
      #logger.warn "Value: #{metric_variant_sym} - #{variant_id}"
      
      if choice_id.blank? #the user just doesn't have this set
        #This is now common-case
        next
      end
      
      choice = Mg::Choice.first(:conditions => { :id => choice_id.to_i } )
      
      if choice.nil?
        logger.error "Choice #{choice_id} not in choices for #{test.title}"
        next
      end
      
      if choice.value != value
        logger.warn "Choice #{choice.name} values differ for test #{test.title}.  '#{choice.value}' != '#{value}'!"
      end
      
      logger.warn "Tallying goal #{goal.name} for #{test.title} - #{choice.name} (#{choice.value} - #{choice.id})"
      choice.tally_goal(goal, reward)
    end
  end
end

#bd(test_type, default, opts = {}, opt = nil) ⇒ Object



128
129
130
# File 'lib/mountain-goat/mg_core.rb', line 128

def bd(test_type, default, opts = {}, opt = nil)
  return get_choice(test_type, default, opts, opt)[:value]
end

#bdd(test_type, default, opts = {}, opt = nil) ⇒ Object



132
133
134
# File 'lib/mountain-goat/mg_core.rb', line 132

def bdd(test_type, default, opts = {}, opt = nil)
  return get_choice(test_type, default, opts, opt)
end

#bds(test_type, &block) ⇒ Object

Bandit Tracking #

Raises:

  • (ArgumentError)


119
120
121
122
123
124
125
126
# File 'lib/mountain-goat/mg_core.rb', line 119

def bds(test_type, &block)
  raise ArgumentError, "Switch choice needs block" if !block_given?
  test = get_test( test_type, true )
  block.call(SwitchChoice.new( logger, test, nil ) )
  
  var = get_switch_choice( test_type )
  block.call(SwitchChoice.new( logger, test, var ) )
end

#mg_apply_strategy(test) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/mountain-goat/mg_core.rb', line 76

def mg_apply_strategy(test)
  case mg_strategy.downcase
    when 'e-greedy'
      logger.warn Mg::Choice.all(:order => "CASE WHEN served = 0 THEN 1 ELSE 0 END DESC, CASE WHEN #{mg_rand(true).to_f} < #{mg_epsilon.to_f} THEN #{mg_rand} ELSE CASE WHEN served = 0 THEN -1 ELSE reward / served END END DESC, #{mg_rand} DESC", :conditions => { :mg_test_id => test.id } )
      return Mg::Choice.first(:order => "CASE WHEN served = 0 THEN 1 ELSE 0 END DESC, CASE WHEN #{mg_rand(true).to_f} < #{mg_epsilon.to_f} THEN #{mg_rand} ELSE CASE WHEN served = 0 THEN -1 ELSE reward / served END END DESC, #{mg_rand} DESC", :conditions => { :mg_test_id => test.id } )
    when 'e-greedy-decreasing'
      return Mg::Choice.first(:order => "CASE WHEN served = 0 THEN 1 ELSE 0 END DESC,
                                                CASE WHEN #{mg_rand(true).to_f} < #{mg_epsilon.to_f} / ( select sum(served) from mg_metric_variants where metric_id = #{ metric.id.to_i } ) THEN #{mg_rand} ELSE CASE WHEN served = 0 THEN -1 ELSE reward / served END END DESC,
                                                #{mg_rand} DESC", :conditions => { :mg_test_id => test.id } ) # * log( ( select sum(served) from mg_metric_variants where metric_id = #{ metric.id.to_i } ) )
    when 'a/b'
      return Mg::Choice.first(:order => "#{mg_rand} DESC", :conditions => { :mg_test_id => test.id } )
    else
      raise "Invalid strategy #{mg_strategy}"
  end
end

#mg_epsilonObject



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/mountain-goat/mg_core.rb', line 38

def mg_epsilon
  if @mg_epsilon.nil?
    @mg_epsilon = 0.1 #default
    mg_yml = nil
    begin
      mg_yml = YAML::load(File.open("#{RAILS_ROOT}/config/mountain-goat.yml"))
    rescue
    end
    if mg_yml
      if mg_yml.has_key?(RAILS_ENV) && mg_yml[RAILS_ENV].has_key?('epsilon')
        @mg_epsilon = mg_yml[RAILS_ENV]['epsilon'].to_f
      elsif mg_yml.has_key?('settings') && mg_yml['settings'].has_key?('epsilon')
        @mg_epsilon = mg_yml['settings']['epsilon'].to_f
      end
    end
  end
  return @mg_epsilon
end

#mg_rand(evaluate = false) ⇒ Object

This is just for testing



33
34
35
36
# File 'lib/mountain-goat/mg_core.rb', line 33

def mg_rand(evaluate = false)
  return "(SELECT #{@mg_i.nil? ? 1 : @mg_i.to_f})" if defined?(MOUNTAIN_GOAT_TEST) && MOUNTAIN_GOAT_TEST
  evaluate ? rand.to_f : "RAND()"
end

#mg_storageObject



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/mountain-goat/mg_core.rb', line 92

def mg_storage 
  if @mg_storage.nil?
    @mg_storage = defined?(cookies) ? cookies : nil
    
    mg_yml = nil
    begin
      mg_yml = YAML::load(File.open("#{RAILS_ROOT}/config/mountain-goat.yml"))
    rescue
    end
    if mg_yml
      if mg_yml.has_key?(RAILS_ENV) && mg_yml[RAILS_ENV].has_key?('storage')
        uc = mg_yml[RAILS_ENV]['storage'].strip
        @mg_storage = ( uc == "cookies" && defined?(cookies) ) ? cookies : ( uc == "session" && defined?(session) ) ? session : nil
      elsif mg_yml.has_key?('settings') && mg_yml['settings'].has_key?('storage')
        uc = mg_yml['settings']['storage'].strip
        @mg_storage = ( uc == "cookies" && defined?(cookies) ) ? cookies : ( uc == "session" && defined?(session) ) ? session : nil
      end
    end
  end
  @mg_storage = {} if @mg_storage.nil? #'none'
  return @mg_storage
end

#mg_strategyObject



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/mountain-goat/mg_core.rb', line 57

def mg_strategy 
  if @mg_strategy.nil?
    @mg_strategy = 'e-greedy' #default
    mg_yml = nil
    begin
      mg_yml = YAML::load(File.open("#{RAILS_ROOT}/config/mountain-goat.yml"))
    rescue
    end
    if mg_yml
      if mg_yml.has_key?(RAILS_ENV) && mg_yml[RAILS_ENV].has_key?('strategy')
        @mg_strategy = mg_yml[RAILS_ENV]['strategy']
      elsif mg_yml.has_key?('settings') && mg_yml['settings'].has_key?('strategy')
        @mg_strategy = mg_yml['settings']['strategy']
      end
    end
  end
  return @mg_strategy
end

#mv(test_type, goal_type, default, opts = {}, opt = nil) ⇒ Object



141
142
143
# File 'lib/mountain-goat/mg_core.rb', line 141

def mv(test_type, goal_type, default, opts = {}, opt = nil)
  bd(test_type, default, opts, opt)
end

#mv_detailed(test_type, goal_type, default, opts = {}, opt = nil) ⇒ Object



145
146
147
# File 'lib/mountain-goat/mg_core.rb', line 145

def mv_detailed(test_type, goal_type, default, opts = {}, opt = nil)
  bdd(test_type, default, opts, opt)  
end

#rc(goal_type, options = {}) ⇒ Object



154
155
156
# File 'lib/mountain-goat/mg_core.rb', line 154

def rc(goal_type, options = {})
  self.bandit_reward(goal_type, 1, options)
end

#record_conversion(goal_type, options = {}) ⇒ Object



158
159
160
# File 'lib/mountain-goat/mg_core.rb', line 158

def record_conversion(goal_type, options = {})
  self.bandit_reward(goal_type, 1, options)
end

#rw(goal_type, reward, options = {}) ⇒ Object

shorthand



150
151
152
# File 'lib/mountain-goat/mg_core.rb', line 150

def rw(goal_type, reward, options = {})
  self.bandit_reward(goal_type, reward, options)
end

#sv(test_type, goal_type, &block) ⇒ Object

Legacy



137
138
139
# File 'lib/mountain-goat/mg_core.rb', line 137

def sv(test_type, goal_type, &block)
  bds(test_type, &block)
end