vanity 1.6.1 → 1.7.0

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 (49) hide show
  1. data/CHANGELOG +28 -0
  2. data/Gemfile +1 -0
  3. data/README.rdoc +10 -3
  4. data/Rakefile +3 -3
  5. data/bin/b2json +16 -0
  6. data/bin/convert_to_should_syntax +16 -0
  7. data/bin/dw +16 -0
  8. data/bin/j2bson +16 -0
  9. data/bin/jekyll +16 -0
  10. data/bin/kramdown +16 -0
  11. data/bin/maruku +16 -0
  12. data/bin/marutex +16 -0
  13. data/bin/mongo_console +16 -0
  14. data/bin/passenger-config +16 -0
  15. data/bin/passenger-install-apache2-module +16 -0
  16. data/bin/passenger-install-nginx-module +16 -0
  17. data/bin/passenger-make-enterprisey +16 -0
  18. data/bin/passenger-memory-stats +16 -0
  19. data/bin/passenger-spawn-server +16 -0
  20. data/bin/passenger-status +16 -0
  21. data/bin/passenger-stress-test +16 -0
  22. data/bin/posix-spawn-benchmark +16 -0
  23. data/bin/rackup +16 -0
  24. data/bin/rails +16 -0
  25. data/bin/rake +16 -0
  26. data/bin/redcloth +16 -0
  27. data/bin/vanity +12 -65
  28. data/bin/yard +16 -0
  29. data/bin/yardoc +16 -0
  30. data/bin/yri +16 -0
  31. data/lib/generators/templates/vanity_migration.rb +53 -0
  32. data/lib/generators/vanity_generator.rb +15 -0
  33. data/lib/vanity/adapters/abstract_adapter.rb +5 -0
  34. data/lib/vanity/adapters/active_record_adapter.rb +10 -69
  35. data/lib/vanity/adapters/redis_adapter.rb +1 -1
  36. data/lib/vanity/frameworks/rails.rb +68 -7
  37. data/lib/vanity/images/x.gif +0 -0
  38. data/lib/vanity/playground.rb +0 -5
  39. data/lib/vanity/templates/vanity.css +1 -1
  40. data/lib/vanity/version.rb +1 -1
  41. data/test/adapters/redis_adapter_test.rb +1 -1
  42. data/test/experiment/ab_test.rb +0 -1
  43. data/test/myapp/log/production.log +522 -0
  44. data/test/myapp/log/test.log +304 -0
  45. data/test/passenger_test.rb +1 -1
  46. data/test/rails_helper_test.rb +8 -0
  47. data/test/rails_test.rb +23 -13
  48. data/test/test_helper.rb +23 -11
  49. metadata +40 -10
data/bin/rake ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rake' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rake', 'rake')
data/bin/redcloth ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'redcloth' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('RedCloth', 'redcloth')
data/bin/vanity CHANGED
@@ -1,69 +1,16 @@
1
1
  #!/usr/bin/env ruby
2
- # Is there a better way to detect Rails?
3
- if File.exist?("config/boot.rb") && File.exist?("config/environment.rb")
4
- require "config/environment"
5
- else
6
- path = File.expand_path("../lib", File.dirname(__FILE__))
7
- $LOAD_PATH.unshift path unless $LOAD_PATH.include?(path)
8
- require "vanity"
9
- end
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'vanity' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
10
8
 
11
- require "optparse"
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
12
 
13
- playground = Vanity.playground
14
- options = Struct.new(:output).new
15
- opts = OptionParser.new("", 24, " ") do |opts|
16
- opts.banner = "Usage: #{File.basename($0)} [options] command\n"
17
- opts.banner << "Commands:\n"
18
- opts.banner << " list List all experiments and metrics\n"
19
- opts.banner << " report Report on all running experiments/metrics\n"
20
- opts.banner << " upgrade Upgrade your database when deploying new release\n"
13
+ require 'rubygems'
14
+ require 'bundler/setup'
21
15
 
