vanity 1.8.0 → 1.8.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA512:
3
+ data.tar.gz: cf97c9c3b23e4edf20b47b84fda3f7f31eed9f770ca16c3fd4850bc38b542a2c335fa91c3b68ec912ffa21ccfa14eb14f4b3fc370016bcf797852d6089f99075
4
+ metadata.gz: 2377c5146a59528271b9bfc244236e8d7a3b142676bafbee8d527a0037a7a1cab0bf065e086922f3b354ef4f798a17d0e0f9aaa5e0fd7f04367970a660fd691e
5
+ SHA1:
6
+ data.tar.gz: e713ef94697abdc32fb3dc46e960b83d703f0451
7
+ metadata.gz: ece1cada68e0f549be1ad0cf0fea02f7ce387c75
data/.travis.yml CHANGED
@@ -1,9 +1,13 @@
1
1
  language: ruby
2
2
  bundler_args: --without development
3
3
  script: 'rake appraisal test'
4
+ services:
5
+ - mongodb
4
6
  rvm:
5
7
  - 1.8.7
6
- - 1.9.2
8
+ - 1.9.3
9
+ - 2.0.0
10
+ - ruby-head
7
11
  env:
8
12
  - DB=mongodb
9
13
  - DB=redis
@@ -13,3 +17,7 @@ before_script:
13
17
  - "mysql -e 'create database vanity_test;' >/dev/null"
14
18
  - "rake appraisal:install"
15
19
  #- "psql -c 'create database vanity_test;' -U postgres >/dev/null"
20
+ matrix:
21
+ allow_failures:
22
+ - rvm: 2.0.0
23
+ - rvm: ruby-head
data/Appraisals CHANGED
@@ -1,7 +1,3 @@
1
- appraise "rails2" do
2
- gem "rails", "2.3.14"
3
- end
4
-
5
1
  appraise "rails3" do
6
2
  gem "rails", "3.0.11"
7
3
  gem "passenger", "~>3.0"
data/CHANGELOG CHANGED
@@ -1,9 +1,23 @@
1
+ == 1.8.1 (2013-10-05)
2
+
3
+ Added vanity_experiments helper for rails to return read only copy of active experiments (@iceberg901).
4
+ Fixed support for Mass Assignment on Rails 3.2 (@hcarver).
5
+ Fixed commandlin usage (@phillbaker).
6
+
7
+
8
+ == 1.8.0 (2012-03-12)
9
+
10
+ Improved support for Rails 3.1 and Rails 3.2. Thanks to masverba, Daan and IsaacLewis.
11
+
12
+ Finer Grained Exceptions for Missing Experiments (Nick Barnwell)
13
+
14
+
1
15
  == 1.7.1 (2011-09-30)
2
16
 
3
17
  Minor change in locating config file (Ariel Salomon)
4
18
 
5
19
  Allow for table names in specified :timestamps (Peter Kovacs)
6
-
20
+
7
21
  When a named scope includes a join, created_at can be ambiguous. Use
8
22
 
9
23
  metric "Sky is limit" do
@@ -28,7 +42,7 @@ connections are deprecated in favor of using the connection option with a URI
28
42
 
29
43
  1.7.0 adds support for ActionMailer, for example:
30
44
 
31
- class UserMailer < ActionMailer::Base
45
+ class UserMailer < ActionMailer::Base
32
46
  def invite_email(user)
33
47
  use_vanity_mailer user
34
48
  mail :to => user.email, :subject => ab_test(:invite_subject)
@@ -218,7 +232,7 @@ To view Google Analytics metrics from within Vanity, first make sure you are
218
232
  using Garb. For example, in your Gemfile:
219
233
 
220
234
  gem "vanity", "1.3.0"
221
- gem "garb", "0.5.0"
235
+ gem "garb", "0.5.0"
222
236
 
223
237
  Next, authenticate using your account credentials. For example, in your
224
238
  config/environments/production.rb:
@@ -310,7 +324,7 @@ Much thanks to Ian Sefferman for fixing issues with Ruby 1.8.7 and Rails support
310
324
  * Added: Metrics.
311
325
  * Added: Use Vanity.playground.mock! when running tests and you'd rather not access a live Redis server.
312
326
  * Changed: A/B tests now using metrics for tracking.
313
- * Changed: Now throwing NameError instead of LoadError when failing to load experiment/metric. NameError can be rescued on same line.
327
+ * Changed: Now throwing NameError instead of LoadError when failing to load experiment/metric. NameError can be rescued on same line.
314
328
  * Changed: New, easier URL mapping for Dashboard: map.vanity "/vanity", :controller=>:vanity.
315
329
  * Changed: All tests are green on Ruby 1.8.6, 1.8.7 and 1.9.1.
316
330
  * Changed: Switched to redis-rb from http://github.com/ezmobius/redis-rb.
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
  gemspec
3
3
 
4
4
  gem "appraisal"
data/Gemfile.lock CHANGED
@@ -1,12 +1,12 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- vanity (1.8.0)
4
+ vanity (1.8.1)
5
5
  redis (~> 2.0)
6
6
  redis-namespace (~> 1.0.0)
7
7
 
8
8
  GEM
9
- remote: http://rubygems.org/
9
+ remote: https://rubygems.org/
10
10
  specs:
11
11
  RedCloth (4.2.9)
12
12
  SystemTimer (1.2.3)
@@ -23,7 +23,7 @@ GEM
23
23
  addressable (2.2.7)
24
24
  albino (1.3.3)
25
25
  posix-spawn (>= 0.3.6)
26
- appraisal (0.4.1)
26
+ appraisal (0.5.2)
27
27
  bundler
28
28
  rake
29
29
  bson (1.6.0)
@@ -69,9 +69,9 @@ GEM
69
69
  activeresource (= 2.3.14)
70
70
  activesupport (= 2.3.14)
71
71
  rake (>= 0.8.3)
72
- rake (0.9.2.2)
72
+ rake (10.1.0)
73
73
  redis (2.2.2)
74
- redis-namespace (1.0.3)
74
+ redis-namespace (1.0.4)
75
75
  redis (< 3.0.0)
76
76
  shoulda (3.0.1)
77
77
  shoulda-context (~> 1.0.0)
data/README.rdoc CHANGED
@@ -1,16 +1,21 @@
1
+ = Vanity
2
+ {<img src="https://travis-ci.org/assaf/vanity.png?branch=feature/readme-update" alt="Build Status" />}[https://travis-ci.org/assaf/vanity]
3
+
1
4
  Vanity is an Experiment Driven Development framework for Rails.
2
5
 
3
6
  * All about Vanity: http://vanity.labnotes.org
4
- * On github: http://github.com/assaf/vanity
7
+ * On Github: http://github.com/assaf/vanity
5
8
 
6
9
  http://farm3.static.flickr.com/2540/4099665871_497f274f68_o.jpg
7
10
 
8
11
 
9
12
  == A/B Testing With Rails (In 5 Easy Steps)
10
13
 
11
- <b>Step 1:</b> Start using Vanity in your Rails application:
14
+ === <b>Step 1:</b> Start using Vanity in your Rails application
15
+
16
+ ==== Step 1.1
12
17
 
13
- == Rails 2.x configuration
18
+ ===== Rails 2.x installation
14
19
 
15
20
  Rails::Initializer.run do |config|
16
21
  gem.config "vanity"
@@ -19,39 +24,49 @@ http://farm3.static.flickr.com/2540/4099665871_497f274f68_o.jpg
19
24
  require "vanity"
20
25
  end
21
26
  end
22
-
23
- == Rails 3 configuration
27
+
28
+ ===== Rails 3 installation
24
29
 
25
30
  Add to your Gemfile:
26
-
31
+
27
32
  gem "vanity"
28
33
 
29
- If using a relational database, run the generator and migrations to create the database schema:
34
+ ===== Rails 4 installation
35
+
36
+ Coming soon!
37
+
38
+ ==== Step 1.2
39
+
40
+ Choose a method to store experiment results: for Redis or an external tracking mechanism, migrations aren't needed. Mongo is also available. To use a relational database, via ActiveRecord, run the generator and migrations to create the database schema:
30
41
 
31
42
  $ rails generate vanity
32
43
  $ rake db:migrate
33
-
34
- Add to your application controller:
44
+
45
+ ==== Step 1.3
46
+
47
+ add to your application controller:
35
48
 
36
49
  class ApplicationController < ActionController::Base
37
50
  use_vanity :current_user
38
51
  end
39
52
 
40
- <b>Step 2:</b> Define your first A/B test. This experiment goes in the file <code>experiments/price_options.rb</code>:
53
+ === <b>Step 2:</b> Define your first A/B test
54
+
55
+ This experiment goes in the file <code>experiments/price_options.rb</code>:
41
56
 
42
57
  ab_test "Price options" do
43
58
  description "Mirror, mirror on the wall, who's the better price of all?"
44
59
  alternatives 19, 25, 29
45
60
  metrics :signups
46
61
  end
47
-
48
- NOTE: If using a metric as above ("signups"), there needs to be a corresponding ruby file for that metric. Inside the "experiments" directory create a "metrics" directory with a file called "signups.rb". The contents of the file can describe the signup metric, refer to the "Metrics" Vanity documentation page for an example.
49
62
 
50
- <b>Step 3:</b> Present the different options to your users:
63
+ NOTE: If using a metric as above ("signups"), there needs to be a corresponding ruby file for that metric. Inside the "experiments" directory create a "metrics" directory with a file called "signups.rb". The contents of the file can describe the signup metric, refer to the "Metrics" Vanity documentation page for an example.
64
+
65
+ === <b>Step 3:</b> Present the different options to your users
51
66
 
52
67
  <h2>Get started for only $<%= ab_test :price_options %> a month!</h2>
53
68
 
54
- <b>Step 4:</b> Measure conversion:
69
+ === <b>Step 4:</b> Measure conversion
55
70
 
56
71
  class SignupController < ApplicationController
57
72
  def signup
@@ -65,21 +80,11 @@ Add to your application controller:
65
80
  end
66
81
  end
67
82
 
68
- <b>Step 5:</b> Check the report:
83
+ === <b>Step 5:</b> Check the report:
69
84
 
70
85
  vanity report --output vanity.html
71
-
72
-
73
- == Rails 3
74
-
75
- There is currently an issue with report generation. The vanity-talk Google Group has a couple posts that outline the issue for now. This is one of the posts: http://groups.google.com/group/vanity-talk/browse_thread/thread/343081a72a0cefb6
76
-
77
- If you are collecting data (in development you need to opt-in to this by setting Vanity.playground.collecting = true in environments/development.rb) you can view experiment results with the vanity dashboard instead of the report.
78
-
79
- The vanity dashboard setup instructions with Vanity work for Rails 3.x except the route is different. A Rails 3.x-style route would look like this:
80
-
81
- `match '/vanity(/:action(/:id(.:format)))', :controller=>:vanity`
82
86
 
87
+ Or see instructions for viewing the results using Rails.
83
88
 
84
89
  == Registering participants with Javascript
85
90
 
@@ -89,6 +94,16 @@ If robots or spiders make up a significant portion of your sites traffic they ca
89
94
  * Set Vanity.playground.add_participant_path = '/path/to/vanity/action', this should point to the add_participant path that is added with Vanity::Rails::Dashboard, make sure that this action is available to all users
90
95
  * Add <%= vanity_js %> to any page that needs uses an ab_test. vanity_js needs to be included after your call to ab_test so that it knows which version of the experiment the participant is a member of. The helper will render nothing if the there are no ab_tests running on the current page, so adding vanity_js to the bottom of your layouts is a good option. Keep in mind that if you call use_js! and don't include vanity_js in your view no participants will be recorded.
91
96
 
97
+ == Compatibility Matrix
98
+
99
+ Running Rails 2 on Ruby 2? Vanity probably won't work for you. But here's what does:
100
+
101
+ Persistence: Redis, Mongo, ActiveRecord
102
+ Rails: 2.3, 3, 3.1, 3.2
103
+ Ruby: 1.8.7, 1.9.3
104
+
105
+ NOTE: Support for Rails 4 and Ruby 2.0 are coming soon!
106
+
92
107
  == Contributing
93
108
 
94
109
  * Fork the project
@@ -102,7 +117,7 @@ If robots or spiders make up a significant portion of your sites traffic they ca
102
117
 
103
118
  == Credits/License
104
119
 
105
- Original code, copyright of Assaf Arkin, released under the MIT license.
120
+ Original code, copyright of Assaf Arkin, released under the MIT license.
106
121
 
107
122
  Documentation available under the Creative Commons Attribution license.
108
123
 
data/Rakefile CHANGED
@@ -17,7 +17,7 @@ task :install=>:build do
17
17
  end
18
18
 
19
19
  desc "Push new release to gemcutter and git tag"
20
- task :push=>["test:all", "build"] do
20
+ task :push=>["build"] do
21
21
  sh "git push"
22
22
  puts "Tagging version #{spec.version} .."
23
23
  sh "git tag v#{spec.version}"
@@ -33,7 +33,7 @@ desc "Test everything"
33
33
  task "test:all"=>"test:adapters"
34
34
 
35
35
  # Ruby versions we're testing with.
36
- RUBIES = %w{1.8.7 1.9.2}
36
+ RUBIES = %w{1.8.7 1.9.2 2.0.0}
37
37
 
38
38
  # Use rake test:rubies to run all combination of tests (see test:adapters) using
39
39
  # all the versions of Ruby specified in RUBIES. Or to test a specific version of
data/bin/vanity CHANGED
@@ -1,16 +1,69 @@
1
1
  #!/usr/bin/env ruby
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
- #
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
8
10
 
9
- require 'pathname'
10
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
- Pathname.new(__FILE__).realpath)
11
+ require "optparse"
12
12
 
13
- require 'rubygems'
14
- require 'bundler/setup'
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"
15
21
 
16
- load Gem.bin_path('vanity', 'vanity')
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 puts "No such command: #{cmd}"
68
+ end
69
+ end
@@ -1,6 +1,6 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source :rubygems
3
+ source "https://rubygems.org"
4
4
 
5
5
  gem "appraisal"
6
6
  gem "garb"
@@ -1,12 +1,12 @@
1
1
  PATH
2
- remote: /Users/dougcole/workspace/vanity
2
+ remote: /Users/phill/Development/ruby/vanity
3
3
  specs:
4
- vanity (1.8.0)
4
+ vanity (1.8.1)
5
5
  redis (~> 2.0)
6
6
  redis-namespace (~> 1.0.0)
7
7
 
8
8
  GEM
9
- remote: http://rubygems.org/
9
+ remote: https://rubygems.org/
10
10
  specs:
11
11
  SystemTimer (1.2.3)
12
12
  abstract (1.0.0)
@@ -97,7 +97,7 @@ GEM
97
97
  rdoc (3.12)
98
98
  json (~> 1.4)
99
99
  redis (2.2.2)
100
- redis-namespace (1.0.3)
100
+ redis-namespace (1.0.4)
101
101
  redis (< 3.0.0)
102
102
  shoulda (3.0.1)
103
103
  shoulda-context (~> 1.0.0)
@@ -1,6 +1,6 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source :rubygems
3
+ source "https://rubygems.org"
4
4
 
5
5
  gem "appraisal"
6
6
  gem "garb"
@@ -1,12 +1,12 @@
1
1
  PATH
2
- remote: /Users/dougcole/workspace/vanity
2
+ remote: /Users/phill/Development/ruby/vanity
3
3
  specs:
4
- vanity (1.8.0)
4
+ vanity (1.8.1)
5
5
  redis (~> 2.0)
6
6
  redis-namespace (~> 1.0.0)
7
7
 
8
8
  GEM
9
- remote: http://rubygems.org/
9
+ remote: https://rubygems.org/
10
10
  specs:
