split 0.6.2 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -4,3 +4,4 @@ Gemfile.lock
4
4
  pkg/*
5
5
  *.rbc
6
6
  .idea
7
+ coverage
data/CHANGELOG.mdown CHANGED
@@ -1,3 +1,13 @@
1
+ ## 0.6.3 (July 8, 2013)
2
+
3
+ Features:
4
+
5
+ - Add hooks for Trial#choose! and Trial#complete! (@bmarini, #176)
6
+
7
+ Bugfixes:
8
+
9
+ - Stores and parses Experiment's start_time as a UNIX integer (@joeroot, #177)
10
+
1
11
  ## 0.6.2 (June 6, 2013)
2
12
 
3
13
  Features:
data/README.mdown CHANGED
@@ -9,7 +9,8 @@ Split is designed to be hacker friendly, allowing for maximum customisation and
9
9
  [![Gem Version](https://badge.fury.io/rb/split.png)](http://badge.fury.io/rb/split)
10
10
  [![Build Status](https://secure.travis-ci.org/andrew/split.png?branch=master)](http://travis-ci.org/andrew/split)
11
11
  [![Dependency Status](https://gemnasium.com/andrew/split.png)](https://gemnasium.com/andrew/split)
12
- [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/andrew/split)
12
+ [![Code Climate](https://codeclimate.com/github/andrew/split.png)](https://codeclimate.com/github/andrew/split)
13
+ [![Coverage Status](https://coveralls.io/repos/andrew/split/badge.png)](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
  ![split_screenshot](https://f.cloud.github.com/assets/78887/306152/99c64650-9670-11e2-93f8-197f49495d02.png)
@@ -12,6 +12,8 @@ module Split
12
12
  attr_accessor :persistence
13
13
  attr_accessor :algorithm
14
14
  attr_accessor :store_override
15
+ attr_accessor :on_trial_choose
16
+ attr_accessor :on_trial_complete
15
17
 
16
18
  attr_reader :experiments
17
19
 
@@ -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
- Time.parse(t) if t
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
@@ -1,6 +1,6 @@
1
1
  module Split
2
2
  MAJOR = 0
3
3
  MINOR = 6
4
- PATCH = 2
4
+ PATCH = 3
5
5
  VERSION = [MAJOR, MINOR, PATCH].join('.')
6
6
  end
@@ -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(/Experiments must be a Hash/)
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(/Experiments must be a Hash/)
162
+ expect { @config.experiments = yaml }.to raise_error
163
163
  end
164
164
  end
165
165
  end
@@ -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.parse("Sat Mar 03 14:01:03")
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(ArgumentError)
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(ArgumentError)
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 = mock('alternative')
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(Errno::ECONNREFUSED)
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(Errno::ECONNREFUSED)
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(Errno::ECONNREFUSED)
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(Errno::ECONNREFUSED)
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(Errno::ECONNREFUSED)
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(Errno::ECONNREFUSED)
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(/not found/i)
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(/Experiments must be a Hash/)
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(/alternatives/i)
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) { mock(:cookies => CookiesMock.new) }
5
+ let(:context) { double(:cookies => CookiesMock.new) }
6
6
  subject { Split::Persistence::CookieAdapter.new(context) }
7
7
 
8
8
  describe "#[] and #[]=" do
@@ -2,7 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe Split::Persistence::SessionAdapter do
4
4
 
5
- let(:context) { mock(:session => {}) }
5
+ let(:context) { double(:session => {}) }
6
6
  subject { Split::Persistence::SessionAdapter.new(context) }
7
7
 
8
8
  describe "#[] and #[]=" do
@@ -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(Split::InvalidPersistenceAdapterError)
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
@@ -2,6 +2,10 @@ ENV['RACK_ENV'] = "test"
2
2
 
3
3
  require 'rubygems'
4
4
  require 'bundler/setup'
5
+
6
+ require 'coveralls'
7
+ Coveralls.wear!
8
+
5
9
  require 'split'
6
10
  require 'ostruct'
7
11
  require 'yaml'
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 = mock('experiment')
7
- alternative = mock('alternative', :kind_of? => Split::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 = mock('alternative', :kind_of? => Split::Alternative)
17
- trial = Split::Trial.new(:experiment => experiment = mock('experiment'), :alternative => alternative)
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 = mock('experiment'))
43
- alternative = mock('alternative', :kind_of? => Split::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.12'
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.2
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-06-06 00:00:00.000000000 Z
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.12'
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.12'
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: