field_test 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of field_test might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f8bbd74bb28099c186bacc0ea7b195c7d89a7e35
4
- data.tar.gz: e87355a520385d34192ab200b80411a4bbc70310
3
+ metadata.gz: 715ea396faed10c5b142f003eb76405d8ba34f01
4
+ data.tar.gz: 43a830f9996b974a917734f39641d8783b9c33c9
5
5
  SHA512:
6
- metadata.gz: 6f717922757f35cd62c835beb58bde3bce74fb326ca1965ddb5bbdd03811d1433fd99f1e677738b52d12f3c9158919faf832f99427d507ce5f23423b03757ccc
7
- data.tar.gz: 5912035f52bfb111970e1fcf3f6bf73a9c4dbe55334e04b39a1ba9e456481ffe23f5838974989f45f560a16960b6f2a835bce2a0603c34991167fe39caa75230
6
+ metadata.gz: fee3d99177f6e2044bc38bba2a557e4832b9a402b4743400a8c6a47cff272885417100caf7522ef48ef62f2641a71790e1107cc4885d079d5624673a26178e18
7
+ data.tar.gz: 07a3450cd3e1ad4da4a909841ea7532c7683b65a366907345f186bd261bac53721d1a4b603f97c0f8e541773dc39d2e7398a41655c0811effeece190b29a9741
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.2.0
2
+
3
+ - Better web UI
4
+ - Removed `cookie:` prefix for unknown participants
5
+
1
6
  ## 0.1.2
2
7
 
3
8
  - Exclude bots
data/README.md CHANGED
@@ -3,8 +3,8 @@
3
3
  :maple_leaf: A/B testing for Rails
4
4
 
5
5
  - Designed for web and email