11
11
  SystemTimer (1.2.3)
12
12
  actionmailer (3.1.3)
@@ -103,7 +103,7 @@ GEM
103
103
  rdoc (3.12)
104
104
  json (~> 1.4)
105
105
  redis (2.2.2)
106
- redis-namespace (1.0.3)
106
+ redis-namespace (1.0.4)
107
107
  redis (< 3.0.0)
108
108
  shoulda (3.0.1)
109
109
  shoulda-context (~> 1.0.0)
@@ -1,6 +1,6 @@
1
1
  # This file was generated by Appraisal
2
2
 
3
- source :rubygems
3
+ source "https://rubygems.org"
4
4
 
5
5
  gem "appraisal"
6
6
  gem "garb"
@@ -1,12 +1,12 @@
1
1
  PATH
2
- remote: /Users/dougcole/workspace/vanity
2
+ remote: /Users/phill/Development/ruby/vanity
3
3
  specs:
4
- vanity (1.8.0)
4
+ vanity (1.8.1)
5
5
  redis (~> 2.0)
6
6
  redis-namespace (~> 1.0.0)
7
7
 
8
8
  GEM
9
- remote: http://rubygems.org/
9
+ remote: https://rubygems.org/
10
10
  specs:
11
11
  SystemTimer (1.2.3)
12
12
  actionmailer (3.2.1)
@@ -101,7 +101,7 @@ GEM
101
101
  rdoc (3.12)
102
102
  json (~> 1.4)
103
103
  redis (2.2.2)
104
- redis-namespace (1.0.3)
104
+ redis-namespace (1.0.4)
105
105
  redis (< 3.0.0)
106
106
  shoulda (3.0.1)
107
107
  shoulda-context (~> 1.0.0)
@@ -32,6 +32,8 @@ module Vanity
32
32
 
33
33
  # Metric value
34
34
  class VanityMetricValue < VanityRecord
35
+ attr_accessible :date, :index, :value if respond_to?(:attr_accessible)
36
+
35
37
  self.table_name = :vanity_metric_values
36
38
  belongs_to :vanity_metric
37
39
  end
@@ -40,6 +42,7 @@ module Vanity
40
42
  class VanityExperiment < VanityRecord
41
43
  self.table_name = :vanity_experiments
42
44
  has_many :vanity_conversions, :dependent => :destroy
45
+ attr_accessible :experiment_id if respond_to?(:attr_accessible)
43
46
 
44
47
  # Finds or creates the experiment
45
48
  def self.retrieve(experiment)
@@ -61,6 +64,7 @@ module Vanity
61
64
  # Participant model
62
65
  class VanityParticipant < VanityRecord
63
66
  self.table_name = :vanity_participants
67
+ attr_accessible :experiment_id, :identity, :seen, :shown, :converted if respond_to?(:attr_accessible)
64
68
 
65
69
  # Finds the participant by experiment and identity. If
66
70
  # create is true then it will create the participant
@@ -2,30 +2,28 @@ require "erb"
2
2
  require "cgi"
3
3
 
4
4
  module Vanity
5
-
5
+
6
6
  # Render method available to templates (when used by Vanity command line,
7
7
  # outside Rails).
8
8
  module Render
9
-
9
+
10
10
  # Render the named template. Used for reporting and the dashboard.
11
- def render(path, locals = {})
12
- locals[:playground] = self
13
- keys = locals.keys
14
- struct = Struct.new(*keys)
15
- struct.send :include, Render
16
- locals = struct.new(*locals.values_at(*keys))
17
- dir, base = File.split(path)
18
- path = File.join(dir, "_#{base}")
19
- erb = ERB.new(File.read(path), nil, '<>')
20
- erb.filename = path
21
- erb.result(locals.instance_eval { binding })
11
+ def render(path_or_options, locals = {})
12
+ if path_or_options.respond_to?(:keys)
13
+ render_erb(
14
+ path_or_options[:file] || path_or_options[:partial],
15
+ path_or_options[:locals]
16
+ )
17
+ else
18
+ render_erb(path_or_options, locals)
19
+ end
22
20
  end
23
21
 
24
22
  # Escape HTML.
25
23
  def vanity_h(html)
26
24
  CGI.escapeHTML(html.to_s)
27
25
  end
28
-
26
+
29
27
  def vanity_html_safe(text)
30
28
  text
31
29
  end
@@ -38,6 +36,29 @@ module Vanity
38
36
  gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') + # 1 newline -> br
39
37
  "</p>"
40
38
  end
39
+
40
+ protected
41
+
42
+ def render_erb(path, locals = {})
43
+ locals[:playground] = self
44
+ keys = locals.keys
45
+ struct = Struct.new(*keys)
46
+ struct.send :include, Render
47
+ locals = struct.new(*locals.values_at(*keys))
48
+ dir, base = File.split(path)
49
+ path = File.join(dir, partialize(base))
50
+ erb = ERB.new(File.read(path), nil, '<>')
51
+ erb.filename = path
52
+ erb.result(locals.instance_eval { binding })
53
+ end
54
+
55
+ def partialize(template_name)
56
+ if template_name[0] != '_'
57
+ "_#{template_name}"
58
+ else
59
+ template_name
60
+ end
61
+ end
41
62
  end
42
63
 
43
64
  # Commands available when running Vanity from the command line (see bin/vanity).
@@ -34,7 +34,7 @@ module Vanity
34
34
  class Base
