split 1.0.0 → 1.1.0

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: a3b1309c8ead9c52a0d0d6fe52c9b7e14caf82ea
4
- data.tar.gz: 79d96d59eb43b6ce77fd9822f4f324cf4cf8b1e7
3
+ metadata.gz: ea348c09640ba8869242588929d2d337856fc834
4
+ data.tar.gz: 0bb555acfabcaf683051058097da01b803c56a50
5
5
  SHA512:
6
- metadata.gz: c206f354515025bfbb3acae7641c99216078493f0d3de1d4bcabee6224ce6b44fd920af04ded93c807c7a07d8167fe6fd6e4c3fe008b83e521d5332f62023c65
7
- data.tar.gz: e7613adebd501d9a4caa5d1c68f41cbec9b6b363a4da61a086865e356ebd360761e39ec47bb6b8de567d1499b8d47ea39ba0015b67a120ef2e5fefacc1fe16c0
6
+ metadata.gz: f411a5636ee2fcc0c296f2e3e1e24515af7524be73eec1fe8bd86998564eb61fcc60419d52dc02588a92ac85ecb28c6525e9a007cc5607efe1942a56c1fa3262
7
+ data.tar.gz: 6ff0d6a3be6295653be81d34e0c2479c2d38bd8e1ac6a2fb0aa20e5796329add32cf465b884d5ede25ba9121343ae888de62e78f9aff5e468604f635c2efabb6
data/.travis.yml CHANGED
@@ -1,32 +1,13 @@
1
1
  language: ruby
2
- bundler_args: ''
3
2
  rvm:
4
- - 1.9.2
5
- - 1.9.3
6
- - 2.0.0
7
- - ruby-head
8
- - jruby-19mode
9
- - jruby-head
3
+ - 2.1.5
10
4
 
11
- - rbx-19mode
12
- matrix:
13
- allow_failures:
14
- - rvm: ruby-head
15
- - rvm: rbx-19mode
16
- - rvm: jruby-head
17
- exclude:
18
- - rvm: 1.9.2
19
- gemfile: gemfiles/4.0.gemfile
20
- - rvm: jruby-18mode
21
- gemfile: gemfiles/4.0.gemfile
22
- - rvm: rbx-18mode
23
- gemfile: gemfiles/4.0.gemfile
24
5
  gemfile:
25
- - gemfiles/3.0.gemfile
26
- - gemfiles/3.1.gemfile
27
6
  - gemfiles/3.2.gemfile
28
7
  - gemfiles/4.0.gemfile
8
+ - gemfiles/4.1.gemfile
29
9
 
30
10
  services:
31
11
  - redis-server
32
12
  cache: bundler
13
+ sudo: false
data/Appraisals CHANGED
@@ -1,15 +1,11 @@
1
- appraise "3.0" do
2
- gem "rails", "~> 3.0.20"
3
- end
4
-
5
- appraise "3.1" do
6
- gem "rails", "~> 3.1.12"
7
- end
8
-
9
1
  appraise "3.2" do
10
2
  gem "rails", "~> 3.2.13"
11
3
  end
12
4
 
13
5
  appraise "4.0" do
14
- gem "rails", "4.0.0.rc1"
6
+ gem "rails", "~> 4.0.12"
7
+ end
8
+
9
+ appraise "4.1" do
10
+ gem "rails", "~> 4.1.8"
15
11
  end