6
+ - Comes with a [nice dashboard](https://fieldtest.dokkuapp.com/)
6
7
  - Seamlessly handles the transition from anonymous visitor to logged in user
7
- - Results are stored in your database
8
8
 
9
9
  Uses [Bayesian methods](http://www.evanmiller.org/bayesian-ab-testing.html) to evaluate results so you don’t need to choose a sample size ahead of time.
10
10
 
@@ -30,7 +30,7 @@ mount FieldTest::Engine, at: "field_test"
30
30
 
31
31
  Be sure to [secure the dashboard](#security) in production.
32
32
 
33
- ![Screenshot](https://ankane.github.io/field_test/screenshot5.png)
33
+ ![Screenshot](https://ankane.github.io/field_test/screenshot6.png)
34
34
 
35
35
  ## Getting Started
36
36
 
@@ -57,13 +57,6 @@ When someone converts, record it with:
57
57
  field_test_converted(:button_color)
58
58
  ```
59
59
 
60
- Get the results with:
61
-
62
- ```ruby
63
- experiment = FieldTest::Experiment.find(:button_color)
64
- experiment.results
65
- ```
66
-
67
60
  When an experiment is over, specify a winner:
68
61
 
69
62
  ```yml
@@ -86,19 +79,7 @@ Assign a specific variant to a user with:
86
79
 
87
80
  ```ruby
88
81
  experiment = FieldTest::Experiment.find(:button_color)
89
- experiment.variant(user, variant: "red")
90
- ```
91
-
92
- Specify a participant with:
93
-
94
- ```ruby
95
- field_test(:button_color, participant: "test@example.org")
96
- ```
97
-
98
- You can pass an object as well.
99
-
100
- ```ruby
101
- field_test(:button_color, participant: user)
82
+ experiment.variant(participant, variant: "red")
102
83
  ```
103
84
 
104
85
  ## Config
@@ -110,13 +91,24 @@ exclude:
110
91
  bots: false
111
92
  ```
112
93
 
113
- Keep track of when experiments started and ended.
94
+ Keep track of when experiments started and ended. Use any format `Time.parse` accepts.
95
+
96
+ ```yml
97
+ experiments:
98
+ button_color:
99
+ started_at: Dec 1, 2016 8 am PST
100
+ ended_at: Dec 8, 2016 2 pm PST
101
+ ```
102
+
103
+ Add a friendlier name and description with:
114
104
 
115
105
  ```yml
116
106
  experiments:
117
107
  button_color:
118
- started_at: 2016-12-01 14:00:00
119
- ended_at: 2016-12-08 14:00:00
108
+ name: Buttons!
109
+ description: >
110
+ Different button colors
111
+ for the landing page.
120
112
  ```
121
113
 
122
114
  By default, variants are given the same probability of being selected. Change this with:
@@ -132,6 +124,14 @@ experiments:
132
124
  - 10
133
125
  ```
134
126
 
127
+ If the dashboard gets slow, you can make it faster with:
128
+
129
+ ```yml
130
+ cache: true
131
+ ```
132
+
133
+ This will use the Rails cache to speed up winning probability calculations.
134
+
135
135
  ## Funnels
136
136
 
137
137
  For advanced funnels, we recommend an analytics platform like [Ahoy](https://github.com/ankane/ahoy) or [Mixpanel](https://mixpanel.com/).
@@ -146,6 +146,14 @@ to get all experiments and variants for a participant, and pass them as properti
146
146
 
147
147
  ## Security
148
148
 
149
+ #### Devise
150
+
151
+ ```ruby
152
+ authenticate :user, -> (user) { user.admin? } do
153
+ mount FieldTest::Engine, at: "field_test"
154
+ end
155
+ ```
156
+
149
157
  #### Basic Authentication
150
158
 
151
159
  Set the following variables in your environment or an initializer.
@@ -155,14 +163,6 @@ ENV["FIELD_TEST_USERNAME"] = "moonrise"
155
163
  ENV["FIELD_TEST_PASSWORD"] = "kingdom"
156
164
  ```
157
165
 
158
- #### Devise
159
-
160
- ```ruby
161
- authenticate :user, -> (user) { user.admin? } do
162
- mount FieldTest::Engine, at: "field_test"
163
- end
164
- ```
165
-
166
166
  ## Credits
167
167
 
168
168
  A huge thanks to [Evan Miller](http://www.evanmiller.org/) for deriving the Bayesian formulas.
@@ -1,13 +1,9 @@
1
1
  module FieldTest
2
- class HomeController < ActionController::Base
2
+ class BaseController < ActionController::Base
3
3
  layout "field_test/application"
4
4
 
5
5
  protect_from_forgery
6
6
 
7
7
  http_basic_authenticate_with name: ENV["FIELD_TEST_USERNAME"], password: ENV["FIELD_TEST_PASSWORD"] if ENV["FIELD_TEST_PASSWORD"]
8
-
9
- def index
10
- @active_experiments, @completed_experiments = FieldTest::Experiment.all.sort_by(&:id).partition { |e| e.active? }
11
- end
12
8
  end
13
9
  end
@@ -0,0 +1,18 @@
1
+ module FieldTest
2
+ class ExperimentsController < BaseController
3
+ def index
4
+ @active_experiments, @completed_experiments = FieldTest::Experiment.all.sort_by(&:id).partition { |e| e.active? }
5
+ end
6
+
7
+ def show
8
+ @experiment = FieldTest::Experiment.find(params[:id])
9
+
10
+ @per_page = 200
11
+ @page = [1, params[:page].to_i].max
12
+ offset = (@page - 1) * @per_page
13
+ @memberships = @experiment.memberships.order(created_at: :desc).limit(@per_page).offset(offset).to_a
14
+ rescue FieldTest::ExperimentNotFound
15
+ raise ActionController::RoutingError, "Experiment not found"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ module FieldTest
2
+ class MembershipsController < BaseController
3
+ def update
4
+ membership = FieldTest::Membership.find(params[:id])
5
+ membership.update_attributes(membership_params)
6
+ redirect_to participant_path(membership.participant)
7
+ end
8
+
9
+ private
10
+
11
+ def membership_params
12
+ params.require(:membership).permit(:variant)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module FieldTest
2
+ class ParticipantsController < BaseController
3
+ def show
4
+ @participant = params[:id]
5
+ end
6
+ end
7
+ end
@@ -1,7 +1,16 @@
1
1
  <% experiments.each do |experiment| %>
2
2
  <% results = experiment.results %>
3
3
 
4
- <h2><%= experiment.name %></h2>
4
+ <h2>
5
+ <%= experiment.name %>
6
+ <small><%= link_to "Details", experiment_path(experiment.id) %></small>
7
+ </h2>
8
+
9
+
10
+ <% if experiment.description %>
11
+ <p class="description"><%= experiment.description %></p>
12
+ <% end %>
13
+
5
14
  <table>
6
15
  <thead>
7
16
  <tr>
@@ -18,7 +27,7 @@
18
27
  <td>
19
28
  <%= variant %>
20
29
  <% if variant == experiment.winner %>
21
- <span style="color: #999;">winner</span>
30
+ <span class="check">✓</span>
22
31
  <% end %>
23
32
  </td>
24
33
  <td><%= result[:participated] %></td>
@@ -32,7 +41,11 @@
32
41
  </td>
33
42
  <td>
34
43
  <% if result[:prob_winning] %>
35
- <%= (100.0 * result[:prob_winning]).round %>%
44
+ <% if result[:prob_winning] < 0.01 %>
45
+ &lt; 1%
46
+ <% else %>
47
+ <%= (100.0 * result[:prob_winning]).round %>%
48
+ <% end %>
36
49
  <% end %>
37
50
  </td>
38
51
  </tr>
@@ -0,0 +1,52 @@
1
+ <p>
2
+ <%= link_to root_path do %>
3
+ &laquo; Experiments
4
+ <% end %>
5
+ </p>
6
+
7
+ <h1><%= @experiment.name %></h1>
8
+
9
+ <table>
10
+ <thead>
11
+ <tr>
12
+ <th>Participant</th>
13
+ <th style="width: 20%;">Variant</th>
14
+ <th style="width: 20%;">Converted</th>
15
+ <th style="width: 20%;">Started</th>
16
+ </tr>
17
+ </thead>
18
+ <tbody>
19
+ <% @memberships.each do |membership| %>
20
+ <tr>
21
+ <td><%= link_to membership.participant, participant_path(membership.participant) %></td>
22
+ <td><%= membership.variant %></td>
23
+ <td>
24
+ <% if membership.converted %>
25
+ <span class="check">✓</span>
26
+ <% end %>
27
+ </td>
28
+ <td>
29
+ <% if membership.created_at > 1.day.ago %>
30
+ <%= time_ago_in_words(membership.created_at, include_seconds: true) %> ago
31
+ <% else %>
32
+ <%= membership.created_at.to_formatted_s(:short) %>
33
+ <% end %>
34
+ </td>
35
+ </tr>
36
+ <% end %>
37
+ </tbody>
38
+ </table>
39
+
40
+ <p class="pagination">
41
+ <% unless @page == 1 %>
42
+ <%= link_to experiment_path(@experiment.id, page: @page - 1) do %>
43
+ &laquo; Prev
44
+ <% end %>
45
+ <% end %>
46
+ <!-- there may not be a next page, but don't want another DB query -->
47
+ <% if @memberships.size == @per_page %>
48
+ <%= link_to experiment_path(@experiment.id, page: @page + 1) do %>
49
+ Next &raquo;
50
+ <% end %>
51
+ <% end %>
52
+ </p>
@@ -0,0 +1,47 @@
1
+ <p>
2
+ <%= link_to root_path do %>
3
+ &laquo; Experiments
4
+ <% end %>
5
+ </p>
6
+
7
+ <h1><%= @participant %></h1>
8
+
9
+ <table>
10
+ <thead>
11
+ <tr>
12
+ <th>Experiment</th>
13
+ <th style="width: 20%;">Variant</th>
14
+ <th style="width: 20%;">Converted</th>
15
+ <th style="width: 20%;">Started</th>
16
+ </tr>
17
+ </thead>
18
+ <tbody>
19
+ <% FieldTest::Membership.where(participant: @participant).each do |membership| %>
20
+ <tr>
21
+ <td><%= link_to membership.experiment, experiment_path(membership.experiment) %></td>
22
+ <td>
23
+ <% experiment = FieldTest::Experiment.find(membership.experiment) rescue nil %>
24
+ <% if experiment %>
25
+ <%= form_for membership do |f| %>
26
+ <%= f.select "variant", options_for_select(FieldTest::Experiment.find(membership.experiment).variants.map { |v| [v, v] }, membership.variant), {}, onchange: "this.form.submit()" %>
27
+ <% end %>
28
+ <% else %>
29
+ <%= membership.variant %>
30
+ <% end %>
31
+ </td>
32
+ <td>
33
+ <% if membership.converted %>
34
+ <span class="check">✓</span>
35
+ <% end %>
36
+ </td>
37
+ <td>
38
+ <% if membership.created_at > 1.day.ago %>
39
+ <%= time_ago_in_words(membership.created_at, include_seconds: true) %> ago
40
+ <% else %>
41
+ <%= membership.created_at.to_formatted_s(:short) %>
42
+ <% end %>
43
+ </td>
44
+ </tr>
45
+ <% end %>
46
+ </tbody>
47
+ </table>
@@ -7,15 +7,21 @@
7
7
  margin: 0;
8
8
  padding: 20px;
9
9
  font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
10
- font-size: 14px;
10
+ font-size: 16px;
11
11
  line-height: 1.4;
12
12
  color: #333;
13
13
  }
14
14
 
15
+ a, a:visited, a:active {
16
+ color: #08c;
17
+ text-decoration: none;
18
+ }
19
+
15
20
  table {
16
21
  width: 100%;
17
22
  border-collapse: collapse;
18
23
  border-spacing: 0;
24
+ font-size: 16px;
19
25
  margin-bottom: 20px;
20
26
  }
21
27
 
@@ -31,6 +37,28 @@
31
37
  td {
32
38
  border-top: solid 1px #ddd;
33
39
  }
40
+
41
+ h2 small {
42
+ font-size: 16px;
43
+ font-weight: normal;
44
+ }
45
+
46
+ .description {
47
+ color: #999;
48
+ }
49
+
50
+ .check {
51
+ color: #5cb85c;
52
+ }
53
+
54
+ .pagination {
55
+ text-align: center;
56
+ }
57
+
58
+ .pagination a {
59
+ padding-left: 10px;
60
+ padding-right: 10px;
61
+ }
34
62
  </style>
35
63
  </head>
36
64
  <body>
data/config/routes.rb CHANGED
@@ -1,3 +1,6 @@
1
1
  FieldTest::Engine.routes.draw do
2
- root "home#index"
2
+ resources :experiments, only: [:show]
3
+ resources :memberships, only: [:update]
4
+ get "participants/:id", to: "participants#show", constraints: {id: /.+/}, as: :participant
5
+ root "experiments#index"
3
6
  end
data/field_test.gemspec CHANGED
@@ -26,4 +26,5 @@ Gem::Specification.new do |spec|
26
26
 
27
27
  spec.add_development_dependency "bundler"
28
28
  spec.add_development_dependency "rake"
29
+ spec.add_development_dependency "minitest"
29
30
  end
data/lib/field_test.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  require "distribution/math_extension"
2
2
  require "browser"
3
+ require "active_support"
4
+ require "field_test/calculations"
3
5
  require "field_test/experiment"
4
- require "field_test/engine"
6
+ require "field_test/engine" if defined?(Rails)
5
7
  require "field_test/helpers"
6
8
  require "field_test/participant"
7
9
  require "field_test/version"
@@ -22,6 +24,10 @@ module FieldTest
22
24
  config = self.config # dev performance
23
25
  config["exclude"] && config["exclude"]["bots"]
24
26
  end
27
+
28
+ def self.cache
29
+ config["cache"]
30
+ end
25
31
  end
26
32
 
27
33
  ActiveSupport.on_load(:action_controller) do
@@ -0,0 +1,59 @@
1
+ # formulas from
2
+ # http://www.evanmiller.org/bayesian-ab-testing.html
3
+
4
+ module FieldTest
5
+ module Calculations
6
+ def self.prob_b_beats_a(alpha_a, beta_a, alpha_b, beta_b)
7
+ total = 0.0
8
+
9
+ # for performance
10
+ logbeta_aa_ba = Math.logbeta(alpha_a, beta_a)
11
+ beta_ba = beta_b + beta_a
12
+
13
+ 0.upto(alpha_b - 1) do |i|
14
+ total += Math.exp(Math.logbeta(alpha_a + i, beta_ba) -
15
+ Math.log(beta_b + i) - Math.logbeta(1 + i, beta_b) -
16
+ logbeta_aa_ba)
17
+ end
18
+
19
+ total
20
+ end
21
+
22
+ def self.prob_c_beats_a_and_b(alpha_a, beta_a, alpha_b, beta_b, alpha_c, beta_c)
23
+ total = 0.0
24
+
25
+ # for performance
26
+ logbeta_ac_bc = Math.logbeta(alpha_c, beta_c)
27
+ abc = beta_a + beta_b + beta_c
28
+ log_bb_j = []
29
+ logbeta_j_bb = []
30
+ 0.upto(alpha_b - 1) do |j|
31
+ log_bb_j[j] = Math.log(beta_b + j)
32
+ logbeta_j_bb[j] = Math.logbeta(1 + j, beta_b)
33
+ end
34
+
35
+ logbeta_ac_i_j = []
36
+ 0.upto(alpha_a - 1) do |i|
37
+ 0.upto(alpha_b - 1) do |j|
38
+ logbeta_ac_i_j[i + j] ||= Math.logbeta(alpha_c + i + j, abc)
39
+ end
40
+ end
41
+
42
+ 0.upto(alpha_a - 1) do |i|
43
+ # for performance
44
+ log_ba_i = Math.log(beta_a + i)
45
+ logbeta_i_ba = Math.logbeta(1 + i, beta_a)
46
+
47
+ 0.upto(alpha_b - 1) do |j|
48
+ total += Math.exp(logbeta_ac_i_j[i + j] -
49
+ log_ba_i - log_bb_j[j] -
50
+ logbeta_i_ba - logbeta_j_bb[j] -
51
+ logbeta_ac_bc)
52
+ end
53
+ end
54
+
55
+ 1 - prob_b_beats_a(alpha_c, beta_c, alpha_a, beta_a) -
56
+ prob_b_beats_a(alpha_c, beta_c, alpha_b, beta_b) + total
57
+ end
58
+ end
59
+ end
@@ -1,11 +1,12 @@
1
1
  module FieldTest
2
2
  class Experiment
3
- attr_reader :id, :name, :variants, :weights, :winner, :started_at, :ended_at
3
+ attr_reader :id, :name, :description, :variants, :weights, :winner, :started_at, :ended_at
4
4
 
5
5
  def initialize(attributes)
6
6
  attributes = attributes.symbolize_keys
7
7
  @id = attributes[:id]
8
8
  @name = attributes[:name] || @id.to_s.titleize
9
+ @description = attributes[:description]
9
10
  @variants = attributes[:variants]
10
11
  @weights = @variants.size.times.map { |i| attributes[:weights].to_a[i] || 1 }
11
12
  @winner = attributes[:winner]
@@ -33,6 +34,17 @@ module FieldTest
33
34
  if membership.changed?
34
35
  begin
35
36
  membership.save!
37
+
38
+ # log it!
39
+ info = {
40
+ experiment: id,
41
+ variant: membership.variant,
42
+ participant: membership.participant
43
+ }.merge(options.slice(:ip, :user_agent))
44
+
45
+ # sorta logfmt :)
46
+ info = info.map { |k, v| v = "\"#{v}\"" if k == :user_agent; "#{k}=#{v}" }.join(" ")
47
+ Rails.logger.info "[field test] #{info}"
36
48
  rescue ActiveRecord::RecordNotUnique
37
49
  membership = memberships.find_by(participant: participants.first)
38
50
  end
@@ -75,10 +87,10 @@ module FieldTest
75
87
  }
76
88
  end
77
89
  case variants.size
78
- when 1
79
- results[variants[0]][:prob_winning] = 1
80
- when 2, 3
81
- variants.size.times do |i|
90
+ when 1, 2, 3
91
+ total = 0.0
92
+
93
+ (variants.size - 1).times do |i|
82
94
  c = results.values[i]
83
95
  b = results.values[(i + 1) % variants.size]
84
96
  a = results.values[(i + 2) % variants.size]
@@ -90,13 +102,23 @@ module FieldTest
90
102
  alpha_c = 1 + c[:converted]
91
103
  beta_c = 1 + c[:participated] - c[:converted]
92
104
 
93
- results[variants[i]][:prob_winning] =
105
+ # TODO calculate this incrementally by caching intermediate results
106
+ prob_winning =
94
107
  if variants.size == 2
95
- prob_b_beats_a(alpha_b, beta_b, alpha_c, beta_c)
108
+ cache_fetch ["field_test", "prob_b_beats_a", alpha_b, beta_b, alpha_c, beta_c] do
109
+ Calculations.prob_b_beats_a(alpha_b, beta_b, alpha_c, beta_c)
110
+ end
96
111
  else
97
- prob_c_beats_a_and_b(alpha_a, beta_a, alpha_b, beta_b, alpha_c, beta_c)
112
+ cache_fetch ["field_test", "prob_c_beats_a_and_b", alpha_a, beta_a, alpha_b, beta_b, alpha_c, beta_c] do
113
+ Calculations.prob_c_beats_a_and_b(alpha_a, beta_a, alpha_b, beta_b, alpha_c, beta_c)
114
+ end
98
115
  end
116
+
117
+ results[variants[i]][:prob_winning] = prob_winning
118
+ total += prob_winning
99
119
  end
120
+
121
+ results[variants.last][:prob_winning] = 1 - total
100
122
  end
101
123
  results
102
124
  end
@@ -140,33 +162,12 @@ module FieldTest
140
162
  variants.last
141
163
  end
142
164
 
143
- # formula from
144
- # http://www.evanmiller.org/bayesian-ab-testing.html
145
- def prob_b_beats_a(alpha_a, beta_a, alpha_b, beta_b)
146
- total = 0.0
147
-
148
- 0.upto(alpha_b - 1) do |i|
149
- total += Math.exp(Math.logbeta(alpha_a + i, beta_b + beta_a) -
150
- Math.log(beta_b + i) - Math.logbeta(1 + i, beta_b) -
151
- Math.logbeta(alpha_a, beta_a))
152
- end
153
-
154
- total
155
- end
156
-
157
- def prob_c_beats_a_and_b(alpha_a, beta_a, alpha_b, beta_b, alpha_c, beta_c)
158
- total = 0.0
159
- 0.upto(alpha_a - 1) do |i|
160
- 0.upto(alpha_b - 1) do |j|
161
- total += Math.exp(Math.logbeta(alpha_c + i + j, beta_a + beta_b + beta_c) -
162
- Math.log(beta_a + i) - Math.log(beta_b + j) -
163
- Math.logbeta(1 + i, beta_a) - Math.logbeta(1 + j, beta_b) -
164
- Math.logbeta(alpha_c, beta_c))
165
- end
165
+ def cache_fetch(key)
166
+ if FieldTest.cache
167
+ Rails.cache.fetch(key.join("/")) { yield }
168
+ else
169
+ yield
166
170
  end
167
-
168
- 1 - prob_b_beats_a(alpha_c, beta_c, alpha_a, beta_a) -
169
- prob_b_beats_a(alpha_c, beta_c, alpha_b, beta_b) + total
170
171
  end
171
172
  end
172
173
  end
@@ -13,6 +13,9 @@ module FieldTest
13
13
  if FieldTest.exclude_bots?
14
14
  options[:exclude] = Browser.new(request.user_agent).bot?
15
15
  end
16
+
17
+ options[:ip] = request.remote_ip
18
+ options[:user_agent] = request.user_agent
16
19
  end
17
20
 
18
21
  # cache results for request
@@ -54,13 +57,19 @@ module FieldTest
54
57
  if try(:request)
55
58
  # use cookie
56
59
  cookie_key = "field_test"
60
+
57
61
  token = cookies[cookie_key]
62
+ token = token.gsub(/[^a-z0-9\-]/i, "") if token
63
+
58
64
  if participants.empty? && !token
59
65
  token = SecureRandom.uuid
60
66
  cookies[cookie_key] = {value: token, expires: 30.days.from_now}
61
67
  end
62
68
  if token
63
- participants << "cookie:#{token.gsub(/[^a-z0-9\-]/i, "")}"
69
+ participants << token
70
+
71
+ # backwards compatibility
72
+ participants << "cookie:#{token}"
64
73
  end
65
74
  end
66
75
 
@@ -1,3 +1,3 @@
1
1
  module FieldTest
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -10,5 +10,6 @@ class <%= migration_class_name %> < ActiveRecord::Migration
10
10
 
11
11
  add_index :field_test_memberships, [:experiment, :participant], unique: true
12
12
  add_index :field_test_memberships, :participant
13
+ add_index :field_test_memberships, [:experiment, :created_at]
13
14
  end
14
15
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: field_test
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-12-17 00:00:00.000000000 Z
11
+ date: 2016-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: minitest
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  description:
98
112
  email:
99
113
  - andrew@chartkick.com
@@ -107,14 +121,20 @@ files:
107
121
  - LICENSE.txt
108
122
  - README.md
109
123
  - Rakefile
110
- - app/controllers/field_test/home_controller.rb
124
+ - app/controllers/field_test/base_controller.rb
125
+ - app/controllers/field_test/experiments_controller.rb
126
+ - app/controllers/field_test/memberships_controller.rb
127
+ - app/controllers/field_test/participants_controller.rb
111
128
  - app/models/field_test/membership.rb
112
- - app/views/field_test/home/_experiments.html.erb
113
- - app/views/field_test/home/index.html.erb
129
+ - app/views/field_test/experiments/_experiments.html.erb
130
+ - app/views/field_test/experiments/index.html.erb
131
+ - app/views/field_test/experiments/show.html.erb
132
+ - app/views/field_test/participants/show.html.erb
114
133
  - app/views/layouts/field_test/application.html.erb
115
134
  - config/routes.rb
116
135
  - field_test.gemspec
117
136
  - lib/field_test.rb
137
+ - lib/field_test/calculations.rb
118
138
  - lib/field_test/engine.rb
119
139
  - lib/field_test/experiment.rb
120
140
  - lib/field_test/helpers.rb