lookout-vanity 1.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. data/.autotest +22 -0
  2. data/.gitignore +10 -0
  3. data/.rvmrc +3 -0
  4. data/.travis.yml +15 -0
  5. data/Appraisals +15 -0
  6. data/CHANGELOG +381 -0
  7. data/Gemfile +25 -0
  8. data/Gemfile.lock +110 -0
  9. data/MIT-LICENSE +21 -0
  10. data/README.rdoc +109 -0
  11. data/Rakefile +169 -0
  12. data/bin/vanity +16 -0
  13. data/doc/_config.yml +2 -0
  14. data/doc/_layouts/_header.html +34 -0
  15. data/doc/_layouts/page.html +47 -0
  16. data/doc/_metrics.textile +12 -0
  17. data/doc/ab_testing.textile +210 -0
  18. data/doc/configuring.textile +45 -0
  19. data/doc/contributing.textile +93 -0
  20. data/doc/credits.textile +23 -0
  21. data/doc/css/page.css +83 -0
  22. data/doc/css/print.css +43 -0
  23. data/doc/css/syntax.css +7 -0
  24. data/doc/email.textile +129 -0
  25. data/doc/experimental.textile +31 -0
  26. data/doc/faq.textile +8 -0
  27. data/doc/identity.textile +43 -0
  28. data/doc/images/ab_in_dashboard.png +0 -0
  29. data/doc/images/clear_winner.png +0 -0
  30. data/doc/images/price_options.png +0 -0
  31. data/doc/images/sidebar_test.png +0 -0
  32. data/doc/images/signup_metric.png +0 -0
  33. data/doc/images/vanity.png +0 -0
  34. data/doc/index.textile +91 -0
  35. data/doc/metrics.textile +231 -0
  36. data/doc/rails.textile +89 -0
  37. data/doc/site.js +27 -0
  38. data/gemfiles/rails3.gemfile +20 -0
  39. data/gemfiles/rails3.gemfile.lock +135 -0
  40. data/gemfiles/rails31.gemfile +20 -0
  41. data/gemfiles/rails31.gemfile.lock +146 -0
  42. data/gemfiles/rails32.gemfile +20 -0
  43. data/gemfiles/rails32.gemfile.lock +144 -0
  44. data/generators/templates/vanity_migration.rb +53 -0
  45. data/generators/vanity_generator.rb +8 -0
  46. data/lib/generators/templates/vanity_migration.rb +53 -0
  47. data/lib/generators/vanity_generator.rb +15 -0
  48. data/lib/vanity.rb +36 -0
  49. data/lib/vanity/adapters/abstract_adapter.rb +145 -0
  50. data/lib/vanity/adapters/active_record_adapter.rb +263 -0
  51. data/lib/vanity/adapters/mock_adapter.rb +157 -0
  52. data/lib/vanity/adapters/mongodb_adapter.rb +178 -0
  53. data/lib/vanity/adapters/redis_adapter.rb +160 -0
  54. data/lib/vanity/backport.rb +26 -0
  55. data/lib/vanity/commands/list.rb +21 -0
  56. data/lib/vanity/commands/report.rb +64 -0
  57. data/lib/vanity/commands/upgrade.rb +34 -0
  58. data/lib/vanity/experiment/ab_test.rb +582 -0
  59. data/lib/vanity/experiment/base.rb +218 -0
  60. data/lib/vanity/frameworks.rb +16 -0
  61. data/lib/vanity/frameworks/rails.rb +325 -0
  62. data/lib/vanity/helpers.rb +71 -0
  63. data/lib/vanity/images/x.gif +0 -0
  64. data/lib/vanity/metric/active_record.rb +93 -0
  65. data/lib/vanity/metric/base.rb +244 -0
  66. data/lib/vanity/metric/google_analytics.rb +83 -0
  67. data/lib/vanity/metric/remote.rb +53 -0
  68. data/lib/vanity/playground.rb +408 -0
  69. data/lib/vanity/templates/_ab_test.erb +28 -0
  70. data/lib/vanity/templates/_experiment.erb +5 -0
  71. data/lib/vanity/templates/_experiments.erb +7 -0
  72. data/lib/vanity/templates/_metric.erb +14 -0
  73. data/lib/vanity/templates/_metrics.erb +13 -0
  74. data/lib/vanity/templates/_report.erb +27 -0
  75. data/lib/vanity/templates/_vanity.js.erb +20 -0
  76. data/lib/vanity/templates/flot.min.js +1 -0
  77. data/lib/vanity/templates/jquery.min.js +19 -0
  78. data/lib/vanity/templates/vanity.css +26 -0
  79. data/lib/vanity/templates/vanity.js +82 -0
  80. data/lib/vanity/version.rb +11 -0
  81. data/lookout-vanity.gemspec +26 -0
  82. data/test/adapters/redis_adapter_test.rb +17 -0
  83. data/test/dummy/Rakefile +7 -0
  84. data/test/dummy/app/controllers/application_controller.rb +3 -0
  85. data/test/dummy/app/helpers/application_helper.rb +2 -0
  86. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  87. data/test/dummy/config.ru +4 -0
  88. data/test/dummy/config/application.rb +44 -0
  89. data/test/dummy/config/boot.rb +10 -0
  90. data/test/dummy/config/database.yml +5 -0
  91. data/test/dummy/config/environment.rb +5 -0
  92. data/test/dummy/config/environments/development.rb +26 -0
  93. data/test/dummy/config/environments/production.rb +49 -0
  94. data/test/dummy/config/environments/test.rb +35 -0
  95. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  96. data/test/dummy/config/initializers/inflections.rb +10 -0
  97. data/test/dummy/config/initializers/mime_types.rb +5 -0
  98. data/test/dummy/config/initializers/secret_token.rb +7 -0
  99. data/test/dummy/config/initializers/session_store.rb +8 -0
  100. data/test/dummy/config/locales/en.yml +5 -0
  101. data/test/dummy/config/routes.rb +58 -0
  102. data/test/dummy/public/favicon.ico +0 -0
  103. data/test/dummy/public/stylesheets/.gitkeep +0 -0
  104. data/test/dummy/script/rails +6 -0
  105. data/test/experiment/ab_test.rb +859 -0
  106. data/test/experiment/base_test.rb +150 -0
  107. data/test/experiments/age_and_zipcode.rb +19 -0
  108. data/test/experiments/metrics/cheers.rb +3 -0
  109. data/test/experiments/metrics/signups.rb +2 -0
  110. data/test/experiments/metrics/yawns.rb +3 -0
  111. data/test/experiments/null_abc.rb +5 -0
  112. data/test/metric/active_record_test.rb +307 -0
  113. data/test/metric/base_test.rb +293 -0
  114. data/test/metric/google_analytics_test.rb +104 -0
  115. data/test/metric/remote_test.rb +109 -0
  116. data/test/myapp/app/controllers/application_controller.rb +2 -0
  117. data/test/myapp/app/controllers/main_controller.rb +7 -0
  118. data/test/myapp/config/boot.rb +110 -0
  119. data/test/myapp/config/environment.rb +10 -0
  120. data/test/myapp/config/environments/production.rb +0 -0
  121. data/test/myapp/config/routes.rb +3 -0
  122. data/test/passenger_test.rb +45 -0
  123. data/test/playground_test.rb +26 -0
  124. data/test/rails_dashboard_test.rb +37 -0
  125. data/test/rails_helper_test.rb +38 -0
  126. data/test/rails_test.rb +412 -0
  127. data/test/test_helper.rb +168 -0
  128. metadata +268 -0
