split 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -2,4 +2,5 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
- *.rbc
5
+ *.rbc
6
+ .idea
@@ -1,3 +1,14 @@
1
+ ## 0.4.1 (April 6, 2012)
2
+
3
+ Features:
4
+
5
+ - Added configuration option to disable Split testing (@ilyakatz, #45)
6
+
7
+ Bugfixes:
8
+
9
+ - Fix weights for existing experiments (@andreas, #40)
10
+ - Fixed dashboard range error (@andrew, #42)
11
+
1
12
  ## 0.4.0 (March 7, 2012)
2
13
 
3
14
  **IMPORTANT**
@@ -182,6 +182,7 @@ You can override the default configuration options of Split like so:
182
182
  config.db_failover = true # handle redis errors gracefully
183
183
  config.db_failover_on_db_error = proc{|error| Rails.logger.error(error.message) }
184
184
  config.allow_multiple_experiments = true
185
+ config.enabled = true
185
186
  end
186
187
 
187
188
  ### DB failover solution
@@ -249,6 +250,10 @@ is configured.
249
250
  - [Split::Export](http://github.com/andrew/split-export) - easily export ab test data out of Split
250
251
  - [Split::Analytics](http://github.com/andrew/split-analytics) - push test data to google analytics
251
252
 
253
+ ## Screencast
254
+
255
+ Ryan bates has produced an excellent 10 minute screencast about split on the Railscasts site: [A/B Testing with Split](http://railscasts.com/episodes/331-a-b-testing-with-split)
256
+
252
257
  ## Contributors
253
258
 
254
259
  Special thanks to the following people for submitting patches:
@@ -264,7 +269,7 @@ Report Issues/Feature requests on [GitHub Issues](http://github.com/andrew/split
264
269
 
265
270
  Tests can be ran with `rake spec`
266
271
 
267
- [![Build Status](https://secure.travis-ci.org/andrew/split.png?branch=master)](http://travis-ci.org/andrew/split)
272
+ [![Build Status](https://secure.travis-ci.org/andrew/split.png?branch=master)](http://travis-ci.org/andrew/split) [![Dependency Status](https://gemnasium.com/andrew/split.png)](https://gemnasium.com/andrew/split)
268
273
 
269
274
  ### Note on Patches/Pull Requests
270
275
 
@@ -5,6 +5,7 @@ module Split
5
5
  attr_accessor :db_failover
6
6
  attr_accessor :db_failover_on_db_error
7
7
  attr_accessor :allow_multiple_experiments
8
+ attr_accessor :enabled
8
9
 
9
10
  def initialize
10
11
  @robot_regex = /\b(Baidu|Gigabot|Googlebot|libwww-perl|lwp-trivial|msnbot|SiteUptime|Slurp|WordPress|ZIBB|ZyBorg)\b/i
@@ -12,6 +13,7 @@ module Split
12
13
  @db_failover = false
13
14
  @db_failover_on_db_error = proc{|error|} # e.g. use Rails logger here
14
15
  @allow_multiple_experiments = false
16
+ @enabled = true
15
17
  end
16
18
  end
17
- end
19
+ end
@@ -19,7 +19,7 @@ module Split
19
19
  def confidence_level(z_score)
20
20
  return z_score if z_score.is_a? String
21
21
 
22
- z = round(z_score.to_f, 3)
22
+ z = round(z_score.to_s.to_f, 3)
23
23
  if z > 0.0
24
24
  if z < 1.96
25
25
  'no confidence'
@@ -138,7 +138,7 @@ module Split
138
138
 
139
139
  if Split.redis.exists(name)
140
140
  if load_alternatives_for(name) == alts.map(&:name)
141
- experiment = self.new(name, *load_alternatives_for(name))
141
+ experiment = self.new(name, *alternatives)
142
142
  else
143
143
  exp = self.new(name, *load_alternatives_for(name))
144
144
  exp.reset
@@ -1,33 +1,14 @@
1
1
  module Split
2
2
  module Helper
3
3
  def ab_test(experiment_name, control, *alternatives)
4
- puts 'WARNING: You should always pass the control alternative through as the second argument with any other alternatives as the third because the order of the hash is not preserved in ruby 1.8' if RUBY_VERSION.match(/1\.8/) && alternatives.length.zero?
5
- begin
6
- experiment = Split::Experiment.find_or_create(experiment_name, *([control] + alternatives))
7
- if experiment.winner
8
- ret = experiment.winner.name
9
- else
10
- if forced_alternative = override(experiment.name, experiment.alternative_names)
11
- ret = forced_alternative
12
- else
13
- clean_old_versions(experiment)
14
- begin_experiment(experiment) if exclude_visitor? or not_allowed_to_test?(experiment.key)
15
4
 
16
- if ab_user[experiment.key]
17
- ret = ab_user[experiment.key]
5
+ puts 'WARNING: You should always pass the control alternative through as the second argument with any other alternatives as the third because the order of the hash is not preserved in ruby 1.8' if RUBY_VERSION.match(/1\.8/) && alternatives.length.zero?
6
+ ret = if Split.configuration.enabled
7
+ experiment_variable(alternatives, control, experiment_name)
18
8
  else
19
- alternative = experiment.next_alternative
20
- alternative.increment_participation
21
- begin_experiment(experiment, alternative.name)
22
- ret = alternative.name
9
+ control_variable(control)
23
10
  end
24
- end
25
- end
26
- rescue Errno::ECONNREFUSED => e
27
- raise unless Split.configuration.db_failover
28
- Split.configuration.db_failover_on_db_error.call(e)
29
- ret = Hash === control ? control.keys.first : control
30
- end
11
+
31
12
  if block_given?
32
13
  if defined?(capture) # a block in a rails view
33
14
  block = Proc.new { yield(ret) }
@@ -42,7 +23,7 @@ module Split
42
23
  end
43
24
 
44
25
  def finished(experiment_name, options = {:reset => true})
45
- return if exclude_visitor?
26
+ return if exclude_visitor? or !Split.configuration.enabled
46
27
  return unless (experiment = Split::Experiment.find(experiment_name))
47
28
  if alternative_name = ab_user[experiment.key]
48
29
  alternative = Split::Alternative.new(alternative_name, experiment_name)
@@ -76,7 +57,7 @@ module Split
76
57
  end
77
58
 
78
59
  def doing_other_tests?(experiment_key)
79
- ab_user.keys.reject{|k| k == experiment_key}.length > 0
60
+ ab_user.keys.reject { |k| k == experiment_key }.length > 0
80
61
  end
81
62
 
82
63
  def clean_old_versions(experiment)
@@ -87,7 +68,7 @@ module Split
87
68
 
88
69
  def old_versions(experiment)
89
70
  if experiment.version > 0
90
- ab_user.keys.select{|k| k.match(Regexp.new(experiment.name))}.reject{|k| k == experiment.key}
71
+ ab_user.keys.select { |k| k.match(Regexp.new(experiment.name)) }.reject { |k| k == experiment.key }
91
72
  else
92
73
  []
93
74
  end
@@ -104,5 +85,44 @@ module Split
104
85
  false
105
86
  end
106
87
  end
88
+
89
+
90
+ protected
91
+
92
+ def control_variable(control)
93
+ Hash === control ? control.keys.first : control
94
+ end
95
+
96
+ def experiment_variable(alternatives, control, experiment_name)
97
+ begin
98
+ experiment = Split::Experiment.find_or_create(experiment_name, *([control] + alternatives))
99
+ if experiment.winner
100
+ ret = experiment.winner.name
101
+ else
102
+ if forced_alternative = override(experiment.name, experiment.alternative_names)
103
+ ret = forced_alternative
104
+ else
105
+ clean_old_versions(experiment)
106
+ begin_experiment(experiment) if exclude_visitor? or not_allowed_to_test?(experiment.key)
107
+
108
+ if ab_user[experiment.key]
109
+ ret = ab_user[experiment.key]
110
+ else
111
+ alternative = experiment.next_alternative
112
+ alternative.increment_participation
113
+ begin_experiment(experiment, alternative.name)
114
+ ret = alternative.name
115
+ end
116
+ end
117
+ end
118
+ rescue Errno::ECONNREFUSED => e
119
+ raise unless Split.configuration.db_failover
120
+ Split.configuration.db_failover_on_db_error.call(e)
121
+ ret = control_variable(control)
122
+ end
123
+ ret
124
+ end
125
+
107
126
  end
127
+
108
128
  end
@@ -1,3 +1,3 @@
1
1
  module Split
2
- VERSION = "0.4.0"
2
+ VERSION = "0.4.1"
3
3
  end
@@ -9,5 +9,6 @@ describe Split::Configuration do
9
9
  config.db_failover.should be_false
10
10
  config.db_failover_on_db_error.should be_a Proc
11
11
  config.allow_multiple_experiments.should be_false
12
+ config.enabled.should be_true
12
13
  end
13
14
  end
@@ -6,7 +6,7 @@ include Split::DashboardHelpers
6
6
  describe Split::DashboardHelpers do
7
7
  describe 'confidence_level' do
8
8
  it 'should handle very small numbers' do
9
- confidence_level(0.000000000000006).should eql('No Change')
9
+ confidence_level(Complex(2e-18, -0.03)).should eql('No Change')
10
10
  end
11
11
  end
12
12
  end
@@ -28,7 +28,7 @@ describe Split::Experiment do
28
28
 
29
29
  Split::Experiment.find('basket_text').start_time.should == experiment_start_time
30
30
  end
31
-
31
+
32
32
  it "should handle not having a start time" do
33
33
  experiment_start_time = Time.parse("Sat Mar 03 14:01:03")
34
34
  Time.stub(:now => experiment_start_time)
@@ -184,4 +184,23 @@ describe Split::Experiment do
184
184
  lambda { Split::Experiment.find_or_create('link_enabled', true, false) }.should raise_error
185
185
  end
186
186
  end
187
- end
187
+
188
+ describe 'specifying weights' do
189
+ it "should work for a new experiment" do
190
+ experiment = Split::Experiment.find_or_create('link_color', {'blue' => 1}, {'red' => 2 })
191
+
192
+ experiment.alternatives.map(&:weight).should == [1, 2]
193
+ end
194
+
195
+ it "should work for an existing experiment" do
196
+ experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
197
+ experiment.save
198
+
199
+ same_experiment = Split::Experiment.find_or_create('link_color', {'blue' => 1}, {'red' => 2 })
200
+ same_experiment.alternatives.map(&:weight).should == [1, 2]
201
+ end
202
+ end
203
+
204
+
205
+
206
+ end
@@ -85,7 +85,7 @@ describe Split::Helper do
85
85
  small = Split::Alternative.new('small', 'button_size')
86
86
  small.participant_count.should eql(0)
87
87
  end
88
-
88
+
89
89
  it "should let a user participate in many experiment with allow_multiple_experiments option" do
90
90
  Split.configure do |config|
91
91
  config.allow_multiple_experiments = true
@@ -346,6 +346,37 @@ describe Split::Helper do
346
346
  end
347
347
  end
348
348
 
349
+ describe "disable split testing" do
350
+
351
+ before(:each) do
352
+ Split.configure do |config|
353
+ config.enabled = false
354
+ end
355
+ end
356
+
357
+ after(:each) do
358
+ Split.configure do |config|
359
+ config.enabled = true
360
+ end
361
+ end
362
+
363
+ it "should not attempt to connect to redis" do
364
+
365
+ lambda {
366
+ ab_test('link_color', 'blue', 'red')
367
+ }.should_not raise_error(Errno::ECONNREFUSED)
368
+ end
369
+
370
+ it "should return control variable" do
371
+ ab_test('link_color', 'blue', 'red').should eq('blue')
372
+ lambda {
373
+ finished('link_color')
374
+ }.should_not raise_error(Errno::ECONNREFUSED)
375
+ end
376
+
377
+ end
378
+
379
+
349
380
  end
350
381
 
351
382
  context 'and db_failover config option is turned on' do
@@ -398,6 +429,7 @@ describe Split::Helper do
398
429
  end
399
430
  end
400
431
 
432
+
401
433
  end
402
434
 
403
435
  end
@@ -1,7 +1,10 @@
1
+ ENV['RACK_ENV'] = "test"
2
+
1
3
  require 'rubygems'
2
4
  require 'bundler/setup'
3
5
  require 'split'
4
6
  require 'ostruct'
7
+ require 'complex' if RUBY_VERSION.match(/1\.8/)
5
8
 
6
9
  def session
7
10
  @session ||= {}
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ["lib"]
20
20
 
21
21
  s.add_dependency 'redis', '~> 2.1'
22
- s.add_dependency 'redis-namespace', '~> 1.0.3'
22
+ s.add_dependency 'redis-namespace', '~> 1.1.0'
23
23
  s.add_dependency 'sinatra', '>= 1.2.6'
24
24
 
25
25
  s.add_development_dependency 'rake'
metadata CHANGED
@@ -1,111 +1,154 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: split
3
- version: !ruby/object:Gem::Version
4
- version: 0.4.0
3
+ version: !ruby/object:Gem::Version
4
+ hash: 13
5
5
  prerelease:
6
+ segments:
7
+ - 0
8
+ - 4
9
+ - 1
10
+ version: 0.4.1
6
11
  platform: ruby
7
- authors:
12
+ authors:
8
13
  - Andrew Nesbitt
9
14
  autorequire:
10
15
  bindir: bin
11
16
  cert_chain: []
12
- date: 2012-03-07 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
17
+
18
+ date: 2012-04-06 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
15
22
  name: redis
16
- requirement: &70291376942100 !ruby/object:Gem::Requirement
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
17
25
  none: false
18
- requirements:
26
+ requirements:
19
27
  - - ~>
20
- - !ruby/object:Gem::Version
21
- version: '2.1'
28
+ - !ruby/object:Gem::Version
29
+ hash: 1
30
+ segments:
31
+ - 2
32
+ - 1
33
+ version: "2.1"
22
34
  type: :runtime
23
- prerelease: false
24
- version_requirements: *70291376942100
25
- - !ruby/object:Gem::Dependency
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
26
37
  name: redis-namespace
27
- requirement: &70291376938960 !ruby/object:Gem::Requirement
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
28
40
  none: false
29
- requirements:
41
+ requirements:
30
42
  - - ~>
31
- - !ruby/object:Gem::Version
32
- version: 1.0.3
43
+ - !ruby/object:Gem::Version
44
+ hash: 19
45
+ segments:
46
+ - 1
47
+ - 1
48
+ - 0
49
+ version: 1.1.0
33
50
  type: :runtime
34
- prerelease: false
35
- version_requirements: *70291376938960
36
- - !ruby/object:Gem::Dependency
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
37
53
  name: sinatra
38
- requirement: &70291376938260 !ruby/object:Gem::Requirement
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
39
56
  none: false
40
- requirements:
41
- - - ! '>='
42
- - !ruby/object:Gem::Version
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 19
61
+ segments:
62
+ - 1
63
+ - 2
64
+ - 6
43
65
  version: 1.2.6
44
66
  type: :runtime
45
- prerelease: false
46
- version_requirements: *70291376938260
47
- - !ruby/object:Gem::Dependency
67
+ version_requirements: *id003
68
+ - !ruby/object:Gem::Dependency
48
69
  name: rake
49
- requirement: &70291376937280 !ruby/object:Gem::Requirement
70
+ prerelease: false
71
+ requirement: &id004 !ruby/object:Gem::Requirement
50
72
  none: false
51
- requirements:
52
- - - ! '>='
53
- - !ruby/object:Gem::Version
54
- version: '0'
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
55
80
  type: :development
56
- prerelease: false
57
- version_requirements: *70291376937280
58
- - !ruby/object:Gem::Dependency
81
+ version_requirements: *id004
82
+ - !ruby/object:Gem::Dependency
59
83
  name: bundler
60
- requirement: &70291376930680 !ruby/object:Gem::Requirement
84
+ prerelease: false
85
+ requirement: &id005 !ruby/object:Gem::Requirement
61
86
  none: false
62
- requirements:
87
+ requirements:
63
88
  - - ~>
64
- - !ruby/object:Gem::Version
65
- version: '1.0'
89
+ - !ruby/object:Gem::Version
90
+ hash: 15
91
+ segments:
92
+ - 1
93
+ - 0
94
+ version: "1.0"
66
95
  type: :development
67
- prerelease: false
68
- version_requirements: *70291376930680
69
- - !ruby/object:Gem::Dependency
96
+ version_requirements: *id005
97
+ - !ruby/object:Gem::Dependency
70
98
  name: rspec
71
- requirement: &70291376929580 !ruby/object:Gem::Requirement
99
+ prerelease: false
100
+ requirement: &id006 !ruby/object:Gem::Requirement
72
101
  none: false
73
- requirements:
102
+ requirements:
74
103
  - - ~>
75
- - !ruby/object:Gem::Version
76
- version: '2.6'
104
+ - !ruby/object:Gem::Version
105
+ hash: 15
106
+ segments:
107
+ - 2
108
+ - 6
109
+ version: "2.6"
77
110
  type: :development
78
- prerelease: false
79
- version_requirements: *70291376929580
80
- - !ruby/object:Gem::Dependency
111
+ version_requirements: *id006
112
+ - !ruby/object:Gem::Dependency
81
113
  name: rack-test
82
- requirement: &70291376928420 !ruby/object:Gem::Requirement
114
+ prerelease: false
115
+ requirement: &id007 !ruby/object:Gem::Requirement
83
116
  none: false
84
- requirements:
117
+ requirements:
85
118
  - - ~>
86
- - !ruby/object:Gem::Version
87
- version: '0.6'
119
+ - !ruby/object:Gem::Version
120
+ hash: 7
121
+ segments:
122
+ - 0
123
+ - 6
124
+ version: "0.6"
88
125
  type: :development
89
- prerelease: false
90
- version_requirements: *70291376928420
91
- - !ruby/object:Gem::Dependency
126
+ version_requirements: *id007
127
+ - !ruby/object:Gem::Dependency
92
128
  name: guard-rspec
93
- requirement: &70291376927240 !ruby/object:Gem::Requirement
129
+ prerelease: false
130
+ requirement: &id008 !ruby/object:Gem::Requirement
94
131
  none: false
95
- requirements:
132
+ requirements:
96
133
  - - ~>
97
- - !ruby/object:Gem::Version
98
- version: '0.4'
134
+ - !ruby/object:Gem::Version
135
+ hash: 3
136
+ segments:
137
+ - 0
138
+ - 4
139
+ version: "0.4"
99
140
  type: :development
100
- prerelease: false
101
- version_requirements: *70291376927240
141
+ version_requirements: *id008
102
142
  description:
103
- email:
143
+ email:
104
144
  - andrewnez@gmail.com
105
145
  executables: []
146
+
106
147
  extensions: []
148
+
107
149
  extra_rdoc_files: []
108
- files:
150
+
151
+ files:
109
152
  - .gitignore
110
153
  - .travis.yml
111
154
  - CHANGELOG.mdown
@@ -136,31 +179,41 @@ files:
136
179
  - spec/helper_spec.rb
137
180
  - spec/spec_helper.rb
138
181
  - split.gemspec
182
+ has_rdoc: true
139
183
  homepage: https://github.com/andrew/split
140
184
  licenses: []
185
+
141
186
  post_install_message:
142
187
  rdoc_options: []
143
- require_paths:
188
+
189
+ require_paths:
144
190
  - lib
145
- required_ruby_version: !ruby/object:Gem::Requirement
191
+ required_ruby_version: !ruby/object:Gem::Requirement
146
192
  none: false
147
- requirements:
148
- - - ! '>='
149
- - !ruby/object:Gem::Version
150
- version: '0'
151
- required_rubygems_version: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ hash: 3
197
+ segments:
198
+ - 0
199
+ version: "0"
200
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
201
  none: false
153
- requirements:
154
- - - ! '>='
155
- - !ruby/object:Gem::Version
156
- version: '0'
202
+ requirements:
203
+ - - ">="
204
+ - !ruby/object:Gem::Version
205
+ hash: 3
206
+ segments:
207
+ - 0
208
+ version: "0"
157
209
  requirements: []
210
+
158
211
  rubyforge_project: split
159
- rubygems_version: 1.8.11
212
+ rubygems_version: 1.6.2
160
213
  signing_key:
161
214
  specification_version: 3
162
215
  summary: Rack based split testing framework
163
- test_files:
216
+ test_files:
164
217
  - spec/alternative_spec.rb
165
218
  - spec/configuration_spec.rb
166
219
  - spec/dashboard_helpers_spec.rb