22
- opts.separator ""
23
- opts.separator "Reporting options:"
24
- opts.on "--output FILE", "Write report to this file (default: stdout)" do |path|
25
- options.output = path
26
- end
27
-
28
- opts.separator ""
29
- opts.separator "Common options:"
30
- opts.on "--load_path PATH", "Path to experiments directory (default: #{playground.load_path})" do |path|
31
- playground.load_path = path
32
- end
33
- opts.on "-d", "--database url", "Database connection URL (e.g. redis:/localhost:6379)" do |conn|
34
- playground.establish_connection conn
35
- end
36
- opts.on "--redis HOST:PORT:DB", "DEPRECATED: Redis server host (default: localhost:6379)" do |redis|
37
- host, port, db = redis.split(":")
38
- playground.establish_connection "redis:/#{host}:#{port}/#{db}"
39
- end
40
- opts.on_tail "-h", "--help", "Show this message" do
41
- puts opts.to_s.gsub(/^.*DEPRECATED.*$/s, '')
42
- exit
43
- end
44
- opts.on_tail "-v", "--version", "Show version" do
45
- puts "Vanity #{Vanity::Version::STRING}"
46
- exit
47
- end
48
- end
49
-
50
- opts.parse!(ARGV)
51
- if ARGV.empty?
52
- puts opts.banner
53
- exit
54
- end
55
-
56
- ARGV.each do |cmd|
57
- case cmd
58
- when "report"
59
- require "vanity/commands/report"
60
- Vanity::Commands.report options.output
61
- when "list"
62
- require "vanity/commands/list"
63
- Vanity::Commands.list
64
- when "upgrade"
65
- require "vanity/commands/upgrade"
66
- Vanity::Commands.upgrade
67
- else fail "No such command: #{cmd}"
68
- end
69
- end
16
+ load Gem.bin_path('vanity', 'vanity')
data/bin/yard ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'yard' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('yard', 'yard')
data/bin/yardoc ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'yardoc' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('yard', 'yardoc')
data/bin/yri ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'yri' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('yard', 'yri')
@@ -0,0 +1,53 @@
1
+ class VanityMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :vanity_metrics do |t|
4
+ t.string :metric_id
5
+ t.datetime :updated_at
6
+ end
7
+ add_index :vanity_metrics, [:metric_id]
8
+
9
+ create_table :vanity_metric_values do |t|
10
+ t.integer :vanity_metric_id
11
+ t.integer :index
12
+ t.integer :value
13
+ t.string :date
14
+ end
15
+ add_index :vanity_metric_values, [:vanity_metric_id]
16
+
17
+ create_table :vanity_experiments do |t|
18
+ t.string :experiment_id
19
+ t.integer :outcome
20
+ t.datetime :created_at
21
+ t.datetime :completed_at
22
+ end
23
+ add_index :vanity_experiments, [:experiment_id]
24
+
25
+ create_table :vanity_conversions do |t|
26
+ t.integer :vanity_experiment_id
27
+ t.integer :alternative
28
+ t.integer :conversions
29
+ end
30
+ add_index :vanity_conversions, [:vanity_experiment_id, :alternative], :name => "by_experiment_id_and_alternative"
31
+
32
+ create_table :vanity_participants do |t|
33
+ t.string :experiment_id
34
+ t.string :identity
35
+ t.integer :shown
36
+ t.integer :seen
37
+ t.integer :converted
38
+ end
39
+ add_index :vanity_participants, [:experiment_id]
40
+ add_index :vanity_participants, [:experiment_id, :identity], :name => "by_experiment_id_and_identity"
41
+ add_index :vanity_participants, [:experiment_id, :shown], :name => "by_experiment_id_and_shown"
42
+ add_index :vanity_participants, [:experiment_id, :seen], :name => "by_experiment_id_and_seen"
43
+ add_index :vanity_participants, [:experiment_id, :converted], :name => "by_experiment_id_and_converted"
44
+ end
45
+
46
+ def self.down
47
+ drop_table :vanity_metrics
48
+ drop_table :vanity_metric_values
49
+ drop_table :vanity_experiments
50
+ drop_table :vanity_conversions
51
+ drop_table :vanity_participants
52
+ end
53
+ end
@@ -0,0 +1,15 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ class VanityGenerator < Rails::Generators::Base
5
+ include Rails::Generators::Migration
6
+ source_root File.expand_path('../templates', __FILE__)
7
+
8
+ def self.next_migration_number(path)
9
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
10
+ end
11
+
12
+ def create_model_file
13
+ migration_template "vanity_migration.rb", "db/migrate/vanity_migration.rb"
14
+ end
15
+ end
@@ -8,6 +8,11 @@ module Vanity
8
8
  #
9
9
  # @since 1.4.0
10
10
  def establish_connection(spec)
11
+ begin
12
+ require "vanity/adapters/#{spec[:adapter]}_adapter"
13
+ rescue LoadError
14
+ raise "Could not find #{spec[:adapter]} in your load path"
15
+ end
11
16
  adapter_method = "#{spec[:adapter]}_connection"
12
17
  send adapter_method, spec
13
18
  end
@@ -3,7 +3,7 @@ module Vanity
3
3
  class << self
4
4
  # Creates new ActiveRecord connection and returns ActiveRecordAdapter.
5
5
  def active_record_connection(spec)
6
- require 'active_record'
6
+ require "active_record"
7
7
  ActiveRecordAdapter.new(spec)
8
8
  end
9
9
  end
@@ -12,61 +12,6 @@ module Vanity
12
12
  class ActiveRecordAdapter < AbstractAdapter
13
13
  # Base model, stores connection and defines schema
14
14
  class VanityRecord < ActiveRecord::Base
15
- def self.define_schema
16
- # Create a schema table to store the schema version
17
- unless connection.tables.include?('vanity_schema')
18
- connection.create_table :vanity_schema do |t|
19
- t.integer :version
20
- end
21
- end
22
-
23
- # Migrate
24
- unless VanitySchema.find_by_version(1)
25
- connection.create_table :vanity_metrics do |t|
26
- t.string :metric_id
27
- t.datetime :updated_at
28
- end
29
- connection.add_index :vanity_metrics, [:metric_id]
30
-
31
- connection.create_table :vanity_metric_values do |t|
32
- t.integer :vanity_metric_id
33
- t.integer :index
34
- t.integer :value
35
- t.string :date
36
- end
37
- connection.add_index :vanity_metric_values, [:vanity_metric_id]
38
-
39
- connection.create_table :vanity_experiments do |t|
40
- t.string :experiment_id
41
- t.integer :outcome
42
- t.datetime :created_at
43
- t.datetime :completed_at
44
- end
45
- connection.add_index :vanity_experiments, [:experiment_id]
46
-
47
- connection.create_table :vanity_conversions do |t|
48
- t.integer :vanity_experiment_id
49
- t.integer :alternative
50
- t.integer :conversions
51
- end
52
- connection.add_index :vanity_conversions, [:vanity_experiment_id, :alternative], :name => "by_experiment_id_and_alternative"
53
-
54
- connection.create_table :vanity_participants do |t|
55
- t.string :experiment_id
56
- t.string :identity
57
- t.integer :shown
58
- t.integer :seen
59
- t.integer :converted
60
- end
61
- connection.add_index :vanity_participants, [:experiment_id]
62
- connection.add_index :vanity_participants, [:experiment_id, :identity], :name => "by_experiment_id_and_identity"
63
- connection.add_index :vanity_participants, [:experiment_id, :shown], :name => "by_experiment_id_and_shown"
64
- connection.add_index :vanity_participants, [:experiment_id, :seen], :name => "by_experiment_id_and_seen"
65
- connection.add_index :vanity_participants, [:experiment_id, :converted], :name => "by_experiment_id_and_converted"
66
-
67
- VanitySchema.create(:version => 1)
68
- end
69
- end
70
15
  end
71
16
 
72
17
  # Schema model
@@ -122,27 +67,19 @@ module Vanity
122
67
  # passed to create if creating, or will be used to
123
68
  # update the found participant.
124
69
  def self.retrieve(experiment, identity, create = true, update_with = nil)
125
- record = VanityParticipant.first(
126
- :conditions =>
127
- {:experiment_id => experiment.to_s, :identity => identity.to_s})
128
-
129
- if record
70
+ if record = VanityParticipant.first(:conditions=>{ :experiment_id=>experiment.to_s, :identity=>identity.to_s })
130
71
  record.update_attributes(update_with) if update_with
131
72
  elsif create
132
- record = VanityParticipant.create(
133
- {:experiment_id => experiment.to_s,
134
- :identity => identity}.merge(update_with || {}))
73
+ record = VanityParticipant.create({ :experiment_id=>experiment.to_s, :identity=>identity }.merge(update_with || {}))
135
74
  end
136
-
137
75
  record
138
76
  end
139
77
  end
140
78
 
141
79
  def initialize(options)
142
- options[:adapter] = options[:active_record_adapter] if options[:active_record_adapter]
143
-
144
- VanityRecord.establish_connection(options)
145
- VanityRecord.define_schema
80
+ @options = options.inject({}) { |h,kv| h[kv.first.to_s] = kv.last ; h }
81
+ @options["adapter"] = @options["active_record_adapter"] if @options["active_record_adapter"]
82
+ VanityRecord.establish_connection(@options)
146
83
  end
147
84
 
148
85
  def active?
@@ -299,6 +236,10 @@ module Vanity
299
236
  record = VanityExperiment.find_by_experiment_id(experiment.to_s)
300
237
  record && record.destroy
301
238
  end
239
+
240
+ def to_s
241
+ @options.to_s
242
+ end
302
243
  end
303
244
  end
304
245
  end
@@ -28,7 +28,7 @@ module Vanity
28
28
  def disconnect!
29
29
  if redis
30
30
  begin
31
- redis.quit
31
+ redis.client.disconnect
32
32
  rescue Exception => e
33
33
  warn("Error while disconnecting from redis: #{e.message}")
34
34
  end
@@ -44,6 +44,10 @@ module Vanity
44
44
  return @vanity_identity if @vanity_identity
45
45
  if symbol && object = send(symbol)
46
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
47
51
  elsif response # everyday use
48
52
  @vanity_identity = cookies["vanity_id"] || ActiveSupport::SecureRandom.hex(16)
49
53
  cookie = { :value=>@vanity_identity, :expires=>1.month.from_now }
@@ -60,11 +64,33 @@ module Vanity
60
64
  around_filter :vanity_context_filter
61
65
  before_filter :vanity_reload_filter unless ::Rails.configuration.cache_classes
62
66
  before_filter :vanity_query_parameter_filter
67
+ after_filter :vanity_track_filter
63
68
  end
64
69
  protected :use_vanity
65
70
  end
66
71
 
67
-
72
+ module UseVanityMailer
73
+ def use_vanity_mailer(symbol = nil)
74
+ # Context is the instance of ActionMailer::Base
75
+ Vanity.context = self
76
+ if symbol && (@object = symbol)
77
+ class << self
78
+ define_method :vanity_identity do
79
+ @vanity_identity = (String === @object ? @object : @object.id)
80
+ end
81
+ end
82
+ else
83
+ class << self
84
+ define_method :vanity_identity do
85
+ @vanity_identity = @vanity_identity || ActiveSupport::SecureRandom.hex(16)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ protected :use_vanity_mailer
91
+ end
92
+
93
+
68
94
  # Vanity needs these filters. They are includes in ActionController and
69
95
  # automatically added when you use #use_vanity in your controller.
70
96
  module Filters
@@ -112,7 +138,15 @@ module Vanity
112
138
  def vanity_reload_filter
113
139
  Vanity.playground.reload!
114
140
  end
115
-
141
+
142
+ # Filter to track metrics
143
+ # pass _track param along to call track! on that alternative
144
+ def vanity_track_filter
145
+ if request.get? && params[:_track]
146
+ track! params[:_track]
147
+ end
148
+ end
149
+
116
150
  protected :vanity_context_filter, :vanity_query_parameter_filter, :vanity_reload_filter
117
151
  end
118
152
 
@@ -159,6 +193,18 @@ module Vanity
159
193
  value
160
194
  end
161
195
  end
196
+
197
+ # Generate url with the identity of the current user and the metric to track on click
198
+ def vanity_track_url_for(identity, metric, options = {})
199
+ options = options.merge(:_identity => identity, :_track => metric)
200
+ url_for(options)
201
+ end
202
+
203
+ # Generate url with the fingerprint for the current Vanity experiment
204
+ def vanity_tracking_image(identity, metric, options = {})
205
+ options = options.merge(:controller => :vanity, :action => :image, :_identity => identity, :_track => metric)
206
+ image_tag(url_for(options), :width => "1px", :height => "1px", :alt => "")
207
+ end
162
208
 
163
209
  def vanity_js
164
210
  return if @_vanity_experiments.nil?
@@ -206,15 +252,22 @@ module Vanity
206
252
  end
207
253
 
208
254
  def add_participant
209
- if params[:e].nil? || params[:e].empty?
210
- render :status => 404, :nothing => true
211
- return
212
- end
255
+ if params[:e].nil? || params[:e].empty?
256
+ render :status => 404, :nothing => true
257
+ return
258
+ end
213
259
  exp = Vanity.playground.experiment(params[:e])
214
260
  exp.chooses(exp.alternatives[params[:a].to_i].value)
215
261
  render :status => 200, :nothing => true
216
262
  end
217
263
  end
264
+
265
+ module TrackingImage
266
+ def image
267
+ # send image
268
+ send_file(File.expand_path("../images/x.gif", File.dirname(__FILE__)), :type => 'image/gif', :stream => false, :disposition => 'inline')
269
+ end
270
+ end
218
271
  end
219
272
  end
220
273
 
@@ -241,6 +294,14 @@ if defined?(ActionController)
241
294
  end
242
295
  end
243
296
 
297
+ if defined?(ActionMailer)
298
+ # Include in mailer, add view helper methods.
299
+ ActionMailer::Base.class_eval do
300
+ include Vanity::Rails::UseVanityMailer
301
+ include Vanity::Rails::Filters
302
+ helper Vanity::Rails::Helpers
303
+ end
304
+ end
244
305
 
245
306
  # Automatically configure Vanity.
246
307
  if defined?(Rails)
@@ -263,7 +324,7 @@ if defined?(PhusionPassenger)
263
324
  PhusionPassenger.on_event(:starting_worker_process) do |forked|
264
325
  if forked
265
326
  begin
266
- Vanity.playground.establish_connection if Vanity.playground.collecting?
327
+ Vanity.playground.reconnect! if Vanity.playground.collecting?
267
328
  rescue Exception=>ex
268
329
  Rails.logger.error "Error reconnecting: #{ex.to_s}"
269
330
  end