split 1.4.2 → 1.4.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eaed323e2d2459412259d82d76e4000c299c1db6
4
- data.tar.gz: 54e05592a68ed6285e0ba613dadac52ed79bf23e
3
+ metadata.gz: 86dd69d253b8dd5ddb9296729f89a04d98a482d4
4
+ data.tar.gz: 561287a2dfc015d8ec5b1751c78ef7852b626edb
5
5
  SHA512:
6
- metadata.gz: a981adeb6a103db7766be39180bd353a31f642c659e4044071fbe8a094f4e36558705b477dd8a4b6eff893b11823209a241c24d5acc8d339201108abb33bf4b0
7
- data.tar.gz: 7ca7d69fa815e194f9273974df0da3c556cb186f01720e8b342edd9482814725e4478006581b1878b1fb36f9fa53ed6f900176d3809101785d03bbac80e52f43
6
+ metadata.gz: c34f1b8d4893ada8c64a27deed0536aaa0f0922531abe9180d6c3eb96742dea570b8dedeef341961184b9cd2bc9ef3d0ee439cd18f945d371069006abee3a530
7
+ data.tar.gz: 7b63e41dc1d105ce2e1e00fd1488d7d149db7b44f5df6b4de6a835ed17532d8511593829babedab310b5258cf7861840a80e0d9bc50c333f04ce91acf997712d
@@ -1,6 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.3.0
3
+ - 2.3.1
4
4
 
5
5
  gemfile:
6
6
  - gemfiles/4.1.gemfile
