field_test 0.2.0 → 0.2.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.
Potentially problematic release.
This version of field_test might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +36 -10
- data/app/controllers/field_test/experiments_controller.rb +7 -0
- data/app/controllers/field_test/participants_controller.rb +9 -0
- data/app/models/field_test/event.rb +8 -0
- data/app/models/field_test/membership.rb +2 -0
- data/app/views/field_test/experiments/_experiments.html.erb +46 -41
- data/app/views/field_test/experiments/show.html.erb +18 -1
- data/app/views/field_test/participants/show.html.erb +17 -2
- data/app/views/layouts/field_test/application.html.erb +15 -0
- data/lib/field_test.rb +14 -1
- data/lib/field_test/calculations.rb +4 -5
- data/lib/field_test/experiment.rb +56 -9
- data/lib/field_test/helpers.rb +1 -1
- data/lib/field_test/version.rb +1 -1
- data/lib/generators/field_test/events_generator.rb +27 -0
- data/lib/generators/field_test/templates/events.rb +11 -0
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f722991205f31c2faf4c59852d717ac229f3043
|
4
|
+
data.tar.gz: 18b45c92adbcec168c41e051ea1c6613c6dadbfc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d18d2688655ebd5a1d523b3ec8452fd96701e1a63b6cb7f93eae395fd2bfae39d7bf1c716e6617cf9a96e47e7fd129035ef31f3243e928653c52839dba499a9b
|
7
|
+
data.tar.gz: 8cedb82ac15520e091e46cc82ec7fa21faa41ec7ed0fc60f0d351b82948cb6c823ecaca645423893feaa5a1a4dc828e173a1255fd39bffe2401d84a868fc7fbf
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -3,10 +3,10 @@
|
|
3
3
|
:maple_leaf: A/B testing for Rails
|
4
4
|
|
5
5
|
- Designed for web and email
|
6
|
-
- Comes with a [
|
6
|
+
- Comes with a [handy dashboard](https://fieldtest.dokkuapp.com/)
|
7
7
|
- Seamlessly handles the transition from anonymous visitor to logged in user
|
8
8
|
|
9
|
-
Uses [Bayesian
|
9
|
+
Uses [Bayesian statistics](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
|
|
11
11
|
## Installation
|
12
12
|
|
@@ -62,7 +62,7 @@ When an experiment is over, specify a winner:
|
|
62
62
|
```yml
|
63
63
|
experiments:
|
64
64
|
button_color:
|
65
|
-
winner:
|
65
|
+
winner: green
|
66
66
|
```
|
67
67
|
|
68
68
|
All calls to `field_test` will now return the winner, and metrics will stop being recorded.
|
@@ -72,16 +72,18 @@ All calls to `field_test` will now return the winner, and metrics will stop bein
|
|
72
72
|
You can specify a variant with query parameters to make testing easier
|
73
73
|
|
74
74
|
```
|
75
|
-
|
75
|
+
?field_test[button_color]=green
|
76
76
|
```
|
77
77
|
|
78
78
|
Assign a specific variant to a user with:
|
79
79
|
|
80
80
|
```ruby
|
81
81
|
experiment = FieldTest::Experiment.find(:button_color)
|
82
|
-
experiment.variant(participant, variant: "
|
82
|
+
experiment.variant(participant, variant: "green")
|
83
83
|
```
|
84
84
|
|
85
|
+
You can also change a user’s variant from the dashboard.
|
86
|
+
|
85
87
|
## Config
|
86
88
|
|
87
89
|
By default, bots are returned the first variant and excluded from metrics. Change this with:
|
@@ -91,7 +93,7 @@ exclude:
|
|
91
93
|
bots: false
|
92
94
|
```
|
93
95
|
|
94
|
-
Keep track of when experiments started and ended. Use any format `Time.parse` accepts.
|
96
|
+
Keep track of when experiments started and ended. Use any format `Time.parse` accepts. Variants assigned outside this window are not included in metrics.
|
95
97
|
|
96
98
|
```yml
|
97
99
|
experiments:
|
@@ -120,8 +122,8 @@ experiments:
|
|
120
122
|
- red
|
121
123
|
- blue
|
122
124
|
weights:
|
123
|
-
-
|
124
|
-
-
|
125
|
+
- 85
|
126
|
+
- 15
|
125
127
|
```
|
126
128
|
|
127
129
|
If the dashboard gets slow, you can make it faster with:
|
@@ -134,9 +136,33 @@ This will use the Rails cache to speed up winning probability calculations.
|
|
134
136
|
|
135
137
|
## Funnels
|
136
138
|
|
137
|
-
|
139
|
+
You can set multiple goals for an experiment to track conversions at different parts of the funnel. First, run:
|
140
|
+
|
141
|
+
```sh
|
142
|
+
rails g field_test:events
|
143
|
+
```
|
144
|
+
|
145
|
+
And add to your config:
|
146
|
+
|
147
|
+
```yml
|
148
|
+
experiments:
|
149
|
+
button_color:
|
150
|
+
goals:
|
151
|
+
- signed_up
|
152
|
+
- ordered
|
153
|
+
```
|
154
|
+
|
155
|
+
Specify a goal during conversion with:
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
field_test_converted(:button_color, goal: "ordered")
|
159
|
+
```
|
160
|
+
|
161
|
+
The results for all goals will appear on the dashboard.
|
162
|
+
|
163
|
+
### Advanced
|
138
164
|
|
139
|
-
You can use:
|
165
|
+
For advanced funnels, we recommend an analytics platform like [Ahoy](https://github.com/ankane/ahoy) or [Mixpanel](https://mixpanel.com/). You can use:
|
140
166
|
|
141
167
|
```ruby
|
142
168
|
field_test_experiments
|
@@ -11,6 +11,13 @@ module FieldTest
|
|
11
11
|
@page = [1, params[:page].to_i].max
|
12
12
|
offset = (@page - 1) * @per_page
|
13
13
|
@memberships = @experiment.memberships.order(created_at: :desc).limit(@per_page).offset(offset).to_a
|
14
|
+
|
15
|
+
@events =
|
16
|
+
if FieldTest.events_supported?
|
17
|
+
@experiment.events.where(field_test_membership_id: @memberships.map(&:id)).group(:field_test_membership_id, :name).count
|
18
|
+
else
|
19
|
+
{}
|
20
|
+
end
|
14
21
|
rescue FieldTest::ExperimentNotFound
|
15
22
|
raise ActionController::RoutingError, "Experiment not found"
|
16
23
|
end
|
@@ -2,6 +2,15 @@ module FieldTest
|
|
2
2
|
class ParticipantsController < BaseController
|
3
3
|
def show
|
4
4
|
@participant = params[:id]
|
5
|
+
# TODO better ordering
|
6
|
+
@memberships = FieldTest::Membership.where(participant: @participant).order(:id)
|
7
|
+
|
8
|
+
@events =
|
9
|
+
if FieldTest.events_supported?
|
10
|
+
FieldTest::Event.where(field_test_membership_id: @memberships.map(&:id)).group(:field_test_membership_id, :name).count
|
11
|
+
else
|
12
|
+
{}
|
13
|
+
end
|
5
14
|
end
|
6
15
|
end
|
7
16
|
end
|
@@ -2,6 +2,8 @@ module FieldTest
|
|
2
2
|
class Membership < ActiveRecord::Base
|
3
3
|
self.table_name = "field_test_memberships"
|
4
4
|
|
5
|
+
has_many :events, class_name: "FieldTest::Event"
|
6
|
+
|
5
7
|
validates :participant, presence: true
|
6
8
|
validates :experiment, presence: true
|
7
9
|
validates :variant, presence: true
|
@@ -1,55 +1,60 @@
|
|
1
1
|
<% experiments.each do |experiment| %>
|
2
|
-
<% results = experiment.results %>
|
3
|
-
|
4
2
|
<h2>
|
5
3
|
<%= experiment.name %>
|
6
4
|
<small><%= link_to "Details", experiment_path(experiment.id) %></small>
|
7
5
|
</h2>
|
8
6
|
|
9
|
-
|
10
7
|
<% if experiment.description %>
|
11
8
|
<p class="description"><%= experiment.description %></p>
|
12
9
|
<% end %>
|
13
10
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
</thead>
|
24
|
-
<tbody>
|
25
|
-
<% results.each do |variant, result| %>
|
11
|
+
<% experiment.goals.each do |goal| %>
|
12
|
+
<% results = experiment.results(goal: goal) %>
|
13
|
+
|
14
|
+
<% if experiment.multiple_goals? %>
|
15
|
+
<h3><%= goal.titleize %></h3>
|
16
|
+
<% end %>
|
17
|
+
|
18
|
+
<table>
|
19
|
+
<thead>
|
26
20
|
<tr>
|
27
|
-
<
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
21
|
+
<th>Variant</th>
|
22
|
+
<th style="width: 20%;">Participants</th>
|
23
|
+
<th style="width: 20%;">Conversions</th>
|
24
|
+
<th style="width: 20%;">Conversion Rate</th>
|
25
|
+
<th style="width: 20%;">Prob Winning</th>
|
26
|
+
</tr>
|
27
|
+
</thead>
|
28
|
+
<tbody>
|
29
|
+
<% results.each do |variant, result| %>
|
30
|
+
<tr>
|
31
|
+
<td>
|
32
|
+
<%= variant %>
|
33
|
+
<% if variant == experiment.winner %>
|
34
|
+
<span class="check">✓</span>
|
35
|
+
<% end %>
|
36
|
+
</td>
|
37
|
+
<td><%= result[:participated] %></td>
|
38
|
+
<td><%= result[:converted] %></td>
|
39
|
+
<td>
|
40
|
+
<% if result[:conversion_rate] %>
|
41
|
+
<%= (100.0 * result[:conversion_rate]).round %>%
|
46
42
|
<% else %>
|
47
|
-
|
43
|
+
-
|
48
44
|
<% end %>
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
45
|
+
</td>
|
46
|
+
<td>
|
47
|
+
<% if result[:prob_winning] %>
|
48
|
+
<% if result[:prob_winning] < 0.01 %>
|
49
|
+
< 1%
|
50
|
+
<% else %>
|
51
|
+
<%= (100.0 * result[:prob_winning]).round %>%
|
52
|
+
<% end %>
|
53
|
+
<% end %>
|
54
|
+
</td>
|
55
|
+
</tr>
|
56
|
+
<% end %>
|
57
|
+
</tbody>
|
58
|
+
</table>
|
59
|
+
<% end %>
|
55
60
|
<% end %>
|
@@ -6,6 +6,10 @@
|
|
6
6
|
|
7
7
|
<h1><%= @experiment.name %></h1>
|
8
8
|
|
9
|
+
<% if @experiment.description %>
|
10
|
+
<p class="description"><%= @experiment.description %></p>
|
11
|
+
<% end %>
|
12
|
+
|
9
13
|
<table>
|
10
14
|
<thead>
|
11
15
|
<tr>
|
@@ -21,7 +25,20 @@
|
|
21
25
|
<td><%= link_to membership.participant, participant_path(membership.participant) %></td>
|
22
26
|
<td><%= membership.variant %></td>
|
23
27
|
<td>
|
24
|
-
<%
|
28
|
+
<% converted = false %>
|
29
|
+
<% @experiment.goals.each do |goal| %>
|
30
|
+
<% if @events[[membership.id, goal]] %>
|
31
|
+
<% converted = true %>
|
32
|
+
<div>
|
33
|
+
<span class="check">✓</span>
|
34
|
+
<% if @experiment.multiple_goals? %>
|
35
|
+
<%= goal.titleize %>
|
36
|
+
<% end %>
|
37
|
+
</div>
|
38
|
+
<% end %>
|
39
|
+
<% end %>
|
40
|
+
|
41
|
+
<% if !converted && membership.try(:converted) %>
|
25
42
|
<span class="check">✓</span>
|
26
43
|
<% end %>
|
27
44
|
</td>
|
@@ -16,7 +16,7 @@
|
|
16
16
|
</tr>
|
17
17
|
</thead>
|
18
18
|
<tbody>
|
19
|
-
<%
|
19
|
+
<% @memberships.each do |membership| %>
|
20
20
|
<tr>
|
21
21
|
<td><%= link_to membership.experiment, experiment_path(membership.experiment) %></td>
|
22
22
|
<td>
|
@@ -30,7 +30,22 @@
|
|
30
30
|
<% end %>
|
31
31
|
</td>
|
32
32
|
<td>
|
33
|
-
<%
|
33
|
+
<% converted = false %>
|
34
|
+
<% if experiment %>
|
35
|
+
<% experiment.goals.each do |goal| %>
|
36
|
+
<% if @events[[membership.id, goal]] %>
|
37
|
+
<% converted = true %>
|
38
|
+
<div>
|
39
|
+
<span class="check">✓</span>
|
40
|
+
<% if experiment.multiple_goals? %>
|
41
|
+
<%= goal.titleize %>
|
42
|
+
<% end %>
|
43
|
+
</div>
|
44
|
+
<% end %>
|
45
|
+
<% end %>
|
46
|
+
<% end %>
|
47
|
+
|
48
|
+
<% if !converted && membership.try(:converted) %>
|
34
49
|
<span class="check">✓</span>
|
35
50
|
<% end %>
|
36
51
|
</td>
|
@@ -36,6 +36,7 @@
|
|
36
36
|
|
37
37
|
td {
|
38
38
|
border-top: solid 1px #ddd;
|
39
|
+
vertical-align: top;
|
39
40
|
}
|
40
41
|
|
41
42
|
h2 small {
|
@@ -43,6 +44,20 @@
|
|
43
44
|
font-weight: normal;
|
44
45
|
}
|
45
46
|
|
47
|
+
form {
|
48
|
+
margin: 0;
|
49
|
+
}
|
50
|
+
|
51
|
+
ul {
|
52
|
+
margin: 0;
|
53
|
+
padding: 0;
|
54
|
+
list-style-type: none;
|
55
|
+
}
|
56
|
+
|
57
|
+
li {
|
58
|
+
margin-bottom: 0;
|
59
|
+
}
|
60
|
+
|
46
61
|
.description {
|
47
62
|
color: #999;
|
48
63
|
}
|
data/lib/field_test.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require "distribution/math_extension"
|
2
1
|
require "browser"
|
3
2
|
require "active_support"
|
4
3
|
require "field_test/calculations"
|
@@ -28,6 +27,20 @@ module FieldTest
|
|
28
27
|
def self.cache
|
29
28
|
config["cache"]
|
30
29
|
end
|
30
|
+
|
31
|
+
def self.events_supported?
|
32
|
+
unless defined?(@events_supported)
|
33
|
+
connection = FieldTest::Membership.connection
|
34
|
+
table_name = "field_test_events"
|
35
|
+
@events_supported =
|
36
|
+
if connection.respond_to?(:data_source_exists?)
|
37
|
+
connection.data_source_exists?(table_name)
|
38
|
+
else
|
39
|
+
connection.table_exists?(table_name)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
@events_supported
|
43
|
+
end
|
31
44
|
end
|
32
45
|
|
33
46
|
ActiveSupport.on_load(:action_controller) do
|
@@ -1,6 +1,7 @@
|
|
1
|
+
require "distribution/math_extension"
|
2
|
+
|
1
3
|
# formulas from
|
2
4
|
# http://www.evanmiller.org/bayesian-ab-testing.html
|
3
|
-
|
4
5
|
module FieldTest
|
5
6
|
module Calculations
|
6
7
|
def self.prob_b_beats_a(alpha_a, beta_a, alpha_b, beta_b)
|
@@ -27,14 +28,12 @@ module FieldTest
|
|
27
28
|
abc = beta_a + beta_b + beta_c
|
28
29
|
log_bb_j = []
|
29
30
|
logbeta_j_bb = []
|
31
|
+
logbeta_ac_i_j = []
|
30
32
|
0.upto(alpha_b - 1) do |j|
|
31
33
|
log_bb_j[j] = Math.log(beta_b + j)
|
32
34
|
logbeta_j_bb[j] = Math.logbeta(1 + j, beta_b)
|
33
|
-
end
|
34
35
|
|
35
|
-
|
36
|
-
0.upto(alpha_a - 1) do |i|
|
37
|
-
0.upto(alpha_b - 1) do |j|
|
36
|
+
0.upto(alpha_a - 1) do |i|
|
38
37
|
logbeta_ac_i_j[i + j] ||= Math.logbeta(alpha_c + i + j, abc)
|
39
38
|
end
|
40
39
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module FieldTest
|
2
2
|
class Experiment
|
3
|
-
attr_reader :id, :name, :description, :variants, :weights, :winner, :started_at, :ended_at
|
3
|
+
attr_reader :id, :name, :description, :variants, :weights, :winner, :started_at, :ended_at, :goals
|
4
4
|
|
5
5
|
def initialize(attributes)
|
6
6
|
attributes = attributes.symbolize_keys
|
@@ -12,6 +12,8 @@ module FieldTest
|
|
12
12
|
@winner = attributes[:winner]
|
13
13
|
@started_at = Time.zone.parse(attributes[:started_at].to_s) if attributes[:started_at]
|
14
14
|
@ended_at = Time.zone.parse(attributes[:ended_at].to_s) if attributes[:ended_at]
|
15
|
+
@goals = attributes[:goals] || ["conversion"]
|
16
|
+
@use_events = attributes[:use_events]
|
15
17
|
end
|
16
18
|
|
17
19
|
def variant(participants, options = {})
|
@@ -53,14 +55,26 @@ module FieldTest
|
|
53
55
|
membership.try(:variant) || variants.first
|
54
56
|
end
|
55
57
|
|
56
|
-
def convert(participants)
|
58
|
+
def convert(participants, goal: nil)
|
59
|
+
goal ||= goals.first
|
60
|
+
|
57
61
|
participants = FieldTest::Participant.standardize(participants)
|
58
62
|
check_participants(participants)
|
59
63
|
membership = membership_for(participants)
|
60
64
|
|
61
65
|
if membership
|
62
|
-
membership.converted
|
63
|
-
|
66
|
+
if membership.respond_to?(:converted)
|
67
|
+
membership.converted = true
|
68
|
+
membership.save! if membership.changed?
|
69
|
+
end
|
70
|
+
|
71
|
+
if use_events?
|
72
|
+
FieldTest::Event.create!(
|
73
|
+
name: goal,
|
74
|
+
field_test_membership_id: membership.id
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
64
78
|
true
|
65
79
|
else
|
66
80
|
false
|
@@ -71,11 +85,36 @@ module FieldTest
|
|
71
85
|
FieldTest::Membership.where(experiment: id)
|
72
86
|
end
|
73
87
|
|
74
|
-
def
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
88
|
+
def events
|
89
|
+
FieldTest::Event.joins(:field_test_membership).where(field_test_memberships: {experiment: id})
|
90
|
+
end
|
91
|
+
|
92
|
+
def multiple_goals?
|
93
|
+
goals.size > 1
|
94
|
+
end
|
95
|
+
|
96
|
+
def results(goal: nil)
|
97
|
+
goal ||= goals.first
|
98
|
+
|
99
|
+
relation = memberships.group(:variant)
|
100
|
+
relation = relation.where("created_at >= ?", started_at) if started_at
|
101
|
+
relation = relation.where("created_at <= ?", ended_at) if ended_at
|
102
|
+
|
103
|
+
if use_events?
|
104
|
+
data = {}
|
105
|
+
sql =
|
106
|
+
relation.joins("LEFT JOIN field_test_events ON field_test_events.field_test_membership_id = field_test_memberships.id")
|
107
|
+
.select("variant, COUNT(DISTINCT participant) AS participated, COUNT(DISTINCT field_test_membership_id) AS converted")
|
108
|
+
.where(field_test_events: {name: goal})
|
109
|
+
|
110
|
+
FieldTest::Membership.connection.select_all(sql).each do |row|
|
111
|
+
data[[row["variant"], true]] = row["converted"].to_i
|
112
|
+
data[[row["variant"], false]] = row["participated"].to_i - row["converted"].to_i
|
113
|
+
end
|
114
|
+
else
|
115
|
+
data = relation.group(:converted).count
|
116
|
+
end
|
117
|
+
|
79
118
|
results = {}
|
80
119
|
variants.each do |variant|
|
81
120
|
converted = data[[variant, true]].to_i
|
@@ -127,6 +166,14 @@ module FieldTest
|
|
127
166
|
!winner
|
128
167
|
end
|
129
168
|
|
169
|
+
def use_events?
|
170
|
+
if @use_events.nil?
|
171
|
+
FieldTest.events_supported?
|
172
|
+
else
|
173
|
+
@use_events
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
130
177
|
def self.find(id)
|
131
178
|
experiment = all.index_by(&:id)[id.to_s]
|
132
179
|
raise FieldTest::ExperimentNotFound unless experiment
|
data/lib/field_test/helpers.rb
CHANGED
data/lib/field_test/version.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
require "rails/generators/migration"
|
3
|
+
require "active_record"
|
4
|
+
require "rails/generators/active_record"
|
5
|
+
|
6
|
+
module FieldTest
|
7
|
+
module Generators
|
8
|
+
class EventsGenerator < Rails::Generators::Base
|
9
|
+
include Rails::Generators::Migration
|
10
|
+
source_root File.expand_path("../templates", __FILE__)
|
11
|
+
|
12
|
+
# Implement the required interface for Rails::Generators::Migration.
|
13
|
+
def self.next_migration_number(dirname) #:nodoc:
|
14
|
+
next_migration_number = current_migration_number(dirname) + 1
|
15
|
+
if ::ActiveRecord::Base.timestamped_migrations
|
16
|
+
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
|
17
|
+
else
|
18
|
+
"%.3d" % next_migration_number
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def copy_migration
|
23
|
+
migration_template "events.rb", "db/migrate/create_field_test_events.rb"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :field_test_events do |t|
|
4
|
+
t.integer :field_test_membership_id
|
5
|
+
t.string :name
|
6
|
+
t.timestamp :created_at
|
7
|
+
end
|
8
|
+
|
9
|
+
add_index :field_test_events, :field_test_membership_id
|
10
|
+
end
|
11
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: field_test
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
@@ -125,6 +125,7 @@ files:
|
|
125
125
|
- app/controllers/field_test/experiments_controller.rb
|
126
126
|
- app/controllers/field_test/memberships_controller.rb
|
127
127
|
- app/controllers/field_test/participants_controller.rb
|
128
|
+
- app/models/field_test/event.rb
|
128
129
|
- app/models/field_test/membership.rb
|
129
130
|
- app/views/field_test/experiments/_experiments.html.erb
|
130
131
|
- app/views/field_test/experiments/index.html.erb
|
@@ -140,8 +141,10 @@ files:
|
|
140
141
|
- lib/field_test/helpers.rb
|
141
142
|
- lib/field_test/participant.rb
|
142
143
|
- lib/field_test/version.rb
|
144
|
+
- lib/generators/field_test/events_generator.rb
|
143
145
|
- lib/generators/field_test/install_generator.rb
|
144
146
|
- lib/generators/field_test/templates/config.yml
|
147
|
+
- lib/generators/field_test/templates/events.rb
|
145
148
|
- lib/generators/field_test/templates/memberships.rb
|
146
149
|
homepage: https://github.com/ankane/field_test
|
147
150
|
licenses: []
|