moses-vanity 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
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