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 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: