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.
@@ -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 = field_test_participants(options)
6
+ participants = FieldTest::Participant.standardize(options[:participant] || field_test_participant)
7
7
 
8
8
  if try(:request)
9
- if params[:field_test] && params[:field_test][experiment]
10
- options[:variant] ||= params[:field_test][experiment]
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
- # cache results for request
22
- @field_test_cache ||= {}
23
- @field_test_cache[experiment] ||= exp.variant(participants, options)
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 = field_test_participants(options)
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
- def field_test_experiments(options = {})
35
- participants = field_test_participants(options)
36
- memberships = FieldTest::Membership.where(participant: participants).group_by(&:participant)
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
- memberships[participant].to_a.each do |membership|
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
- def self.standardize(participants)
4
- Array(participants).map { |v| v.respond_to?(:model_name) ? "#{v.model_name.name}:#{v.id}" : v.to_s }
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
@@ -1,3 +1,3 @@
1
1
  module FieldTest
2
- VERSION = "0.2.4"
2
+ VERSION = "0.4.1"
3
3
  end
@@ -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 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
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
- if ActiveRecord::VERSION::MAJOR >= 5
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 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
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
- if ActiveRecord::VERSION::MAJOR >= 5
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.integer :field_test_membership_id
4
+ t.references :field_test_membership
5
5
  t.string :name
6
- t.timestamp :created_at
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 :participant
4
+ t.string :participant_type
5
+ t.string :participant_id
5
6
  t.string :experiment
6
7
  t.string :variant
7
- t.timestamp :created_at
8
+ t.datetime :created_at
8
9
  t.boolean :converted, default: false
9
10
  end
10
11
 
11
- add_index :field_test_memberships, [:experiment, :participant], unique: true
12
- add_index :field_test_memberships, :participant
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.2.4
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-04 00:00:00.000000000 Z
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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
- rubyforge_project:
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