split 0.6.2 → 0.6.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.
- data/.gitignore +1 -0
- data/CHANGELOG.mdown +10 -0
- data/README.mdown +42 -1
- data/lib/split/configuration.rb +2 -0
- data/lib/split/experiment.rb +9 -2
- data/lib/split/helper.rb +11 -0
- data/lib/split/version.rb +1 -1
- data/spec/configuration_spec.rb +2 -2
- data/spec/experiment_spec.rb +10 -1
- data/spec/helper_spec.rb +28 -12
- data/spec/persistence/cookie_adapter_spec.rb +1 -1
- data/spec/persistence/session_adapter_spec.rb +1 -1
- data/spec/persistence_spec.rb +1 -1
- data/spec/spec_helper.rb +4 -0
- data/spec/trial_spec.rb +6 -6
- data/split.gemspec +2 -1
- metadata +21 -4
data/.gitignore
CHANGED
data/CHANGELOG.mdown
CHANGED
data/README.mdown
CHANGED
@@ -9,7 +9,8 @@ Split is designed to be hacker friendly, allowing for maximum customisation and
|
|
9
9
|
[](http://badge.fury.io/rb/split)
|
10
10
|
[](http://travis-ci.org/andrew/split)
|
11
11
|
[](https://gemnasium.com/andrew/split)
|
12
|
-
[](https://codeclimate.com/github/andrew/split)
|
13
|
+
[](https://coveralls.io/r/andrew/split)
|
13
14
|
|
14
15
|
## Requirements
|
15
16
|
|
@@ -217,6 +218,35 @@ Split.configure do |config|
|
|
217
218
|
end
|
218
219
|
```
|
219
220
|
|
221
|
+
### Trial Event Hooks
|
222
|
+
|
223
|
+
You can define methods that will be called at the same time as experiment
|
224
|
+
alternative participation and goal completion.
|
225
|
+
|
226
|
+
For example:
|
227
|
+
|
228
|
+
``` ruby
|
229
|
+
Split.configure do |config|
|
230
|
+
config.on_trial_choose = :log_trial_choice
|
231
|
+
config.on_trial_complete = :log_trial_complete
|
232
|
+
end
|
233
|
+
```
|
234
|
+
|
235
|
+
Set these attributes to a method name available in the same context as the
|
236
|
+
`ab_test` method. These methods should accept one argument, a `Trial` instance.
|
237
|
+
|
238
|
+
``` ruby
|
239
|
+
def log_trial_choose(trial)
|
240
|
+
logger.info "experiment=%s alternative=%s user=%s" %
|
241
|
+
[ trial.experiment.name, trial.alternative, current_user.id ]
|
242
|
+
end
|
243
|
+
|
244
|
+
def log_trial_complete(trial)
|
245
|
+
logger.info "experiment=%s alternative=%s user=%s complete=true" %
|
246
|
+
[ trial.experiment.name, trial.alternative, current_user.id ]
|
247
|
+
end
|
248
|
+
```
|
249
|
+
|
220
250
|
## Web Interface
|
221
251
|
|
222
252
|
Split comes with a Sinatra-based front end to get an overview of how your experiments are doing.
|
@@ -251,6 +281,17 @@ Split::Dashboard.use Rack::Auth::Basic do |username, password|
|
|
251
281
|
end
|
252
282
|
```
|
253
283
|
|
284
|
+
You can even use Devise or any other Warden-based authentication method to authorize users. Just replace `mount Split::Dashboard, :at => 'split'` in `config/routes.rb` with the following:
|
285
|
+
```ruby
|
286
|
+
match "/split" => Split::Dashboard, :anchor => false, :constraints => lambda { |request|
|
287
|
+
request.env['warden'].authenticated? # are we authenticated?
|
288
|
+
request.env['warden'].authenticate! # authenticate if not already
|
289
|
+
# or even check any other condition such as request.env['warden'].user.is_admin?
|
290
|
+
}
|
291
|
+
```
|
292
|
+
|
293
|
+
More information on this [here](http://steve.dynedge.co.uk/2011/12/09/controlling-access-to-routes-and-rack-apps-in-rails-3-with-devise-and-warden/)
|
294
|
+
|
254
295
|
### Screenshot
|
255
296
|
|
256
297
|

|
data/lib/split/configuration.rb
CHANGED
data/lib/split/experiment.rb
CHANGED
@@ -65,7 +65,7 @@ module Split
|
|
65
65
|
|
66
66
|
if new_record?
|
67
67
|
Split.redis.sadd(:experiments, name)
|
68
|
-
Split.redis.hset(:experiment_start_times, @name, Time.now)
|
68
|
+
Split.redis.hset(:experiment_start_times, @name, Time.now.to_i)
|
69
69
|
@alternatives.reverse.each {|a| Split.redis.lpush(name, a.name)}
|
70
70
|
@goals.reverse.each {|a| Split.redis.lpush(goals_key, a)} unless @goals.nil?
|
71
71
|
else
|
@@ -157,7 +157,14 @@ module Split
|
|
157
157
|
|
158
158
|
def start_time
|
159
159
|
t = Split.redis.hget(:experiment_start_times, @name)
|
160
|
-
|
160
|
+
if t
|
161
|
+
# Check if stored time is an integer
|
162
|
+
if t =~ /^[-+]?[0-9]+$/
|
163
|
+
t = Time.at(t.to_i)
|
164
|
+
else
|
165
|
+
t = Time.parse(t)
|
166
|
+
end
|
167
|
+
end
|
161
168
|
end
|
162
169
|
|
163
170
|
def next_alternative
|
data/lib/split/helper.rb
CHANGED
@@ -63,6 +63,8 @@ module Split
|
|
63
63
|
alternative_name = ab_user[experiment.key]
|
64
64
|
trial = Trial.new(:experiment => experiment, :alternative => alternative_name, :goals => options[:goals])
|
65
65
|
trial.complete!
|
66
|
+
call_trial_complete_hook(trial)
|
67
|
+
|
66
68
|
if should_reset
|
67
69
|
reset!(experiment)
|
68
70
|
else
|
@@ -178,6 +180,7 @@ module Split
|
|
178
180
|
ret = ab_user[experiment.key]
|
179
181
|
else
|
180
182
|
trial.choose!
|
183
|
+
call_trial_choose_hook(trial)
|
181
184
|
ret = begin_experiment(experiment, trial.alternative.name)
|
182
185
|
end
|
183
186
|
end
|
@@ -186,6 +189,14 @@ module Split
|
|
186
189
|
ret
|
187
190
|
end
|
188
191
|
|
192
|
+
def call_trial_choose_hook(trial)
|
193
|
+
send(Split.configuration.on_trial_choose, trial) if Split.configuration.on_trial_choose
|
194
|
+
end
|
195
|
+
|
196
|
+
def call_trial_complete_hook(trial)
|
197
|
+
send(Split.configuration.on_trial_complete, trial) if Split.configuration.on_trial_complete
|
198
|
+
end
|
199
|
+
|
189
200
|
def keys_without_experiment(keys, experiment_key)
|
190
201
|
keys.reject { |k| k.match(Regexp.new("^#{experiment_key}(:finished)?$")) }
|
191
202
|
end
|
data/lib/split/version.rb
CHANGED
data/spec/configuration_spec.rb
CHANGED
@@ -151,7 +151,7 @@ describe Split::Configuration do
|
|
151
151
|
let(:input) { '' }
|
152
152
|
|
153
153
|
it "should raise an error" do
|
154
|
-
expect { @config.experiments = yaml }.to raise_error
|
154
|
+
expect { @config.experiments = yaml }.to raise_error
|
155
155
|
end
|
156
156
|
end
|
157
157
|
|
@@ -159,7 +159,7 @@ describe Split::Configuration do
|
|
159
159
|
let(:input) { '---' }
|
160
160
|
|
161
161
|
it "should raise an error" do
|
162
|
-
expect { @config.experiments = yaml }.to raise_error
|
162
|
+
expect { @config.experiments = yaml }.to raise_error
|
163
163
|
end
|
164
164
|
end
|
165
165
|
end
|
data/spec/experiment_spec.rb
CHANGED
@@ -44,7 +44,7 @@ describe Split::Experiment do
|
|
44
44
|
end
|
45
45
|
|
46
46
|
it "should save the start time to redis" do
|
47
|
-
experiment_start_time = Time.
|
47
|
+
experiment_start_time = Time.at(1372167761)
|
48
48
|
Time.stub(:now => experiment_start_time)
|
49
49
|
experiment.save
|
50
50
|
|
@@ -59,6 +59,15 @@ describe Split::Experiment do
|
|
59
59
|
Split::Experiment.find('basket_text').algorithm.should == experiment_algorithm
|
60
60
|
end
|
61
61
|
|
62
|
+
it "should handle having a start time stored as a string" do
|
63
|
+
experiment_start_time = Time.parse("Sat Mar 03 14:01:03")
|
64
|
+
Time.stub(:now => experiment_start_time)
|
65
|
+
experiment.save
|
66
|
+
Split.redis.hset(:experiment_start_times, experiment.name, experiment_start_time)
|
67
|
+
|
68
|
+
Split::Experiment.find('basket_text').start_time.should == experiment_start_time
|
69
|
+
end
|
70
|
+
|
62
71
|
it "should handle not having a start time" do
|
63
72
|
experiment_start_time = Time.parse("Sat Mar 03 14:01:03")
|
64
73
|
Time.stub(:now => experiment_start_time)
|
data/spec/helper_spec.rb
CHANGED
@@ -20,11 +20,11 @@ describe Split::Helper do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
it "should raise the appropriate error when passed integers for alternatives" do
|
23
|
-
lambda { ab_test('xyz', 1, 2, 3) }.should raise_error
|
23
|
+
lambda { ab_test('xyz', 1, 2, 3) }.should raise_error
|
24
24
|
end
|
25
25
|
|
26
26
|
it "should raise the appropriate error when passed symbols for alternatives" do
|
27
|
-
lambda { ab_test('xyz', :a, :b, :c) }.should raise_error
|
27
|
+
lambda { ab_test('xyz', :a, :b, :c) }.should raise_error
|
28
28
|
end
|
29
29
|
|
30
30
|
it "should not raise error when passed an array for goals" do
|
@@ -112,6 +112,14 @@ describe Split::Helper do
|
|
112
112
|
end
|
113
113
|
end
|
114
114
|
|
115
|
+
context "when on_trial_choose is set" do
|
116
|
+
before { Split.configuration.on_trial_choose = :some_method }
|
117
|
+
it "should call the method" do
|
118
|
+
self.should_receive(:some_method)
|
119
|
+
ab_test('link_color', 'blue', 'red')
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
115
123
|
it "should allow passing a block" do
|
116
124
|
alt = ab_test('link_color', 'blue', 'red')
|
117
125
|
ret = ab_test('link_color', 'blue', 'red') { |alternative| "shared/#{alternative}" }
|
@@ -247,6 +255,14 @@ describe Split::Helper do
|
|
247
255
|
doing_other_tests?(@experiment.key).should be false
|
248
256
|
end
|
249
257
|
|
258
|
+
context "when on_trial_complete is set" do
|
259
|
+
before { Split.configuration.on_trial_complete = :some_method }
|
260
|
+
it "should call the method" do
|
261
|
+
self.should_receive(:some_method)
|
262
|
+
finished(@experiment_name)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
250
266
|
end
|
251
267
|
|
252
268
|
context "finished with config" do
|
@@ -273,7 +289,7 @@ describe Split::Helper do
|
|
273
289
|
alts = Split.configuration.experiments[experiment_name][:alternatives]
|
274
290
|
experiment = Split::Experiment.find_or_create(experiment_name, *alts)
|
275
291
|
alt_name = ab_user[experiment.key] = alts.first
|
276
|
-
alt =
|
292
|
+
alt = double('alternative')
|
277
293
|
alt.stub(:name).and_return(alt_name)
|
278
294
|
Split::Alternative.stub(:new).with(alt_name, experiment_name.to_s).and_return(alt)
|
279
295
|
if should_finish
|
@@ -588,7 +604,7 @@ describe Split::Helper do
|
|
588
604
|
it 'should raise an exception' do
|
589
605
|
lambda {
|
590
606
|
ab_test('link_color', 'blue', 'red')
|
591
|
-
}.should raise_error
|
607
|
+
}.should raise_error
|
592
608
|
end
|
593
609
|
end
|
594
610
|
|
@@ -596,7 +612,7 @@ describe Split::Helper do
|
|
596
612
|
it 'should raise an exception' do
|
597
613
|
lambda {
|
598
614
|
finished('link_color')
|
599
|
-
}.should raise_error
|
615
|
+
}.should raise_error
|
600
616
|
end
|
601
617
|
end
|
602
618
|
|
@@ -618,14 +634,14 @@ describe Split::Helper do
|
|
618
634
|
|
619
635
|
lambda {
|
620
636
|
ab_test('link_color', 'blue', 'red')
|
621
|
-
}.should_not raise_error
|
637
|
+
}.should_not raise_error
|
622
638
|
end
|
623
639
|
|
624
640
|
it "should return control variable" do
|
625
641
|
ab_test('link_color', 'blue', 'red').should eq('blue')
|
626
642
|
lambda {
|
627
643
|
finished('link_color')
|
628
|
-
}.should_not raise_error
|
644
|
+
}.should_not raise_error
|
629
645
|
end
|
630
646
|
|
631
647
|
end
|
@@ -645,7 +661,7 @@ describe Split::Helper do
|
|
645
661
|
it 'should not raise an exception' do
|
646
662
|
lambda {
|
647
663
|
ab_test('link_color', 'blue', 'red')
|
648
|
-
}.should_not raise_error
|
664
|
+
}.should_not raise_error
|
649
665
|
end
|
650
666
|
it 'should call db_failover_on_db_error proc with error as parameter' do
|
651
667
|
Split.configure do |config|
|
@@ -703,7 +719,7 @@ describe Split::Helper do
|
|
703
719
|
it 'should not raise an exception' do
|
704
720
|
lambda {
|
705
721
|
finished('link_color')
|
706
|
-
}.should_not raise_error
|
722
|
+
}.should_not raise_error
|
707
723
|
end
|
708
724
|
it 'should call db_failover_on_db_error proc with error as parameter' do
|
709
725
|
Split.configure do |config|
|
@@ -817,16 +833,16 @@ describe Split::Helper do
|
|
817
833
|
|
818
834
|
it "fails gracefully if config is missing experiment" do
|
819
835
|
Split.configuration.experiments = { :other_experiment => { :foo => "Bar" } }
|
820
|
-
lambda { ab_test :my_experiment }.should raise_error
|
836
|
+
lambda { ab_test :my_experiment }.should raise_error
|
821
837
|
end
|
822
838
|
|
823
839
|
it "fails gracefully if config is missing" do
|
824
|
-
lambda { Split.configuration.experiments = nil }.should raise_error
|
840
|
+
lambda { Split.configuration.experiments = nil }.should raise_error
|
825
841
|
end
|
826
842
|
|
827
843
|
it "fails gracefully if config is missing alternatives" do
|
828
844
|
Split.configuration.experiments[:my_experiment] = { :foo => "Bar" }
|
829
|
-
lambda { ab_test :my_experiment }.should raise_error
|
845
|
+
lambda { ab_test :my_experiment }.should raise_error
|
830
846
|
end
|
831
847
|
end
|
832
848
|
|
@@ -2,7 +2,7 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
describe Split::Persistence::CookieAdapter do
|
4
4
|
|
5
|
-
let(:context) {
|
5
|
+
let(:context) { double(:cookies => CookiesMock.new) }
|
6
6
|
subject { Split::Persistence::CookieAdapter.new(context) }
|
7
7
|
|
8
8
|
describe "#[] and #[]=" do
|
data/spec/persistence_spec.rb
CHANGED
@@ -18,7 +18,7 @@ describe Split::Persistence do
|
|
18
18
|
|
19
19
|
it "should raise if the adapter cannot be found" do
|
20
20
|
Split.configuration.stub(:persistence).and_return(:something_weird)
|
21
|
-
expect { subject.adapter }.to raise_error
|
21
|
+
expect { subject.adapter }.to raise_error
|
22
22
|
end
|
23
23
|
end
|
24
24
|
context "when the persistence config is a class" do
|
data/spec/spec_helper.rb
CHANGED
data/spec/trial_spec.rb
CHANGED
@@ -3,8 +3,8 @@ require 'split/trial'
|
|
3
3
|
|
4
4
|
describe Split::Trial do
|
5
5
|
it "should be initializeable" do
|
6
|
-
experiment =
|
7
|
-
alternative =
|
6
|
+
experiment = double('experiment')
|
7
|
+
alternative = double('alternative', :kind_of? => Split::Alternative)
|
8
8
|
trial = Split::Trial.new(:experiment => experiment, :alternative => alternative)
|
9
9
|
trial.experiment.should == experiment
|
10
10
|
trial.alternative.should == alternative
|
@@ -13,8 +13,8 @@ describe Split::Trial do
|
|
13
13
|
|
14
14
|
describe "alternative" do
|
15
15
|
it "should use the alternative if specified" do
|
16
|
-
alternative =
|
17
|
-
trial = Split::Trial.new(:experiment => experiment =
|
16
|
+
alternative = double('alternative', :kind_of? => Split::Alternative)
|
17
|
+
trial = Split::Trial.new(:experiment => experiment = double('experiment'), :alternative => alternative)
|
18
18
|
trial.should_not_receive(:choose)
|
19
19
|
trial.alternative.should == alternative
|
20
20
|
end
|
@@ -39,8 +39,8 @@ describe Split::Trial do
|
|
39
39
|
|
40
40
|
|
41
41
|
it "should choose from the available alternatives" do
|
42
|
-
trial = Split::Trial.new(:experiment => experiment =
|
43
|
-
alternative =
|
42
|
+
trial = Split::Trial.new(:experiment => experiment = double('experiment'))
|
43
|
+
alternative = double('alternative', :kind_of? => Split::Alternative)
|
44
44
|
experiment.should_receive(:next_alternative).and_return(alternative)
|
45
45
|
alternative.should_receive(:increment_participation)
|
46
46
|
experiment.stub(:winner).and_return nil
|
data/split.gemspec
CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |s|
|
|
29
29
|
|
30
30
|
s.add_development_dependency 'rake'
|
31
31
|
s.add_development_dependency 'bundler', '~> 1.3'
|
32
|
-
s.add_development_dependency 'rspec', '~> 2.
|
32
|
+
s.add_development_dependency 'rspec', '~> 2.14'
|
33
33
|
s.add_development_dependency 'rack-test', '>= 0.5.7'
|
34
|
+
s.add_development_dependency 'coveralls'
|
34
35
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: split
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-07-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
@@ -114,7 +114,7 @@ dependencies:
|
|
114
114
|
requirements:
|
115
115
|
- - ~>
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: '2.
|
117
|
+
version: '2.14'
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -122,7 +122,7 @@ dependencies:
|
|
122
122
|
requirements:
|
123
123
|
- - ~>
|
124
124
|
- !ruby/object:Gem::Version
|
125
|
-
version: '2.
|
125
|
+
version: '2.14'
|
126
126
|
- !ruby/object:Gem::Dependency
|
127
127
|
name: rack-test
|
128
128
|
requirement: !ruby/object:Gem::Requirement
|
@@ -139,6 +139,22 @@ dependencies:
|
|
139
139
|
- - ! '>='
|
140
140
|
- !ruby/object:Gem::Version
|
141
141
|
version: 0.5.7
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: coveralls
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
142
158
|
description:
|
143
159
|
email:
|
144
160
|
- andrewnez@gmail.com
|
@@ -247,3 +263,4 @@ test_files:
|
|
247
263
|
- spec/spec_helper.rb
|
248
264
|
- spec/support/cookies_mock.rb
|
249
265
|
- spec/trial_spec.rb
|
266
|
+
has_rdoc:
|