moses-vanity 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/.autotest +22 -0
  2. data/.gitignore +7 -0
  3. data/.rvmrc +3 -0
  4. data/.travis.yml +13 -0
  5. data/CHANGELOG +374 -0
  6. data/Gemfile +28 -0
  7. data/MIT-LICENSE +21 -0
  8. data/README.rdoc +108 -0
  9. data/Rakefile +189 -0
  10. data/bin/vanity +16 -0
  11. data/doc/_config.yml +2 -0
  12. data/doc/_layouts/_header.html +34 -0
  13. data/doc/_layouts/page.html +47 -0
  14. data/doc/_metrics.textile +12 -0
  15. data/doc/ab_testing.textile +210 -0
  16. data/doc/configuring.textile +45 -0
  17. data/doc/contributing.textile +93 -0
  18. data/doc/credits.textile +23 -0
  19. data/doc/css/page.css +83 -0
  20. data/doc/css/print.css +43 -0
  21. data/doc/css/syntax.css +7 -0
  22. data/doc/email.textile +129 -0
  23. data/doc/experimental.textile +31 -0
  24. data/doc/faq.textile +8 -0
  25. data/doc/identity.textile +43 -0
  26. data/doc/images/ab_in_dashboard.png +0 -0
  27. data/doc/images/clear_winner.png +0 -0
  28. data/doc/images/price_options.png +0 -0
  29. data/doc/images/sidebar_test.png +0 -0
  30. data/doc/images/signup_metric.png +0 -0
  31. data/doc/images/vanity.png +0 -0
  32. data/doc/index.textile +91 -0
  33. data/doc/metrics.textile +231 -0
  34. data/doc/rails.textile +89 -0
  35. data/doc/site.js +27 -0
  36. data/generators/templates/vanity_migration.rb +53 -0
  37. data/generators/vanity_generator.rb +8 -0
  38. data/lib/generators/templates/vanity_migration.rb +53 -0
  39. data/lib/generators/vanity_generator.rb +15 -0
  40. data/lib/vanity.rb +36 -0
  41. data/lib/vanity/adapters/abstract_adapter.rb +140 -0
  42. data/lib/vanity/adapters/active_record_adapter.rb +248 -0
  43. data/lib/vanity/adapters/mock_adapter.rb +157 -0
  44. data/lib/vanity/adapters/mongodb_adapter.rb +178 -0
  45. data/lib/vanity/adapters/redis_adapter.rb +160 -0
  46. data/lib/vanity/backport.rb +26 -0
  47. data/lib/vanity/commands/list.rb +21 -0
  48. data/lib/vanity/commands/report.rb +64 -0
  49. data/lib/vanity/commands/upgrade.rb +34 -0
  50. data/lib/vanity/experiment/ab_test.rb +507 -0
  51. data/lib/vanity/experiment/base.rb +214 -0
  52. data/lib/vanity/frameworks.rb +16 -0
  53. data/lib/vanity/frameworks/rails.rb +318 -0
  54. data/lib/vanity/helpers.rb +66 -0
  55. data/lib/vanity/images/x.gif +0 -0
  56. data/lib/vanity/metric/active_record.rb +85 -0
  57. data/lib/vanity/metric/base.rb +244 -0
  58. data/lib/vanity/metric/google_analytics.rb +83 -0
  59. data/lib/vanity/metric/remote.rb +53 -0
  60. data/lib/vanity/playground.rb +396 -0
  61. data/lib/vanity/templates/_ab_test.erb +28 -0
  62. data/lib/vanity/templates/_experiment.erb +5 -0
  63. data/lib/vanity/templates/_experiments.erb +7 -0
  64. data/lib/vanity/templates/_metric.erb +14 -0
  65. data/lib/vanity/templates/_metrics.erb +13 -0
  66. data/lib/vanity/templates/_report.erb +27 -0
  67. data/lib/vanity/templates/_vanity.js.erb +20 -0
  68. data/lib/vanity/templates/flot.min.js +1 -0
  69. data/lib/vanity/templates/jquery.min.js +19 -0
  70. data/lib/vanity/templates/vanity.css +26 -0
  71. data/lib/vanity/templates/vanity.js +82 -0
  72. data/lib/vanity/version.rb +11 -0
  73. data/test/adapters/redis_adapter_test.rb +17 -0
  74. data/test/experiment/ab_test.rb +771 -0
  75. data/test/experiment/base_test.rb +150 -0
  76. data/test/experiments/age_and_zipcode.rb +19 -0
  77. data/test/experiments/metrics/cheers.rb +3 -0
  78. data/test/experiments/metrics/signups.rb +2 -0
  79. data/test/experiments/metrics/yawns.rb +3 -0
  80. data/test/experiments/null_abc.rb +5 -0
  81. data/test/metric/active_record_test.rb +277 -0
  82. data/test/metric/base_test.rb +293 -0
  83. data/test/metric/google_analytics_test.rb +104 -0
  84. data/test/metric/remote_test.rb +109 -0
  85. data/test/myapp/app/controllers/application_controller.rb +2 -0
  86. data/test/myapp/app/controllers/main_controller.rb +7 -0
  87. data/test/myapp/config/boot.rb +110 -0
  88. data/test/myapp/config/environment.rb +10 -0
  89. data/test/myapp/config/environments/production.rb +0 -0
  90. data/test/myapp/config/routes.rb +3 -0
  91. data/test/passenger_test.rb +43 -0
  92. data/test/playground_test.rb +26 -0
  93. data/test/rails_dashboard_test.rb +37 -0
  94. data/test/rails_helper_test.rb +36 -0
  95. data/test/rails_test.rb +389 -0
  96. data/test/test_helper.rb +145 -0
  97. data/vanity.gemspec +26 -0
  98. metadata +202 -0
@@ -0,0 +1,27 @@
1
+ $(function() {
2
+ var issuesTable = $("table#issues");
3
+ if (issuesTable.size() > 0) {
4
+ $.getJSON("http://github.com/api/v2/json/issues/list/assaf/vanity/open?callback=?", function(response) {
5
+ $.each(response.issues, function(i, issue) {
6
+ issuesTable.append(
7
+ $("<tr>").append(
8
+ $("<td>").append(
9
+ $("<a>").text(issue.title).attr("href", "http://github.com/assaf/vanity/issues#issue/" + issue.number)
10
+ ).append(
11
+ $("<span class='votes'>").text(issue.votes == 0 ? "no votes" : issue.votes == 1 ? "1 vote" : issue.votes + " votes")
12
+ )
13
+ )
14
+ );
15
+ });
16
+ });
17
+ }
18
+
19
+ var statsTable = $("#sidebar ul#stats");
20
+ if (statsTable.size() > 0) {
21
+ $.getJSON("http://github.com/api/v2/json/repos/show/assaf/vanity?callback=?", function(response) {
22
+ statsTable.
23
+ prepend( $("<li>").append("Forks: " + response.repository.forks) ).
24
+ prepend( $("<li>").append("Watchers: " + response.repository.watchers) )
25
+ })
26
+ }
27
+ });
@@ -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,8 @@
1
+ class VanityGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ m.migration_template 'vanity_migration.rb', 'db/migrate',
5
+ :migration_file_name => "vanity_migration"
6
+ end
7
+ end
8
+ end
@@ -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
@@ -0,0 +1,36 @@
1
+ require "date"
2
+ require "time"
3
+ require "logger"
4
+ require "cgi"
5
+ require "erb"
6
+ require "yaml"
7
+
8
+ # All the cool stuff happens in other places.
9
+ # @see Vanity::Helper
10
+ # @see Vanity::Rails
11
+ # @see Vanity::Playground
12
+ # @see Vanity::Metric
13
+ # @see Vanity::Experiment
14
+ module Vanity
15
+ end
16
+
17
+ require "vanity/version"
18
+ require "vanity/backport" if RUBY_VERSION < "1.9"
19
+ # Metrics.
20
+ require "vanity/metric/base"
21
+ require "vanity/metric/active_record"
22
+ require "vanity/metric/google_analytics"
23
+ require "vanity/metric/remote"
24
+ # Experiments.
25
+ require "vanity/experiment/base"
26
+ require "vanity/experiment/ab_test"
27
+ # Database adapters
28
+ require "vanity/adapters/abstract_adapter"
29
+ require "vanity/adapters/redis_adapter"
30
+ require "vanity/adapters/mongodb_adapter"
31
+ require "vanity/adapters/mock_adapter"
32
+ # Playground.
33
+ require "vanity/playground"
34
+ require "vanity/helpers"
35
+ # Integration with various frameworks.
36
+ require "vanity/frameworks"
@@ -0,0 +1,140 @@
1
+ module Vanity
2
+ module Adapters
3
+
4
+ class << self
5
+ # Creates new connection to underlying datastore and returns suitable
6
+ # adapter (adapter object extends AbstractAdapter and wraps the
7
+ # connection). Vanity.playgroup.establish_connection uses this.
8
+ #
9
+ # @since 1.4.0
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
16
+ adapter_method = "#{spec[:adapter]}_connection"
17
+ send adapter_method, spec
18
+ end
19
+ end
20
+
21
+ # Base class for all adapters. Adapters wrap underlying connection to a
22
+ # datastore and implement an API that Vanity can use to store/access
23
+ # metrics, experiments, etc.
24
+ class AbstractAdapter
25
+ # Returns true if connected.
26
+ def active?
27
+ false
28
+ end
29
+
30
+ # Close connection, release any resources.
31
+ def disconnect!
32
+ end
33
+
34
+ # Close and reopen connection.
35
+ def reconnect!
36
+ end
37
+
38
+ # Empty the database. This is used during tests.
39
+ def flushdb
40
+ end
41
+
42
+
43
+ # -- Metrics --
44
+
45
+ # Return when metric was last updated.
46
+ def get_metric_last_update_at(metric)
47
+ fail "Not implemented"
48
+ end
49
+
50
+ # Track metric data.
51
+ def metric_track(metric, timestamp, identity, values)
52
+ fail "Not implemented"
53
+ end
54
+
55
+ # Returns all the metric values between from and to time instances
56
+ # (inclusive). Returns pairs of date and total count for that date.
57
+ def metric_values(metric, from, to)
58
+ fail "Not implemented"
59
+ end
60
+
61
+ # Deletes all information about this metric.
62
+ def destroy_metric(metric)
63
+ fail "Not implemented"
64
+ end
65
+
66
+
67
+ # -- Experiments --
68
+
69
+ # Store when experiment was created (do not write over existing value).
70
+ def set_experiment_created_at(experiment, time)
71
+ fail "Not implemented"
72
+ end
73
+
74
+ # Return when experiment was created.
75
+ def get_experiment_created_at(experiment)
76
+ fail "Not implemented"
77
+ end
78
+
79
+ # Returns true if experiment completed.
80
+ def is_experiment_completed?(experiment)
81
+ fail "Not implemented"
82
+ end
83
+
84
+ # Returns counts for given A/B experiment and alternative (by index).
85
+ # Returns hash with values for the keys :participants, :converted and
86
+ # :conversions.
87
+ def ab_counts(experiment, alternative)
88
+ fail "Not implemented"
89
+ end
90
+
91
+ # Pick particular alternative (by index) to show to this particular
92
+ # participant (by identity).
93
+ def ab_show(experiment, identity, alternative)
94
+ fail "Not implemented"
95
+ end
96
+
97
+ # Indicates which alternative to show to this participant. See #ab_show.
98
+ def ab_showing(experiment, identity)
99
+ fail "Not implemented"
100
+ end
101
+
102
+ # Cancels previously set association between identity and alternative. See
103
+ # #ab_show.
104
+ def ab_not_showing(experiment, identity)
105
+ fail "Not implemented"
106
+ end
107
+
108
+ # Records a participant in this experiment for the given alternative.
109
+ def ab_add_participant(experiment, alternative, identity)
110
+ fail "Not implemented"
111
+ end
112
+
113
+ # Records a conversion in this experiment for the given alternative.
114
+ # Associates a value with the conversion (default to 1). If implicit is
115
+ # true, add particpant if not already recorded for this experiment. If
116
+ # implicit is false (default), only add conversion is participant
117
+ # previously recorded as participating in this experiment.
118
+ def ab_add_conversion(experiment, alternative, identity, count = 1, implicit = false)
119
+ fail "Not implemented"
120
+ end
121
+
122
+ # Returns the outcome of this expriment (if set), the index of a
123
+ # particular alternative.
124
+ def ab_get_outcome(experiment)
125
+ fail "Not implemented"
126
+ end
127
+
128
+ # Sets the outcome of this experiment to a particular alternative.
129
+ def ab_set_outcome(experiment, alternative = 0)
130
+ fail "Not implemented"
131
+ end
132
+
133
+ # Deletes all information about this experiment.
134
+ def destroy_experiment(experiment)
135
+ fail "Not implemented"
136
+ end
137
+
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,248 @@
1
+ module Vanity
2
+ module Adapters
3
+ class << self
4
+ # Creates new ActiveRecord connection and returns ActiveRecordAdapter.
5
+ def active_record_connection(spec)
6
+ require "active_record"
7
+ ActiveRecordAdapter.new(spec)
8
+ end
9
+ end
10
+
11
+ # ActiveRecord adapter
12
+ class ActiveRecordAdapter < AbstractAdapter
13
+ # Base model, stores connection and defines schema
14
+ class VanityRecord < ActiveRecord::Base
15
+ self.abstract_class = true
16
+ end
17
+
18
+ # Schema model
19
+ class VanitySchema < VanityRecord
20
+ set_table_name :vanity_schema
21
+ end
22
+
23
+ # Metric model
24
+ class VanityMetric < VanityRecord
25
+ set_table_name :vanity_metrics
26
+ has_many :vanity_metric_values
27
+
28
+ def self.retrieve(metric)
29
+ find_or_create_by_metric_id(metric.to_s)
30
+ end
31
+ end
32
+
33
+ # Metric value
34
+ class VanityMetricValue < VanityRecord
35
+ set_table_name :vanity_metric_values
36
+ belongs_to :vanity_metric
37
+ end
38
+
39
+ # Experiment model
40
+ class VanityExperiment < VanityRecord
41
+ set_table_name :vanity_experiments
42
+ has_many :vanity_conversions, :dependent => :destroy
43
+
44
+ # Finds or creates the experiment
45
+ def self.retrieve(experiment)
46
+ find_or_create_by_experiment_id(experiment.to_s)
47
+ end
48
+
49
+ def increment_conversion(alternative, count = 1)
50
+ record = vanity_conversions.find_or_create_by_alternative(alternative)
51
+ record.increment!(:conversions, count)
52
+ end
53
+ end
54
+
55
+ # Conversion model
56
+ class VanityConversion < VanityRecord
57
+ set_table_name :vanity_conversions
58
+ belongs_to :vanity_experiment
59
+ end
60
+
61
+ # Participant model
62
+ class VanityParticipant < VanityRecord
63
+ set_table_name :vanity_participants
64
+
65
+ # Finds the participant by experiment and identity. If
66
+ # create is true then it will create the participant
67
+ # if not found. If a hash is passed then this will be
68
+ # passed to create if creating, or will be used to
69
+ # update the found participant.
70
+ def self.retrieve(experiment, identity, create = true, update_with = nil)
71
+ if record = VanityParticipant.first(:conditions=>{ :experiment_id=>experiment.to_s, :identity=>identity.to_s })
72
+ record.update_attributes(update_with) if update_with
73
+ elsif create
74
+ record = VanityParticipant.create({ :experiment_id=>experiment.to_s, :identity=>identity }.merge(update_with || {}))
75
+ end
76
+ record
77
+ end
78
+ end
79
+
80
+ def initialize(options)
81
+ @options = options.inject({}) { |h,kv| h[kv.first.to_s] = kv.last ; h }
82
+ if @options["active_record_adapter"] && (@options["active_record_adapter"] != "default")
83
+ @options["adapter"] = @options["active_record_adapter"]
84
+ VanityRecord.establish_connection(@options)
85
+ end
86
+ end
87
+
88
+ def active?
89
+ VanityRecord.connected?
90
+ end
91
+
92
+ def disconnect!
93
+ VanityRecord.connection.disconnect! if active?
94
+ end
95
+
96
+ def reconnect!
97
+ VanityRecord.connection.reconnect!
98
+ end
99
+
100
+ def flushdb
101
+ [VanityExperiment, VanityMetric, VanityParticipant, VanityMetricValue, VanityConversion].each do |klass|
102
+ klass.delete_all
103
+ end
104
+ end
105
+
106
+ def get_metric_last_update_at(metric)
107
+ record = VanityMetric.find_by_metric_id(metric.to_s)
108
+ record && record.updated_at
109
+ end
110
+
111
+ def metric_track(metric, timestamp, identity, values)
112
+ record = VanityMetric.retrieve(metric)
113
+
114
+ values.each_with_index do |value, index|
115
+ record.vanity_metric_values.create(:date => timestamp.to_date.to_s, :index => index, :value => value)
116
+ end
117
+
118
+ record.updated_at = Time.now
119
+ record.save
120
+ end
121
+
122
+ def metric_values(metric, from, to)
123
+ connection = VanityMetric.connection
124
+ record = VanityMetric.retrieve(metric)
125
+ dates = (from.to_date..to.to_date).map(&:to_s)
126
+ conditions = [connection.quote_column_name('date') + ' IN (?)', dates]
127
+ order = "#{connection.quote_column_name('date')}"
128
+ select = "sum(#{connection.quote_column_name('value')}) AS value, #{connection.quote_column_name('date')}"
129
+ group_by = "#{connection.quote_column_name('date')}"
130
+
131
+ values = record.vanity_metric_values.all(
132
+ :select => select,
133
+ :conditions => conditions,
134
+ :order => order,
135
+ :group => group_by
136
+ )
137
+
138
+ dates.map do |date|
139
+ value = values.detect{|v| v.date == date }
140
+ [(value && value.value) || 0]
141
+ end
142
+ end
143
+
144
+ def destroy_metric(metric)
145
+ record = VanityMetric.find_by_metric_id(metric.to_s)
146
+ record && record.destroy
147
+ end
148
+
149
+ # Store when experiment was created (do not write over existing value).
150
+ def set_experiment_created_at(experiment, time)
151
+ record = VanityExperiment.find_by_experiment_id(experiment.to_s) ||
152
+ VanityExperiment.new(:experiment_id => experiment.to_s)
153
+ record.created_at ||= time
154
+ record.save
155
+ end
156
+
157
+ # Return when experiment was created.
158
+ def get_experiment_created_at(experiment)
159
+ record = VanityExperiment.retrieve(experiment)
160
+ record && record.created_at
161
+ end
162
+
163
+ def set_experiment_completed_at(experiment, time)
164
+ VanityExperiment.retrieve(experiment).update_attribute(:completed_at, time)
165
+ end
166
+
167
+ def get_experiment_completed_at(experiment)
168
+ VanityExperiment.retrieve(experiment).completed_at
169
+ end
170
+
171
+ # Returns true if experiment completed.
172
+ def is_experiment_completed?(experiment)
173
+ !!VanityExperiment.retrieve(experiment).completed_at
174
+ end
175
+
176
+ # Returns counts for given A/B experiment and alternative (by index).
177
+ # Returns hash with values for the keys :participants, :converted and
178
+ # :conversions.
179
+ def ab_counts(experiment, alternative)
180
+ record = VanityExperiment.retrieve(experiment)
181
+ participants = VanityParticipant.count(:conditions => {:experiment_id => experiment.to_s, :seen => alternative})
182
+ converted = VanityParticipant.count(:conditions => {:experiment_id => experiment.to_s, :converted => alternative})
183
+ conversions = record.vanity_conversions.sum(:conversions, :conditions => {:alternative => alternative})
184
+
185
+ {
186
+ :participants => participants,
187
+ :converted => converted,
188
+ :conversions => conversions
189
+ }
190
+ end
191
+
192
+ # Pick particular alternative (by index) to show to this particular
193
+ # participant (by identity).
194
+ def ab_show(experiment, identity, alternative)
195
+ VanityParticipant.retrieve(experiment, identity, true, :shown => alternative)
196
+ end
197
+
198
+ # Indicates which alternative to show to this participant. See #ab_show.
199
+ def ab_showing(experiment, identity)
200
+ participant = VanityParticipant.retrieve(experiment, identity, false)
201
+ participant && participant.shown
202
+ end
203
+
204
+ # Cancels previously set association between identity and alternative. See
205
+ # #ab_show.
206
+ def ab_not_showing(experiment, identity)
207
+ VanityParticipant.retrieve(experiment, identity, true, :shown => nil)
208
+ end
209
+
210
+ # Records a participant in this experiment for the given alternative.
211
+ def ab_add_participant(experiment, alternative, identity)
212
+ VanityParticipant.retrieve(experiment, identity, true, :seen => alternative)
213
+ end
214
+
215
+ # Records a conversion in this experiment for the given alternative.
216
+ # Associates a value with the conversion (default to 1). If implicit is
217
+ # true, add particpant if not already recorded for this experiment. If
218
+ # implicit is false (default), only add conversion is participant
219
+ # previously recorded as participating in this experiment.
220
+ def ab_add_conversion(experiment, alternative, identity, count = 1, implicit = false)
221
+ VanityParticipant.retrieve(experiment, identity, implicit, :converted => alternative)
222
+ VanityExperiment.retrieve(experiment).increment_conversion(alternative, count)
223
+ end
224
+
225
+ # Returns the outcome of this experiment (if set), the index of a
226
+ # particular alternative.
227
+ def ab_get_outcome(experiment)
228
+ VanityExperiment.retrieve(experiment).outcome
229
+ end
230
+
231
+ # Sets the outcome of this experiment to a particular alternative.
232
+ def ab_set_outcome(experiment, alternative = 0)
233
+ VanityExperiment.retrieve(experiment).update_attribute(:outcome, alternative)
234
+ end
235
+
236
+ # Deletes all information about this experiment.
237
+ def destroy_experiment(experiment)
238
+ VanityParticipant.delete_all(:experiment_id => experiment.to_s)
239
+ record = VanityExperiment.find_by_experiment_id(experiment.to_s)
240
+ record && record.destroy
241
+ end
242
+
243
+ def to_s
244
+ @options.to_s
245
+ end
246
+ end
247
+ end
248
+ end