35
35
 
36
36
  class << self
37
-
37
+
38
38
  # Returns the type of this class as a symbol (e.g. AbTest becomes
39
39
  # ab_test).
40
40
  def type
@@ -94,7 +94,7 @@ module Vanity
94
94
  def type
95
95
  self.class.type
96
96
  end
97
-
97
+
98
98
  # Defines how we obtain an identity for the current experiment. Usually
99
99
  # Vanity gets the identity form a session object (see use_vanity), but
100
100
  # there are cases where you want a particular experiment to use a
@@ -126,7 +126,7 @@ module Vanity
126
126
  @description = text if text
127
127
  @description
128
128
  end
129
-
129
+
130
130
 
131
131
  # -- Experiment completion --
132
132
 
@@ -152,7 +152,7 @@ module Vanity
152
152
  def completed_at
153
153
  @completed_at ||= connection.get_experiment_completed_at(@id)
154
154
  end
155
-
155
+
156
156
  # Returns true if experiment active, false if completed.
157
157
  def active?
158
158
  !@playground.collecting? || !connection.is_experiment_completed?(@id)
@@ -180,7 +180,7 @@ module Vanity
180
180
 
181
181
  def default_identify(context)
182
182
  raise "No Vanity.context" unless context
183
- raise "Vanity.context does not respond to vanity_identity" unless context.respond_to?(:vanity_identity)
183
+ raise "Vanity.context does not respond to vanity_identity" unless context.respond_to?(:vanity_identity, true)
184
184
  context.send(:vanity_identity) or raise "Vanity.context.vanity_identity - no identity"
185
185
  end
186
186
 
@@ -195,7 +195,7 @@ module Vanity
195
195
  end
196
196
  end
197
197
  end
198
-
198
+
199
199
  # Returns key for this experiment, or with an argument, return a key
200
200
  # using the experiment as the namespace. Examples:
201
201
  # key => "vanity:experiments:green_button"
@@ -208,11 +208,11 @@ module Vanity
208
208
  def connection
209
209
  @playground.connection
210
210
  end
211
-
211
+
212
212
  end
213
213
  end
214
214
 
215
215
  class NoExperimentError < NameError
216
216
  end
217
-
217
+
218
218
  end
@@ -1,5 +1,5 @@
1
1
  module Vanity
2
- module Rails #:nodoc:
2
+ module Rails
3
3
  def self.load!
4
4
  Vanity.playground.load_path = ::Rails.root + Vanity.playground.load_path
5
5
  Vanity.playground.logger ||= ::Rails.logger
@@ -93,8 +93,8 @@ module Vanity
93
93
  end
94
94
  protected :use_vanity_mailer
95
95
  end
96
-
97
-
96
+
97
+
98
98
  # Vanity needs these filters. They are includes in ActionController and
99
99
  # automatically added when you use #use_vanity in your controller.
100
100
  module Filters
@@ -142,7 +142,7 @@ module Vanity
142
142
  def vanity_reload_filter
143
143
  Vanity.playground.reload!
144
144
  end
145
-
145
+
146
146
  # Filter to track metrics
147
147
  # pass _track param along to call track! on that alternative
148
148
  def vanity_track_filter
@@ -150,7 +150,7 @@ module Vanity
150
150
  track! params[:_track]
151
151
  end
152
152
  end
153
-
153
+
154
154
  protected :vanity_context_filter, :vanity_query_parameter_filter, :vanity_reload_filter
155
155
  end
156
156
 
@@ -182,14 +182,10 @@ module Vanity
182
182
  # <%= count %> features to choose from!
183
183
  # <% end %>
184
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
-
185
+ @_vanity_experiments ||= {}
186
+ @_vanity_experiments[name] ||= Vanity.playground.experiment(name.to_sym).choose
187
+ value = @_vanity_experiments[name].value
188
+
193
189
  if block
194
190
  content = capture(value, &block)
195
191
  if defined?(block_called_from_erb?) && block_called_from_erb?(block)
@@ -201,13 +197,13 @@ module Vanity
201
197
  value
202
198
  end
203
199
  end
204
-
200
+
205
201
  # Generate url with the identity of the current user and the metric to track on click
206
202
  def vanity_track_url_for(identity, metric, options = {})
207
203
  options = options.merge(:_identity => identity, :_track => metric)
208
204
  url_for(options)
209
205
  end
210
-
206
+
211
207
  # Generate url with the fingerprint for the current Vanity experiment
212
208
  def vanity_tracking_image(identity, metric, options = {})
213
209
  options = options.merge(:controller => :vanity, :action => :image, :_identity => identity, :_track => metric)
@@ -215,7 +211,7 @@ module Vanity
215
211
  end
216
212
 
217
213
  def vanity_js
218
- return if @_vanity_experiments.nil?
214
+ return if @_vanity_experiments.nil? || @_vanity_experiments.empty?
219
215
  javascript_tag do
220
216
  render :file => Vanity.template("_vanity.js.erb")
221
217
  end
@@ -236,6 +232,34 @@ module Vanity
236
232
  def vanity_simple_format(text, html_options={})
237
233
  vanity_html_safe(simple_format(text, html_options))
238
234
  end
