field_test 0.2.4 → 0.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -8
- data/LICENSE.txt +1 -1
- data/README.md +242 -22
- data/app/controllers/field_test/base_controller.rb +1 -1
- data/app/controllers/field_test/memberships_controller.rb +2 -2
- data/app/controllers/field_test/participants_controller.rb +10 -2
- data/app/helpers/field_test/base_helper.rb +12 -0
- data/app/models/field_test/membership.rb +2 -1
- data/app/views/field_test/experiments/_experiments.html.erb +3 -2
- data/app/views/field_test/experiments/show.html.erb +1 -1
- data/app/views/layouts/field_test/application.html.erb +4 -0
- data/config/routes.rb +2 -1
- data/lib/field_test.rb +47 -12
- data/lib/field_test/controller.rb +76 -0
- data/lib/field_test/experiment.rb +81 -48
- data/lib/field_test/helpers.rb +24 -53
- data/lib/field_test/mailer.rb +20 -0
- data/lib/field_test/participant.rb +33 -2
- data/lib/field_test/version.rb +1 -1
- data/lib/generators/field_test/events_generator.rb +3 -18
- data/lib/generators/field_test/install_generator.rb +3 -18
- data/lib/generators/field_test/templates/events.rb.tt +2 -4
- data/lib/generators/field_test/templates/memberships.rb.tt +5 -4
- metadata +59 -19
- data/.gitignore +0 -9
- data/Gemfile +0 -4
- data/Rakefile +0 -10
- data/field_test.gemspec +0 -30
data/lib/field_test/helpers.rb
CHANGED
@@ -1,86 +1,57 @@
|
|
1
1
|
module FieldTest
|
2
2
|
module Helpers
|
3
|
-
def field_test(experiment, options
|
3
|
+
def field_test(experiment, **options)
|
4
4
|
exp = FieldTest::Experiment.find(experiment)
|
5
5
|
|
6
|
-
participants =
|
6
|
+
participants = FieldTest::Participant.standardize(options[:participant] || field_test_participant)
|
7
7
|
|
8
8
|
if try(:request)
|
9
|
-
|
10
|
-
|
9
|
+
options = options.dup
|
10
|
+
|
11
|
+
if !options[:variant] && params[:field_test] && params[:field_test][experiment] && exp.variants.include?(params[:field_test][experiment])
|
12
|
+
params_variant = params[:field_test][experiment]
|
11
13
|
end
|
12
14
|
|
13
15
|
if FieldTest.exclude_bots?
|
14
16
|
options[:exclude] = Browser.new(request.user_agent).bot?
|
15
17
|
end
|
16
18
|
|
19
|
+
options[:exclude] ||= FieldTest.excluded_ips.any? { |ip| ip.include?(request.remote_ip) }
|
20
|
+
|
17
21
|
options[:ip] = request.remote_ip
|
18
22
|
options[:user_agent] = request.user_agent
|
19
23
|
end
|
20
24
|
|
21
|
-
#
|
22
|
-
|
23
|
-
|
25
|
+
# don't update variant when passed via params
|
26
|
+
if params_variant
|
27
|
+
params_variant
|
28
|
+
else
|
29
|
+
# cache results for request
|
30
|
+
# TODO possibly remove in 0.4.0
|
31
|
+
cache_key = [exp.id, participants.map(&:where_values), options.slice(:variant, :exclude)]
|
32
|
+
@field_test_cache ||= {}
|
33
|
+
@field_test_cache[cache_key] ||= exp.variant(participants, options)
|
34
|
+
end
|
24
35
|
end
|
25
36
|
|
26
|
-
def field_test_converted(experiment, options
|
37
|
+
def field_test_converted(experiment, **options)
|
27
38
|
exp = FieldTest::Experiment.find(experiment)
|
28
39
|
|
29
|
-
participants =
|
40
|
+
participants = FieldTest::Participant.standardize(options[:participant] || field_test_participant)
|
30
41
|
|
31
42
|
exp.convert(participants, goal: options[:goal])
|
32
43
|
end
|
33
44
|
|
34
|
-
|
35
|
-
|
36
|
-
|
45
|
+
# TODO fetch in single query
|
46
|
+
def field_test_experiments(**options)
|
47
|
+
participants = FieldTest::Participant.standardize(options[:participant] || field_test_participant)
|
37
48
|
experiments = {}
|
38
49
|
participants.each do |participant|
|
39
|
-
|
50
|
+
FieldTest::Membership.where(participant.where_values).each do |membership|
|
40
51
|
experiments[membership.experiment] ||= membership.variant
|
41
52
|
end
|
42
53
|
end
|
43
54
|
experiments
|
44
55
|
end
|
45
|
-
|
46
|
-
def field_test_participants(options = {})
|
47
|
-
participants = []
|
48
|
-
|
49
|
-
if options[:participant]
|
50
|
-
participants << options[:participant]
|
51
|
-
else
|
52
|
-
if respond_to?(:current_user, true) && current_user
|
53
|
-
participants << current_user
|
54
|
-
end
|
55
|
-
|
56
|
-
# controllers and views
|
57
|
-
if try(:request)
|
58
|
-
# use cookie
|
59
|
-
cookie_key = "field_test"
|
60
|
-
|
61
|
-
token = cookies[cookie_key]
|
62
|
-
token = token.gsub(/[^a-z0-9\-]/i, "") if token
|
63
|
-
|
64
|
-
if participants.empty? && !token
|
65
|
-
token = SecureRandom.uuid
|
66
|
-
cookies[cookie_key] = {value: token, expires: 30.days.from_now}
|
67
|
-
end
|
68
|
-
if token
|
69
|
-
participants << token
|
70
|
-
|
71
|
-
# backwards compatibility
|
72
|
-
participants << "cookie:#{token}"
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
# mailers
|
77
|
-
to = try(:message).try(:to).try(:first)
|
78
|
-
if to
|
79
|
-
participants << to
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
FieldTest::Participant.standardize(participants)
|
84
|
-
end
|
85
56
|
end
|
86
57
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module FieldTest
|
2
|
+
module Mailer
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Helpers
|
5
|
+
|
6
|
+
included do
|
7
|
+
helper_method :field_test
|
8
|
+
helper_method :field_test_converted
|
9
|
+
helper_method :field_test_experiments
|
10
|
+
end
|
11
|
+
|
12
|
+
def field_test_participant
|
13
|
+
if @user
|
14
|
+
@user
|
15
|
+
elsif respond_to?(:params) && params
|
16
|
+
params[:user]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,7 +1,38 @@
|
|
1
1
|
module FieldTest
|
2
2
|
class Participant
|
3
|
-
|
4
|
-
|
3
|
+
attr_reader :type, :id
|
4
|
+
|
5
|
+
def initialize(object)
|
6
|
+
if object.is_a?(FieldTest::Participant)
|
7
|
+
@type = object.type
|
8
|
+
@id = object.id
|
9
|
+
elsif object.respond_to?(:model_name)
|
10
|
+
@type = object.model_name.name
|
11
|
+
@id = object.id.to_s
|
12
|
+
else
|
13
|
+
@id = object.to_s
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def participant
|
18
|
+
[type, id].compact.join(":")
|
19
|
+
end
|
20
|
+
|
21
|
+
def where_values
|
22
|
+
if FieldTest.legacy_participants
|
23
|
+
{
|
24
|
+
participant: participant
|
25
|
+
}
|
26
|
+
else
|
27
|
+
{
|
28
|
+
participant_type: type,
|
29
|
+
participant_id: id
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.standardize(participant)
|
35
|
+
Array(participant).compact.map { |v| FieldTest::Participant.new(v) }
|
5
36
|
end
|
6
37
|
end
|
7
38
|
end
|
data/lib/field_test/version.rb
CHANGED
@@ -1,32 +1,17 @@
|
|
1
|
-
require "rails/generators"
|
2
|
-
require "rails/generators/migration"
|
3
|
-
require "active_record"
|
4
1
|
require "rails/generators/active_record"
|
5
2
|
|
6
3
|
module FieldTest
|
7
4
|
module Generators
|
8
5
|
class EventsGenerator < Rails::Generators::Base
|
9
|
-
include
|
10
|
-
source_root 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
|
6
|
+
include ActiveRecord::Generators::Migration
|
7
|
+
source_root File.join(__dir__, "templates")
|
21
8
|
|
22
9
|
def copy_migration
|
23
10
|
migration_template "events.rb", "db/migrate/create_field_test_events.rb", migration_version: migration_version
|
24
11
|
end
|
25
12
|
|
26
13
|
def migration_version
|
27
|
-
|
28
|
-
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
29
|
-
end
|
14
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
30
15
|
end
|
31
16
|
end
|
32
17
|
end
|
@@ -1,23 +1,10 @@
|
|
1
|
-
require "rails/generators"
|
2
|
-
require "rails/generators/migration"
|
3
|
-
require "active_record"
|
4
1
|
require "rails/generators/active_record"
|
5
2
|
|
6
3
|
module FieldTest
|
7
4
|
module Generators
|
8
5
|
class InstallGenerator < Rails::Generators::Base
|
9
|
-
include
|
10
|
-
source_root 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
|
6
|
+
include ActiveRecord::Generators::Migration
|
7
|
+
source_root File.join(__dir__, "templates")
|
21
8
|
|
22
9
|
def copy_migration
|
23
10
|
migration_template "memberships.rb", "db/migrate/create_field_test_memberships.rb", migration_version: migration_version
|
@@ -28,9 +15,7 @@ module FieldTest
|
|
28
15
|
end
|
29
16
|
|
30
17
|
def migration_version
|
31
|
-
|
32
|
-
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
33
|
-
end
|
18
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
34
19
|
end
|
35
20
|
end
|
36
21
|
end
|
@@ -1,11 +1,9 @@
|
|
1
1
|
class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
|
2
2
|
def change
|
3
3
|
create_table :field_test_events do |t|
|
4
|
-
t.
|
4
|
+
t.references :field_test_membership
|
5
5
|
t.string :name
|
6
|
-
t.
|
6
|
+
t.datetime :created_at
|
7
7
|
end
|
8
|
-
|
9
|
-
add_index :field_test_events, :field_test_membership_id
|
10
8
|
end
|
11
9
|
end
|
@@ -1,15 +1,16 @@
|
|
1
1
|
class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
|
2
2
|
def change
|
3
3
|
create_table :field_test_memberships do |t|
|
4
|
-
t.string :
|
4
|
+
t.string :participant_type
|
5
|
+
t.string :participant_id
|
5
6
|
t.string :experiment
|
6
7
|
t.string :variant
|
7
|
-
t.
|
8
|
+
t.datetime :created_at
|
8
9
|
t.boolean :converted, default: false
|
9
10
|
end
|
10
11
|
|
11
|
-
add_index :field_test_memberships, [:
|
12
|
-
|
12
|
+
add_index :field_test_memberships, [:participant_type, :participant_id, :experiment],
|
13
|
+
unique: true, name: "index_field_test_memberships_on_participant"
|
13
14
|
add_index :field_test_memberships, [:experiment, :created_at]
|
14
15
|
end
|
15
16
|
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.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-09-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '5'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '5'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activerecord
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '5'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '5'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: distribution
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -56,14 +56,14 @@ dependencies:
|
|
56
56
|
name: browser
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '2.0'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '2.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
@@ -108,23 +108,62 @@ dependencies:
|
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: combustion
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rails
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: sqlite3
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
111
153
|
description:
|
112
|
-
email:
|
113
|
-
- andrew@chartkick.com
|
154
|
+
email: andrew@chartkick.com
|
114
155
|
executables: []
|
115
156
|
extensions: []
|
116
157
|
extra_rdoc_files: []
|
117
158
|
files:
|
118
|
-
- ".gitignore"
|
119
159
|
- CHANGELOG.md
|
120
|
-
- Gemfile
|
121
160
|
- LICENSE.txt
|
122
161
|
- README.md
|
123
|
-
- Rakefile
|
124
162
|
- app/controllers/field_test/base_controller.rb
|
125
163
|
- app/controllers/field_test/experiments_controller.rb
|
126
164
|
- app/controllers/field_test/memberships_controller.rb
|
127
165
|
- app/controllers/field_test/participants_controller.rb
|
166
|
+
- app/helpers/field_test/base_helper.rb
|
128
167
|
- app/models/field_test/event.rb
|
129
168
|
- app/models/field_test/membership.rb
|
130
169
|
- app/views/field_test/experiments/_experiments.html.erb
|
@@ -133,12 +172,13 @@ files:
|
|
133
172
|
- app/views/field_test/participants/show.html.erb
|
134
173
|
- app/views/layouts/field_test/application.html.erb
|
135
174
|
- config/routes.rb
|
136
|
-
- field_test.gemspec
|
137
175
|
- lib/field_test.rb
|
138
176
|
- lib/field_test/calculations.rb
|
177
|
+
- lib/field_test/controller.rb
|
139
178
|
- lib/field_test/engine.rb
|
140
179
|
- lib/field_test/experiment.rb
|
141
180
|
- lib/field_test/helpers.rb
|
181
|
+
- lib/field_test/mailer.rb
|
142
182
|
- lib/field_test/participant.rb
|
143
183
|
- lib/field_test/version.rb
|
144
184
|
- lib/generators/field_test/events_generator.rb
|
@@ -147,7 +187,8 @@ files:
|
|
147
187
|
- lib/generators/field_test/templates/events.rb.tt
|
148
188
|
- lib/generators/field_test/templates/memberships.rb.tt
|
149
189
|
homepage: https://github.com/ankane/field_test
|
150
|
-
licenses:
|
190
|
+
licenses:
|
191
|
+
- MIT
|
151
192
|
metadata: {}
|
152
193
|
post_install_message:
|
153
194
|
rdoc_options: []
|
@@ -157,15 +198,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
157
198
|
requirements:
|
158
199
|
- - ">="
|
159
200
|
- !ruby/object:Gem::Version
|
160
|
-
version: '
|
201
|
+
version: '2.4'
|
161
202
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
162
203
|
requirements:
|
163
204
|
- - ">="
|
164
205
|
- !ruby/object:Gem::Version
|
165
206
|
version: '0'
|
166
207
|
requirements: []
|
167
|
-
|
168
|
-
rubygems_version: 2.7.6
|
208
|
+
rubygems_version: 3.1.2
|
169
209
|
signing_key:
|
170
210
|
specification_version: 4
|
171
211
|
summary: A/B testing for Rails
|