a_b 0.1.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 (173) hide show
  1. data/MIT-LICENSE +18 -0
  2. data/README.markdown +4 -0
  3. data/Rakefile +74 -0
  4. data/config.ru +3 -0
  5. data/config/database.example.yml +6 -0
  6. data/config/externals.yml +6 -0
  7. data/config/mail.example.yml +32 -0
  8. data/db/migrate/001_a_b_tests.rb +14 -0
  9. data/db/migrate/002_a_b_variants.rb +19 -0
  10. data/db/migrate/003_a_b_users.rb +25 -0
  11. data/db/migrate/004_a_b_tokens.rb +12 -0
  12. data/features/example.feature +10 -0
  13. data/features/support/env.rb +35 -0
  14. data/features/support/rspec.rb +11 -0
  15. data/features/support/webrat.rb +3 -0
  16. data/gemspec.rb +41 -0
  17. data/lib/a_b.rb +31 -0
  18. data/lib/a_b/boot.rb +30 -0
  19. data/lib/a_b/controller/api.rb +32 -0
  20. data/lib/a_b/controller/application.rb +6 -0
  21. data/lib/a_b/controller/index.rb +16 -0
  22. data/lib/a_b/controller/sessions.rb +28 -0
  23. data/lib/a_b/controller/tests.rb +37 -0
  24. data/lib/a_b/controller/variants.rb +14 -0
  25. data/lib/a_b/helper/api.rb +8 -0
  26. data/lib/a_b/helper/application.rb +56 -0
  27. data/lib/a_b/helper/index.rb +12 -0
  28. data/lib/a_b/model/a_b_test.rb +65 -0
  29. data/lib/a_b/model/a_b_variant.rb +131 -0
  30. data/lib/a_b/model/token.rb +22 -0
  31. data/lib/a_b/model/user.rb +5 -0
  32. data/lib/a_b/model/user_session.rb +4 -0
  33. data/lib/a_b/view/index.haml +129 -0
  34. data/lib/a_b/view/index.sass +55 -0
  35. data/lib/a_b/view/layout.haml +15 -0
  36. data/lib/a_b/view/layout.sass +34 -0
  37. data/lib/a_b/view/log_in.haml +15 -0
  38. data/lib/a_b/view/log_in.sass +11 -0
  39. data/public/css/blueprint/ie.css +35 -0
  40. data/public/css/blueprint/print.css +29 -0
  41. data/public/css/blueprint/screen.css +257 -0
  42. data/public/js/index.js +126 -0
  43. data/public/js/jquery.js +19 -0
  44. data/public/js/visit.js +59 -0
  45. data/script/console +2 -0
  46. data/script/env.rb +30 -0
  47. data/spec/spec.opts +1 -0
  48. data/spec/spec_helper.rb +16 -0
  49. data/vendor/authlogic/CHANGELOG.rdoc +345 -0
  50. data/vendor/authlogic/LICENSE +20 -0
  51. data/vendor/authlogic/README.rdoc +246 -0
  52. data/vendor/authlogic/Rakefile +42 -0
  53. data/vendor/authlogic/VERSION.yml +5 -0
  54. data/vendor/authlogic/authlogic.gemspec +217 -0
  55. data/vendor/authlogic/generators/session/session_generator.rb +9 -0
  56. data/vendor/authlogic/generators/session/templates/session.rb +2 -0
  57. data/vendor/authlogic/init.rb +1 -0
  58. data/vendor/authlogic/lib/authlogic.rb +57 -0
  59. data/vendor/authlogic/lib/authlogic/acts_as_authentic/base.rb +107 -0
  60. data/vendor/authlogic/lib/authlogic/acts_as_authentic/email.rb +110 -0
  61. data/vendor/authlogic/lib/authlogic/acts_as_authentic/logged_in_status.rb +60 -0
  62. data/vendor/authlogic/lib/authlogic/acts_as_authentic/login.rb +141 -0
  63. data/vendor/authlogic/lib/authlogic/acts_as_authentic/magic_columns.rb +24 -0
  64. data/vendor/authlogic/lib/authlogic/acts_as_authentic/password.rb +344 -0
  65. data/vendor/authlogic/lib/authlogic/acts_as_authentic/perishable_token.rb +105 -0
  66. data/vendor/authlogic/lib/authlogic/acts_as_authentic/persistence_token.rb +68 -0
  67. data/vendor/authlogic/lib/authlogic/acts_as_authentic/restful_authentication.rb +61 -0
  68. data/vendor/authlogic/lib/authlogic/acts_as_authentic/session_maintenance.rb +139 -0
  69. data/vendor/authlogic/lib/authlogic/acts_as_authentic/single_access_token.rb +65 -0
  70. data/vendor/authlogic/lib/authlogic/acts_as_authentic/validations_scope.rb +32 -0
  71. data/vendor/authlogic/lib/authlogic/authenticates_many/association.rb +42 -0
  72. data/vendor/authlogic/lib/authlogic/authenticates_many/base.rb +55 -0
  73. data/vendor/authlogic/lib/authlogic/controller_adapters/abstract_adapter.rb +67 -0
  74. data/vendor/authlogic/lib/authlogic/controller_adapters/merb_adapter.rb +30 -0
  75. data/vendor/authlogic/lib/authlogic/controller_adapters/rails_adapter.rb +48 -0
  76. data/vendor/authlogic/lib/authlogic/controller_adapters/sinatra_adapter.rb +61 -0
  77. data/vendor/authlogic/lib/authlogic/crypto_providers/aes256.rb +43 -0
  78. data/vendor/authlogic/lib/authlogic/crypto_providers/bcrypt.rb +90 -0
  79. data/vendor/authlogic/lib/authlogic/crypto_providers/md5.rb +34 -0
  80. data/vendor/authlogic/lib/authlogic/crypto_providers/sha1.rb +35 -0
  81. data/vendor/authlogic/lib/authlogic/crypto_providers/sha256.rb +50 -0
  82. data/vendor/authlogic/lib/authlogic/crypto_providers/sha512.rb +50 -0
  83. data/vendor/authlogic/lib/authlogic/crypto_providers/wordpress.rb +43 -0
  84. data/vendor/authlogic/lib/authlogic/i18n.rb +83 -0
  85. data/vendor/authlogic/lib/authlogic/i18n/translator.rb +15 -0
  86. data/vendor/authlogic/lib/authlogic/random.rb +33 -0
  87. data/vendor/authlogic/lib/authlogic/regex.rb +25 -0
  88. data/vendor/authlogic/lib/authlogic/session/activation.rb +58 -0
  89. data/vendor/authlogic/lib/authlogic/session/active_record_trickery.rb +61 -0
  90. data/vendor/authlogic/lib/authlogic/session/base.rb +37 -0
  91. data/vendor/authlogic/lib/authlogic/session/brute_force_protection.rb +96 -0
  92. data/vendor/authlogic/lib/authlogic/session/callbacks.rb +88 -0
  93. data/vendor/authlogic/lib/authlogic/session/cookies.rb +130 -0
  94. data/vendor/authlogic/lib/authlogic/session/existence.rb +93 -0
  95. data/vendor/authlogic/lib/authlogic/session/foundation.rb +63 -0
  96. data/vendor/authlogic/lib/authlogic/session/http_auth.rb +58 -0
  97. data/vendor/authlogic/lib/authlogic/session/id.rb +41 -0
  98. data/vendor/authlogic/lib/authlogic/session/klass.rb +78 -0
  99. data/vendor/authlogic/lib/authlogic/session/magic_columns.rb +95 -0
  100. data/vendor/authlogic/lib/authlogic/session/magic_states.rb +59 -0
  101. data/vendor/authlogic/lib/authlogic/session/params.rb +101 -0
  102. data/vendor/authlogic/lib/authlogic/session/password.rb +240 -0
  103. data/vendor/authlogic/lib/authlogic/session/perishable_token.rb +18 -0
  104. data/vendor/authlogic/lib/authlogic/session/persistence.rb +70 -0
  105. data/vendor/authlogic/lib/authlogic/session/priority_record.rb +34 -0
  106. data/vendor/authlogic/lib/authlogic/session/scopes.rb +101 -0
  107. data/vendor/authlogic/lib/authlogic/session/session.rb +62 -0
  108. data/vendor/authlogic/lib/authlogic/session/timeout.rb +82 -0
  109. data/vendor/authlogic/lib/authlogic/session/unauthorized_record.rb +50 -0
  110. data/vendor/authlogic/lib/authlogic/session/validation.rb +82 -0
  111. data/vendor/authlogic/lib/authlogic/test_case.rb +120 -0
  112. data/vendor/authlogic/lib/authlogic/test_case/mock_controller.rb +45 -0
  113. data/vendor/authlogic/lib/authlogic/test_case/mock_cookie_jar.rb +14 -0
  114. data/vendor/authlogic/lib/authlogic/test_case/mock_logger.rb +10 -0
  115. data/vendor/authlogic/lib/authlogic/test_case/mock_request.rb +19 -0
  116. data/vendor/authlogic/lib/authlogic/test_case/rails_request_adapter.rb +30 -0
  117. data/vendor/authlogic/rails/init.rb +1 -0
  118. data/vendor/authlogic/shoulda_macros/authlogic.rb +69 -0
  119. data/vendor/authlogic/test/acts_as_authentic_test/base_test.rb +18 -0
  120. data/vendor/authlogic/test/acts_as_authentic_test/email_test.rb +97 -0
  121. data/vendor/authlogic/test/acts_as_authentic_test/logged_in_status_test.rb +36 -0
  122. data/vendor/authlogic/test/acts_as_authentic_test/login_test.rb +109 -0
  123. data/vendor/authlogic/test/acts_as_authentic_test/magic_columns_test.rb +27 -0
  124. data/vendor/authlogic/test/acts_as_authentic_test/password_test.rb +236 -0
  125. data/vendor/authlogic/test/acts_as_authentic_test/perishable_token_test.rb +90 -0
  126. data/vendor/authlogic/test/acts_as_authentic_test/persistence_token_test.rb +55 -0
  127. data/vendor/authlogic/test/acts_as_authentic_test/restful_authentication_test.rb +40 -0
  128. data/vendor/authlogic/test/acts_as_authentic_test/session_maintenance_test.rb +84 -0
  129. data/vendor/authlogic/test/acts_as_authentic_test/single_access_test.rb +44 -0
  130. data/vendor/authlogic/test/authenticates_many_test.rb +16 -0
  131. data/vendor/authlogic/test/crypto_provider_test/aes256_test.rb +14 -0
  132. data/vendor/authlogic/test/crypto_provider_test/bcrypt_test.rb +14 -0
  133. data/vendor/authlogic/test/crypto_provider_test/sha1_test.rb +23 -0
  134. data/vendor/authlogic/test/crypto_provider_test/sha256_test.rb +14 -0
  135. data/vendor/authlogic/test/crypto_provider_test/sha512_test.rb +14 -0
  136. data/vendor/authlogic/test/fixtures/companies.yml +5 -0
  137. data/vendor/authlogic/test/fixtures/employees.yml +17 -0
  138. data/vendor/authlogic/test/fixtures/projects.yml +3 -0
  139. data/vendor/authlogic/test/fixtures/users.yml +24 -0
  140. data/vendor/authlogic/test/i18n_test.rb +33 -0
  141. data/vendor/authlogic/test/libs/affiliate.rb +7 -0
  142. data/vendor/authlogic/test/libs/company.rb +6 -0
  143. data/vendor/authlogic/test/libs/employee.rb +7 -0
  144. data/vendor/authlogic/test/libs/employee_session.rb +2 -0
  145. data/vendor/authlogic/test/libs/ldaper.rb +3 -0
  146. data/vendor/authlogic/test/libs/ordered_hash.rb +9 -0
  147. data/vendor/authlogic/test/libs/project.rb +3 -0
  148. data/vendor/authlogic/test/libs/user.rb +5 -0
  149. data/vendor/authlogic/test/libs/user_session.rb +6 -0
  150. data/vendor/authlogic/test/random_test.rb +49 -0
  151. data/vendor/authlogic/test/session_test/activation_test.rb +43 -0
  152. data/vendor/authlogic/test/session_test/active_record_trickery_test.rb +36 -0
  153. data/vendor/authlogic/test/session_test/brute_force_protection_test.rb +101 -0
  154. data/vendor/authlogic/test/session_test/callbacks_test.rb +6 -0
  155. data/vendor/authlogic/test/session_test/cookies_test.rb +112 -0
  156. data/vendor/authlogic/test/session_test/credentials_test.rb +0 -0
  157. data/vendor/authlogic/test/session_test/existence_test.rb +64 -0
  158. data/vendor/authlogic/test/session_test/http_auth_test.rb +28 -0
  159. data/vendor/authlogic/test/session_test/id_test.rb +17 -0
  160. data/vendor/authlogic/test/session_test/klass_test.rb +40 -0
  161. data/vendor/authlogic/test/session_test/magic_columns_test.rb +62 -0
  162. data/vendor/authlogic/test/session_test/magic_states_test.rb +60 -0
  163. data/vendor/authlogic/test/session_test/params_test.rb +53 -0
  164. data/vendor/authlogic/test/session_test/password_test.rb +106 -0
  165. data/vendor/authlogic/test/session_test/perishability_test.rb +15 -0
  166. data/vendor/authlogic/test/session_test/persistence_test.rb +21 -0
  167. data/vendor/authlogic/test/session_test/scopes_test.rb +60 -0
  168. data/vendor/authlogic/test/session_test/session_test.rb +59 -0
  169. data/vendor/authlogic/test/session_test/timeout_test.rb +52 -0
  170. data/vendor/authlogic/test/session_test/unauthorized_record_test.rb +13 -0
  171. data/vendor/authlogic/test/session_test/validation_test.rb +23 -0
  172. data/vendor/authlogic/test/test_helper.rb +182 -0
  173. metadata +325 -0
@@ -0,0 +1,28 @@
1
+ Application.class_eval do
2
+
3
+ get '/sessions/new' do
4
+ @css = %w(log_in)
5
+ @error = flash[:error]
6
+ haml :log_in, :layout => :layout
7
+ end
8
+
9
+ post '/sessions/create' do
10
+ @user_session = UserSession.new(params[:session])
11
+ if @user_session.save
12
+ redirect '/'
13
+ else
14
+ flash[:error] = "Log in failed."
15
+ redirect '/sessions/new'
16
+ end
17
+ end
18
+
19
+ get '/sessions/destroy' do
20
+ current_user_session.destroy
21
+ redirect '/sessions/new'
22
+ end
23
+
24
+ get '/css/log_in.css' do
25
+ headers 'Content-Type' => 'text/css; charset=utf-8'
26
+ sass :log_in
27
+ end
28
+ end
@@ -0,0 +1,37 @@
1
+ Application.class_eval do
2
+
3
+ get '/tests/:id/destroy' do
4
+ restrict
5
+ @test = ABTest.find params[:id]
6
+ if @test
7
+ @test.destroy
8
+ flash[:success] = 'Test deleted successfully.'
9
+ else
10
+ flash[:error] = 'Could not delete test.'
11
+ end
12
+ redirect '/'
13
+ end
14
+
15
+ post '/tests/:id/update' do
16
+ restrict
17
+ @test = ABTest.find params[:id]
18
+ if @test
19
+ @test.update_attributes params[:test]
20
+ flash[:success] = 'Test updated successfully.'
21
+ else
22
+ flash[:error] = 'Could not update test.'
23
+ end
24
+ redirect '/'
25
+ end
26
+
27
+ post '/tests/create' do
28
+ restrict
29
+ @test = ABTest.create params[:test]
30
+ if @test.id
31
+ flash[:success] = 'Test created successfully.'
32
+ else
33
+ flash[:error] = 'Could not create test.'
34
+ end
35
+ redirect '/'
36
+ end
37
+ end
@@ -0,0 +1,14 @@
1
+ Application.class_eval do
2
+
3
+ get '/variants/:id/destroy' do
4
+ restricts
5
+ @variant = ABVariant.find params[:id]
6
+ if @variant
7
+ @variant.destroy
8
+ flash[:success] = 'Variant deleted successfully.'
9
+ else
10
+ flash[:error] = 'Could not delete variant.'
11
+ end
12
+ redirect '/'
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ Application.class_eval do
2
+ helpers do
3
+
4
+ def valid_token?
5
+ Digest::SHA256.hexdigest(params[:session_id] + Token.cached) == params[:token]
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,56 @@
1
+ Application.class_eval do
2
+ helpers do
3
+
4
+ # Assets
5
+
6
+ def css(name, media='screen, projection')
7
+ options = {
8
+ :href => "/css/#{name}.css",
9
+ :media => "#{media}",
10
+ :rel => "stylesheet",
11
+ :type => "text/css"
12
+ }
13
+ haml "%link#{options.inspect}", :layout => false
14
+ end
15
+
16
+ def javascripts(&block)
17
+ (@js || []).each do |path|
18
+ block.call path
19
+ end
20
+ end
21
+
22
+ def js(name)
23
+ options = {
24
+ :type => "text/javascript",
25
+ :src => "/js/#{name}.js"
26
+ }
27
+ haml "%script#{options.inspect}", :layout => false
28
+ end
29
+
30
+ def partial(name, options={})
31
+ haml name, options.merge(:layout => false)
32
+ end
33
+
34
+ def stylesheets(&block)
35
+ (@css || []).each do |path|
36
+ block.call path
37
+ end
38
+ end
39
+
40
+ # Authentication
41
+
42
+ def current_user_session
43
+ return @current_user_session if defined?(@current_user_session)
44
+ @current_user_session = UserSession.find
45
+ end
46
+
47
+ def current_user
48
+ return @current_user if defined?(@current_user)
49
+ @current_user = current_user_session && current_user_session.record
50
+ end
51
+
52
+ def restrict
53
+ redirect '/sessions/new' unless current_user
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,12 @@
1
+ Application.class_eval do
2
+ helpers do
3
+
4
+ def table_class(test, index)
5
+ (index + 1) % 3 == 0 || @tests.last == test ? 'last' : nil
6
+ end
7
+
8
+ def table_json(test)
9
+ { :name => test.name, :ticket_url => test.ticket_url, :variant_names => test.variant_names }.to_json
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,65 @@
1
+ class ABTest < ActiveRecord::Base
2
+
3
+ set_table_name :a_b_tests
4
+ has_many :variants, :class_name => 'ABVariant', :foreign_key => 'a_b_test_id', :dependent => :delete_all
5
+
6
+ validates_uniqueness_of :name
7
+
8
+ after_save :destroy_variants
9
+ after_save :create_variants
10
+ after_save :set_control
11
+
12
+ def control
13
+ self.variants.find_by_control true
14
+ end
15
+
16
+ def sorted_variants
17
+ self.variants.find(:all, :order => 'control desc, conversions / visitors desc, visitors desc')
18
+ end
19
+
20
+ def variant_names
21
+ names = self.variants.collect &:name
22
+ names.delete self.control.name
23
+ names.unshift self.control.name
24
+ end
25
+
26
+ def variants=(names)
27
+ names = names.gsub(/[^,\w]/, '').split(',').uniq
28
+ current_names = variants.collect(&:name).uniq
29
+ @control = names.first
30
+ @new_variant_names = names - current_names
31
+ @removed_variant_names = current_names - names
32
+ end
33
+
34
+ private
35
+
36
+ def create_variants
37
+ return unless @new_variant_names
38
+ @new_variant_names.each do |name|
39
+ self.variants.create(:name => name)
40
+ end
41
+ end
42
+
43
+ def destroy_variants
44
+ return unless @removed_variant_names
45
+ remove = self.variants.find(:all, :conditions => { :name => @removed_variant_names })
46
+ remove.each do |variant|
47
+ variant.destroy
48
+ end
49
+ end
50
+
51
+ def set_control
52
+ if @control
53
+ if self.control
54
+ self.control.update_attribute(:control, false)
55
+ end
56
+ if name = self.variants.find_by_name(@control)
57
+ name.update_attribute(:control, true)
58
+ elsif !self.variants.empty?
59
+ self.variants.first.update_attribute(:control, true)
60
+ end
61
+ elsif !self.control && !self.variants.empty?
62
+ self.variants.first.update_attribute(:control, true)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,131 @@
1
+ class ABVariant < ActiveRecord::Base
2
+
3
+ set_table_name :a_b_variants
4
+ belongs_to :test, :class_name => 'ABTest', :foreign_key => 'a_b_test_id'
5
+
6
+ after_destroy :save_test
7
+
8
+ validates_uniqueness_of :name
9
+
10
+ def confidence
11
+ if compute_confidence?
12
+ cumulative_normal_distribution(z_score(self.test.control))
13
+ else
14
+ 'n/a'
15
+ end
16
+ end
17
+
18
+ def confidence_ok?
19
+ cumulative_normal_distribution(z_score(self.test.control)) >= 0.95
20
+ end
21
+
22
+ def conversion_rate
23
+ if conversions > 0
24
+ 1.0 * conversions / visitors
25
+ else
26
+ 0.0
27
+ end
28
+ end
29
+
30
+ def conversion_rate_ok?
31
+ conversion_rate > self.test.control.conversion_rate
32
+ end
33
+
34
+ def pretty_confidence
35
+ pretty confidence
36
+ end
37
+
38
+ def pretty_conversion_rate
39
+ pretty conversion_rate
40
+ end
41
+
42
+ def suggested_visitors
43
+ size = sample_size(self.test.control)
44
+ if conversion_rate == 0 || size < 100
45
+ 100
46
+ else
47
+ commafy size
48
+ end
49
+ end
50
+
51
+ def suggested_visitors_ok?
52
+ visitors > sample_size(self.test.control)
53
+ end
54
+
55
+ def suggested_visitors_with_commas
56
+ commafy suggested_visitors
57
+ end
58
+
59
+ def visitors_with_commas
60
+ commafy visitors
61
+ end
62
+
63
+ private
64
+
65
+ def commafy(num)
66
+ num.to_s.gsub(/(\d)(?=\d{3}+(?:\.|$))(\d{3}\..*)?/,'\1,\2')
67
+ end
68
+
69
+ def cumulative_normal_distribution(z)
70
+ b1 = 0.319381530
71
+ b2 = -0.356563782
72
+ b3 = 1.781477937
73
+ b4 = -1.821255978
74
+ b5 = 1.330274429
75
+ p = 0.2316419
76
+ c = 0.39894228
77
+
78
+ if z >= 0.0
79
+ t = 1.0 / (1.0 + p * z)
80
+ (1.0 - c * Math.exp(-z * z / 2.0) * t * (t * (t * (t * (t * b5 + b4) + b3) + b2) + b1))
81
+ else
82
+ t = 1.0 / (1.0 - p * z)
83
+ (c * Math.exp(-z * z / 2.0) * t * (t * (t * (t * (t * b5 + b4) + b3) + b2) + b1))
84
+ end
85
+ end
86
+
87
+ def compute_confidence?
88
+ self.test &&
89
+ self.test.control && self != self.test.control &&
90
+ self.visitors > 0
91
+ end
92
+
93
+ def pretty(num)
94
+ if num.respond_to?(:strip)
95
+ num
96
+ else
97
+ sprintf("%.3f", num)[1..-1]
98
+ end
99
+ end
100
+
101
+ def sample_size(control)
102
+ # confidence level is 95% and the desired power is 80%
103
+ variance = control.conversion_rate * (1 - control.conversion_rate)
104
+ sensitivity = (self.conversion_rate - control.conversion_rate) ** 2
105
+ if sensitivity != 0
106
+ (16 * variance / sensitivity).to_i
107
+ else
108
+ 0
109
+ end
110
+ end
111
+
112
+ def save_test
113
+ self.test.save if self.test
114
+ end
115
+
116
+ def z_score(control)
117
+ cr1 = control.conversion_rate
118
+ cr2 = self.conversion_rate
119
+
120
+ v1 = control.visitors
121
+ v2 = self.visitors
122
+
123
+ if v1 == 0.0 || v2 == 0.0
124
+ 0.0
125
+ else
126
+ z = cr2 - cr1
127
+ s = (cr2 * (1 - cr2)) / v2 + (cr1 * (1 - cr1)) / v1
128
+ z / Math.sqrt(s)
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,22 @@
1
+ class Token < ActiveRecord::Base
2
+
3
+ set_table_name :a_b_tokens
4
+ @@cached_at = nil
5
+
6
+ def self.cached
7
+ if @@cached_at.nil? || @@cached_at < Time.now.utc - 60
8
+ @@cached = self.last.token
9
+ @@cached_at = Time.now.utc
10
+ end
11
+ @@cached
12
+ end
13
+
14
+ def self.generate!
15
+ if !self.last or (self.last.created_at < Time.now.utc - 60 * 60)
16
+ token = self.create(:token => Authlogic::Random.friendly_token).token
17
+ else
18
+ token = self.last.token
19
+ end
20
+ token
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ class User < ActiveRecord::Base
2
+
3
+ set_table_name :a_b_users
4
+ acts_as_authentic
5
+ end
@@ -0,0 +1,4 @@
1
+ class UserSession < Authlogic::Session::Base
2
+ params_key 'token'
3
+ single_access_allowed_request_types :any
4
+ end
@@ -0,0 +1,129 @@
1
+ %hr.space
2
+ %hr.space
3
+
4
+ .span-16
5
+ %h2{ :class => @tests.empty? ? 'hide' : nil }
6
+ %a{ :href => '#' } New A/B Test
7
+ .span-15.first
8
+ #new_test.box{ :class => @tests.empty? ? nil : 'hide' }
9
+ %form{ :action => '/tests/create', :method => 'post', :class => 'clearfix' }
10
+ .span-7
11
+ %label{ :for => 'test[name]' } Test name
12
+ %br/
13
+ %input{ :type => 'text', :name => 'test[name]' }
14
+ .span-7.last
15
+ %label{ :for => 'test[ticket_url]' } Ticket URL
16
+ %br/
17
+ %input{ :type => 'text', :name => 'test[ticket_url]' }
18
+ %hr.space
19
+ .span-10
20
+ %label{ :for => 'test[variants]' }
21
+ Variant names
22
+ (<a class="tooltip" title="variant_names">more info</a>)
23
+ %br/
24
+ %input{ :type => 'text', :name => 'test[variants]' }
25
+ .buttons.span-4.last
26
+ %input{ :type => 'submit', :value => 'Create', :class => 'submit' }
27
+ %input{ :type => 'submit', :value => 'Cancel', :class => 'cancel' }
28
+
29
+ #search.span-8.last
30
+ - unless @tests.empty?
31
+ %form
32
+ %input.span-5{ :type => 'text' }/
33
+ %input{ :type => 'submit', :value => 'Search' }
34
+
35
+ - if @success || @error
36
+ .span-23.first
37
+ - if @success
38
+ .success= @success
39
+ - if @error
40
+ .error= @error
41
+
42
+ %hr.space
43
+
44
+ - @tests.each_with_index do |test, index|
45
+ .table.span-8{ :class => table_class(test, index) }[test]
46
+ .data.hide
47
+ = table_json(test)
48
+ .title.clearfix
49
+ .span-5.search
50
+ %h3= test.name
51
+ .span-1.ticket
52
+ %a{ :href => test.ticket_url } Ticket
53
+ .span-1.edit
54
+ %a{ :href => '#' } Edit
55
+ .span-1.last x
56
+ .rows
57
+ .header.clearfix
58
+ .span-2 Name
59
+ .span-3.tooltip{ :title => 'visitors_suggested' } Visitors/Suggested
60
+ .span-1.tooltip{ :title => 'conf' } Conf
61
+ .span-1.last.tooltip{ :title => 'rate' } Rate
62
+ - test.sorted_variants.each do |variant|
63
+ .row.clearfix.search[variant]
64
+ .span-2
65
+ - if variant.control?
66
+ %strong= variant.name
67
+ - else
68
+ = variant.name
69
+ .span-3
70
+ %span{ :class => variant.suggested_visitors_ok? ? 'green' : nil }
71
+ = variant.visitors_with_commas
72
+ \/
73
+ = variant.suggested_visitors_with_commas
74
+ .span-1
75
+ %span{ :class => variant.confidence_ok? ? 'green' : nil }
76
+ = variant.pretty_confidence
77
+ .span-1
78
+ %span{ :class => variant.conversion_rate_ok? ? 'green' : nil }
79
+ = variant.pretty_conversion_rate
80
+ .span-1.last x
81
+ - if table_class(test, index)
82
+ %hr.space
83
+ %hr.space
84
+
85
+ %textarea.hide#tooltip_variant_names
86
+ %b Variant names
87
+ %ul
88
+ %li This field is a comma separated list of variant names.
89
+ %li The first variant is the control.
90
+ %li Variant names must be unique across all tests.
91
+ %li Variant names must only consist of letters, numbers, and underscores.
92
+
93
+ %textarea.hide#tooltip_visitors_suggested
94
+ %b Visitors
95
+ Number of exposures for this variant.
96
+ %hr.space
97
+ %b Suggested Visitors
98
+ Number of exposures necessary to trust that the amount of change between the control and the
99
+ variant is significantly different.
100
+
101
+ %textarea.hide#tooltip_conf
102
+ %b Confidence
103
+ A variant is statistically better than the control if confidence is greater than or equal to 0.95.
104
+
105
+ %textarea.hide#tooltip_rate
106
+ %b Conversion Rate
107
+ The percentage of users who converted in decimal form.
108
+
109
+ %textarea.hide#edit_template
110
+ .span-7.first.edit_container
111
+ .box.clearfix
112
+ %form{ :action => '/tests/:id/update', :method => 'post' }
113
+ %p
114
+ %label{ :for => 'test[name]' } Test name
115
+ %br/
116
+ %input{ :type => 'text', :name => 'test[name]' }
117
+ %p
118
+ %label{ :for => 'test[ticket_url]' } Ticket URL
119
+ %br/
120
+ %input{ :type => 'text', :name => 'test[ticket_url]' }
121
+ %p
122
+ %label{ :for => 'test[variants]' }
123
+ Variant names
124
+ (<a class="tooltip" title="variant_names">more info</a>)
125
+ %br/
126
+ %input{ :type => 'text', :name => 'test[variants]' }
127
+ .buttons
128
+ %input{ :type => 'submit', :value => 'Update', :class => 'submit' }
129
+ %input{ :type => 'submit', :value => 'Cancel', :class => 'cancel' }