split 1.4.2 → 1.4.3

Sign up to get free protection for your applications and to get access to all the features.
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