split 3.1.0 → 3.2.0
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -2
- data/.travis.yml +27 -2
- data/CHANGELOG.md +21 -0
- data/README.md +43 -5
- data/lib/split/alternative.rb +3 -0
- data/lib/split/combined_experiments_helper.rb +8 -1
- data/lib/split/configuration.rb +2 -0
- data/lib/split/experiment.rb +5 -4
- data/lib/split/helper.rb +1 -1
- data/lib/split/version.rb +1 -1
- data/spec/alternative_spec.rb +12 -0
- data/spec/combined_experiments_helper_spec.rb +3 -3
- data/spec/experiment_spec.rb +1 -1
- data/spec/persistence/dual_adapter_spec.rb +2 -2
- data/split.gemspec +2 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 861fb73c59529de9a24f3a78ddd0b5cbe257eb8b
|
|
4
|
+
data.tar.gz: dd67eac616dd9435c3a0ca448de504b3824bccb8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d078b157629b473b654b751c18fe11ddae08486f38bce2fef2bdd71b7a8536ce048204adaf9b1e242e6cf76f1c33650db91994e34a23734ecaf4357663b750cf
|
|
7
|
+
data.tar.gz: aa994978184569f624b9d5bb3503b93b498ef4601bb1989fe2e16eabee07a0450adc9555c02c251d70d9a73dce65e40bac0e05bf071dc8d229e4ab87c0b817ec
|
data/.rubocop.yml
CHANGED
|
@@ -716,7 +716,7 @@ Style/LineEndConcatenation:
|
|
|
716
716
|
line end.
|
|
717
717
|
Enabled: false
|
|
718
718
|
|
|
719
|
-
Style/
|
|
719
|
+
Style/MethodCallWithoutArgsParentheses:
|
|
720
720
|
Description: 'Do not use parentheses for method calls with no arguments.'
|
|
721
721
|
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-args-no-parens'
|
|
722
722
|
Enabled: false
|
|
@@ -816,7 +816,7 @@ Style/OneLineConditional:
|
|
|
816
816
|
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator'
|
|
817
817
|
Enabled: false
|
|
818
818
|
|
|
819
|
-
|
|
819
|
+
Naming/BinaryOperatorParameter:
|
|
820
820
|
Description: 'When defining binary operators, name the argument other.'
|
|
821
821
|
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg'
|
|
822
822
|
Enabled: false
|
data/.travis.yml
CHANGED
|
@@ -1,13 +1,38 @@
|
|
|
1
1
|
language: ruby
|
|
2
2
|
rvm:
|
|
3
|
-
-
|
|
3
|
+
- 1.9.3
|
|
4
|
+
- 2.0
|
|
5
|
+
- 2.1
|
|
6
|
+
- 2.2.0
|
|
7
|
+
- 2.2.2
|
|
8
|
+
- 2.4.2
|
|
4
9
|
|
|
5
10
|
gemfile:
|
|
6
11
|
- gemfiles/4.2.gemfile
|
|
7
12
|
- gemfiles/5.0.gemfile
|
|
13
|
+
- gemfiles/5.1.gemfile
|
|
14
|
+
|
|
15
|
+
matrix:
|
|
16
|
+
exclude:
|
|
17
|
+
- rvm: 1.9.3
|
|
18
|
+
gemfile: gemfiles/5.0.gemfile
|
|
19
|
+
- rvm: 1.9.3
|
|
20
|
+
gemfile: gemfiles/5.1.gemfile
|
|
21
|
+
- rvm: 2.0
|
|
22
|
+
gemfile: gemfiles/5.0.gemfile
|
|
23
|
+
- rvm: 2.0
|
|
24
|
+
gemfile: gemfiles/5.1.gemfile
|
|
25
|
+
- rvm: 2.1
|
|
26
|
+
gemfile: gemfiles/5.0.gemfile
|
|
27
|
+
- rvm: 2.1
|
|
28
|
+
gemfile: gemfiles/5.1.gemfile
|
|
29
|
+
- rvm: 2.2.0
|
|
30
|
+
gemfile: gemfiles/5.0.gemfile
|
|
31
|
+
- rvm: 2.2.0
|
|
32
|
+
gemfile: gemfiles/5.1.gemfile
|
|
8
33
|
|
|
9
34
|
before_install:
|
|
10
|
-
- gem install bundler
|
|
35
|
+
- gem update --system && gem install bundler
|
|
11
36
|
|
|
12
37
|
script:
|
|
13
38
|
- RAILS_ENV=test bundle exec rake spec && bundle exec codeclimate-test-reporter
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
## 3.2.0 (September 21st, 2017)
|
|
2
|
+
|
|
3
|
+
Features:
|
|
4
|
+
|
|
5
|
+
- Allow configuration of how often winning alternatives are recalculated (@patbl, #501)
|
|
6
|
+
|
|
7
|
+
Bugfixes:
|
|
8
|
+
|
|
9
|
+
- Avoid z_score numeric exception for conversion rates >1 (@cmantas, #503)
|
|
10
|
+
- Fix combined experiments (@semanticart, #502)
|
|
11
|
+
|
|
12
|
+
## 3.1.1 (August 30th, 2017)
|
|
13
|
+
|
|
14
|
+
Bugfixes:
|
|
15
|
+
|
|
16
|
+
- Bring back support for ruby 1.9.3 and greater (rubygems 2.0.0 or greater now required) (@patbl, #498)
|
|
17
|
+
|
|
18
|
+
Misc:
|
|
19
|
+
|
|
20
|
+
- Document testing with RSpec (@eliotsykes, #495)
|
|
21
|
+
|
|
1
22
|
## 3.1.0 (August 14th, 2017)
|
|
2
23
|
|
|
3
24
|
Features:
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# [Split](http://libraries.io/rubygems/split)
|
|
1
|
+
# [Split](http://libraries.io/rubygems/split)
|
|
2
2
|
|
|
3
3
|
[](http://badge.fury.io/rb/split)
|
|
4
4
|
[](http://travis-ci.org/splitrb/split)
|
|
@@ -18,7 +18,7 @@ Split is designed to be hacker friendly, allowing for maximum customisation and
|
|
|
18
18
|
|
|
19
19
|
### Requirements
|
|
20
20
|
|
|
21
|
-
Split currently requires Ruby 1.9.
|
|
21
|
+
Split currently requires Ruby 1.9.3 or higher. If your project requires compatibility with Ruby 1.8.x and Rails 2.3, please use v0.8.0.
|
|
22
22
|
|
|
23
23
|
Split uses Redis as a datastore.
|
|
24
24
|
|
|
@@ -115,6 +115,14 @@ As per this [blog post](http://www.evanmiller.org/how-not-to-run-an-ab-test.html
|
|
|
115
115
|
|
|
116
116
|
The second option uses simulations from a beta distribution to determine the probability that the given alternative is the winner compared to all other alternatives. You can view these probabilities by clicking on the drop-down menu labeled "Confidence." This option should be used when the experiment has more than just 1 control and 1 alternative. It can also be used for a simple, 2-alternative A/B test.
|
|
117
117
|
|
|
118
|
+
Calculating the beta-distribution simulations for a large number of experiments can be slow, so the results are cached. You can specify how often they should be recalculated (the default is once per day).
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
Split.configure do |config|
|
|
122
|
+
config.winning_alternative_recalculation_interval = 3600 # 1 hour
|
|
123
|
+
end
|
|
124
|
+
```
|
|
125
|
+
|
|
118
126
|
## Extras
|
|
119
127
|
|
|
120
128
|
### Weighted alternatives
|
|
@@ -150,6 +158,36 @@ In the event you want to disable all tests without having to know the individual
|
|
|
150
158
|
|
|
151
159
|
It is not required to send `SPLIT_DISABLE=false` to activate Split.
|
|
152
160
|
|
|
161
|
+
To aid testing with RSpec, write `split_helper.rb` and call `use_ab_test(alternatives_by_experiment)` in your specs as instructed below:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
# Recommended path for this file is 'spec/support/split_helper.rb', and you will need to ensure it
|
|
165
|
+
# is `require`-d by rails_helper.rb or spec_helper.rb
|
|
166
|
+
module SplitHelper
|
|
167
|
+
|
|
168
|
+
# Usage:
|
|
169
|
+
#
|
|
170
|
+
# Force a specific experiment alternative to always be returned:
|
|
171
|
+
# use_ab_test(signup_form: "single_page")
|
|
172
|
+
#
|
|
173
|
+
# Force alternatives for multiple experiments:
|
|
174
|
+
# use_ab_test(signup_form: "single_page", pricing: "show_enterprise_prices")
|
|
175
|
+
#
|
|
176
|
+
def use_ab_test(alternatives_by_experiment)
|
|
177
|
+
allow_any_instance_of(Split::Helper).to receive(:ab_test) do |_receiver, experiment|
|
|
178
|
+
alternative =
|
|
179
|
+
alternatives_by_experiment.fetch(experiment) { |key| raise "Unknown experiment '#{key}'" }
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
RSpec.configure do |config|
|
|
185
|
+
# Make the `use_ab_test` method available to all specs:
|
|
186
|
+
config.include SplitHelper
|
|
187
|
+
end
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
|
|
153
191
|
### Starting experiments manually
|
|
154
192
|
|
|
155
193
|
By default new A/B tests will be active right after deployment. In case you would like to start new test a while after
|
|
@@ -625,7 +663,7 @@ Once you finish one of the goals, the test is considered to be completed, and fi
|
|
|
625
663
|
|
|
626
664
|
#### Combined Experiments
|
|
627
665
|
If you want to test how how button color affects signup *and* how it affects login, at the same time. Use combined tests
|
|
628
|
-
Configure like so
|
|
666
|
+
Configure like so
|
|
629
667
|
```ruby
|
|
630
668
|
Split.configuration.experiments = {
|
|
631
669
|
:button_color_experiment => {
|
|
@@ -646,8 +684,8 @@ Finish each combined test as normal
|
|
|
646
684
|
ab_finished(:button_color_on_signup)
|
|
647
685
|
```
|
|
648
686
|
|
|
649
|
-
**Additional Configuration**:
|
|
650
|
-
* Be sure to enable `allow_multiple_experiments`
|
|
687
|
+
**Additional Configuration**:
|
|
688
|
+
* Be sure to enable `allow_multiple_experiments`
|
|
651
689
|
* In Sinatra include the CombinedExperimentsHelper
|
|
652
690
|
```
|
|
653
691
|
helpers Split::CombinedExperimentsHelper
|
data/lib/split/alternative.rb
CHANGED
|
@@ -6,6 +6,7 @@ module Split
|
|
|
6
6
|
raise(Split::InvalidExperimentsFormatError, 'Unable to find experiment #{metric_descriptor} in configuration') if experiment[:combined_experiments].nil?
|
|
7
7
|
|
|
8
8
|
alternative = nil
|
|
9
|
+
weighted_alternatives = nil
|
|
9
10
|
experiment[:combined_experiments].each do |combined_experiment|
|
|
10
11
|
if alternative.nil?
|
|
11
12
|
if control
|
|
@@ -15,9 +16,15 @@ module Split
|
|
|
15
16
|
alternative = ab_test(combined_experiment, normalized_alternatives[0], *normalized_alternatives[1])
|
|
16
17
|
end
|
|
17
18
|
else
|
|
18
|
-
|
|
19
|
+
weighted_alternatives ||= experiment[:alternatives].each_with_object({}) do |alt, memo|
|
|
20
|
+
alt = Alternative.new(alt, experiment[:name]).name
|
|
21
|
+
memo[alt] = (alt == alternative ? 1 : 0)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
ab_test(combined_experiment, [weighted_alternatives])
|
|
19
25
|
end
|
|
20
26
|
end
|
|
27
|
+
alternative
|
|
21
28
|
end
|
|
22
29
|
|
|
23
30
|
def find_combined_experiment(metric_descriptor)
|
data/lib/split/configuration.rb
CHANGED
|
@@ -25,6 +25,7 @@ module Split
|
|
|
25
25
|
attr_accessor :on_before_experiment_delete
|
|
26
26
|
attr_accessor :include_rails_helper
|
|
27
27
|
attr_accessor :beta_probability_simulations
|
|
28
|
+
attr_accessor :winning_alternative_recalculation_interval
|
|
28
29
|
attr_accessor :redis
|
|
29
30
|
|
|
30
31
|
attr_reader :experiments
|
|
@@ -217,6 +218,7 @@ module Split
|
|
|
217
218
|
@algorithm = Split::Algorithms::WeightedSample
|
|
218
219
|
@include_rails_helper = true
|
|
219
220
|
@beta_probability_simulations = 10000
|
|
221
|
+
@winning_alternative_recalculation_interval = 60 * 60 * 24 # 1 day
|
|
220
222
|
@redis = ENV.fetch(ENV.fetch('REDIS_PROVIDER', 'REDIS_URL'), 'redis://localhost:6379')
|
|
221
223
|
end
|
|
222
224
|
|
data/lib/split/experiment.rb
CHANGED
|
@@ -262,10 +262,11 @@ module Split
|
|
|
262
262
|
end
|
|
263
263
|
|
|
264
264
|
def calc_winning_alternatives
|
|
265
|
-
#
|
|
266
|
-
|
|
265
|
+
# Cache the winning alternatives so we recalculate them once per the specified interval.
|
|
266
|
+
intervals_since_epoch =
|
|
267
|
+
Time.now.utc.to_i / Split.configuration.winning_alternative_recalculation_interval
|
|
267
268
|
|
|
268
|
-
if self.calc_time !=
|
|
269
|
+
if self.calc_time != intervals_since_epoch
|
|
269
270
|
if goals.empty?
|
|
270
271
|
self.estimate_winning_alternative
|
|
271
272
|
else
|
|
@@ -274,7 +275,7 @@ module Split
|
|
|
274
275
|
end
|
|
275
276
|
end
|
|
276
277
|
|
|
277
|
-
self.calc_time =
|
|
278
|
+
self.calc_time = intervals_since_epoch
|
|
278
279
|
|
|
279
280
|
self.save
|
|
280
281
|
end
|
data/lib/split/helper.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Split
|
|
|
10
10
|
experiment = ExperimentCatalog.find_or_initialize(metric_descriptor, control, *alternatives)
|
|
11
11
|
alternative = if Split.configuration.enabled
|
|
12
12
|
experiment.save
|
|
13
|
-
raise(Split::InvalidExperimentsFormatError) unless Split
|
|
13
|
+
raise(Split::InvalidExperimentsFormatError) unless (Split.configuration.experiments || {}).fetch(experiment.name.to_sym, {})[:combined_experiments].nil?
|
|
14
14
|
trial = Trial.new(:user => ab_user, :experiment => experiment,
|
|
15
15
|
:override => override_alternative(experiment.name), :exclude => exclude_visitor?,
|
|
16
16
|
:disabled => split_generically_disabled?)
|
data/lib/split/version.rb
CHANGED
data/spec/alternative_spec.rb
CHANGED
|
@@ -273,6 +273,18 @@ describe Split::Alternative do
|
|
|
273
273
|
expect(control.z_score(goal1)).to eq('N/A')
|
|
274
274
|
expect(control.z_score(goal2)).to eq('N/A')
|
|
275
275
|
end
|
|
276
|
+
|
|
277
|
+
it "should not blow up for Conversion Rates > 1" do
|
|
278
|
+
control = experiment.control
|
|
279
|
+
control.participant_count = 3474
|
|
280
|
+
control.set_completed_count(4244)
|
|
281
|
+
|
|
282
|
+
alternative2.participant_count = 3434
|
|
283
|
+
alternative2.set_completed_count(4358)
|
|
284
|
+
|
|
285
|
+
expect { control.z_score }.not_to raise_error
|
|
286
|
+
expect { alternative2.z_score }.not_to raise_error
|
|
287
|
+
end
|
|
276
288
|
end
|
|
277
289
|
|
|
278
290
|
describe "extra_info" do
|
|
@@ -46,12 +46,12 @@ describe Split::CombinedExperimentsHelper do
|
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
it "uses same
|
|
49
|
+
it "uses same alternative for all sub experiments and returns the alternative" do
|
|
50
50
|
allow(self).to receive(:get_alternative) { "test-alt" }
|
|
51
51
|
expect(self).to receive(:ab_test).with(:exp_1_click, {"control"=>0.5}, {"test-alt"=>0.5}) { "test-alt" }
|
|
52
|
-
expect(self).to receive(:ab_test).with(:exp_1_scroll, [{"test-alt" => 1}]
|
|
52
|
+
expect(self).to receive(:ab_test).with(:exp_1_scroll, [{"control" => 0, "test-alt" => 1}])
|
|
53
53
|
|
|
54
|
-
ab_combined_test('combined_exp_1')
|
|
54
|
+
expect(ab_combined_test('combined_exp_1')).to eq('test-alt')
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
end
|
data/spec/experiment_spec.rb
CHANGED
|
@@ -458,7 +458,7 @@ describe Split::Experiment do
|
|
|
458
458
|
expect(experiment.alternatives[0].p_winner).to be_within(0.04).of(0.50)
|
|
459
459
|
end
|
|
460
460
|
|
|
461
|
-
it "should calculate the probability of being the winning alternative separately for each goal" do
|
|
461
|
+
it "should calculate the probability of being the winning alternative separately for each goal", :skip => true do
|
|
462
462
|
experiment = Split::ExperimentCatalog.find_or_create({'link_color3' => ["purchase", "refund"]}, 'blue', 'red', 'green')
|
|
463
463
|
goal1 = experiment.goals[0]
|
|
464
464
|
goal2 = experiment.goals[1]
|
|
@@ -47,7 +47,7 @@ describe Split::Persistence::DualAdapter do
|
|
|
47
47
|
context "when logged in" do
|
|
48
48
|
subject {
|
|
49
49
|
described_class.with_config(
|
|
50
|
-
logged_in:
|
|
50
|
+
logged_in: lambda { |context| true },
|
|
51
51
|
logged_in_adapter: selected_adapter,
|
|
52
52
|
logged_out_adapter: not_selected_adapter
|
|
53
53
|
).new(context)
|
|
@@ -59,7 +59,7 @@ describe Split::Persistence::DualAdapter do
|
|
|
59
59
|
context "when not logged in" do
|
|
60
60
|
subject {
|
|
61
61
|
described_class.with_config(
|
|
62
|
-
logged_in:
|
|
62
|
+
logged_in: lambda { |context| false },
|
|
63
63
|
logged_in_adapter: not_selected_adapter,
|
|
64
64
|
logged_out_adapter: selected_adapter
|
|
65
65
|
).new(context)
|
data/split.gemspec
CHANGED
|
@@ -21,7 +21,8 @@ Gem::Specification.new do |s|
|
|
|
21
21
|
"mailing_list_uri" => "https://groups.google.com/d/forum/split-ruby"
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
s.required_ruby_version = '>= 1.9.
|
|
24
|
+
s.required_ruby_version = '>= 1.9.3'
|
|
25
|
+
s.required_rubygems_version = '>= 2.0.0'
|
|
25
26
|
|
|
26
27
|
s.rubyforge_project = "split"
|
|
27
28
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: split
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Nesbitt
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2017-
|
|
11
|
+
date: 2017-09-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: redis
|
|
@@ -257,12 +257,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
257
257
|
requirements:
|
|
258
258
|
- - ">="
|
|
259
259
|
- !ruby/object:Gem::Version
|
|
260
|
-
version: 1.9.
|
|
260
|
+
version: 1.9.3
|
|
261
261
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
262
262
|
requirements:
|
|
263
263
|
- - ">="
|
|
264
264
|
- !ruby/object:Gem::Version
|
|
265
|
-
version:
|
|
265
|
+
version: 2.0.0
|
|
266
266
|
requirements: []
|
|
267
267
|
rubyforge_project: split
|
|
268
268
|
rubygems_version: 2.6.4
|