235
+
236
+ # Return a copy of the active experiments on a page
237
+ #
238
+ # @example Render some info about each active experiment in development mode
239
+ # <% if Rails.env.development? %>
240
+ # <% vanity_experiments.each do |name, alternative| %>
241
+ # <span>Participating in <%= name %>, seeing <%= alternative %>:<%= alternative.value %> </span>
242
+ # <% end %>
243
+ # <% end %>
244
+ # @example Push experiment values into javascript for use there
245
+ # <% experiments = vanity_experiments %>
246
+ # <% unless experiments.empty? %>
247
+ # <script>
248
+ # <% experiments.each do |name, alternative| %>
249
+ # myAbTests.<%= name.to_s.camelize(:lower) %> = '<%= alternative.value %>';
250
+ # <% end %>
251
+ # </script>
252
+ # <% end %>
253
+ def vanity_experiments
254
+ @_vanity_experiments ||= {}
255
+ experiments = {}
256
+
257
+ @_vanity_experiments.each do |name, alternative|
258
+ experiments[name] = alternative.clone
259
+ end
260
+
261
+ return experiments
262
+ end
239
263
  end
240
264
 
241
265
 
@@ -269,7 +293,7 @@ module Vanity
269
293
  render :status => 200, :nothing => true
270
294
  end
271
295
  end
272
-
296
+
273
297
  module TrackingImage
274
298
  def image
275
299
  # send image
@@ -1,20 +1,16 @@
1
- <% unless @_vanity_experiments.empty? %>
2
- var httpRequest;
3
- <%
4
- @_vanity_experiments.each do |name, alternative|
5
- %>
6
- var params = "e=<%= name %>&a=<%= alternative.id %>&authenticity_token=" + encodeURIComponent("<%= form_authenticity_token %>");
7
- if (window.XMLHttpRequest) { // Mozilla, Safari, ...
8
- httpRequest = new XMLHttpRequest();
9
- } else if (window.ActiveXObject) { // IE
10
- try { httpRequest = new ActiveXObject("Msxml2.XMLHTTP"); }
11
- catch (e) { }
12
- }
13
- if (httpRequest) {
14
- httpRequest.open('POST', "<%= Vanity.playground.add_participant_path %>", true);
15
- httpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
16
- httpRequest.setRequestHeader("X-Requested-With", "XMLHttpRequest");
17
- httpRequest.send(params);
18
- }
19
- <% end %>
20
- <% end %>
1
+ var httpRequest;
2
+ <% @_vanity_experiments.each do |name, alternative| %>
3
+ var params = "e=<%= name %>&a=<%= alternative.id %>&authenticity_token=" + encodeURIComponent("<%= form_authenticity_token %>");
4
+ if (window.XMLHttpRequest) { // Mozilla, Safari, ...
5
+ httpRequest = new XMLHttpRequest();
6
+ } else if (window.ActiveXObject) { // IE
7
+ try { httpRequest = new ActiveXObject("Msxml2.XMLHTTP"); }
8
+ catch (e) { }
9
+ }
10
+ if (httpRequest) {
11
+ httpRequest.open('POST', "<%= Vanity.playground.add_participant_path %>", true);
12
+ httpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
13
+ httpRequest.setRequestHeader("X-Requested-With", "XMLHttpRequest");
14
+ httpRequest.send(params);
15
+ }
16
+ <% end %>
@@ -1,5 +1,5 @@
1
1
  module Vanity
2
- VERSION = "1.8.0"
2
+ VERSION = "1.8.1"
3
3
 
4
4
  module Version
5
5
  version = VERSION.to_s.split(".").map { |i| i.to_i }
data/test/cli_test.rb ADDED
@@ -0,0 +1,53 @@
1
+ require "test/test_helper"
2
+
3
+ class PlaygroundTest < Test::Unit::TestCase
4
+
5
+ def test_responds_to_version
6
+ (RUBY_VERSION == "1.8.7" ? Object : IO).any_instance.expects(:puts)
7
+ ARGV.clear
8
+ ARGV << '--version'
9
+ load "bin/vanity"
10
+ rescue SystemExit => e
11
+ assert e.status == 0
12
+ end
13
+
14
+ def test_responds_to_help
15
+ (RUBY_VERSION == "1.8.7" ? Object : IO).any_instance.expects(:puts)
16
+ ARGV.clear
17
+ ARGV << '--help'
18
+ load "bin/vanity"
19
+ rescue SystemExit => e
20
+ assert e.status == 0
21
+ end
22
+
23
+ def test_responds_to_list
24
+ require "vanity/commands/list"
25
+ Vanity::Commands.expects(:list)
26
+ ARGV.clear
27
+ ARGV << 'list'
28
+ load "bin/vanity"
29
+ rescue SystemExit => e
30
+ assert e.status == 0
31
+ end
32
+
33
+ def test_responds_to_report
34
+ require "vanity/commands/report"
35
+ Vanity::Commands.expects(:report)
36
+ ARGV.clear
37
+ ARGV << 'report'
38
+ load "bin/vanity"
39
+ rescue SystemExit => e
40
+ assert e.status == 0
41
+ end
42
+
43
+ def test_responds_to_unknown_commands
44
+ require "vanity/commands/upgrade"
45
+ Vanity::Commands.expects(:upgrade)
46
+ ARGV.clear
47
+ ARGV << 'upgrade'
48
+ load "bin/vanity"
49
+ rescue SystemExit => e
50
+ assert e.status == 0
51
+ end
52
+
53
+ end
@@ -35,6 +35,12 @@ module Dummy
35
35
  # JavaScript files you want as :defaults (application.js is always included).
36
36
  # config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
37
37
 
38
+ # Enforce whitelist mode for mass assignment.
39
+ # This will create an empty whitelist of attributes available for mass-assignment for all models
40
+ # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
41
+ # parameters by using an attr_accessible or attr_protected declaration.
42
+ config.active_record.whitelist_attributes = true if ActiveRecord::Base.respond_to?(:whitelist_attributes=)
43
+
38
44
  # Configure the default encoding used in templates for Ruby 1.9.
39
45
  config.encoding = "utf-8"
40
46
 
@@ -21,18 +21,39 @@ class RailsHelperTest < ActionView::TestCase
21
21
  Vanity.playground.use_js!
22
22
  result = ab_test(:pie_or_cake)
23
23
  assert [:pie, :cake].include?(result)
24
- 10.times do
24
+ 10.times do
25
25
  assert result == ab_test(:pie_or_cake)
26
26
  end
27
27
  end
28
-
28
+
29
29
  def test_vanity_track_url_for_returns_url_with_identity_and_metrics
30
30
  self.expects(:url_for).with(:controller => "controller", :action => "action", :_identity => '123', :_track => :sugar_high)
31
31
  vanity_track_url_for("123", :sugar_high, :controller => "controller", :action => "action")
32
32
  end
33
-
33
+
34
34
  def test_vanity_tracking_image
35
35
  self.expects(:url_for).with(:controller => :vanity, :action => :image, :_identity => '123', :_track => :sugar_high).returns("/url")
36
36
  assert_equal image_tag("/url", :width => "1px", :height => "1px", :alt => ""), vanity_tracking_image("123", :sugar_high, options = {})
37
37
  end
38
+
39
+ def test_vanity_experiments
40
+ result = ab_test(:pie_or_cake)
41
+ test_description = ''
42
+ vanity_experiments.each do |name, alternative|
43
+ test_description += "Experiment #{name} chose #{alternative.value}"
44
+ end
45
+ assert_equal test_description, "Experiment pie_or_cake chose #{result}"
46
+ end
47
+
48
+ def test_vanity_experiments_return_is_read_only
49
+ result = ab_test(:pie_or_cake)
50
+ vanity_experiments_returns = vanity_experiments
51
+ vanity_experiments_returns[:some_new_key] = 'some new value'
52
+ assert_equal vanity_experiments.keys.length, 1
53
+ end
54
+
55
+ def test_vanity_experiments_returns_empty_hash_when_no_experiments
56
+ assert_equal vanity_experiments, {}
57
+ end
58
+
38
59
  end
metadata CHANGED
@@ -1,47 +1,46 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: vanity
3
- version: !ruby/object:Gem::Version
4
- version: 1.8.0
5
- prerelease:
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.8.1
6
5
  platform: ruby
7
- authors:
6
+ authors:
8
7
  - Assaf Arkin
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-03-12 00:00:00.000000000Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
11
+
12
+ date: 2013-10-05 00:00:00 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
15
  name: redis
16
- requirement: &70288496150040 !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
16
+ prerelease: false
17
+ requirement: &id001 !ruby/object:Gem::Requirement
18
+ requirements:
19
19
  - - ~>
20
- - !ruby/object:Gem::Version
21
- version: '2.0'
20
+ - !ruby/object:Gem::Version
21
+ version: "2.0"
22
22
  type: :runtime
23
- prerelease: false
24
- version_requirements: *70288496150040
25
- - !ruby/object:Gem::Dependency
23
+ version_requirements: *id001
24
+ - !ruby/object:Gem::Dependency
26
25
  name: redis-namespace
27
- requirement: &70288496149580 !ruby/object:Gem::Requirement
28
- none: false
29
- requirements:
26
+ prerelease: false
27
+ requirement: &id002 !ruby/object:Gem::Requirement
28
+ requirements:
30
29
  - - ~>
31
- - !ruby/object:Gem::Version
30
+ - !ruby/object:Gem::Version
32
31
  version: 1.0.0
33
32
  type: :runtime
34
- prerelease: false
35
- version_requirements: *70288496149580
33
+ version_requirements: *id002
36
34
  description: Mirror, mirror on the wall ...
37
35
  email: assaf@labnotes.org
38
- executables:
36
+ executables:
39
37
  - vanity
40
38
  extensions: []
41
- extra_rdoc_files:
39
+
40
+ extra_rdoc_files:
42
41
  - README.rdoc
43
42
  - CHANGELOG
44
- files:
43
+ files:
45
44
  - .autotest
46
45
  - .gitignore
47
46
  - .rvmrc
@@ -79,8 +78,6 @@ files:
79
78
  - doc/metrics.textile
80
79
  - doc/rails.textile
81
80
  - doc/site.js
82
- - gemfiles/rails2.gemfile
83
- - gemfiles/rails2.gemfile.lock
84
81
  - gemfiles/rails3.gemfile
85
82
  - gemfiles/rails3.gemfile.lock
86
83
  - gemfiles/rails31.gemfile
@@ -125,6 +122,7 @@ files:
125
122
  - lib/vanity/templates/vanity.js
126
123
  - lib/vanity/version.rb
127
124
  - test/adapters/redis_adapter_test.rb
125
+ - test/cli_test.rb
128
126
  - test/dummy/Rakefile
