moses-vanity 1.7.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.
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