@@ -1,3 +1,17 @@
1
+ ## 1.4.3 (April 28th, 2016)
2
+
3
+ Features:
4
+
5
+ - add on_trial callback whenever a trial is started (@mtyeh411, #375)
6
+
7
+ Bugfixes:
8
+
9
+ - Allow algorithm configuration at experiment level (@007sumit, #376)
10
+
11
+ Misc:
12
+
13
+ - only choose override if it exists as valid alternative (@spheric, #377)
14
+
1
15
  ## 1.4.2 (April 25th, 2016)
2
16
 
3
17
  Misc:
data/README.md CHANGED
@@ -242,7 +242,8 @@ For example:
242
242
 
243
243
  ``` ruby
244
244
  Split.configure do |config|
245
- config.on_trial_choose = :log_trial_choose
245
+ config.on_trial = :log_trial # run on every trial
246
+ config.on_trial_choose = :log_trial_choose # run on trials with new users only
246
247
  config.on_trial_complete = :log_trial_complete
247
248
  end
248
249
  ```
@@ -251,11 +252,16 @@ Set these attributes to a method name available in the same context as the
251
252
  `ab_test` method. These methods should accept one argument, a `Trial` instance.
252
253
 
253
254
  ``` ruby
254
- def log_trial_choose(trial)
255
+ def log_trial(trial)
255
256
  logger.info "experiment=%s alternative=%s user=%s" %
256
257
  [ trial.experiment.name, trial.alternative, current_user.id ]
257
258
  end
258
259
 
260
+ def log_trial_choose(trial)
261
+ logger.info "[new user] experiment=%s alternative=%s user=%s" %
262
+ [ trial.experiment.name, trial.alternative, current_user.id ]
263
+ end
264
+
259
265
  def log_trial_complete(trial)
260
266
  logger.info "experiment=%s alternative=%s user=%s complete=true" %
261
267
  [ trial.experiment.name, trial.alternative, current_user.id ]
@@ -15,6 +15,7 @@ module Split
15
15
  attr_accessor :algorithm
16
16
  attr_accessor :store_override
17
17
  attr_accessor :start_manually
18
+ attr_accessor :on_trial
18
19
  attr_accessor :on_trial_choose
19
20
  attr_accessor :on_trial_complete
20
21
  attr_accessor :on_experiment_reset
@@ -150,6 +151,10 @@ module Split
150
151
  experiment_config[experiment_name.to_sym][:metadata] = metadata
151
152
  end
152
153
 
154
+ if algorithm = value_for(settings, :algorithm)
155
+ experiment_config[experiment_name.to_sym][:algorithm] = algorithm
156
+ end
157
+
153
158
  if (resettable = value_for(settings, :resettable)) != nil
154
159
  experiment_config[experiment_name.to_sym][:resettable] = resettable
155
160
  end
@@ -41,8 +41,7 @@ module Split
41
41
  Array(goals).each {|g| alternative.increment_completion(g) }
42
42
  end
43
43
 
44
- context.send(Split.configuration.on_trial_complete, self) \
45
- if Split.configuration.on_trial_complete && context
44
+ run_callback context, Split.configuration.on_trial_complete
46
45
  end
47
46
  end
48
47
 
@@ -54,7 +53,7 @@ module Split
54
53
  # Only run the process once
55
54
  return alternative if @alternative_choosen
56
55
 
57
- if @options[:override]
56
+ if override_is_alternative?
58
57
  self.alternative = @options[:override]
59
58
  elsif @options[:disabled] || Split.configuration.disabled?
60
59
  self.alternative = @experiment.control
@@ -73,19 +72,26 @@ module Split
73
72
  # Increment the number of participants since we are actually choosing a new alternative
74
73
  self.alternative.increment_participation
75
74
 
76
- # Run the post-choosing hook on the context
77
- context.send(Split.configuration.on_trial_choose, self) \
78
- if Split.configuration.on_trial_choose && context
75
+ run_callback context, Split.configuration.on_trial_choose
79
76
  end
80
77
  end
81
78
 
82
79
  @user[@experiment.key] = alternative.name if should_store_alternative?
83
80
  @alternative_choosen = true
81
+ run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled?
84
82
  alternative
85
83
  end
86
84
 
87
85
  private
88
86
 
87
+ def run_callback(context, callback_name)
88
+ context.send(callback_name, self) if callback_name && context.respond_to?(callback_name, true)
89
+ end
90
+
91
+ def override_is_alternative?
92
+ @experiment.alternatives.map(&:name).include?(@options[:override])
93
+ end
94
+
89
95
  def should_store_alternative?
90
96
  if @options[:override] || @options[:disabled]
91
97
  Split.configuration.store_override
@@ -2,6 +2,6 @@
2
2
  module Split
3
3
  MAJOR = 1
4
4
  MINOR = 4
5
- PATCH = 2
5
+ PATCH = 3
6
6
  VERSION = [MAJOR, MINOR, PATCH].join('.')
7
7
  end
@@ -4,8 +4,9 @@ require 'split/trial'
4
4
 
5
5
  describe Split::Trial do
6
6
  let(:user) { mock_user }
7
+ let(:alternatives) { ['basket', 'cart'] }
7
8
  let(:experiment) do
8
- Split::Experiment.new('basket_text', :alternatives => ['basket', 'cart']).save
9
+ Split::Experiment.new('basket_text', :alternatives => alternatives).save
9
10
  end
10
11
 
11
12
  it "should be initializeable" do
@@ -19,7 +20,7 @@ describe Split::Trial do
19
20
  describe "alternative" do
20
21
  it "should use the alternative if specified" do
21
22
  alternative = double('alternative', :kind_of? => Split::Alternative)
22
- trial = Split::Trial.new(:experiment => experiment = double('experiment'),
23
+ trial = Split::Trial.new(:experiment => double('experiment'),
23
24
  :alternative => alternative, :user => user)
24
25
  expect(trial).not_to receive(:choose)
25
26
  expect(trial.alternative).to eq(alternative)
@@ -35,7 +36,6 @@ describe Split::Trial do
35
36
  end
36
37
 
37
38
  describe "metadata" do
38
- let(:alternatives) { ['basket', 'cart'] }
39
39
  let(:metadata) { Hash[alternatives.map { |k| [k, "Metadata for #{k}"] }] }
40
40
  let(:experiment) do
41
41
  Split::Experiment.new('basket_text', :alternatives => alternatives, :metadata => metadata).save
@@ -51,50 +51,102 @@ describe Split::Trial do
51
51
  trial = Split::Trial.new(:experiment => experiment, :user => user)
52
52
  trial.choose!
53
53
  expect(trial.metadata).to eq(metadata[trial.alternative.name])
54
- expect(trial.metadata).to match /#{trial.alternative.name}/
54
+ expect(trial.metadata).to match(/#{trial.alternative.name}/)
55
55
  end
56
56
  end
57
57
 
58
58
  describe "#choose!" do
59
+ let(:context) { double(on_trial_callback: 'test callback') }
60
+ let(:trial) do
61
+ Split::Trial.new(:user => user, :experiment => experiment)
62
+ end
63
+
64
+ shared_examples_for 'a trial with callbacks' do
65
+ it 'does not run if on_trial callback is not respondable' do
66
+ Split.configuration.on_trial = :foo
67
+ allow(context).to receive(:respond_to?).with(:foo, true).and_return false
68
+ expect(context).to_not receive(:foo)
69
+ trial.choose! context
70
+ end
71
+ it 'runs on_trial callback' do
72
+ Split.configuration.on_trial = :on_trial_callback
73
+ expect(context).to receive(:on_trial_callback)
74
+ trial.choose! context
75
+ end
76
+ it 'does not run nil on_trial callback' do
77
+ Split.configuration.on_trial = nil
78
+ expect(context).not_to receive(:on_trial_callback)
79
+ trial.choose! context
80
+ end
81
+ end
82
+
59
83
  def expect_alternative(trial, alternative_name)
60
84
  3.times do
61
- trial.choose!
62
- expect(trial.alternative.name).to eq(alternative_name)
85
+ trial.choose! context
86
+ expect(alternative_name).to include(trial.alternative.name)
63
87
  end
64
88
  end
65
89
 
66
90
  context "when override is present" do
91
+ let(:override) { 'cart' }
92
+ let(:trial) do
93
+ Split::Trial.new(:user => user, :experiment => experiment, :override => override)
94
+ end
95
+
96
+ it_behaves_like 'a trial with callbacks'
97
+
67
98
  it "picks the override" do
68
- trial = Split::Trial.new(:user => user, :experiment => experiment, :override => 'cart')
69
99
  expect(experiment).to_not receive(:next_alternative)
100
+ expect_alternative(trial, override)
101
+ end
70
102
 
71
- expect_alternative(trial, 'cart')
103
+ context "when alternative doesn't exist" do
104
+ let(:override) { nil }
105
+ it 'falls back on next_alternative' do
106
+ expect(experiment).to receive(:next_alternative).and_call_original
107
+ expect_alternative(trial, alternatives)
108
+ end
72
109
  end
73
110
  end
74
111
 
75
112
  context "when disabled option is true" do
76
- it "picks the control" do
77
- trial = Split::Trial.new(:user => user, :experiment => experiment, :disabled => true)
113
+ let(:trial) do
114
+ Split::Trial.new(:user => user, :experiment => experiment, :disabled => true)
115
+ end
116
+
117
+ it "picks the control", :aggregate_failures do
118
+ Split.configuration.on_trial = :on_trial_callback
78
119
  expect(experiment).to_not receive(:next_alternative)
79
120
 
121
+ expect(context).not_to receive(:on_trial_callback)
122
+
80
123
  expect_alternative(trial, 'basket')
124
+ Split.configuration.on_trial = nil
81
125
  end
82
126
  end
83
127
 
84
128
  context "when Split is globally disabled" do
85
- it "picks the control" do
129
+ it "picks the control and does not run on_trial callbacks", :aggregate_failures do
86
130
  Split.configuration.enabled = false
87
- trial = Split::Trial.new(:user => user, :experiment => experiment)
88
- expect(experiment).to_not receive(:next_alternative)
131
+ Split.configuration.on_trial = :on_trial_callback
89
132
 
133
+ expect(experiment).to_not receive(:next_alternative)
134
+ expect(context).not_to receive(:on_trial_callback)
90
135
  expect_alternative(trial, 'basket')
136
+
91
137
  Split.configuration.enabled = true
138
+ Split.configuration.on_trial = nil
92
139
  end
93
140
  end
94
141
 
95
142
  context "when experiment has winner" do
143
+ let(:trial) do
144
+ Split::Trial.new(:user => user, :experiment => experiment)
145
+ end
146
+
147
+ it_behaves_like 'a trial with callbacks'
148
+
96
149
  it "picks the winner" do
97
- trial = Split::Trial.new(:user => user, :experiment => experiment)
98
150
  experiment.winner = 'cart'
99
151
  expect(experiment).to_not receive(:next_alternative)
100
152
 
@@ -103,18 +155,23 @@ describe Split::Trial do
103
155
  end
104
156
 
105
157
  context "when exclude is true" do
158
+ let(:trial) do
159
+ Split::Trial.new(:user => user, :experiment => experiment, :exclude => true)
160
+ end
161
+
162
+ it_behaves_like 'a trial with callbacks'
163
+
106
164
  it "picks the control" do
107
- trial = Split::Trial.new(:user => user, :experiment => experiment, :exclude => true)
108
165
  expect(experiment).to_not receive(:next_alternative)
109
-
110
166
  expect_alternative(trial, 'basket')
111
167
  end
112
168
  end
113
169
 
114
170
  context "when user is already participating" do
171
+ it_behaves_like 'a trial with callbacks'
172
+
115
173
  it "picks the same alternative" do
116
174
  user[experiment.key] = 'basket'
117
- trial = Split::Trial.new(:user => user, :experiment => experiment)
118
175
  expect(experiment).to_not receive(:next_alternative)
119
176
 
120
177
  expect_alternative(trial, 'basket')
@@ -122,95 +179,99 @@ describe Split::Trial do
122
179
  end
123
180
 
124
181
  context "when user is a new participant" do
125
- it "picks a new alternative" do
126
- trial = Split::Trial.new(:user => user, :experiment => experiment)
182
+ it "picks a new alternative and runs on_trial_choose callback", :aggregate_failures do
183
+ Split.configuration.on_trial_choose = :on_trial_choose_callback
184
+
127
185
  expect(experiment).to receive(:next_alternative).and_call_original
186
+ expect(context).to receive(:on_trial_choose_callback)
187
+
188
+ trial.choose! context
128
189
 
129
- trial.choose!
130
190
  expect(trial.alternative.name).to_not be_empty
191
+ Split.configuration.on_trial_choose = nil
131
192
  end
132
193
  end
194
+ end
133
195
 
134
- describe "#complete!" do
135
- let(:trial) { Split::Trial.new(:user => user, :experiment => experiment) }
136
- context 'when there are no goals' do
137
- it 'should complete the trial' do
196
+ describe "#complete!" do
197
+ let(:trial) { Split::Trial.new(:user => user, :experiment => experiment) }
198
+ context 'when there are no goals' do
199
+ it 'should complete the trial' do
200
+ trial.choose!
201
+ old_completed_count = trial.alternative.completed_count
202
+ trial.complete!
203
+ expect(trial.alternative.completed_count).to be(old_completed_count+1)
204
+ end
205
+ end
206
+
207
+ context 'when there are many goals' do
208
+ let(:goals) { ['first', 'second'] }
209
+ let(:trial) { Split::Trial.new(:user => user, :experiment => experiment, :goals => goals) }
210
+ shared_examples_for "goal completion" do
211
+ it 'should not complete the trial' do
138
212
  trial.choose!
139
213
  old_completed_count = trial.alternative.completed_count
140
- trial.complete!
141
- expect(trial.alternative.completed_count).to be(old_completed_count+1)
214
+ trial.complete!(goal)
215
+ expect(trial.alternative.completed_count).to_not be(old_completed_count+1)
142
216
  end
143
217
  end
144
218
 
145
- context 'when there are many goals' do
146
- let(:goals) { ['first', 'second'] }
147
- let(:trial) { Split::Trial.new(:user => user, :experiment => experiment, :goals => goals) }
148
- shared_examples_for "goal completion" do
149
- it 'should not complete the trial' do
150
- trial.choose!
151
- old_completed_count = trial.alternative.completed_count
152
- trial.complete!(goal)
153
- expect(trial.alternative.completed_count).to_not be(old_completed_count+1)
154
- end
155
- end
156
-
157
- describe 'Array of Goals' do
158
- let(:goal) { [goals.first] }
159
- it_behaves_like 'goal completion'
160
- end
161
-
162
- describe 'String of Goal' do
163
- let(:goal) { goals.first }
164
- it_behaves_like 'goal completion'
165
- end
219
+ describe 'Array of Goals' do
220
+ let(:goal) { [goals.first] }
221
+ it_behaves_like 'goal completion'
222
+ end
166
223
 
224
+ describe 'String of Goal' do
225
+ let(:goal) { goals.first }
226
+ it_behaves_like 'goal completion'
167
227
  end
228
+
168
229
  end
230
+ end
169
231
 
170
- describe "alternative recording" do
171
- before(:each) { Split.configuration.store_override = false }
232
+ describe "alternative recording" do
233
+ before(:each) { Split.configuration.store_override = false }
172
234
 
173
- context "when override is present" do
174
- it "stores when store_override is true" do
175
- trial = Split::Trial.new(:user => user, :experiment => experiment, :override => 'basket')
235
+ context "when override is present" do
236
+ it "stores when store_override is true" do
237
+ trial = Split::Trial.new(:user => user, :experiment => experiment, :override => 'basket')
176
238
 
177
- Split.configuration.store_override = true
178
- expect(user).to receive("[]=")
179
- trial.choose!
180
- end
239
+ Split.configuration.store_override = true
240
+ expect(user).to receive("[]=")
241
+ trial.choose!
242
+ end
181
243
 
182
- it "does not store when store_override is false" do
183
- trial = Split::Trial.new(:user => user, :experiment => experiment, :override => 'basket')
244
+ it "does not store when store_override is false" do
245
+ trial = Split::Trial.new(:user => user, :experiment => experiment, :override => 'basket')
184
246
 
185
- expect(user).to_not receive("[]=")
186
- trial.choose!
187
- end
247
+ expect(user).to_not receive("[]=")
248
+ trial.choose!
188
249
  end
250
+ end
189
251
 
190
- context "when disabled is present" do
191
- it "stores when store_override is true" do
192
- trial = Split::Trial.new(:user => user, :experiment => experiment, :disabled => true)
252
+ context "when disabled is present" do
253
+ it "stores when store_override is true" do
254
+ trial = Split::Trial.new(:user => user, :experiment => experiment, :disabled => true)
193
255
 
194
- Split.configuration.store_override = true
195
- expect(user).to receive("[]=")
196
- trial.choose!
197
- end
256
+ Split.configuration.store_override = true
257
+ expect(user).to receive("[]=")
258
+ trial.choose!
259
+ end
198
260
 
199
- it "does not store when store_override is false" do
200
- trial = Split::Trial.new(:user => user, :experiment => experiment, :disabled => true)
261
+ it "does not store when store_override is false" do
262
+ trial = Split::Trial.new(:user => user, :experiment => experiment, :disabled => true)
201
263
 
202
- expect(user).to_not receive("[]=")
203
- trial.choose!
204
- end
264
+ expect(user).to_not receive("[]=")
265
+ trial.choose!
205
266
  end
267
+ end
206
268
 
207
- context "when exclude is present" do
208
- it "does not store" do
209
- trial = Split::Trial.new(:user => user, :experiment => experiment, :exclude => true)
269
+ context "when exclude is present" do
270
+ it "does not store" do
271
+ trial = Split::Trial.new(:user => user, :experiment => experiment, :exclude => true)
210
272
 
211
- expect(user).to_not receive("[]=")
212
- trial.choose!
213
- end
273
+ expect(user).to_not receive("[]=")
274
+ trial.choose!
214
275
  end
215
276
  end
216
277
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: split
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Nesbitt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-25 00:00:00.000000000 Z
11
+ date: 2016-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis