moses-vanity 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. data/.autotest +22 -0
  2. data/.gitignore +7 -0
  3. data/.rvmrc +3 -0
  4. data/.travis.yml +13 -0
  5. data/CHANGELOG +374 -0
  6. data/Gemfile +28 -0
  7. data/MIT-LICENSE +21 -0
  8. data/README.rdoc +108 -0
  9. data/Rakefile +189 -0
  10. data/bin/vanity +16 -0
  11. data/doc/_config.yml +2 -0
  12. data/doc/_layouts/_header.html +34 -0
  13. data/doc/_layouts/page.html +47 -0
  14. data/doc/_metrics.textile +12 -0
  15. data/doc/ab_testing.textile +210 -0
  16. data/doc/configuring.textile +45 -0
  17. data/doc/contributing.textile +93 -0
  18. data/doc/credits.textile +23 -0
  19. data/doc/css/page.css +83 -0
  20. data/doc/css/print.css +43 -0
  21. data/doc/css/syntax.css +7 -0
  22. data/doc/email.textile +129 -0
  23. data/doc/experimental.textile +31 -0
  24. data/doc/faq.textile +8 -0
  25. data/doc/identity.textile +43 -0
  26. data/doc/images/ab_in_dashboard.png +0 -0
  27. data/doc/images/clear_winner.png +0 -0
  28. data/doc/images/price_options.png +0 -0
  29. data/doc/images/sidebar_test.png +0 -0
  30. data/doc/images/signup_metric.png +0 -0
  31. data/doc/images/vanity.png +0 -0
  32. data/doc/index.textile +91 -0
  33. data/doc/metrics.textile +231 -0
  34. data/doc/rails.textile +89 -0
  35. data/doc/site.js +27 -0
  36. data/generators/templates/vanity_migration.rb +53 -0
  37. data/generators/vanity_generator.rb +8 -0
  38. data/lib/generators/templates/vanity_migration.rb +53 -0
  39. data/lib/generators/vanity_generator.rb +15 -0
  40. data/lib/vanity.rb +36 -0
  41. data/lib/vanity/adapters/abstract_adapter.rb +140 -0
  42. data/lib/vanity/adapters/active_record_adapter.rb +248 -0
  43. data/lib/vanity/adapters/mock_adapter.rb +157 -0
  44. data/lib/vanity/adapters/mongodb_adapter.rb +178 -0
  45. data/lib/vanity/adapters/redis_adapter.rb +160 -0
  46. data/lib/vanity/backport.rb +26 -0
  47. data/lib/vanity/commands/list.rb +21 -0
  48. data/lib/vanity/commands/report.rb +64 -0
  49. data/lib/vanity/commands/upgrade.rb +34 -0
  50. data/lib/vanity/experiment/ab_test.rb +507 -0
  51. data/lib/vanity/experiment/base.rb +214 -0
  52. data/lib/vanity/frameworks.rb +16 -0
  53. data/lib/vanity/frameworks/rails.rb +318 -0
  54. data/lib/vanity/helpers.rb +66 -0
  55. data/lib/vanity/images/x.gif +0 -0
  56. data/lib/vanity/metric/active_record.rb +85 -0
  57. data/lib/vanity/metric/base.rb +244 -0
  58. data/lib/vanity/metric/google_analytics.rb +83 -0
  59. data/lib/vanity/metric/remote.rb +53 -0
  60. data/lib/vanity/playground.rb +396 -0
  61. data/lib/vanity/templates/_ab_test.erb +28 -0
  62. data/lib/vanity/templates/_experiment.erb +5 -0
  63. data/lib/vanity/templates/_experiments.erb +7 -0
  64. data/lib/vanity/templates/_metric.erb +14 -0
  65. data/lib/vanity/templates/_metrics.erb +13 -0
  66. data/lib/vanity/templates/_report.erb +27 -0
  67. data/lib/vanity/templates/_vanity.js.erb +20 -0
  68. data/lib/vanity/templates/flot.min.js +1 -0
  69. data/lib/vanity/templates/jquery.min.js +19 -0
  70. data/lib/vanity/templates/vanity.css +26 -0
  71. data/lib/vanity/templates/vanity.js +82 -0
  72. data/lib/vanity/version.rb +11 -0
  73. data/test/adapters/redis_adapter_test.rb +17 -0
  74. data/test/experiment/ab_test.rb +771 -0
  75. data/test/experiment/base_test.rb +150 -0
  76. data/test/experiments/age_and_zipcode.rb +19 -0
  77. data/test/experiments/metrics/cheers.rb +3 -0
  78. data/test/experiments/metrics/signups.rb +2 -0
  79. data/test/experiments/metrics/yawns.rb +3 -0
  80. data/test/experiments/null_abc.rb +5 -0
  81. data/test/metric/active_record_test.rb +277 -0
  82. data/test/metric/base_test.rb +293 -0
  83. data/test/metric/google_analytics_test.rb +104 -0
  84. data/test/metric/remote_test.rb +109 -0
  85. data/test/myapp/app/controllers/application_controller.rb +2 -0
  86. data/test/myapp/app/controllers/main_controller.rb +7 -0
  87. data/test/myapp/config/boot.rb +110 -0
  88. data/test/myapp/config/environment.rb +10 -0
  89. data/test/myapp/config/environments/production.rb +0 -0
  90. data/test/myapp/config/routes.rb +3 -0
  91. data/test/passenger_test.rb +43 -0
  92. data/test/playground_test.rb +26 -0
  93. data/test/rails_dashboard_test.rb +37 -0
  94. data/test/rails_helper_test.rb +36 -0
  95. data/test/rails_test.rb +389 -0
  96. data/test/test_helper.rb +145 -0
  97. data/vanity.gemspec +26 -0
  98. metadata +202 -0
@@ -0,0 +1,214 @@
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
+ 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,318 @@
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
+ @vanity_identity = cookies["vanity_id"] || ActiveSupport::SecureRandom.hex(16)
53
+ cookie = { :value=>@vanity_identity, :expires=>1.month.from_now }
54
+ # Useful if application and admin console are on separate domains.
55
+ # This only works in Rails 3.x.
56
+ cookie[:domain] ||= ::Rails.application.config.session_options[:domain] if ::Rails.respond_to?(:application)
57
+ cookies["vanity_id"] = cookie
58
+ @vanity_identity
59
+ else # during functional testing
60
+ @vanity_identity = "test"
61
+ end
62
+ end
63
+ end
64
+ protected :vanity_identity
65
+ around_filter :vanity_context_filter
66
+ before_filter :vanity_reload_filter unless ::Rails.configuration.cache_classes
67
+ before_filter :vanity_query_parameter_filter
68
+ after_filter :vanity_track_filter
69
+ end
70
+ protected :use_vanity
71
+ end
72
+
73
+ module UseVanityMailer
74
+ def use_vanity_mailer(symbol = nil)
75
+ # Context is the instance of ActionMailer::Base
76
+ Vanity.context = self
77
+ if symbol && (@object = symbol)
78
+ class << self
79
+ define_method :vanity_identity do
80
+ @vanity_identity = (String === @object ? @object : @object.id)
81
+ end
82
+ end
83
+ else
84
+ class << self
85
+ define_method :vanity_identity do
86
+ @vanity_identity = @vanity_identity || ActiveSupport::SecureRandom.hex(16)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ protected :use_vanity_mailer
92
+ end
93
+
94
+
95
+ # Vanity needs these filters. They are includes in ActionController and
96
+ # automatically added when you use #use_vanity in your controller.
97
+ module Filters
98
+ # Around filter that sets Vanity.context to controller.
99
+ def vanity_context_filter
100
+ previous, Vanity.context = Vanity.context, self
101
+ yield
102
+ ensure
103
+ Vanity.context = previous
104
+ end
105
+
106
+ # This filter allows user to choose alternative in experiment using query
107
+ # parameter.
108
+ #
109
+ # Each alternative has a unique fingerprint (run vanity list command to
110
+ # see them all). A request with the _vanity query parameter is
111
+ # intercepted, the alternative is chosen, and the user redirected to the
112
+ # same request URL sans _vanity parameter. This only works for GET
113
+ # requests.
114
+ #
115
+ # For example, if the user requests the page
116
+ # http://example.com/?_vanity=2907dac4de, the first alternative of the
117
+ # :null_abc experiment is chosen and the user redirected to
118
+ # http://example.com/.
119
+ def vanity_query_parameter_filter
120
+ if request.get? && params[:_vanity]
121
+ hashes = Array(params.delete(:_vanity))
122
+ Vanity.playground.experiments.each do |id, experiment|
123
+ if experiment.respond_to?(:alternatives)
124
+ experiment.alternatives.each do |alt|
125
+ if hash = hashes.delete(experiment.fingerprint(alt))
126
+ experiment.chooses alt.value
127
+ break
128
+ end
129
+ end
130
+ end
131
+ break if hashes.empty?
132
+ end
133
+ redirect_to url_for(params)
134
+ end
135
+ end
136
+
137
+ # Before filter to reload Vanity experiments/metrics. Enabled when
138
+ # cache_classes is false (typically, testing environment).
139
+ def vanity_reload_filter
140
+ Vanity.playground.reload!
141
+ end
142
+
143
+ # Filter to track metrics
144
+ # pass _track param along to call track! on that alternative
145
+ def vanity_track_filter
146
+ if request.get? && params[:_track]
147
+ track! params[:_track]
148
+ end
149
+ end
150
+
151
+ protected :vanity_context_filter, :vanity_query_parameter_filter, :vanity_reload_filter
152
+ end
153
+
154
+
155
+ # Introduces ab_test helper (controllers and views). Similar to the generic
156
+ # ab_test method, with the ability to capture content (applicable to views,
157
+ # see examples).
158
+ module Helpers
159
+ # This method returns one of the alternative values in the named A/B test.
160
+ #
161
+ # @example A/B two alternatives for a page
162
+ # def index
163
+ # if ab_test(:new_page) # true/false test
164
+ # render action: "new_page"
165
+ # else
166
+ # render action: "index"
167
+ # end
168
+ # end
169
+ # @example Similar, alternative value is page name
170
+ # def index
171
+ # render action: ab_test(:new_page)
172
+ # end
173
+ # @example A/B test inside ERB template (condition)
174
+ # <%= if ab_test(:banner) %>100% less complexity!<% end %>
175
+ # @example A/B test inside ERB template (value)
176
+ # <%= ab_test(:greeting) %> <%= current_user.name %>
177
+ # @example A/B test inside ERB template (capture)
178
+ # <% ab_test :features do |count| %>
179
+ # <%= count %> features to choose from!
180
+ # <% end %>
181
+ def ab_test(name, &block)
182
+ if Vanity.playground.using_js?
183
+ @_vanity_experiments ||= {}
184
+ @_vanity_experiments[name] ||= Vanity.playground.experiment(name.to_sym).choose
185
+ value = @_vanity_experiments[name].value
186
+ else
187
+ value = Vanity.playground.experiment(name.to_sym).choose.value
188
+ end
189
+
190
+ if block
191
+ content = capture(value, &block)
192
+ block_called_from_erb?(block) ? concat(content) : content
193
+ else
194
+ value
195
+ end
196
+ end
197
+
198
+ # Generate url with the identity of the current user and the metric to track on click
199
+ def vanity_track_url_for(identity, metric, options = {})
200
+ options = options.merge(:_identity => identity, :_track => metric)
201
+ url_for(options)
202
+ end
203
+
204
+ # Generate url with the fingerprint for the current Vanity experiment
205
+ def vanity_tracking_image(identity, metric, options = {})
206
+ options = options.merge(:controller => :vanity, :action => :image, :_identity => identity, :_track => metric)
207
+ image_tag(url_for(options), :width => "1px", :height => "1px", :alt => "")
208
+ end
209
+
210
+ def vanity_js
211
+ return if @_vanity_experiments.nil?
212
+ javascript_tag do
213
+ render Vanity.template("vanity.js.erb")
214
+ end
215
+ end
216
+
217
+ def vanity_h(text)
218
+ h(text)
219
+ end
220
+
221
+ def vanity_html_safe(text)
222
+ if text.respond_to?(:html_safe)
223
+ text.html_safe
224
+ else
225
+ text
226
+ end
227
+ end
228
+
229
+ def vanity_simple_format(text, html_options={})
230
+ vanity_html_safe(simple_format(text, html_options))
231
+ end
232
+ end
233
+
234
+
235
+ # Step 1: Add a new resource in config/routes.rb:
236
+ # map.vanity "/vanity/:action/:id", :controller=>:vanity
237
+ #
238
+ # Step 2: Create a new experiments controller:
239
+ # class VanityController < ApplicationController
240
+ # include Vanity::Rails::Dashboard
241
+ # end
242
+ #
243
+ # Step 3: Open your browser to http://localhost:3000/vanity
244
+ module Dashboard
245
+ def index
246
+ render :file=>Vanity.template("_report"), :content_type=>Mime::HTML, :layout=>false
247
+ end
248
+
249
+ def chooses
250
+ exp = Vanity.playground.experiment(params[:e].to_sym)
251
+ exp.chooses(exp.alternatives[params[:a].to_i].value)
252
+ render :file=>Vanity.template("_experiment"), :locals=>{:experiment=>exp}
253
+ end
254
+
255
+ def add_participant
256
+ if params[:e].nil? || params[:e].empty?
257
+ render :status => 404, :nothing => true
258
+ return
259
+ end
260
+ exp = Vanity.playground.experiment(params[:e].to_sym)
261
+ exp.chooses(exp.alternatives[params[:a].to_i].value)
262
+ render :status => 200, :nothing => true
263
+ end
264
+ end
265
+
266
+ module TrackingImage
267
+ def image
268
+ # send image
269
+ send_file(File.expand_path("../images/x.gif", File.dirname(__FILE__)), :type => 'image/gif', :stream => false, :disposition => 'inline')
270
+ end
271
+ end
272
+ end
273
+ end
274
+
275
+
276
+ # Enhance ActionController with use_vanity, filters and helper methods.
277
+ if defined?(ActionController)
278
+ # Include in controller, add view helper methods.
279
+ ActionController::Base.class_eval do
280
+ extend Vanity::Rails::UseVanity
281
+ include Vanity::Rails::Filters
282
+ helper Vanity::Rails::Helpers
283
+ end
284
+
285
+ module ActionController
286
+ class TestCase
287
+ alias :setup_controller_request_and_response_without_vanity :setup_controller_request_and_response
288
+ # Sets Vanity.context to the current controller, so you can do things like:
289
+ # experiment(:simple).chooses(:green)
290
+ def setup_controller_request_and_response
291
+ setup_controller_request_and_response_without_vanity
292
+ Vanity.context = @controller
293
+ end
294
+ end
295
+ end
296
+ end
297
+
298
+ if defined?(ActionMailer)
299
+ # Include in mailer, add view helper methods.
300
+ ActionMailer::Base.class_eval do
301
+ include Vanity::Rails::UseVanityMailer
302
+ include Vanity::Rails::Filters
303
+ helper Vanity::Rails::Helpers
304
+ end
305
+ end
306
+
307
+ # Reconnect whenever we fork under Passenger.
308
+ if defined?(PhusionPassenger)
309
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
310
+ if forked
311
+ begin
312
+ Vanity.playground.reconnect! if Vanity.playground.collecting?
313
+ rescue Exception=>ex
314
+ Rails.logger.error "Error reconnecting: #{ex.to_s}"
315
+ end
316
+ end
317
+ end
318
+ end