@@ -1,3 +1,20 @@
1
+ ## 1.1.0 (January 9th, 2015)
2
+
3
+ Features:
4
+
5
+ - Decouple trial from Split::Helper (@joshdover, #286)
6
+ - Helper method for Active Experiments (@blahblahblah-, #273)
7
+
8
+ Misc:
9
+
10
+ - Use the new travis container based infrastructure for tests (@andrew, #280)
11
+
12
+ ## 1.0.0 (October 12th, 2014)
13
+
14
+ Changes:
15
+
16
+ - Remove support for Ruby 1.8.7 and Rails 2.3 (@qpowell, #271)
17
+
1
18
  ## 0.8.0 (September 25th, 2014)
2
19
 
3
20
  Features:
data/Gemfile CHANGED
@@ -2,4 +2,4 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- gem "appraisal"
5
+ gem "appraisal"
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2013 Andrew Nesbitt
3
+ Copyright (c) 2015 Andrew Nesbitt
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining
6
6
  a copy of this software and associated documentation files (the
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
19
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
20
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
21
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -220,7 +220,7 @@ Using Redis will allow ab_users to persist across sessions or machines.
220
220
 
221
221
  ```ruby
222
222
  Split.configure do |config|
223
- config.persistence = Split::Persistence::RedisAdapter.with_config(:lookup_by => proc { |context| context.current_user_id }
223
+ config.persistence = Split::Persistence::RedisAdapter.with_config(:lookup_by => proc { |context| context.current_user_id })
224
224
  # Equivalent
225
225
  # config.persistence = Split::Persistence::RedisAdapter.with_config(:lookup_by => :current_user_id }
226
226
  end
@@ -638,18 +638,7 @@ Ryan bates has produced an excellent 10 minute screencast about split on the Rai
638
638
 
639
639
  ## Contributors
640
640
 
641
- Special thanks to the following people for submitting patches:
642
-
643
- * Lloyd Pick
644
- * Jeffery Chupp
645
- * Andrew Appleton
646
- * Phil Nash
647
- * Dave Goodchild
648
- * Ian Young
649
- * Nathan Woodhull
650
- * Ville Lautanala
651
- * Liu Jin
652
- * Peter Schröder
641
+ Over 70 different people have contributed to the project, you can see them all here: https://github.com/andrew/split/graphs/contributors
653
642
 
654
643
  ## Development
655
644
 
@@ -672,4 +661,4 @@ Tests can be ran with `rake spec`
672
661
 
673
662
  ## Copyright
674
663
 
675
- Copyright (c) 2013 Andrew Nesbitt. See [LICENSE](https://github.com/andrew/split/blob/master/LICENSE) for details.
664
+ Copyright (c) 2015 Andrew Nesbitt. See [LICENSE](https://github.com/andrew/split/blob/master/LICENSE) for details.
data/gemfiles/4.0.gemfile CHANGED
@@ -3,6 +3,6 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "appraisal"
6
- gem "rails", "4.0.0.rc1"
6
+ gem "rails", "~> 4.0.12"
7
7
 
8
8
  gemspec :path => "../"
@@ -3,6 +3,6 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "appraisal"
6
- gem "rails", "~> 3.1.12"
6
+ gem "rails", "~> 4.1.8"
7
7
 
8
8
  gemspec :path => "../"
@@ -104,7 +104,7 @@ module Split
104
104
  end
105
105
 
106
106
  def experiment
107
- Split::Experiment.find(experiment_name)
107
+ Split::ExperimentCatalog.find(experiment_name)
108
108
  end
109
109
 
110
110
  def z_score(goal = nil)
@@ -16,7 +16,7 @@ module Split
16
16
 
17
17
  get '/' do
18
18
  # Display experiments without a winner at the top of the dashboard
19
- @experiments = Split::Experiment.all_active_first
19
+ @experiments = Split::ExperimentCatalog.all_active_first
20
20
 
21
21
  @metrics = Split::Metric.all
22
22
 
@@ -30,32 +30,32 @@ module Split
30
30
  end
31
31
 
32
32
  post '/:experiment' do
33
- @experiment = Split::Experiment.find(params[:experiment])
33
+ @experiment = Split::ExperimentCatalog.find(params[:experiment])
34
34
  @alternative = Split::Alternative.new(params[:alternative], params[:experiment])
35
35
  @experiment.winner = @alternative.name
36
36
  redirect url('/')
37
37
  end
38
38
 
39
39
  post '/start/:experiment' do
40
- @experiment = Split::Experiment.find(params[:experiment])
40
+ @experiment = Split::ExperimentCatalog.find(params[:experiment])
41
41
  @experiment.start
42
42
  redirect url('/')
43
43
  end
44
44
 
45
45
  post '/reset/:experiment' do
46
- @experiment = Split::Experiment.find(params[:experiment])
46
+ @experiment = Split::ExperimentCatalog.find(params[:experiment])
47
47
  @experiment.reset
48
48
  redirect url('/')
49
49
  end
50
50
 
51
51
  post '/reopen/:experiment' do
52
- @experiment = Split::Experiment.find(params[:experiment])
52
+ @experiment = Split::ExperimentCatalog.find(params[:experiment])
53
53
  @experiment.reset_winner
54
54
  redirect url('/')
55
55
  end
56
56
 
57
57
  delete '/:experiment' do
58
- @experiment = Split::Experiment.find(params[:experiment])
58
+ @experiment = Split::ExperimentCatalog.find(params[:experiment])
59
59
  @experiment.delete
60
60
  redirect url('/')
61
61
  end
@@ -1,4 +1,4 @@
1
- module Split
1
+ module Split
2
2
  class Experiment
3
3
  attr_accessor :name
4
4
  attr_writer :algorithm
@@ -8,7 +8,7 @@
8
8
  attr_accessor :alternative_probabilities
9
9
 
10
10
  DEFAULT_OPTIONS = {
11
- :resettable => true,
11
+ :resettable => true
12
12
  }
13
13
 
14
14
  def initialize(name, options = {})
@@ -71,23 +71,6 @@
71
71
  alts
72
72
  end
73
73
 
74
- def self.all
75
- ExperimentCatalog.all
76
- end
77
-
78
- # Return experiments without a winner (considered "active") first
79
- def self.all_active_first
80
- ExperimentCatalog.all_active_first
81
- end
82
-
83
- def self.find(name)
84
- ExperimentCatalog.find(name)
85
- end
86
-
87
- def self.find_or_create(label, *alternatives)
88
- ExperimentCatalog.find_or_create(label, *alternatives)
89
- end
90
-
91
74
  def save
92
75
  validate!
93
76
 
@@ -21,23 +21,32 @@ module Split
21
21
  obj
22
22
  end
23
23
 
24
- def self.find_or_create(label, *alternatives)
25
- experiment_name_with_version, goals = normalize_experiment(label)
26
- name = experiment_name_with_version.to_s.split(':')[0]
24
+ def self.find_or_initialize(metric_descriptor, control = nil, *alternatives)
25
+ # Check if array is passed to ab_test
26
+ # e.g. ab_test('name', ['Alt 1', 'Alt 2', 'Alt 3'])
27
+ if control.is_a? Array and alternatives.length.zero?
28
+ control, alternatives = control.first, control[1..-1]
29
+ end
30
+
31
+ experiment_name_with_version, goals = normalize_experiment(metric_descriptor)
32
+ experiment_name = experiment_name_with_version.to_s.split(':')[0]
33
+ Split::Experiment.new(experiment_name,
34
+ :alternatives => [control].compact + alternatives, :goals => goals)
35
+ end
27
36
 
28
- exp = Experiment.new name, :alternatives => alternatives, :goals => goals
29
- exp.save
30
- exp
37
+ def self.find_or_create(metric_descriptor, control = nil, *alternatives)
38
+ experiment = find_or_initialize(metric_descriptor, control, *alternatives)
39
+ experiment.save
31
40
  end
32
41
 
33
42
  private
34
43
 
35
- def self.normalize_experiment(label)
36
- if Hash === label
37
- experiment_name = label.keys.first
38
- goals = label.values.first
44
+ def self.normalize_experiment(metric_descriptor)
45
+ if Hash === metric_descriptor
46
+ experiment_name = metric_descriptor.keys.first
47
+ goals = Array(metric_descriptor.values.first)
39
48
  else
40
- experiment_name = label
49
+ experiment_name = metric_descriptor
41
50
  goals = []
42
51
  end
43
52
  return experiment_name, goals
data/lib/split/helper.rb CHANGED
@@ -1,51 +1,42 @@
1
1
  module Split
2
2
  module Helper
3
3
 
4
- def ab_test(metric_descriptor, control=nil, *alternatives)
5
-
6
- # Check if array is passed to ab_test
7
- # e.g. ab_test('name', ['Alt 1', 'Alt 2', 'Alt 3'])
8
- if control.is_a? Array and alternatives.length.zero?
9
- control, alternatives = control.first, control[1..-1]
10
- end
11
-
4
+ def ab_test(metric_descriptor, control = nil, *alternatives)
12
5
  begin
13
- experiment_name_with_version, goals = normalize_experiment(metric_descriptor)
14
- experiment_name = experiment_name_with_version.to_s.split(':')[0]
15
- experiment = Split::Experiment.new(
16
- experiment_name,
17
- :alternatives => [control].compact + alternatives,
18
- :goals => goals)
19
- control ||= experiment.control && experiment.control.name
20
-
21
- ret = if Split.configuration.enabled
6
+ experiment = ExperimentCatalog.find_or_initialize(metric_descriptor, control, *alternatives)
7
+
8
+ alternative = if Split.configuration.enabled
22
9
  experiment.save
23
- start_trial( Trial.new(:experiment => experiment) )
10
+ trial = Trial.new(:user => ab_user, :experiment => experiment,
11
+ :override => override_alternative(experiment.name), :exclude => exclude_visitor?,
12
+ :disabled => split_generically_disabled?)
13
+ alt = trial.choose!(self)
14
+ alt ? alt.name : nil
24
15
  else
25
- control_variable(control)
16
+ control_variable(experiment.control)
26
17
  end
27
18
  rescue Errno::ECONNREFUSED, Redis::CannotConnectError => e
28
19
  raise(e) unless Split.configuration.db_failover
29
20
  Split.configuration.db_failover_on_db_error.call(e)
30
21
 
31
22
  if Split.configuration.db_failover_allow_parameter_override
32
- ret = override_alternative(experiment_name) if override_present?(experiment_name)
33
- ret = control_variable(control) if split_generically_disabled?
23
+ alternative = override_alternative(experiment.name) if override_present?(experiment.name)
24
+ alternative = control_variable(experiment.control) if split_generically_disabled?
34
25
  end
35
26
  ensure
36
- ret ||= control_variable(control)
27
+ alternative ||= control_variable(experiment.control)
37
28
  end
38
29
 
39
30
  if block_given?
40
31
  if defined?(capture) # a block in a rails view
41
- block = Proc.new { yield(ret) }
42
- concat(capture(ret, &block))
32
+ block = Proc.new { yield(alternative) }
33
+ concat(capture(alternative, &block))
43
34
  false
44
35
  else
45
- yield(ret)
36
+ yield(alternative)
46
37
  end
47
38
  else
48
- ret
39
+ alternative
49
40
  end
50
41
  end
51
42
 
@@ -60,8 +51,9 @@ module Split
60
51
  return true
61
52
  else
62
53
  alternative_name = ab_user[experiment.key]
63
- trial = Trial.new(:experiment => experiment, :alternative => alternative_name, :goals => options[:goals])
64
- call_trial_complete_hook(trial) if trial.complete!
54
+ trial = Trial.new(:user => ab_user, :experiment => experiment,
55
+ :alternative => alternative_name)
56
+ trial.complete!(options[:goals], self)
65
57
 
66
58
  if should_reset
67
59
  reset!(experiment)
@@ -74,7 +66,7 @@ module Split
74
66
 
75
67
  def finished(metric_descriptor, options = {:reset => true})
76
68
  return if exclude_visitor? || Split.configuration.disabled?
77
- metric_descriptor, goals = normalize_experiment(metric_descriptor)
69
+ metric_descriptor, goals = normalize_metric(metric_descriptor)
78
70
  experiments = Metric.possible_experiments(metric_descriptor)
79
71
 
80
72
  if experiments.any?
@@ -110,30 +102,7 @@ module Split
110
102
  end
111
103
 
112
104
  def exclude_visitor?
113
- instance_eval(&Split.configuration.ignore_filter)
114
- end
115
-
116
- def not_allowed_to_test?(experiment_key)
117
- !Split.configuration.allow_multiple_experiments && doing_other_tests?(experiment_key)
118
- end
119
-
120
- def doing_other_tests?(experiment_key)
121
- keys_without_experiment(ab_user.keys, experiment_key).length > 0
122
- end
123
-
124
- def clean_old_versions(experiment)
125
- old_versions(experiment).each do |old_key|
126
- ab_user.delete old_key
127
- end
128
- end
129
-
130
- def old_versions(experiment)
131
- if experiment.version > 0
132
- keys = ab_user.keys.select { |k| k.match(Regexp.new(experiment.name)) }
133
- keys_without_experiment(keys, experiment.key)
134
- else
135
- []
136
- end
105
+ instance_eval(&Split.configuration.ignore_filter) || is_ignored_ip_address? || is_robot?
137
106
  end
138
107
 
139
108
  def is_robot?
@@ -149,9 +118,21 @@ module Split
149
118
  false
150
119
  end
151
120
 
121
+ def active_experiments
122
+ experiment_pairs = {}
123
+ ab_user.keys.each do |key|
124
+ Metric.possible_experiments(key).each do |experiment|
125
+ if !experiment.has_winner?
126
+ experiment_pairs[key] = ab_user[key]
127
+ end
128
+ end
129
+ end
130
+ return experiment_pairs
131
+ end
132
+
152
133
  protected
153
134
 
154
- def normalize_experiment(metric_descriptor)
135
+ def normalize_metric(metric_descriptor)
155
136
  if Hash === metric_descriptor
156
137
  experiment_name = metric_descriptor.keys.first
157
138
  goals = Array(metric_descriptor.values.first)
@@ -163,51 +144,7 @@ module Split
163
144
  end
164
145
 
165
146
  def control_variable(control)
166
- Hash === control ? control.keys.first : control
167
- end
168
-
169
- def start_trial(trial)
170
- experiment = trial.experiment
171
- if override_present?(experiment.name) and experiment[override_alternative(experiment.name)]
172
- ret = override_alternative(experiment.name)
173
- ab_user[experiment.key] = ret if Split.configuration.store_override
174
- elsif split_generically_disabled?
175
- ret = experiment.control.name
176
- ab_user[experiment.key] = ret if Split.configuration.store_override
177
- elsif experiment.has_winner?
178
- ret = experiment.winner.name
179
- else
180
- clean_old_versions(experiment)
181
- if exclude_visitor? || not_allowed_to_test?(experiment.key) || not_started?(experiment)
182
- ret = experiment.control.name
183
- else
184
- if ab_user[experiment.key]
185
- ret = ab_user[experiment.key]
186
- else
187
- trial.choose!
188
- call_trial_choose_hook(trial)
189
- ret = begin_experiment(experiment, trial.alternative.name)
190
- end
191
- end
192
- end
193
-
194
- ret
195
- end
196
-
197
- def not_started?(experiment)
198
- experiment.start_time.nil?
199
- end
200
-
201
- def call_trial_choose_hook(trial)
202
- send(Split.configuration.on_trial_choose, trial) if Split.configuration.on_trial_choose
203
- end
204
-
205
- def call_trial_complete_hook(trial)
206
- send(Split.configuration.on_trial_complete, trial) if Split.configuration.on_trial_complete
207
- end
208
-
209
- def keys_without_experiment(keys, experiment_key)
210
- keys.reject { |k| k.match(Regexp.new("^#{experiment_key}(:finished)?$")) }
147
+ Hash === control ? control.keys.first.to_s : control.to_s
211
148
  end
212
149
  end
213
150
  end