@@ -0,0 +1,218 @@
1
+ module Vanity
2
+ module Experiment
3
+
4
+ # These methods are available from experiment definitions (files located in
5
+ # the experiments directory, automatically loaded by Vanity). Use these
6
+ # methods to define you experiments, for example:
7
+ # ab_test "New Banner" do
8
+ # alternatives :red, :green, :blue
9
+ # metrics :signup
10
+ # end
11
+ module Definition
12
+
13
+ attr_reader :playground
14
+
15
+ # Defines a new experiment, given the experiment's name, type and
16
+ # definition block.
17
+ def define(name, type, options = nil, &block)
18
+ fail "Experiment #{@experiment_id} already defined in playground" if playground.experiments[@experiment_id]
19
+ klass = Experiment.const_get(type.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase })
20
+ experiment = klass.new(playground, @experiment_id, name, options)
21
+ experiment.instance_eval &block
22
+ experiment.save
23
+ playground.experiments[@experiment_id] = experiment
24
+ end
25
+
26
+ def new_binding(playground, id)
27
+ @playground, @experiment_id = playground, id
28
+ binding
29
+ end
30
+
31
+ end
32
+
33
+ # Base class that all experiment types are derived from.
34
+ class Base
35
+
36
+ class << self
37
+
38
+ # Returns the type of this class as a symbol (e.g. AbTest becomes
39
+ # ab_test).
40
+ def type
41
+ name.split("::").last.gsub(/([a-z])([A-Z])/) { "#{$1}_#{$2}" }.gsub(/([A-Z])([A-Z][a-z])/) { "#{$1}_#{$2}" }.downcase
42
+ end
43
+
44
+ # Playground uses this to load experiment definitions.
45
+ def load(playground, stack, file)
46
+ fail "Circular dependency detected: #{stack.join('=>')}=>#{file}" if stack.include?(file)
47
+ source = File.read(file)
48
+ stack.push file
49
+ id = File.basename(file, ".rb").downcase.gsub(/\W/, "_").to_sym
50
+ context = Object.new
51
+ context.instance_eval do
52
+ extend Definition
53
+ experiment = eval(source, context.new_binding(playground, id), file)
54
+ fail NameError.new("Expected #{file} to define experiment #{id}", id) unless playground.experiments[id]
55
+ return experiment
56
+ end
57
+ rescue
58
+ error = NameError.exception($!.message, id)
59
+ error.set_backtrace $!.backtrace
60
+ raise error
61
+ ensure
62
+ stack.pop
63
+ end
64
+
65
+ end
66
+
67
+ def initialize(playground, id, name, options = nil)
68
+ @playground = playground
69
+ @id, @name = id.to_sym, name
70
+ @options = options || {}
71
+ @identify_block = method(:default_identify)
72
+ end
73
+
74
+ # Human readable experiment name (first argument you pass when creating a
75
+ # new experiment).
76
+ attr_reader :name
77
+ alias :to_s :name
78
+
79
+ # Unique identifier, derived from name experiment name, e.g. "Green
80
+ # Button" becomes :green_button.
81
+ attr_reader :id
82
+
83
+ attr_reader :playground
84
+
85
+ # Time stamp when experiment was created.
86
+ def created_at
87
+ @created_at ||= connection.get_experiment_created_at(@id)
88
+ end
89
+
90
+ # Time stamp when experiment was completed.
91
+ attr_reader :completed_at
92
+
93
+ # Returns the type of this experiment as a symbol (e.g. :ab_test).
94
+ def type
95
+ self.class.type
96
+ end
97
+
98
+ # Defines how we obtain an identity for the current experiment. Usually
99
+ # Vanity gets the identity form a session object (see use_vanity), but
100
+ # there are cases where you want a particular experiment to use a
101
+ # different identity.
102
+ #
103
+ # For example, if all your experiments use current_user and you need one
104
+ # experiment to use the current project:
105
+ # ab_test "Project widget" do
106
+ # alternatives :small, :medium, :large
107
+ # identify do |controller|
108
+ # controller.project.id
109
+ # end
110
+ # end
111
+ def identify(&block)
112
+ fail "Missing block" unless block
113
+ @identify_block = block
114
+ end
115
+
116
+
117
+ # -- Reporting --
118
+
119
+ # Sets or returns description. For example
120
+ # ab_test "Simple" do
121
+ # description "A simple A/B experiment"
122
+ # end
123
+ #
124
+ # puts "Just defined: " + experiment(:simple).description
125
+ def description(text = nil)
126
+ @description = text if text
127
+ @description
128
+ end
129
+
130
+
131
+ # -- Experiment completion --
132
+
133
+ # Define experiment completion condition. For example:
134
+ # complete_if do
135
+ # !score(95).chosen.nil?
136
+ # end
137
+ def complete_if(&block)
138
+ raise ArgumentError, "Missing block" unless block
139
+ raise "complete_if already called on this experiment" if @complete_block
140
+ @complete_block = block
141
+ end
142
+
143
+ # Force experiment to complete.
144
+ def complete!
145
+ @playground.logger.info "vanity: completed experiment #{id}"
146
+ return unless @playground.collecting?
147
+ connection.set_experiment_completed_at @id, Time.now
148
+ @completed_at = connection.get_experiment_completed_at(@id)
149
+ end
150
+
151
+ # Time stamp when experiment was completed.
152
+ def completed_at
153
+ @completed_at ||= connection.get_experiment_completed_at(@id)
154
+ end
155
+
156
+ # Returns true if experiment active, false if completed.
157
+ def active?
158
+ !@playground.collecting? || !connection.is_experiment_completed?(@id)
159
+ end
160
+
161
+ # -- Store/validate --
162
+
163
+ # Get rid of all experiment data.
164
+ def destroy
165
+ connection.destroy_experiment @id
166
+ @created_at = @completed_at = nil
167
+ end
168
+
169
+ # Called by Playground to save the experiment definition.
170
+ def save
171
+ return unless @playground.collecting?
172
+ connection.set_experiment_created_at @id, Time.now
173
+ end
174
+
175
+ protected
176
+
177
+ def identity
178
+ @identify_block.call(Vanity.context)
179
+ end
180
+
181
+ def default_identify(context)
182
+ raise "No Vanity.context" unless context
183
+ raise "Vanity.context does not respond to vanity_identity" unless context.respond_to?(:vanity_identity)
184
+ context.send(:vanity_identity) or raise "Vanity.context.vanity_identity - no identity"
185
+ end
186
+
187
+ # Derived classes call this after state changes that may lead to
188
+ # experiment completing.
189
+ def check_completion!
190
+ if @complete_block
191
+ begin
192
+ complete! if @complete_block.call
193
+ rescue
194
+ warn "Error in Vanity::Experiment::Base: #{$!}"
195
+ end
196
+ end
197
+ end
198
+
199
+ # Returns key for this experiment, or with an argument, return a key
200
+ # using the experiment as the namespace. Examples:
201
+ # key => "vanity:experiments:green_button"
202
+ # key("participants") => "vanity:experiments:green_button:participants"
203
+ def key(name = nil)
204
+ "#{@id}:#{name}"
205
+ end
206
+
207
+ # Shortcut for Vanity.playground.connection
208
+ def connection
209
+ @playground.connection
210
+ end
211
+
212
+ end
213
+ end
214
+
215
+ class NoExperimentError < NameError
216
+ end
217
+
218
+ end
@@ -0,0 +1,16 @@
1
+ # Automatically configure Vanity.
2
+ if defined?(Rails)
3
+ if Rails.const_defined?(:Railtie) # Rails 3
4
+ class Plugin < Rails::Railtie # :nodoc:
5
+ initializer "vanity.require" do |app|
6
+ require 'vanity/frameworks/rails'
7
+ Vanity::Rails.load!
8
+ end
9
+ end
10
+ else
11
+ Rails.configuration.after_initialize do
12
+ require 'vanity/frameworks/rails'
13
+ Vanity::Rails.load!
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,325 @@
1
+ module Vanity
2
+ module Rails #:nodoc:
3
+ def self.load!
4
+ Vanity.playground.load_path = ::Rails.root + Vanity.playground.load_path
5
+ Vanity.playground.logger ||= ::Rails.logger
6
+
7
+ # Do this at the very end of initialization, allowing you to change
8
+ # connection adapter, turn collection on/off, etc.
9
+ ::Rails.configuration.after_initialize do
10
+ Vanity.playground.load!
11
+ end
12
+ end
13
+
14
+ # The use_vanity method will setup the controller to allow testing and
15
+ # tracking of the current user.
16
+ module UseVanity
17
+ # Defines the vanity_identity method and the set_identity_context filter.
18
+ #
19
+ # Call with the name of a method that returns an object whose identity
20
+ # will be used as the Vanity identity. Confusing? Let's try by example:
21
+ #
22
+ # class ApplicationController < ActionController::Base
23
+ # use_vanity :current_user
24
+ #
25
+ # def current_user
26
+ # User.find(session[:user_id])
27
+ # end
28
+ # end
29
+ #
30
+ # If that method (current_user in this example) returns nil, Vanity will
31
+ # set the identity for you (using a cookie to remember it across
32
+ # requests). It also uses this mechanism if you don't provide an
33
+ # identity object, by calling use_vanity with no arguments.
34
+ #
35
+ # Of course you can also use a block:
36
+ # class ProjectController < ApplicationController
37
+ # use_vanity { |controller| controller.params[:project_id] }
38
+ # end
39
+ def use_vanity(symbol = nil, &block)
40
+ if block
41
+ define_method(:vanity_identity) { block.call(self) }
42
+ else
43
+ define_method :vanity_identity do
44
+ return @vanity_identity if @vanity_identity
45
+ if symbol && object = send(symbol)
46
+ @vanity_identity = object.id
47
+ elsif request.get? && params[:_identity]
48
+ @vanity_identity = params[:_identity]
49
+ cookies["vanity_id"] = { :value=>@vanity_identity, :expires=>1.month.from_now }
50
+ @vanity_identity
51
+ elsif response # everyday use
52
+ #conditional for Rails2 support
53
+ secure_random = defined?(SecureRandom) ? SecureRandom : ActiveSupport::SecureRandom
54
+ @vanity_identity = cookies["vanity_id"] || secure_random.hex(16)
55
+ cookie = { :value=>@vanity_identity, :expires=>1.month.from_now }
56
+ # Useful if application and admin console are on separate domains.
57
+ # This only works in Rails 3.x.
58
+ cookie[:domain] ||= ::Rails.application.config.session_options[:domain] if ::Rails.respond_to?(:application)
59
+ cookies["vanity_id"] = cookie
60
+ @vanity_identity
61
+ else # during functional testing
62
+ @vanity_identity = "test"
63
+ end
64
+ end
65
+ end
66
+ protected :vanity_identity
67
+ around_filter :vanity_context_filter
68
+ before_filter :vanity_reload_filter unless ::Rails.configuration.cache_classes
69
+ before_filter :vanity_query_parameter_filter
70
+ after_filter :vanity_track_filter
71
+ end
72
+ protected :use_vanity
73
+ end
74
+
75
+ module UseVanityMailer
76
+ def use_vanity_mailer(symbol = nil)
77
+ # Context is the instance of ActionMailer::Base
78
+ Vanity.context = self
79
+ if symbol && (@object = symbol)
80
+ class << self
81
+ define_method :vanity_identity do
82
+ @vanity_identity = (String === @object ? @object : @object.id)
83
+ end
84
+ end
85
+ else
86
+ class << self
87
+ define_method :vanity_identity do
88
+ secure_random = defined?(SecureRandom) ? SecureRandom : ActiveSupport::SecureRandom
89
+ @vanity_identity = @vanity_identity || secure_random.hex(16)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ protected :use_vanity_mailer
95
+ end
96
+
97
+
98
+ # Vanity needs these filters. They are includes in ActionController and
99
+ # automatically added when you use #use_vanity in your controller.
100
+ module Filters
101
+ # Around filter that sets Vanity.context to controller.
102
+ def vanity_context_filter
103
+ previous, Vanity.context = Vanity.context, self
104
+ yield
105
+ ensure
106
+ Vanity.context = previous
107
+ end
108
+
109
+ # This filter allows user to choose alternative in experiment using query
110
+ # parameter.
111
+ #
112
+ # Each alternative has a unique fingerprint (run vanity list command to
113
+ # see them all). A request with the _vanity query parameter is
114
+ # intercepted, the alternative is chosen, and the user redirected to the
115
+ # same request URL sans _vanity parameter. This only works for GET
116
+ # requests.
117
+ #
118
+ # For example, if the user requests the page
119
+ # http://example.com/?_vanity=2907dac4de, the first alternative of the
120
+ # :null_abc experiment is chosen and the user redirected to
121
+ # http://example.com/.
122
+ def vanity_query_parameter_filter
123
+ if request.get? && params[:_vanity]
124
+ hashes = Array(params.delete(:_vanity))
125
+ Vanity.playground.experiments.each do |id, experiment|
126
+ if experiment.respond_to?(:alternatives)
127
+ experiment.alternatives.each do |alt|
128
+ if hash = hashes.delete(experiment.fingerprint(alt))
129
+ experiment.chooses alt.value
130
+ break
131
+ end
132
+ end
133
+ end
134
+ break if hashes.empty?
135
+ end
136
+ redirect_to url_for(params)
137
+ end
138
+ end
139
+
140
+ # Before filter to reload Vanity experiments/metrics. Enabled when
141
+ # cache_classes is false (typically, testing environment).
142
+ def vanity_reload_filter
143
+ Vanity.playground.reload!
144
+ end
145
+
146
+ # Filter to track metrics
147
+ # pass _track param along to call track! on that alternative
148
+ def vanity_track_filter
149
+ if request.get? && params[:_track]
150
+ track! params[:_track]
151
+ end
152
+ end
153
+
154
+ protected :vanity_context_filter, :vanity_query_parameter_filter, :vanity_reload_filter
155
+ end
156
+
157
+
158
+ # Introduces ab_test helper (controllers and views). Similar to the generic
159
+ # ab_test method, with the ability to capture content (applicable to views,
160
+ # see examples).
161
+ module Helpers
162
+ # This method returns one of the alternative values in the named A/B test.
163
+ #
164
+ # @example A/B two alternatives for a page
165
+ # def index
166
+ # if ab_test(:new_page) # true/false test
167
+ # render action: "new_page"
168
+ # else
169
+ # render action: "index"
170
+ # end
171
+ # end
172
+ # @example Similar, alternative value is page name
173
+ # def index
174
+ # render action: ab_test(:new_page)
175
+ # end
176
+ # @example A/B test inside ERB template (condition)
177
+ # <%= if ab_test(:banner) %>100% less complexity!<% end %>
178
+ # @example A/B test inside ERB template (value)
179
+ # <%= ab_test(:greeting) %> <%= current_user.name %>
180
+ # @example A/B test inside ERB template (capture)
181
+ # <% ab_test :features do |count| %>
182
+ # <%= count %> features to choose from!
183
+ # <% end %>
184
+ def ab_test(name, &block)
185
+ if Vanity.playground.using_js?
186
+ @_vanity_experiments ||= {}
187
+ @_vanity_experiments[name] ||= Vanity.playground.experiment(name.to_sym).choose
188
+ value = @_vanity_experiments[name].value
189
+ else
190
+ value = Vanity.playground.experiment(name.to_sym).choose.value
191
+ end
192
+
193
+ if block
194
+ content = capture(value, &block)
195
+ if defined?(block_called_from_erb?) && block_called_from_erb?(block)
196
+ concat(content)
197
+ else
198
+ content
199
+ end
200
+ else
201
+ value
202
+ end
203
+ end
204
+
205
+ # Generate url with the identity of the current user and the metric to track on click
206
+ def vanity_track_url_for(identity, metric, options = {})
207
+ options = options.merge(:_identity => identity, :_track => metric)
208
+ url_for(options)
209
+ end
210
+
211
+ # Generate url with the fingerprint for the current Vanity experiment
212
+ def vanity_tracking_image(identity, metric, options = {})
213
+ options = options.merge(:controller => :vanity, :action => :image, :_identity => identity, :_track => metric)
214
+ image_tag(url_for(options), :width => "1px", :height => "1px", :alt => "")
215
+ end
216
+
217
+ def vanity_js
218
+ return if @_vanity_experiments.nil?
219
+ javascript_tag do
220
+ render :file => Vanity.template("_vanity.js.erb")
221
+ end
222
+ end
223
+
224
+ def vanity_h(text)
225
+ h(text)
226
+ end
227
+
228
+ def vanity_html_safe(text)
229
+ if text.respond_to?(:html_safe)
230
+ text.html_safe
231
+ else
232
+ text
233
+ end
234
+ end
235
+
236
+ def vanity_simple_format(text, html_options={})
237
+ vanity_html_safe(simple_format(text, html_options))
238
+ end
239
+ end
240
+
241
+
242
+ # Step 1: Add a new resource in config/routes.rb:
243
+ # map.vanity "/vanity/:action/:id", :controller=>:vanity
244
+ #
245
+ # Step 2: Create a new experiments controller:
246
+ # class VanityController < ApplicationController
247
+ # include Vanity::Rails::Dashboard
248
+ # end
249
+ #
250
+ # Step 3: Open your browser to http://localhost:3000/vanity
251
+ module Dashboard
252
+ def index
253
+ render :file=>Vanity.template("_report"), :content_type=>Mime::HTML, :layout=>false
254
+ end
255
+
256
+ def chooses
257
+ exp = Vanity.playground.experiment(params[:e].to_sym)
258
+ exp.chooses(exp.alternatives[params[:a].to_i].value)
259
+ render :file=>Vanity.template("_experiment"), :locals=>{:experiment=>exp}
260
+ end
261
+
262
+ def add_participant
263
+ if params[:e].nil? || params[:e].empty?
264
+ render :status => 404, :nothing => true
265
+ return
266
+ end
267
+ exp = Vanity.playground.experiment(params[:e].to_sym)
268
+ exp.chooses(exp.alternatives[params[:a].to_i].value)
269
+ render :status => 200, :nothing => true
270
+ end
271
+ end
272
+
273
+ module TrackingImage
274
+ def image
275
+ # send image
276
+ send_file(File.expand_path("../images/x.gif", File.dirname(__FILE__)), :type => 'image/gif', :stream => false, :disposition => 'inline')
277
+ end
278
+ end
279
+ end
280
+ end
281
+
282
+
283
+ # Enhance ActionController with use_vanity, filters and helper methods.
284
+ if defined?(ActionController)
285
+ # Include in controller, add view helper methods.
286
+ ActionController::Base.class_eval do
287
+ extend Vanity::Rails::UseVanity
288
+ include Vanity::Rails::Filters
289
+ helper Vanity::Rails::Helpers
290
+ end
291
+
292
+ module ActionController
293
+ class TestCase
294
+ alias :setup_controller_request_and_response_without_vanity :setup_controller_request_and_response
295
+ # Sets Vanity.context to the current controller, so you can do things like:
296
+ # experiment(:simple).chooses(:green)
297
+ def setup_controller_request_and_response
298
+ setup_controller_request_and_response_without_vanity
299
+ Vanity.context = @controller
300
+ end
301
+ end
302
+ end
303
+ end
304
+
305
+ if defined?(ActionMailer)
306
+ # Include in mailer, add view helper methods.
307
+ ActionMailer::Base.class_eval do
308
+ include Vanity::Rails::UseVanityMailer
309
+ include Vanity::Rails::Filters
310
+ helper Vanity::Rails::Helpers
311
+ end
312
+ end
313
+
314
+ # Reconnect whenever we fork under Passenger.
315
+ if defined?(PhusionPassenger)
316
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
317
+ if forked
318
+ begin
319
+ Vanity.playground.reconnect! if Vanity.playground.collecting?
320
+ rescue Exception=>ex
321
+ Rails.logger.error "Error reconnecting: #{ex.to_s}"
322
+ end
323
+ end
324
+ end
325
+ end