vanity 1.2.0 → 1.3.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.
- data/CHANGELOG +34 -0
- data/Gemfile +16 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +10 -5
- data/Rakefile +119 -0
- data/bin/vanity +23 -18
- data/lib/vanity.rb +12 -4
- data/lib/vanity/commands.rb +1 -0
- data/lib/vanity/commands/list.rb +21 -0
- data/lib/vanity/experiment/ab_test.rb +8 -1
- data/lib/vanity/experiment/base.rb +40 -30
- data/lib/vanity/frameworks/rails.rb +222 -0
- data/lib/vanity/metric/active_record.rb +77 -0
- data/lib/vanity/{metric.rb → metric/base.rb} +6 -71
- data/lib/vanity/metric/google_analytics.rb +76 -0
- data/lib/vanity/playground.rb +93 -44
- data/lib/vanity/templates/_metric.erb +12 -7
- data/lib/vanity/templates/vanity.css +1 -0
- data/test/ab_test_test.rb +69 -48
- data/test/experiment_test.rb +29 -15
- data/test/metric_test.rb +104 -0
- data/test/myapp/app/controllers/application_controller.rb +2 -0
- data/test/myapp/app/controllers/main_controller.rb +7 -0
- data/test/myapp/config/boot.rb +110 -0
- data/test/myapp/config/environment.rb +10 -0
- data/test/myapp/config/environments/production.rb +0 -0
- data/test/myapp/config/routes.rb +3 -0
- data/test/myapp/log/production.log +80 -0
- data/test/passenger_test.rb +34 -0
- data/test/rails_test.rb +129 -1
- data/test/test_helper.rb +12 -4
- data/vanity.gemspec +2 -2
- data/vendor/cache/RedCloth-4.2.2.gem +0 -0
- data/vendor/cache/actionmailer-2.3.5.gem +0 -0
- data/vendor/cache/actionpack-2.3.5.gem +0 -0
- data/vendor/cache/activerecord-2.3.5.gem +0 -0
- data/vendor/cache/activeresource-2.3.5.gem +0 -0
- data/vendor/cache/activesupport-2.3.5.gem +0 -0
- data/vendor/cache/autotest-4.2.7.gem +0 -0
- data/vendor/cache/autotest-fsevent-0.2.1.gem +0 -0
- data/vendor/cache/autotest-growl-0.2.0.gem +0 -0
- data/vendor/cache/bundler-0.9.7.gem +0 -0
- data/vendor/cache/classifier-1.3.1.gem +0 -0
- data/vendor/cache/directory_watcher-1.3.1.gem +0 -0
- data/vendor/cache/fastthread-1.0.7.gem +0 -0
- data/vendor/cache/garb-0.7.0.gem +0 -0
- data/vendor/cache/happymapper-0.3.0.gem +0 -0
- data/vendor/cache/jekyll-0.5.7.gem +0 -0
- data/vendor/cache/libxml-ruby-1.1.3.gem +0 -0
- data/vendor/cache/liquid-2.0.0.gem +0 -0
- data/vendor/cache/maruku-0.6.0.gem +0 -0
- data/vendor/cache/mocha-0.9.8.gem +0 -0
- data/vendor/cache/open4-1.0.1.gem +0 -0
- data/vendor/cache/passenger-2.2.9.gem +0 -0
- data/vendor/cache/rack-1.0.1.gem +0 -0
- data/vendor/cache/rails-2.3.5.gem +0 -0
- data/vendor/cache/rake-0.8.7.gem +0 -0
- data/vendor/cache/rubygems-update-1.3.5.gem +0 -0
- data/vendor/cache/shoulda-2.10.3.gem +0 -0
- data/vendor/cache/sqlite3-ruby-1.2.5.gem +0 -0
- data/vendor/cache/stemmer-1.0.1.gem +0 -0
- data/vendor/cache/syntax-1.0.0.gem +0 -0
- data/vendor/cache/sys-uname-0.8.4.gem +0 -0
- data/vendor/cache/timecop-0.3.4.gem +0 -0
- metadata +60 -11
- data/lib/vanity/rails.rb +0 -22
- data/lib/vanity/rails/dashboard.rb +0 -24
- data/lib/vanity/rails/helpers.rb +0 -101
- data/lib/vanity/rails/testing.rb +0 -11
data/CHANGELOG
CHANGED
@@ -1,3 +1,37 @@
|
|
1
|
+
== 1.3.0 (2010-03-01)
|
2
|
+
This release adds support for Google Analytics, AdWords and forking servers (Passenger, Unicorn).
|
3
|
+
|
4
|
+
To view Google Analytics metrics from within Vanity, first make sure you are using Garb. For example, in your Gemfile:
|
5
|
+
|
6
|
+
gem "vanity", "1.3.0"
|
7
|
+
gem "garb", "0.5.0"
|
8
|
+
|
9
|
+
Next, authenticate using your account credentials. For example, in your config/environments/production.rb:
|
10
|
+
|
11
|
+
require "garb"
|
12
|
+
Garb::Session.login('..email..', '..password..', account_type: "GOOGLE") rescue nil
|
13
|
+
|
14
|
+
Last, define Vanity metrics that tap to Google Analytics metrics. For example:
|
15
|
+
|
16
|
+
metric "Acquisition: Visitors" do
|
17
|
+
description "Unique visitors on any given page, as tracked by Google Analytics"
|
18
|
+
google_analytics "UA-1828623-6", :visitors
|
19
|
+
end
|
20
|
+
|
21
|
+
* Added: Support for Google Analytics metrics, thanks to Tony Pitale's Garb and blog post: http://www.viget.com/extend/user-goal-tracking-in-rails-with-vanity-and-google-analytics/
|
22
|
+
* Added: Vanity query parameter that you can use to choose a particular alternative, e.g. to tie an advertisement banner with content of the site.
|
23
|
+
* Added: Command line "vanity list" catalogs all ongoing experiments, their alternatives (and fingerprints) and all metrics.
|
24
|
+
* Added: Playground.reconnect!, particularly useful when forking (Passenger, Unicorn, etc).
|
25
|
+
* Added: Vanity loads Redis configuration from config/redis.yml (if you have such a file).
|
26
|
+
* Changed: New way to specify connection configuration: Vanity.playground.redis = "localhost:6379". Use this instead of the separate host/port/db attribute.
|
27
|
+
* Changed: Rails integration now separates use_vanity method, filters and helpers.
|
28
|
+
* Changed: Explicit vanity_context_filter and vanity_reload_filter so you can skip them, or order filters relative to them.
|
29
|
+
* Fixed: If metric cannot be loaded (e.g. offline, no db access) show error message for that metric but don't break dashboard.
|
30
|
+
* Fixed: AbTest incorrectly calls identify method instead of identity (issue #2)
|
31
|
+
* Fixed: Running vanity command, automatically detects and loads Rails.
|
32
|
+
* Fixed: Vanity now picks up on load_path set from within config/environment.rb.
|
33
|
+
* Removed: Vanity.playground.define is deprecated. Bad choice for a method name. If you need this feature, make a suggestion and let's create a better API.
|
34
|
+
|
1
35
|
== 1.2.0 (2009-12-14)
|
2
36
|
This release introduces metrics backed by ActiveRecord. Use them when your model is already tracking a metric, and you get instant historical data.
|
3
37
|
|
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source "http://rubygems.org/"
|
2
|
+
gem "autotest"
|
3
|
+
gem "autotest-fsevent"
|
4
|
+
gem "autotest-growl"
|
5
|
+
gem "bundler"
|
6
|
+
gem "garb"
|
7
|
+
gem "jekyll"
|
8
|
+
gem "mocha"
|
9
|
+
gem "passenger"
|
10
|
+
gem "rails", "2.3.5"
|
11
|
+
gem "rack", "1.0.1"
|
12
|
+
gem "rubygems-update"
|
13
|
+
gem "shoulda"
|
14
|
+
gem "sqlite3-ruby"
|
15
|
+
gem "timecop"
|
16
|
+
gem "yard"
|
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -11,7 +11,13 @@ http://farm3.static.flickr.com/2540/4099665871_497f274f68_o.jpg
|
|
11
11
|
|
12
12
|
<b>Step 1:</b> Start using Vanity in your Rails application:
|
13
13
|
|
14
|
-
|
14
|
+
Rails::Initializer.run do |config|
|
15
|
+
gem.config "vanity"
|
16
|
+
|
17
|
+
config.after_initialize do
|
18
|
+
require "vanity"
|
19
|
+
end
|
20
|
+
end
|
15
21
|
|
16
22
|
And:
|
17
23
|
|
@@ -47,21 +53,20 @@ And:
|
|
47
53
|
|
48
54
|
<b>Step 5:</b> Check the report:
|
49
55
|
|
50
|
-
vanity --output vanity.html
|
56
|
+
vanity report --output vanity.html
|
51
57
|
|
52
58
|
|
53
59
|
== Building From Source
|
54
60
|
|
55
61
|
To run the test suite for the first time:
|
56
62
|
|
57
|
-
$
|
58
|
-
$ rake
|
63
|
+
$ bundle install
|
64
|
+
$ bundle exec rake
|
59
65
|
|
60
66
|
You can also +rake test+ if you insist on being explicit.
|
61
67
|
|
62
68
|
To build the documentation:
|
63
69
|
|
64
|
-
$ gem install yardoc jekyll
|
65
70
|
$ rake docs
|
66
71
|
$ open html/index.html
|
67
72
|
|
data/Rakefile
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require "rake/testtask"
|
2
|
+
|
3
|
+
spec = Gem::Specification.load(File.expand_path("vanity.gemspec", File.dirname(__FILE__)))
|
4
|
+
|
5
|
+
desc "Build the Gem"
|
6
|
+
task :build=>:test do
|
7
|
+
sh "gem build #{spec.name}.gemspec"
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "Install #{spec.name} locally"
|
11
|
+
task :install=>:build do
|
12
|
+
sudo = "sudo" unless File.writable?( Gem::ConfigMap[:bindir])
|
13
|
+
sh "#{sudo} gem install #{spec.name}-#{spec.version}.gem"
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Push new release to gemcutter and git tag"
|
17
|
+
task :push=>:build do
|
18
|
+
sh "git push"
|
19
|
+
puts "Tagging version #{spec.version} .."
|
20
|
+
sh "git tag v#{spec.version}"
|
21
|
+
sh "git push --tag"
|
22
|
+
puts "Building and pushing gem .."
|
23
|
+
sh "gem push #{spec.name}-#{spec.version}.gem"
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
task :default=>:test
|
28
|
+
desc "Run all tests using Redis mock (also default task)"
|
29
|
+
Rake::TestTask.new do |task|
|
30
|
+
task.test_files = FileList['test/*_test.rb']
|
31
|
+
if Rake.application.options.trace
|
32
|
+
#task.warning = true
|
33
|
+
task.verbose = true
|
34
|
+
elsif Rake.application.options.silent
|
35
|
+
task.ruby_opts << "-W0"
|
36
|
+
else
|
37
|
+
task.verbose = true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "Run all tests using live redis server"
|
42
|
+
task "test:redis" do
|
43
|
+
ENV["REDIS"] = "true"
|
44
|
+
task(:test).invoke
|
45
|
+
end
|
46
|
+
|
47
|
+
task(:clobber) { rm_rf "tmp" }
|
48
|
+
|
49
|
+
|
50
|
+
begin
|
51
|
+
require "yard"
|
52
|
+
YARD::Rake::YardocTask.new(:yardoc) do |task|
|
53
|
+
task.files = FileList["lib/**/*.rb"].exclude("lib/vanity/backport.rb")
|
54
|
+
task.options = "--output", "html/api", "--title", "Vanity #{spec.version}", "--main", "README.rdoc", "--files", "CHANGELOG"
|
55
|
+
end
|
56
|
+
rescue LoadError
|
57
|
+
end
|
58
|
+
|
59
|
+
desc "Jekyll generates the main documentation (sans API)"
|
60
|
+
task(:jekyll) { sh "jekyll", "doc", "html" }
|
61
|
+
|
62
|
+
desc "Create documentation in docs directory (including API)"
|
63
|
+
task :docs=>[:jekyll, :yardoc]
|
64
|
+
desc "Remove temporary files and directories"
|
65
|
+
task(:clobber) { rm_rf "html" }
|
66
|
+
|
67
|
+
desc "Publish documentation to vanity.labnotes.org"
|
68
|
+
task :publish=>[:clobber, :docs] do
|
69
|
+
sh "rsync -cr --del --progress html/ labnotes.org:/var/www/vanity/"
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
task :report do
|
74
|
+
$LOAD_PATH.unshift "lib"
|
75
|
+
require "vanity"
|
76
|
+
require "timecop"
|
77
|
+
Vanity.playground.load_path = "test/experiments"
|
78
|
+
Vanity.playground.experiments.values.each(&:destroy)
|
79
|
+
Vanity.playground.metrics.values.each(&:destroy!)
|
80
|
+
Vanity.playground.reload!
|
81
|
+
|
82
|
+
# Control 182 35 19.23% N/A
|
83
|
+
# Treatment A 180 45 25.00% 1.33
|
84
|
+
# Treatment B 189 28 14.81% -1.13
|
85
|
+
# Treatment C 188 61 32.45% 2.94
|
86
|
+
Vanity.playground.experiment(:null_abc).instance_eval do
|
87
|
+
fake nil=>[182,35], :red=>[180,45], :green=>[189,28], :blue=>[188,61]
|
88
|
+
@created_at = (Date.today - 40).to_time
|
89
|
+
@completed_at = (Date.today - 35).to_time
|
90
|
+
end
|
91
|
+
|
92
|
+
Vanity.playground.experiment(:age_and_zipcode).instance_eval do
|
93
|
+
fake false=>[80,35], true=>[84,32]
|
94
|
+
@created_at = (Date.today - 30).to_time
|
95
|
+
@completed_at = (Date.today - 15).to_time
|
96
|
+
end
|
97
|
+
|
98
|
+
Vanity.context = Object.new
|
99
|
+
Vanity.context.instance_eval { def vanity_identity ; 0 ; end }
|
100
|
+
signups = 50
|
101
|
+
(Date.today - 90..Date.today).each do |date|
|
102
|
+
Timecop.travel date do
|
103
|
+
signups += rand(15) - 5
|
104
|
+
Vanity.playground.track! :signups, signups
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
cheers, yawns = 0, 0
|
109
|
+
(Date.today - 80..Date.today).each do |date|
|
110
|
+
Timecop.travel date do
|
111
|
+
cheers = cheers - 5 + rand(20)
|
112
|
+
Vanity.playground.track! :yawns, cheers
|
113
|
+
yawns = yawns - 5 + rand(30)
|
114
|
+
Vanity.playground.track! :cheers, yawns
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
Vanity::Commands.report ENV["OUTPUT"]
|
119
|
+
end
|
data/bin/vanity
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
|
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
|
4
10
|
|
5
|
-
require "vanity"
|
6
11
|
require "optparse"
|
7
12
|
|
8
13
|
playground = Vanity.playground
|
@@ -10,24 +15,24 @@ options = Struct.new(:output).new
|
|
10
15
|
opts = OptionParser.new("", 24, " ") do |opts|
|
11
16
|
opts.banner = "Usage: #{File.basename($0)} [options] command\n"
|
12
17
|
opts.banner << "Commands:\n"
|
13
|
-
opts.banner << "
|
14
|
-
|
15
|
-
opts.separator ""
|
16
|
-
opts.separator "General options:"
|
17
|
-
opts.on("--path PATH", "Path to experiments directory (default: #{playground.load_path})") { |v| playground.load_path = v }
|
18
|
-
opts.on("--output FILE", "Write report to this file (default: stdout)") { |v| options.output = v }
|
18
|
+
opts.banner << " list List all experiments and metrics\n"
|
19
|
+
opts.banner << " report Report on all running experiments/metrics\n"
|
19
20
|
|
20
21
|
opts.separator ""
|
21
|
-
opts.separator "
|
22
|
-
opts.on
|
23
|
-
|
24
|
-
|
25
|
-
opts.on("--password PWD", "Redis database password") { |v| playground.password = v }
|
26
|
-
opts.on("--namespace NS", "Redis namespace (default: #{playground.namespace})") { |v| playground.namespace = v }
|
22
|
+
opts.separator "Reporting options:"
|
23
|
+
opts.on "--output FILE", "Write report to this file (default: stdout)" do |path|
|
24
|
+
options.output = path
|
25
|
+
end
|
27
26
|
|
28
27
|
opts.separator ""
|
29
28
|
opts.separator "Common options:"
|
30
|
-
opts.
|
29
|
+
opts.on "--load_path PATH", "Path to experiments directory (default: #{playground.load_path})" do |path|
|
30
|
+
playground.load_path = path
|
31
|
+
end
|
32
|
+
opts.on "--redis HOST:PORT:DB", "Redis server host (default: localhost:6379)" do |redis|
|
33
|
+
playground.redis = redis
|
34
|
+
end
|
35
|
+
opts.on_tail "-h", "--help", "Show this message" do
|
31
36
|
puts opts.to_s.gsub(/^.*DEPRECATED.*$/s, '')
|
32
37
|
exit
|
33
38
|
end
|
@@ -45,8 +50,8 @@ end
|
|
45
50
|
|
46
51
|
ARGV.each do |cmd|
|
47
52
|
case cmd
|
48
|
-
when "report"
|
49
|
-
Vanity::Commands.
|
53
|
+
when "report"; Vanity::Commands.report options.output
|
54
|
+
when "list"; Vanity::Commands.list
|
50
55
|
else fail "No such command: #{cmd}"
|
51
56
|
end
|
52
57
|
end
|
data/lib/vanity.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), "../vendor/redis-rb/lib")
|
2
|
-
require "redis"
|
3
1
|
require "date"
|
4
2
|
require "time"
|
5
3
|
require "logger"
|
6
4
|
|
5
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), "../vendor/redis-rb/lib")
|
6
|
+
autoload :Redis, "redis"
|
7
|
+
|
7
8
|
# All the cool stuff happens in other places.
|
8
9
|
# @see Vanity::Helper
|
9
10
|
# @see Vanity::Rails
|
@@ -19,14 +20,21 @@ module Vanity
|
|
19
20
|
PATCH = version[2]
|
20
21
|
STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
|
21
22
|
end
|
23
|
+
|
22
24
|
end
|
23
25
|
|
24
26
|
require "vanity/backport" if RUBY_VERSION < "1.9"
|
25
|
-
|
27
|
+
# Metrics.
|
28
|
+
require "vanity/metric/base"
|
29
|
+
require "vanity/metric/active_record"
|
30
|
+
require "vanity/metric/google_analytics"
|
31
|
+
# Experiments.
|
26
32
|
require "vanity/experiment/base"
|
27
33
|
require "vanity/experiment/ab_test"
|
34
|
+
# Playground.
|
28
35
|
require "vanity/playground"
|
29
36
|
require "vanity/helpers"
|
30
37
|
Vanity.autoload :MockRedis, "vanity/mock_redis"
|
31
38
|
Vanity.autoload :Commands, "vanity/commands"
|
32
|
-
|
39
|
+
# Integration with various frameworks.
|
40
|
+
require "vanity/frameworks/rails" if defined?(Rails)
|
data/lib/vanity/commands.rb
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Vanity
|
2
|
+
module Commands
|
3
|
+
class << self
|
4
|
+
# Lists all experiments and metrics.
|
5
|
+
def list
|
6
|
+
Vanity.playground.experiments.each do |id, experiment|
|
7
|
+
puts "experiment :%-.20s (%-.40s)" % [id, experiment.name]
|
8
|
+
if experiment.respond_to?(:alternatives)
|
9
|
+
experiment.alternatives.each do |alt|
|
10
|
+
hash = experiment.fingerprint(alt)
|
11
|
+
puts " %s: %-40.40s (%s)" % [alt.name, alt.value, hash]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
Vanity.playground.metrics.each do |id, metric|
|
16
|
+
puts "metric :%-.20s (%-.40s)" % [id, metric.name]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -195,11 +195,18 @@ module Vanity
|
|
195
195
|
check_completion!
|
196
196
|
end
|
197
197
|
else
|
198
|
-
index = redis[key("outcome")] || alternative_for(
|
198
|
+
index = redis[key("outcome")] || alternative_for(identity)
|
199
199
|
end
|
200
200
|
@alternatives[index.to_i]
|
201
201
|
end
|
202
202
|
|
203
|
+
# Returns fingerprint (hash) for given alternative. Can be used to lookup
|
204
|
+
# alternative for experiment without revealing what values are available
|
205
|
+
# (e.g. choosing alternative from HTTP query parameter).
|
206
|
+
def fingerprint(alternative)
|
207
|
+
Digest::MD5.hexdigest("#{id}:#{alternative.id}")[-10,10]
|
208
|
+
end
|
209
|
+
|
203
210
|
|
204
211
|
# -- Testing --
|
205
212
|
|
@@ -15,11 +15,16 @@ module Vanity
|
|
15
15
|
# Defines a new experiment, given the experiment's name, type and
|
16
16
|
# definition block.
|
17
17
|
def define(name, type, options = nil, &block)
|
18
|
-
|
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
|
19
24
|
end
|
20
25
|
|
21
|
-
def
|
22
|
-
@playground = playground
|
26
|
+
def new_binding(playground, id)
|
27
|
+
@playground, @experiment_id = playground, id
|
23
28
|
binding
|
24
29
|
end
|
25
30
|
|
@@ -37,16 +42,16 @@ module Vanity
|
|
37
42
|
end
|
38
43
|
|
39
44
|
# Playground uses this to load experiment definitions.
|
40
|
-
def load(playground, stack,
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
45
50
|
context = Object.new
|
46
51
|
context.instance_eval do
|
47
52
|
extend Definition
|
48
|
-
experiment = eval(source, context.
|
49
|
-
fail NameError.new("Expected #{
|
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]
|
50
55
|
experiment
|
51
56
|
end
|
52
57
|
rescue
|
@@ -59,12 +64,12 @@ module Vanity
|
|
59
64
|
|
60
65
|
end
|
61
66
|
|
62
|
-
def initialize(playground, id, name, options
|
67
|
+
def initialize(playground, id, name, options = nil)
|
63
68
|
@playground = playground
|
64
69
|
@id, @name = id.to_sym, name
|
65
70
|
@options = options || {}
|
66
71
|
@namespace = "#{@playground.namespace}:#{@id}"
|
67
|
-
@identify_block =
|
72
|
+
@identify_block = method(:default_identify)
|
68
73
|
end
|
69
74
|
|
70
75
|
# Human readable experiment name (first argument you pass when creating a
|
@@ -101,14 +106,10 @@ module Vanity
|
|
101
106
|
# end
|
102
107
|
# end
|
103
108
|
def identify(&block)
|
109
|
+
fail "Missing block" unless block
|
104
110
|
@identify_block = block
|
105
111
|
end
|
106
112
|
|
107
|
-
def identity
|
108
|
-
@identify_block.call(Vanity.context)
|
109
|
-
end
|
110
|
-
protected :identity
|
111
|
-
|
112
113
|
|
113
114
|
# -- Reporting --
|
114
115
|
|
@@ -136,19 +137,6 @@ module Vanity
|
|
136
137
|
@complete_block = block
|
137
138
|
end
|
138
139
|
|
139
|
-
# Derived classes call this after state changes that may lead to
|
140
|
-
# experiment completing.
|
141
|
-
def check_completion!
|
142
|
-
if @complete_block
|
143
|
-
begin
|
144
|
-
complete! if @complete_block.call
|
145
|
-
rescue
|
146
|
-
# TODO: logging
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
protected :check_completion!
|
151
|
-
|
152
140
|
# Force experiment to complete.
|
153
141
|
def complete!
|
154
142
|
redis.setnx key(:completed_at), Time.now.to_i
|
@@ -184,6 +172,28 @@ module Vanity
|
|
184
172
|
|
185
173
|
protected
|
186
174
|
|
175
|
+
def identity
|
176
|
+
@identify_block.call(Vanity.context)
|
177
|
+
end
|
178
|
+
|
179
|
+
def default_identify(context)
|
180
|
+
raise "No Vanity.context" unless context
|
181
|
+
raise "Vanity.context does not respond to vanity_identity" unless context.respond_to?(:vanity_identity)
|
182
|
+
context.vanity_identity or raise "Vanity.context.vanity_identity - no identity"
|
183
|
+
end
|
184
|
+
|
185
|
+
# Derived classes call this after state changes that may lead to
|
186
|
+
# experiment completing.
|
187
|
+
def check_completion!
|
188
|
+
if @complete_block
|
189
|
+
begin
|
190
|
+
complete! if @complete_block.call
|
191
|
+
rescue
|
192
|
+
# TODO: logging
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
187
197
|
# Returns key for this experiment, or with an argument, return a key
|
188
198
|
# using the experiment as the namespace. Examples:
|
189
199
|
# key => "vanity:experiments:green_button"
|