129
127
  - test/dummy/app/controllers/application_controller.rb
130
128
  - test/dummy/app/helpers/application_helper.rb
@@ -176,36 +174,39 @@ files:
176
174
  - vanity.gemspec
177
175
  homepage: http://vanity.labnotes.org
178
176
  licenses: []
177
+
178
+ metadata: {}
179
+
179
180
  post_install_message: To get started run vanity --help
180
- rdoc_options:
181
+ rdoc_options:
181
182
  - --title
182
- - Vanity 1.8.0
183
+ - Vanity 1.8.1
183
184
  - --main
184
185
  - README.rdoc
185
186
  - --webcvs
186
187
  - http://github.com/assaf/vanity
187
- require_paths:
188
+ require_paths:
188
189
  - lib
189
- required_ruby_version: !ruby/object:Gem::Requirement
190
- none: false
191
- requirements:
192
- - - ! '>='
193
- - !ruby/object:Gem::Version
190
+ required_ruby_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
194
  version: 1.8.7
195
- required_rubygems_version: !ruby/object:Gem::Requirement
196
- none: false
197
- requirements:
198
- - - ! '>='
199
- - !ruby/object:Gem::Version
200
- version: '0'
195
+ required_rubygems_version: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ">="
198
+ - !ruby/object:Gem::Version
199
+ version: "0"
201
200
  requirements: []
201
+
202
202
  rubyforge_project:
203
- rubygems_version: 1.8.10
203
+ rubygems_version: 2.1.5
204
204
  signing_key:
205
- specification_version: 3
205
+ specification_version: 4
206
206
  summary: Experience Driven Development framework for Ruby
207
- test_files:
207
+ test_files:
208
208
  - test/adapters/redis_adapter_test.rb
209
+ - test/cli_test.rb
209
210
  - test/dummy/Rakefile
210
211
  - test/dummy/app/controllers/application_controller.rb
211
212
  - test/dummy/app/helpers/application_helper.rb
@@ -1,20 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source :rubygems
4
-
5
- gem "appraisal"
6
- gem "garb"
7
- gem "mocha"
8
- gem "mongo"
9
- gem "bson_ext"
10
- gem "mysql"
11
- gem "passenger", "~>2.0"
12
- gem "pg"
13
- gem "rack"
14
- gem "shoulda"
15
- gem "timecop"
16
- gem "webmock"
17
- gem "SystemTimer", "1.2.3", :platforms=>:mri_18
18
- gem "rails", "2.3.14"
19
-
20
- gemspec :path=>"../"
@@ -1,85 +0,0 @@
1
- PATH
2
- remote: /Users/dougcole/workspace/vanity
3
- specs:
4
- vanity (1.8.0)
5
- redis (~> 2.0)
6
- redis-namespace (~> 1.0.0)
7
-
8
- GEM
9
- remote: http://rubygems.org/
10
- specs:
11
- SystemTimer (1.2.3)
12
- actionmailer (2.3.14)
13
- actionpack (= 2.3.14)
14
- actionpack (2.3.14)
15
- activesupport (= 2.3.14)
16
- rack (~> 1.1.0)
17
- activerecord (2.3.14)
18
- activesupport (= 2.3.14)
19
- activeresource (2.3.14)
20
- activesupport (= 2.3.14)
21
- activesupport (2.3.14)
22
- addressable (2.2.7)
23
- appraisal (0.4.1)
24
- bundler
25
- rake
26
- bson (1.6.0)
27
- bson_ext (1.6.0)
28
- bson (= 1.6.0)
29
- crack (0.3.1)
30
- fastthread (1.0.7)
31
- garb (0.9.1)
32
- activesupport (>= 2.2.0)
33
- crack (>= 0.1.6)
34
- metaclass (0.0.1)
35
- mocha (0.10.5)
36
- metaclass (~> 0.0.1)
37
- mongo (1.6.0)
38
- bson (= 1.6.0)
39
- mysql (2.8.1)
40
- passenger (2.2.15)
41
- fastthread (>= 1.0.1)
42
- rack
43
- rake (>= 0.8.1)
44
- pg (0.13.2)
45
- rack (1.1.3)
46
- rails (2.3.14)
47
- actionmailer (= 2.3.14)
48
- actionpack (= 2.3.14)
49
- activerecord (= 2.3.14)
50
- activeresource (= 2.3.14)
51
- activesupport (= 2.3.14)
52
- rake (>= 0.8.3)
53
- rake (0.9.2.2)
54
- redis (2.2.2)
55
- redis-namespace (1.0.3)
56
- redis (< 3.0.0)
57
- shoulda (3.0.1)
58
- shoulda-context (~> 1.0.0)
59
- shoulda-matchers (~> 1.0.0)
60
- shoulda-context (1.0.0)
61
- shoulda-matchers (1.0.0)
62
- timecop (0.3.5)
63
- webmock (1.8.0)
64
- addressable (>= 2.2.7)
65
- crack (>= 0.1.7)
66
-
67
- PLATFORMS
68
- ruby
69
-
70
- DEPENDENCIES
71
- SystemTimer (= 1.2.3)
72
- appraisal
73
- bson_ext
74
- garb
75
- mocha
76
- mongo
77
- mysql
78
- passenger (~> 2.0)
79
- pg
80
- rack
81
- rails (= 2.3.14)
82
- shoulda
83
- timecop
84
- vanity!
85
- webmock