vanity 1.5.3 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,51 @@
1
+ == 1.6.0 (2011-07-18)
2
+
3
+ If robots or spiders make up a significant portion of your sites traffic they
4
+ can affect your conversion rate. Vanity can optionally add participants to the
5
+ experiments using asynchronous JavaScript callbacks, which will keep almost all
6
+ robots out. To set this up simply do the following:
7
+
8
+ 1. Vanity.playground.use_js!
9
+ 2. Set Vanity.playground.add_participant_path = '/path/to/vanity/action'
10
+ 3. Add <%= Vanity.vanity_js %> to the bottom of any view that needs to set up
11
+ an ab_test
12
+
13
+
14
+ Fix for metrics on rails 3 models (Esteban Pastorino).
15
+
16
+ Use JavaScript to report participants, useful for ignoring bots on publicly
17
+ accessible pages (Doug Cole).
18
+
19
+ AbTest#choose returns an Alternative rather than just the value (Doug Cole).
20
+
21
+ Add warnings instead of swallowing errors (Anthony Eden).
22
+
23
+ Fixing broken test for mongodb adapter (Joshua Krall).
24
+
25
+ Fix returning correct experiment when in test mode and manually set via
26
+ #chooses (Ryan Sonnek).
27
+
28
+ Don't round the conversion rate before using it, it affects the test results,
29
+ making them less accurate (Doug Cole).
30
+
31
+ Fixed loading config from yml when using other than redis adapter (Arttu Tervo)
32
+
33
+ Default to localhost unless host in config file (Arttu Tervo)
34
+
35
+ Fixed mongo connection adapter connect! when called after disconnect! (Arttu
36
+ Tervo)
37
+
38
+ Use mongo replica set connection if multiple hosts were given in YAML
39
+ configuration file (Arttu Tervo)
40
+
41
+ Cookie domain from rails configuration (Arttu Tervo)
42
+
43
+ Add bson_ext to Gemfile to load C extension for mongodb ruby driver, and
44
+ prevent Notice messages as the tests run (tenaciousflea)
45
+
46
+ Update redis-namespace dependency to 1.0 (Ville Lautanala)
47
+
48
+
1
49
  == 1.5.3 (2011-04-11)
2
50
 
3
51
  Added number of participants and number of converted participants to ab_test
@@ -59,9 +107,9 @@ to multiple adapters (not just Redis). The easiest is to use the configuration
59
107
  file config/vanity.yml. For example:
60
108
 
61
109
  development:
62
- adapter: redis
63
- production:
64
- adapter: mongodb
110
+ adapter: redis
111
+ production:
112
+ adapter: mongodb
65
113
 
66
114
  We get to keep Redis, add new MongoDB adapter, but lose Mock. It's still there,
67
115
  but there's a new way to use Vanity outside production: you can turn data
@@ -117,7 +165,7 @@ Next, authenticate using your account credentials. For example, in your
117
165
  config/environments/production.rb:
118
166
 
119
167
  require "garb"
120
- Garb::Session.login('..email..', '..password..', account_type: "GOOGLE") rescue nil
168
+ Garb::Session.login('..email..', '..password..', account_type: "GOOGLE") rescue nil
121
169
 
122
170
  Last, define Vanity metrics that tap to Google Analytics metrics. For example:
123
171
 
@@ -179,22 +227,22 @@ This release introduces metrics. Metrics are the gateway drug to better softwar
179
227
 
180
228
  It’s as simple as defining a metric:
181
229
 
182
- metric "Cheers" do
183
- description "They love us, don't they?"
184
- end
230
+ metric "Cheers" do
231
+ description "They love us, don't they?"
232
+ end
185
233
 
186
234
  Tracking it from your code:
187
235
 
188
- track! :cheers
236
+ track! :cheers
189
237
 
190
238
  And watching the graph from the Dashboard.
191
239
 
192
240
  You can (should) also use metrics with your A/B tests, for example:
193
241
 
194
242
  ab_test "Pricing options" do
195
- metrics :signup
196
- alternatives 15, 25, 29
197
- end
243
+ metrics :signup
244
+ alternatives 15, 25, 29
245
+ end
198
246
 
199
247
  This new usage may become requirement in a future release.
200
248
 
@@ -213,8 +261,8 @@ Much thanks to Ian Sefferman for fixing issues with Ruby 1.8.7 and Rails support
213
261
  This release changes the way you define a new experiment. You can use a method suitable for the type of experiment you want to define, or call the generic define method (previously: experiment method). For example:
214
262
 
215
263
  ab_test "My A/B test" do
216
- alternatives :a, :b
217
- end
264
+ alternatives :a, :b
265
+ end
218
266
 
219
267
  The experiment method is no longer overloaded: it looks up an experiment (loading its definition if necessary), but does not define an experiment. The ab_test method is overloaded, though this may change in the future.
220
268
 
data/Gemfile CHANGED
@@ -13,12 +13,12 @@ group :test do
13
13
  gem "garb"
14
14
  gem "mocha"
15
15
  gem "mongo"
16
- gem "passenger"
17
- gem "rails", "2.3.8"
18
- gem "rack", "1.1.0"
16
+ gem "bson_ext"
17
+ gem "mysql"
18
+ gem "passenger", "~>2.0"
19
+ gem "rails", "~>2.3.8"
20
+ gem "rack"
19
21
  gem "shoulda"
20
- gem "sqlite3-ruby", "1.2.5" # 1.3.0 doesn't like Ruby 1.9.1
21
22
  gem "timecop"
22
- #gem "SystemTimer"
23
23
  gem "webmock"
24
24
  end
data/README.rdoc CHANGED
@@ -62,7 +62,8 @@ And:
62
62
 
63
63
  vanity report --output vanity.html
64
64
 
65
- == Rails 3
65
+
66
+ == Rails 3
66
67
 
67
68
  There is currently an issue with report generation. The vanity-talk Google Group has a couple posts that outline the issue for now. This is one of the posts: http://groups.google.com/group/vanity-talk/browse_thread/thread/343081a72a0cefb6
68
69
 
@@ -73,6 +74,14 @@ And:
73
74
  `match '/vanity(/:action(/:id(.:format)))', :controller=>:vanity`
74
75
 
75
76
 
77
+ == Registering participants with Javascript
78
+
79
+ If robots or spiders make up a significant portion of your sites traffic they can affect your conversion rate. Vanity can optionally add participants to the experiments using asynchronous javascript callbacks, which will keep almost all robots out. To set this up simply do the following:
80
+
81
+ * Add Vanity.playground.use_js!
82
+ * Set Vanity.playground.add_participant_path = '/path/to/vanity/action', this should point to the add_participant path that is added with Vanity::Rails::Dashboard, make sure that this action is available to all users
83
+ * Add <%= vanity_js %> to any page that needs uses an ab_test. vanity_js needs to be included after your call to ab_test so that it knows which version of the experiment the participant is a member of. The helper will render nothing if the there are no ab_tests running on the current page, so adding vanity_js to the bottom of your layouts is a good option. Keep in mind that if you call use_js! and don't include vanity_js in your view no participants will be recorded.
84
+
76
85
  == Contributing
77
86
 
78
87
  * Fork the project
@@ -15,13 +15,25 @@ module Vanity
15
15
  #
16
16
  # @since 1.4.0
17
17
  class MongodbAdapter < AbstractAdapter
18
+ attr_reader :mongo
19
+
18
20
  def initialize(options)
19
- @mongo = Mongo::Connection.new(options[:host], options[:port], :connect=>false)
21
+ setup_connection(options)
20
22
  @options = options.clone
21
23
  @options[:database] ||= (@options[:path] && @options[:path].split("/")[1]) || "vanity"
22
24
  connect!
23
25
  end
24
26
 
27
+ def setup_connection(options = {})
28
+ if options[:hosts]
29
+ args = (options[:hosts].map{|host| [host, options[:port]] } << {:connect => false})
30
+ @mongo = Mongo::ReplSetConnection.new(*args)
31
+ else
32
+ @mongo = Mongo::Connection.new(options[:host], options[:port], :connect => false)
33
+ end
34
+ @mongo
35
+ end
36
+
25
37
  def active?
26
38
  @mongo.connected?
27
39
  end
@@ -38,6 +50,7 @@ module Vanity
38
50
  end
39
51
 
40
52
  def connect!
53
+ @mongo ||= setup_connection(@options)
41
54
  @mongo.connect
42
55
  database = @mongo.db(@options[:database])
43
56
  database.authenticate @options[:username], @options[:password], true if @options[:username]
@@ -45,11 +58,14 @@ module Vanity
45
58
  @experiments = database.collection("vanity.experiments")
46
59
  @participants = database.collection("vanity.participants")
47
60
  @participants.create_index [[:experiment, 1], [:identity, 1]], :unique=>true
61
+ @participants.create_index [[:experiment, 1], [:seen, 1]]
62
+ @participants.create_index [[:experiment, 1], [:converted, 1]]
63
+ @mongo
48
64
  end
49
65
 
50
66
  def to_s
51
67
  userinfo = @options.values_at(:username, :password).join(":") if @options[:username]
52
- URI::Generic.build(:scheme=>"mongodb", :userinfo=>userinfo, :host=>@options[:host], :port=>@options[:port], :path=>"/#{@options[:database]}").to_s
68
+ URI::Generic.build(:scheme=>"mongodb", :userinfo=>userinfo, :host=>(@mongo.host || @options[:host]), :port=>(@mongo.port || @options[:port]), :path=>"/#{@options[:database]}").to_s
53
69
  end
54
70
 
55
71
  def flushdb
@@ -112,8 +128,8 @@ module Vanity
112
128
  def ab_counts(experiment, alternative)
113
129
  record = @experiments.find_one({ :_id=>experiment }, { :fields=>[:conversions] })
114
130
  conversions = record && record["conversions"]
115
- { :participants => @participants.find({ :experiment=>experiment, :seen=>alternative }, { :fields=>[] }).count,
116
- :converted => @participants.find({ :experiment=>experiment, :converted=>alternative }, { :fields=>[] }).count,
131
+ { :participants => @participants.find({ :experiment=>experiment, :seen=>alternative }).count,
132
+ :converted => @participants.find({ :experiment=>experiment, :converted=>alternative }).count,
117
133
  :conversions => conversions && conversions[alternative.to_s] || 0 }
118
134
  end
119
135
 
@@ -131,16 +147,16 @@ module Vanity
131
147
  end
132
148
 
133
149
  def ab_add_participant(experiment, alternative, identity)
134
- @participants.update({ :experiment=>experiment, :identity=>identity }, { "$set"=>{ :seen=>alternative } }, :upsert=>true)
150
+ @participants.update({ :experiment=>experiment, :identity=>identity }, { "$push"=>{ :seen=>alternative } }, :upsert=>true)
135
151
  end
136
152
 
137
153
  def ab_add_conversion(experiment, alternative, identity, count = 1, implicit = false)
138
154
  if implicit
139
- @participants.update({ :experiment=>experiment, :identity=>identity }, { "$set"=>{ :seen=>alternative } }, :upsert=>true)
155
+ @participants.update({ :experiment=>experiment, :identity=>identity }, { "$push"=>{ :seen=>alternative } }, :upsert=>true)
140
156
  else
141
157
  participating = @participants.find_one(:experiment=>experiment, :identity=>identity, :seen=>alternative)
142
158
  end
143
- @participants.update({ :experiment=>experiment, :identity=>identity }, { "$set"=>{ :converted=>alternative } }, :upsert=>true) if implicit || participating
159
+ @participants.update({ :experiment=>experiment, :identity=>identity }, { "$push"=>{ :converted=>alternative } }, :upsert=>true) if implicit || participating
144
160
  @experiments.update({ :_id=>experiment }, { "$inc"=>{ "conversions.#{alternative}"=>count } }, :upsert=>true)
145
161
  end
146
162
 
@@ -26,7 +26,13 @@ module Vanity
26
26
  end
27
27
 
28
28
  def disconnect!
29
- @redis.quit rescue nil if @redis
29
+ if redis
30
+ begin
31
+ redis.quit
32
+ rescue Exception => e
33
+ warn("Error while disconnecting from redis: #{e.message}")
34
+ end
35
+ end
30
36
  @redis = nil
31
37
  end
32
38
 
@@ -42,7 +48,7 @@ module Vanity
42
48
  end
43
49
 
44
50
  def to_s
45
- @redis.id
51
+ redis.id
46
52
  end
47
53
 
48
54
  def redis
@@ -6,12 +6,11 @@ module Vanity
6
6
  # One of several alternatives in an A/B test (see AbTest#alternatives).
7
7
  class Alternative
8
8
 
9
- def initialize(experiment, id, value, participants, converted, conversions)
9
+ def initialize(experiment, id, value) #, participants, converted, conversions)
10
10
  @experiment = experiment
11
11
  @id = id
12
12
  @name = "option #{(@id + 65).chr}"
13
13
  @value = value
14
- @participants, @converted, @conversions = participants, converted, conversions
15
14
  end
16
15
 
17
16
  # Alternative id, only unique for this experiment.
@@ -27,13 +26,22 @@ module Vanity
27
26
  attr_reader :experiment
28
27
 
29
28
  # Number of participants who viewed this alternative.
30
- attr_reader :participants
29
+ def participants
30
+ load_counts unless @participants
31
+ @participants
32
+ end
31
33
 
32
34
  # Number of participants who converted on this alternative (a participant is counted only once).
33
- attr_reader :converted
35
+ def converted
36
+ load_counts unless @converted
37
+ @converted
38
+ end
34
39
 
35
40
  # Number of conversions for this alternative (same participant may be counted more than once).
36
- attr_reader :conversions
41
+ def conversions
42
+ load_counts unless @conversions
43
+ @conversions
44
+ end
37
45
 
38
46
  # Z-score for this alternative, related to 2nd-best performing alternative. Populated by AbTest#score.
39
47
  attr_accessor :z_score
@@ -44,9 +52,9 @@ module Vanity
44
52
  # Difference from least performing alternative. Populated by AbTest#score.
45
53
  attr_accessor :difference
46
54
 
47
- # Conversion rate calculated as converted/participants, rounded to 3 places.
55
+ # Conversion rate calculated as converted/participants
48
56
  def conversion_rate
49
- @conversion_rate ||= (participants > 0 ? (converted.to_f/participants.to_f * 1000).round / 1000.0 : 0.0)
57
+ @conversion_rate ||= (participants > 0 ? converted.to_f/participants.to_f : 0.0)
50
58
  end
51
59
 
52
60
  # The measure we use to order (sort) alternatives and decide which one is better (by calculating z-score).
@@ -71,25 +79,32 @@ module Vanity
71
79
  "#{name}: #{value} #{converted}/#{participants}"
72
80
  end
73
81
 
82
+ def load_counts
83
+ if @experiment.playground.collecting?
84
+ @participants, @converted, @conversions = @experiment.playground.connection.ab_counts(@experiment.id, id).values_at(:participants, :converted, :conversions)
85
+ else
86
+ @participants = @converted = @conversions = 0
87
+ end
88
+ end
74
89
  end
75
90
 
76
91
 
77
- # The meat.
78
- class AbTest < Base
79
- class << self
92
+ # The meat.
93
+ class AbTest < Base
94
+ class << self
80
95
 
81
- # Convert z-score to probability.
82
- def probability(score)
83
- score = score.abs
84
- probability = AbTest::Z_TO_PROBABILITY.find { |z,p| score >= z }
85
- probability ? probability.last : 0
86
- end
96
+ # Convert z-score to probability.
97
+ def probability(score)
98
+ score = score.abs
99
+ probability = AbTest::Z_TO_PROBABILITY.find { |z,p| score >= z }
100
+ probability ? probability.last : 0
101
+ end
87
102
 
88
- def friendly_name
89
- "A/B Test"
90
- end
103
+ def friendly_name
104
+ "A/B Test"
105
+ end
91
106
 
92
- end
107
+ end
93
108
 
94
109
  def initialize(*args)
95
110
  super
@@ -139,8 +154,7 @@ module Vanity
139
154
  def _alternatives
140
155
  alts = []
141
156
  @alternatives.each_with_index do |value, i|
142
- counts = @playground.collecting? ? connection.ab_counts(@id, i) : Hash.new(0)
143
- alts << Alternative.new(self, i, value, counts[:participants], counts[:converted], counts[:conversions])
157
+ alts << Alternative.new(self, i, value)
144
158
  end
145
159
  alts
146
160
  end
@@ -187,8 +201,10 @@ module Vanity
187
201
  index = connection.ab_showing(@id, identity)
188
202
  unless index
189
203
  index = alternative_for(identity)
190
- connection.ab_add_participant @id, index, identity
191
- check_completion!
204
+ if !@playground.using_js?
205
+ connection.ab_add_participant @id, index, identity
206
+ check_completion!
207
+ end
192
208
  end
193
209
  else
194
210
  index = connection.ab_get_outcome(@id) || alternative_for(identity)
@@ -197,8 +213,9 @@ module Vanity
197
213
  identity = identity()
198
214
  @showing ||= {}
199
215
  @showing[identity] ||= alternative_for(identity)
216
+ index = @showing[identity]
200
217
  end
201
- @alternatives[index.to_i]
218
+ alternatives[index.to_i]
202
219
  end
203
220
 
204
221
  # Returns fingerprint (hash) for given alternative. Can be used to lookup
@@ -233,8 +250,16 @@ module Vanity
233
250
  connection.ab_not_showing @id, identity
234
251
  else
235
252
  index = @alternatives.index(value)
253
+ #add them to the experiment unless they are already in it
254
+ unless index == connection.ab_showing(@id, identity)
255
+ connection.ab_add_participant @id, index, identity
256
+ check_completion!
257
+ end
236
258
  raise ArgumentError, "No alternative #{value.inspect} for #{name}" unless index
237
- connection.ab_show @id, identity, index
259
+ if (connection.ab_showing(@id, identity) && connection.ab_showing(@id, identity) != index) ||
260
+ alternative_for(identity) != index
261
+ connection.ab_show @id, identity, index
262
+ end
238
263
  end
239
264
  else
240
265
  @showing ||= {}
@@ -247,7 +272,7 @@ module Vanity
247
272
  def showing?(alternative)
248
273
  identity = identity()
249
274
  if @playground.collecting?
250
- connection.ab_showing(@id, identity) == alternative.id
275
+ (connection.ab_showing(@id, identity) || alternative_for(identity)) == alternative.id
251
276
  else
252
277
  @showing ||= {}
253
278
  @showing[identity] == alternative.id
@@ -379,9 +404,9 @@ module Vanity
379
404
  if @outcome_is
380
405
  begin
381
406
  result = @outcome_is.call
382
- outcome = result.id if result && result.experiment == self
383
- rescue
384
- # TODO: logging
407
+ outcome = result.id if Alternative === result && result.experiment == self
408
+ rescue
409
+ warn "Error in AbTest#complete!: #{$!}"
385
410
  end
386
411
  else
387
412
  best = score.best
@@ -80,6 +80,8 @@ module Vanity
80
80
  # Button" becomes :green_button.
81
81
  attr_reader :id
82
82
 
83
+ attr_reader :playground
84
+
83
85
  # Time stamp when experiment was created.
84
86
  def created_at
85
87
  @created_at ||= connection.get_experiment_created_at(@id)
@@ -124,7 +126,7 @@ module Vanity
124
126
  @description = text if text
125
127
  @description
126
128
  end
127
-
129
+
128
130
 
129
131
  # -- Experiment completion --
130
132
 
@@ -189,7 +191,7 @@ module Vanity
189
191
  begin
190
192
  complete! if @complete_block.call
191
193
  rescue
192
- # TODO: logging
194
+ warn "Error in Vanity::Experiment::Base: #{$!}"
193
195
  end
194
196
  end
195
197
  end
@@ -46,7 +46,11 @@ module Vanity
46
46
  @vanity_identity = object.id
47
47
  elsif response # everyday use
48
48
  @vanity_identity = cookies["vanity_id"] || ActiveSupport::SecureRandom.hex(16)
49
- cookies["vanity_id"] = { :value=>@vanity_identity, :expires=>1.month.from_now }
49
+ cookie = { :value=>@vanity_identity, :expires=>1.month.from_now }
50
+ # Useful if application and admin console are on separate domains.
51
+ # This only works in Rails 3.x.
52
+ cookie_options[:domain] ||= ::Rails.application.config.session_options[:domain] if ::Rails.respond_to?(:application)
53
+ cookies["vanity_id"] = cookie
50
54
  @vanity_identity
51
55
  else # during functional testing
52
56
  @vanity_identity = "test"
@@ -140,7 +144,14 @@ module Vanity
140
144
  # <%= count %> features to choose from!
141
145
  # <% end %>
142
146
  def ab_test(name, &block)
143
- value = Vanity.playground.experiment(name).choose
147
+ if Vanity.playground.using_js?
148
+ @_vanity_experiments ||= {}
149
+ @_vanity_experiments[name] ||= Vanity.playground.experiment(name).choose
150
+ value = @_vanity_experiments[name].value
151
+ else
152
+ value = Vanity.playground.experiment(name).choose.value
153
+ end
154
+
144
155
  if block
145
156
  content = capture(value, &block)
146
157
  block_called_from_erb?(block) ? concat(content) : content
@@ -149,6 +160,13 @@ module Vanity
149
160
  end
150
161
  end
151
162
 
163
+ def vanity_js
164
+ return if @_vanity_experiments.nil?
165
+ javascript_tag do
166
+ render Vanity.template("vanity.js.erb")
167
+ end
168
+ end
169
+
152
170
  def vanity_h(text)
153
171
  h(text)
154
172
  end
@@ -186,6 +204,16 @@ module Vanity
186
204
  exp.chooses(exp.alternatives[params[:a].to_i].value)
187
205
  render :file=>Vanity.template("_experiment"), :locals=>{:experiment=>exp}
188
206
  end
207
+
208
+ def add_participant
209
+ if params[:e].nil? || params[:e].empty?
210
+ render :status => 404, :nothing => true
211
+ return
212
+ end
213
+ exp = Vanity.playground.experiment(params[:e])
214
+ exp.chooses(exp.alternatives[params[:a].to_i].value)
215
+ render :status => 200, :nothing => true
216
+ end
189
217
  end
190
218
  end
191
219
  end
@@ -34,7 +34,14 @@ module Vanity
34
34
  # end
35
35
  # @since 1.2.0
36
36
  def ab_test(name, &block)
37
- value = Vanity.playground.experiment(name).choose
37
+ if Vanity.playground.using_js?
38
+ @_vanity_experiments ||= {}
39
+ @_vanity_experiments[name] ||= Vanity.playground.experiment(name).choose
40
+ value = @_vanity_experiments[name].value
41
+ else
42
+ value = Vanity.playground.experiment(name).choose.value
43
+ end
44
+
38
45
  if block
39
46
  content = capture(value, &block)
40
47
  block_called_from_erb?(block) ? concat(content) : content
@@ -10,6 +10,7 @@ module Vanity
10
10
  class Playground
11
11
 
12
12
  DEFAULTS = { :collecting => true, :load_path=>"experiments" }
13
+ DEFAULT_ADD_PARTICIPANT_PATH = '/vanity/add_participant'
13
14
 
14
15
  # Created new Playground. Unless you need to, use the global
15
16
  # Vanity.playground.
@@ -38,7 +39,7 @@ module Vanity
38
39
  end
39
40
 
40
41
  @options = defaults.merge(config).merge(options)
41
- if @options.values_at(:host, :port, :db).any?
42
+ if @options[:host] == 'redis' && @options.values_at(:host, :port, :db).any?
42
43
  warn "Deprecated: please specify Redis connection as URL (\"redis://host:port/db\")"
43
44
  establish_connection :adapter=>"redis", :host=>@options[:host], :port=>@options[:port], :database=>@options[:db] || @options[:database]
44
45
  elsif @options[:redis]
@@ -58,6 +59,8 @@ module Vanity
58
59
  @logger.level = Logger::ERROR
59
60
  end
60
61
  @loading = []
62
+ @use_js = false
63
+ self.add_participant_path = DEFAULT_ADD_PARTICIPANT_PATH
61
64
  @collecting = !!@options[:collecting]
62
65
  end
63
66
 
@@ -70,6 +73,9 @@ module Vanity
70
73
  # Logger.
71
74
  attr_accessor :logger
72
75
 
76
+ # Path to the add_participant action, necessary if you have called use_js!
77
+ attr_accessor :add_participant_path
78
+
73
79
  # Defines a new experiment. Generally, do not call this directly,
74
80
  # use one of the definition methods (ab_test, measure, etc).
75
81
  #
@@ -95,6 +101,34 @@ module Vanity
95
101
  experiments[id.to_sym] or raise NameError, "No experiment #{id}"
96
102
  end
97
103
 
104
+
105
+ # -- Robot Detection --
106
+
107
+ # Call to indicate that participants should be added via js
108
+ # This helps keep robots from participating in the ab test
109
+ # and skewing results.
110
+ #
111
+ # If you use this, there are two more steps:
112
+ # - Set Vanity.playground.add_participant_path = '/path/to/vanity/action',
113
+ # this should point to the add_participant path that is added with
114
+ # Vanity::Rails::Dashboard, make sure that this action is available
115
+ # to all users
116
+ # - Add <%= vanity_js %> to any page that needs uses an ab_test. vanity_js
117
+ # needs to be included after your call to ab_test so that it knows which
118
+ # version of the experiment the participant is a member of. The helper
119
+ # will render nothing if the there are no ab_tests running on the current
120
+ # page, so adding vanity_js to the bottom of your layouts is a good
121
+ # option. Keep in mind that if you call use_js! and don't include
122
+ # vanity_js in your view no participants will be recorded.
123
+ def use_js!
124
+ @use_js = true
125
+ end
126
+
127
+ def using_js?
128
+ @use_js
129
+ end
130
+
131
+
98
132
  # Returns hash of experiments (key is experiment id).
99
133
  #
100
134
  # @see Vanity::Experiment
@@ -329,7 +363,6 @@ module Vanity
329
363
  path << ".erb" unless name["."]
330
364
  path
331
365
  end
332
-
333
366
  end
334
367
  end
335
368
 
@@ -0,0 +1,20 @@
1
+ <% unless @_vanity_experiments.empty? %>
2
+ var httpRequest;
3
+ <%
4
+ @_vanity_experiments.each do |name, alternative|
5
+ %>
6
+ var params = "e=<%= name %>&a=<%= alternative.id %>&authenticity_token=" + encodeURIComponent("<%= form_authenticity_token %>");
7
+ if (window.XMLHttpRequest) { // Mozilla, Safari, ...
8
+ httpRequest = new XMLHttpRequest();
9
+ } else if (window.ActiveXObject) { // IE
10
+ try { httpRequest = new ActiveXObject("Msxml2.XMLHTTP"); }
11
+ catch (e) { }
12
+ }
13
+ if (httpRequest) {
14
+ httpRequest.open('POST', "<%= Vanity.playground.add_participant_path %>", true);
15
+ httpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
16
+ httpRequest.setRequestHeader("X-Requested-With", "XMLHttpRequest");
17
+ httpRequest.send(params);
18
+ }
19
+ <% end %>
20
+ <% end %>
@@ -1,5 +1,5 @@
1
1
  module Vanity
2
- VERSION = "1.5.3"
2
+ VERSION = "1.6.0"
3
3
 
4
4
  module Version
5
5
  version = VERSION.to_s.split(".").map { |i| i.to_i }
@@ -0,0 +1,17 @@
1
+ require 'test/test_helper'
2
+
3
+ class RedisAdapterTest < Test::Unit::TestCase
4
+ def test_warn_on_disconnect_error
5
+ if defined?(Redis)
6
+ assert_nothing_raised do
7
+ Redis.any_instance.stubs(:connect!)
8
+ mocked_redis = stub("Redis")
9
+ mocked_redis.expects(:quit).raises(RuntimeError)
10
+ redis_adapter = Vanity::Adapters::RedisAdapter.new({})
11
+ redis_adapter.expects(:warn).with("Error while disconnecting from redis: RuntimeError")
12
+ redis_adapter.stubs(:redis).returns(mocked_redis)
13
+ redis_adapter.disconnect!
14
+ end
15
+ end
16
+ end
17
+ end