split 3.4.1 → 4.0.0.pre
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/.github/FUNDING.yml +1 -0
- data/.rubocop.yml +177 -1
- data/.rubocop_todo.yml +40 -493
- data/.travis.yml +14 -42
- data/CHANGELOG.md +35 -0
- data/Gemfile +1 -0
- data/README.md +19 -1
- data/Rakefile +1 -0
- data/lib/split.rb +8 -2
- data/lib/split/algorithms/block_randomization.rb +1 -0
- data/lib/split/algorithms/weighted_sample.rb +2 -1
- data/lib/split/algorithms/whiplash.rb +3 -2
- data/lib/split/alternative.rb +1 -0
- data/lib/split/cache.rb +28 -0
- data/lib/split/combined_experiments_helper.rb +1 -0
- data/lib/split/configuration.rb +6 -12
- data/lib/split/dashboard.rb +17 -2
- data/lib/split/dashboard/helpers.rb +1 -0
- data/lib/split/dashboard/pagination_helpers.rb +1 -0
- data/lib/split/dashboard/paginator.rb +1 -0
- data/lib/split/dashboard/public/dashboard.js +10 -0
- data/lib/split/dashboard/public/style.css +5 -0
- data/lib/split/dashboard/views/_controls.erb +13 -0
- data/lib/split/encapsulated_helper.rb +3 -2
- data/lib/split/engine.rb +1 -0
- data/lib/split/exceptions.rb +1 -0
- data/lib/split/experiment.rb +81 -59
- data/lib/split/experiment_catalog.rb +1 -3
- data/lib/split/extensions/string.rb +1 -0
- data/lib/split/goals_collection.rb +1 -0
- data/lib/split/helper.rb +26 -7
- data/lib/split/metric.rb +2 -1
- data/lib/split/persistence.rb +4 -2
- data/lib/split/persistence/cookie_adapter.rb +1 -0
- data/lib/split/persistence/redis_adapter.rb +5 -0
- data/lib/split/persistence/session_adapter.rb +1 -0
- data/lib/split/redis_interface.rb +8 -28
- data/lib/split/trial.rb +20 -10
- data/lib/split/user.rb +14 -2
- data/lib/split/version.rb +2 -4
- data/lib/split/zscore.rb +1 -0
- data/spec/alternative_spec.rb +1 -1
- data/spec/cache_spec.rb +88 -0
- data/spec/configuration_spec.rb +1 -14
- data/spec/dashboard_spec.rb +45 -5
- data/spec/encapsulated_helper_spec.rb +1 -1
- data/spec/experiment_spec.rb +78 -7
- data/spec/goals_collection_spec.rb +1 -1
- data/spec/helper_spec.rb +68 -32
- data/spec/persistence/cookie_adapter_spec.rb +1 -1
- data/spec/persistence/redis_adapter_spec.rb +9 -0
- data/spec/redis_interface_spec.rb +0 -69
- data/spec/spec_helper.rb +5 -6
- data/spec/trial_spec.rb +45 -19
- data/spec/user_spec.rb +17 -0
- data/split.gemspec +7 -7
- metadata +23 -34
- data/gemfiles/4.2.gemfile +0 -9
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Split
|
3
4
|
# Simplifies the interface to Redis.
|
4
5
|
class RedisInterface
|
@@ -7,40 +8,19 @@ module Split
|
|
7
8
|
end
|
8
9
|
|
9
10
|
def persist_list(list_name, list_values)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
set_list_index(list_name, index, value)
|
11
|
+
if list_values.length > 0
|
12
|
+
redis.multi do |multi|
|
13
|
+
tmp_list = "#{list_name}_tmp"
|
14
|
+
multi.rpush(tmp_list, list_values)
|
15
|
+
multi.rename(tmp_list, list_name)
|
16
16
|
end
|
17
17
|
end
|
18
|
-
make_list_length(list_name, list_values.length)
|
19
|
-
list_values
|
20
|
-
end
|
21
|
-
|
22
|
-
def add_to_list(list_name, value)
|
23
|
-
redis.rpush(list_name, value)
|
24
|
-
end
|
25
|
-
|
26
|
-
def set_list_index(list_name, index, value)
|
27
|
-
redis.lset(list_name, index, value)
|
28
|
-
end
|
29
|
-
|
30
|
-
def list_length(list_name)
|
31
|
-
redis.llen(list_name)
|
32
|
-
end
|
33
18
|
|
34
|
-
|
35
|
-
redis.rpop(list_name)
|
36
|
-
end
|
37
|
-
|
38
|
-
def make_list_length(list_name, new_length)
|
39
|
-
redis.ltrim(list_name, 0, new_length - 1)
|
19
|
+
list_values
|
40
20
|
end
|
41
21
|
|
42
22
|
def add_to_set(set_name, value)
|
43
|
-
redis.sadd(set_name, value)
|
23
|
+
redis.sadd(set_name, value)
|
44
24
|
end
|
45
25
|
|
46
26
|
private
|
data/lib/split/trial.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Split
|
3
4
|
class Trial
|
5
|
+
attr_accessor :goals
|
4
6
|
attr_accessor :experiment
|
5
7
|
attr_writer :metadata
|
6
8
|
|
@@ -8,11 +10,12 @@ module Split
|
|
8
10
|
self.experiment = attrs.delete(:experiment)
|
9
11
|
self.alternative = attrs.delete(:alternative)
|
10
12
|
self.metadata = attrs.delete(:metadata)
|
13
|
+
self.goals = attrs.delete(:goals) || []
|
11
14
|
|
12
15
|
@user = attrs.delete(:user)
|
13
16
|
@options = attrs
|
14
17
|
|
15
|
-
@
|
18
|
+
@alternative_chosen = false
|
16
19
|
end
|
17
20
|
|
18
21
|
def metadata
|
@@ -33,7 +36,7 @@ module Split
|
|
33
36
|
end
|
34
37
|
end
|
35
38
|
|
36
|
-
def complete!(
|
39
|
+
def complete!(context = nil)
|
37
40
|
if alternative
|
38
41
|
if Array(goals).empty?
|
39
42
|
alternative.increment_completion
|
@@ -51,8 +54,9 @@ module Split
|
|
51
54
|
def choose!(context = nil)
|
52
55
|
@user.cleanup_old_experiments!
|
53
56
|
# Only run the process once
|
54
|
-
return alternative if @
|
57
|
+
return alternative if @alternative_chosen
|
55
58
|
|
59
|
+
new_participant = @user[@experiment.key].nil?
|
56
60
|
if override_is_alternative?
|
57
61
|
self.alternative = @options[:override]
|
58
62
|
if should_store_alternative? && !@user[@experiment.key]
|
@@ -70,19 +74,25 @@ module Split
|
|
70
74
|
else
|
71
75
|
self.alternative = @user[@experiment.key]
|
72
76
|
if alternative.nil?
|
73
|
-
|
77
|
+
if @experiment.cohorting_disabled?
|
78
|
+
self.alternative = @experiment.control
|
79
|
+
else
|
80
|
+
self.alternative = @experiment.next_alternative
|
74
81
|
|
75
|
-
|
76
|
-
|
82
|
+
# Increment the number of participants since we are actually choosing a new alternative
|
83
|
+
self.alternative.increment_participation
|
77
84
|
|
78
|
-
|
85
|
+
run_callback context, Split.configuration.on_trial_choose
|
86
|
+
end
|
79
87
|
end
|
80
88
|
end
|
81
89
|
end
|
82
90
|
|
83
|
-
|
84
|
-
|
85
|
-
|
91
|
+
new_participant_and_cohorting_disabled = new_participant && @experiment.cohorting_disabled?
|
92
|
+
|
93
|
+
@user[@experiment.key] = alternative.name unless @experiment.has_winner? || !should_store_alternative? || new_participant_and_cohorting_disabled
|
94
|
+
@alternative_chosen = true
|
95
|
+
run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled? || new_participant_and_cohorting_disabled
|
86
96
|
alternative
|
87
97
|
end
|
88
98
|
|
data/lib/split/user.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'forwardable'
|
3
4
|
|
4
5
|
module Split
|
@@ -7,7 +8,7 @@ module Split
|
|
7
8
|
def_delegators :@user, :keys, :[], :[]=, :delete
|
8
9
|
attr_reader :user
|
9
10
|
|
10
|
-
def initialize(context, adapter=nil)
|
11
|
+
def initialize(context, adapter = nil)
|
11
12
|
@user = adapter || Split::Persistence.adapter.new(context)
|
12
13
|
@cleaned_up = false
|
13
14
|
end
|
@@ -27,7 +28,8 @@ module Split
|
|
27
28
|
def max_experiments_reached?(experiment_key)
|
28
29
|
if Split.configuration.allow_multiple_experiments == 'control'
|
29
30
|
experiments = active_experiments
|
30
|
-
|
31
|
+
experiment_key_without_version = key_without_version(experiment_key)
|
32
|
+
count_control = experiments.count {|k, v| k == experiment_key_without_version || v == 'control'}
|
31
33
|
experiments.size > count_control
|
32
34
|
else
|
33
35
|
!Split.configuration.allow_multiple_experiments &&
|
@@ -52,6 +54,16 @@ module Split
|
|
52
54
|
experiment_pairs
|
53
55
|
end
|
54
56
|
|
57
|
+
def self.find(user_id, adapter)
|
58
|
+
adapter = adapter.is_a?(Symbol) ? Split::Persistence::ADAPTERS[adapter] : adapter
|
59
|
+
|
60
|
+
if adapter.respond_to?(:find)
|
61
|
+
User.new(nil, adapter.find(user_id))
|
62
|
+
else
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
55
67
|
private
|
56
68
|
|
57
69
|
def keys_without_experiment(keys, experiment_key)
|
data/lib/split/version.rb
CHANGED
data/lib/split/zscore.rb
CHANGED
data/spec/alternative_spec.rb
CHANGED
@@ -126,7 +126,7 @@ describe Split::Alternative do
|
|
126
126
|
|
127
127
|
it "should save to redis" do
|
128
128
|
alternative.save
|
129
|
-
expect(Split.redis.exists('basket_text:Basket')).to be true
|
129
|
+
expect(Split.redis.exists?('basket_text:Basket')).to be true
|
130
130
|
end
|
131
131
|
|
132
132
|
it "should increment participation count" do
|
data/spec/cache_spec.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Split::Cache do
|
5
|
+
|
6
|
+
let(:namespace) { :test_namespace }
|
7
|
+
let(:key) { :test_key }
|
8
|
+
let(:now) { 1606189017 }
|
9
|
+
|
10
|
+
before { allow(Time).to receive(:now).and_return(now) }
|
11
|
+
|
12
|
+
describe 'clear' do
|
13
|
+
|
14
|
+
before { Split.configuration.cache = true }
|
15
|
+
|
16
|
+
it 'clears the cache' do
|
17
|
+
expect(Time).to receive(:now).and_return(now).exactly(2).times
|
18
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
19
|
+
Split::Cache.clear
|
20
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'clear_key' do
|
25
|
+
before { Split.configuration.cache = true }
|
26
|
+
|
27
|
+
it 'clears the cache' do
|
28
|
+
expect(Time).to receive(:now).and_return(now).exactly(3).times
|
29
|
+
Split::Cache.fetch(namespace, :key1) { Time.now }
|
30
|
+
Split::Cache.fetch(namespace, :key2) { Time.now }
|
31
|
+
Split::Cache.clear_key(:key1)
|
32
|
+
|
33
|
+
Split::Cache.fetch(namespace, :key1) { Time.now }
|
34
|
+
Split::Cache.fetch(namespace, :key2) { Time.now }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'fetch' do
|
39
|
+
|
40
|
+
subject { Split::Cache.fetch(namespace, key) { Time.now } }
|
41
|
+
|
42
|
+
context 'when cache disabled' do
|
43
|
+
|
44
|
+
before { Split.configuration.cache = false }
|
45
|
+
|
46
|
+
it 'returns the yield' do
|
47
|
+
expect(subject).to eql(now)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'yields every time' do
|
51
|
+
expect(Time).to receive(:now).and_return(now).exactly(2).times
|
52
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
53
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when cache enabled' do
|
58
|
+
|
59
|
+
before { Split.configuration.cache = true }
|
60
|
+
|
61
|
+
it 'returns the yield' do
|
62
|
+
expect(subject).to eql(now)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'yields once' do
|
66
|
+
expect(Time).to receive(:now).and_return(now).once
|
67
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
68
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'honors namespace' do
|
72
|
+
expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a)
|
73
|
+
expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b)
|
74
|
+
|
75
|
+
expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a)
|
76
|
+
expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'honors key' do
|
80
|
+
expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a)
|
81
|
+
expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b)
|
82
|
+
|
83
|
+
expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a)
|
84
|
+
expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/spec/configuration_spec.rb
CHANGED
@@ -212,20 +212,6 @@ describe Split::Configuration do
|
|
212
212
|
expect(@config.normalized_experiments).to eq({:my_experiment=>{:alternatives=>[{"control_opt"=>0.67}, [{"second_opt"=>0.1}, {"third_opt"=>0.23}]]}})
|
213
213
|
end
|
214
214
|
|
215
|
-
context 'redis_url configuration [DEPRECATED]' do
|
216
|
-
it 'should warn on set and assign to #redis' do
|
217
|
-
expect(@config).to receive(:warn).with(/\[DEPRECATED\]/) { nil }
|
218
|
-
@config.redis_url = 'example_url'
|
219
|
-
expect(@config.redis).to eq('example_url')
|
220
|
-
end
|
221
|
-
|
222
|
-
it 'should warn on get and return #redis' do
|
223
|
-
expect(@config).to receive(:warn).with(/\[DEPRECATED\]/) { nil }
|
224
|
-
@config.redis = 'example_url'
|
225
|
-
expect(@config.redis_url).to eq('example_url')
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
215
|
context "redis configuration" do
|
230
216
|
it "should default to local redis server" do
|
231
217
|
expect(@config.redis).to eq("redis://localhost:6379")
|
@@ -240,6 +226,7 @@ describe Split::Configuration do
|
|
240
226
|
it "should use the ENV variable" do
|
241
227
|
ENV['REDIS_URL'] = "env_redis_url"
|
242
228
|
expect(Split::Configuration.new.redis).to eq("env_redis_url")
|
229
|
+
ENV.delete('REDIS_URL')
|
243
230
|
end
|
244
231
|
end
|
245
232
|
end
|
data/spec/dashboard_spec.rb
CHANGED
@@ -6,8 +6,16 @@ require 'split/dashboard'
|
|
6
6
|
describe Split::Dashboard do
|
7
7
|
include Rack::Test::Methods
|
8
8
|
|
9
|
+
class TestDashboard < Split::Dashboard
|
10
|
+
include Split::Helper
|
11
|
+
|
12
|
+
get '/my_experiment' do
|
13
|
+
ab_test(params[:experiment], 'blue', 'red')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
9
17
|
def app
|
10
|
-
@app ||=
|
18
|
+
@app ||= TestDashboard
|
11
19
|
end
|
12
20
|
|
13
21
|
def link(color)
|
@@ -90,8 +98,17 @@ describe Split::Dashboard do
|
|
90
98
|
it "should set current user's alternative" do
|
91
99
|
blue_link.participant_count = 7
|
92
100
|
post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
|
93
|
-
|
94
|
-
|
101
|
+
|
102
|
+
get "/my_experiment?experiment=#{experiment.name}"
|
103
|
+
expect(last_response.body).to include("blue")
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should not modify an existing user" do
|
107
|
+
blue_link.participant_count = 7
|
108
|
+
post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
|
109
|
+
|
110
|
+
expect(user[experiment.key]).to eq("red")
|
111
|
+
expect(blue_link.participant_count).to eq(7)
|
95
112
|
end
|
96
113
|
end
|
97
114
|
|
@@ -108,8 +125,9 @@ describe Split::Dashboard do
|
|
108
125
|
it "should set current user's alternative" do
|
109
126
|
blue_link.participant_count = 7
|
110
127
|
post "/force_alternative?experiment=#{experiment.name}", alternative: "blue"
|
111
|
-
|
112
|
-
|
128
|
+
|
129
|
+
get "/my_experiment?experiment=#{experiment.name}"
|
130
|
+
expect(last_response.body).to include("blue")
|
113
131
|
end
|
114
132
|
end
|
115
133
|
end
|
@@ -161,6 +179,28 @@ describe Split::Dashboard do
|
|
161
179
|
end
|
162
180
|
end
|
163
181
|
|
182
|
+
describe "update cohorting" do
|
183
|
+
it "calls enable of cohorting when action is enable" do
|
184
|
+
post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "enable" }
|
185
|
+
|
186
|
+
expect(experiment.cohorting_disabled?).to eq false
|
187
|
+
end
|
188
|
+
|
189
|
+
it "calls disable of cohorting when action is disable" do
|
190
|
+
post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "disable" }
|
191
|
+
|
192
|
+
expect(experiment.cohorting_disabled?).to eq true
|
193
|
+
end
|
194
|
+
|
195
|
+
it "calls neither enable or disable cohorting when passed invalid action" do
|
196
|
+
previous_value = experiment.cohorting_disabled?
|
197
|
+
|
198
|
+
post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "other" }
|
199
|
+
|
200
|
+
expect(experiment.cohorting_disabled?).to eq previous_value
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
164
204
|
it "should reset an experiment" do
|
165
205
|
red_link.participant_count = 5
|
166
206
|
blue_link.participant_count = 7
|
@@ -21,7 +21,7 @@ describe Split::EncapsulatedHelper do
|
|
21
21
|
end
|
22
22
|
|
23
23
|
it "calls the block with selected alternative" do
|
24
|
-
expect{|block| ab_test('link_color', 'red', 'red', &block) }.to yield_with_args('red',
|
24
|
+
expect{|block| ab_test('link_color', 'red', 'red', &block) }.to yield_with_args('red', {})
|
25
25
|
end
|
26
26
|
|
27
27
|
context "inside a view" do
|
data/spec/experiment_spec.rb
CHANGED
@@ -37,7 +37,7 @@ describe Split::Experiment do
|
|
37
37
|
|
38
38
|
it "should save to redis" do
|
39
39
|
experiment.save
|
40
|
-
expect(Split.redis.exists('basket_text')).to be true
|
40
|
+
expect(Split.redis.exists?('basket_text')).to be true
|
41
41
|
end
|
42
42
|
|
43
43
|
it "should save the start time to redis" do
|
@@ -85,7 +85,7 @@ describe Split::Experiment do
|
|
85
85
|
it "should not create duplicates when saving multiple times" do
|
86
86
|
experiment.save
|
87
87
|
experiment.save
|
88
|
-
expect(Split.redis.exists('basket_text')).to be true
|
88
|
+
expect(Split.redis.exists?('basket_text')).to be true
|
89
89
|
expect(Split.redis.lrange('basket_text', 0, -1)).to eq(['{"Basket":1}', '{"Cart":1}'])
|
90
90
|
end
|
91
91
|
|
@@ -118,6 +118,23 @@ describe Split::Experiment do
|
|
118
118
|
experiment = Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :resettable => false)
|
119
119
|
expect(experiment.resettable).to be_falsey
|
120
120
|
end
|
121
|
+
|
122
|
+
context 'from configuration' do
|
123
|
+
let(:experiment_name) { :my_experiment }
|
124
|
+
let(:experiments) do
|
125
|
+
{
|
126
|
+
experiment_name => {
|
127
|
+
:alternatives => ['Control Opt', 'Alt one']
|
128
|
+
}
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
before { Split.configuration.experiments = experiments }
|
133
|
+
|
134
|
+
it 'assigns default values to the experiment' do
|
135
|
+
expect(Split::Experiment.new(experiment_name).resettable).to eq(true)
|
136
|
+
end
|
137
|
+
end
|
121
138
|
end
|
122
139
|
|
123
140
|
describe 'persistent configuration' do
|
@@ -134,10 +151,23 @@ describe Split::Experiment do
|
|
134
151
|
|
135
152
|
describe '#metadata' do
|
136
153
|
let(:experiment) { Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :algorithm => Split::Algorithms::Whiplash, :metadata => meta) }
|
154
|
+
let(:meta) { { a: 'b' }}
|
155
|
+
|
156
|
+
before do
|
157
|
+
experiment.save
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should delete the key when metadata is removed" do
|
161
|
+
experiment.metadata = nil
|
162
|
+
experiment.save
|
163
|
+
|
164
|
+
expect(Split.redis.exists?(experiment.metadata_key)).to be_falsey
|
165
|
+
end
|
166
|
+
|
137
167
|
context 'simple hash' do
|
138
168
|
let(:meta) { { 'basket' => 'a', 'cart' => 'b' } }
|
169
|
+
|
139
170
|
it "should persist metadata in redis" do
|
140
|
-
experiment.save
|
141
171
|
e = Split::ExperimentCatalog.find('basket_text')
|
142
172
|
expect(e).to eq(experiment)
|
143
173
|
expect(e.metadata).to eq(meta)
|
@@ -147,7 +177,6 @@ describe Split::Experiment do
|
|
147
177
|
context 'nested hash' do
|
148
178
|
let(:meta) { { 'basket' => { 'one' => 'two' }, 'cart' => 'b' } }
|
149
179
|
it "should persist metadata in redis" do
|
150
|
-
experiment.save
|
151
180
|
e = Split::ExperimentCatalog.find('basket_text')
|
152
181
|
expect(e).to eq(experiment)
|
153
182
|
expect(e.metadata).to eq(meta)
|
@@ -180,7 +209,7 @@ describe Split::Experiment do
|
|
180
209
|
experiment.save
|
181
210
|
|
182
211
|
experiment.delete
|
183
|
-
expect(Split.redis.exists('link_color')).to be false
|
212
|
+
expect(Split.redis.exists?('link_color')).to be false
|
184
213
|
expect(Split::ExperimentCatalog.find('link_color')).to be_nil
|
185
214
|
end
|
186
215
|
|
@@ -206,8 +235,14 @@ describe Split::Experiment do
|
|
206
235
|
experiment.delete
|
207
236
|
expect(experiment.start_time).to be_nil
|
208
237
|
end
|
209
|
-
end
|
210
238
|
|
239
|
+
it "should default cohorting back to false" do
|
240
|
+
experiment.disable_cohorting
|
241
|
+
expect(experiment.cohorting_disabled?).to eq(true)
|
242
|
+
experiment.delete
|
243
|
+
expect(experiment.cohorting_disabled?).to eq(false)
|
244
|
+
end
|
245
|
+
end
|
211
246
|
|
212
247
|
describe 'winner' do
|
213
248
|
it "should have no winner initially" do
|
@@ -216,12 +251,17 @@ describe Split::Experiment do
|
|
216
251
|
end
|
217
252
|
|
218
253
|
describe 'winner=' do
|
219
|
-
it
|
254
|
+
it 'should allow you to specify a winner' do
|
220
255
|
experiment.save
|
221
256
|
experiment.winner = 'red'
|
222
257
|
expect(experiment.winner.name).to eq('red')
|
223
258
|
end
|
224
259
|
|
260
|
+
it 'should call the on_experiment_winner_choose hook' do
|
261
|
+
expect(Split.configuration.on_experiment_winner_choose).to receive(:call)
|
262
|
+
experiment.winner = 'green'
|
263
|
+
end
|
264
|
+
|
225
265
|
context 'when has_winner state is memoized' do
|
226
266
|
before { expect(experiment).to_not have_winner }
|
227
267
|
|
@@ -370,6 +410,22 @@ describe Split::Experiment do
|
|
370
410
|
end
|
371
411
|
end
|
372
412
|
|
413
|
+
describe "#cohorting_disabled?" do
|
414
|
+
it "returns false when nothing has been configured" do
|
415
|
+
expect(experiment.cohorting_disabled?).to eq false
|
416
|
+
end
|
417
|
+
|
418
|
+
it "returns true when enable_cohorting is performed" do
|
419
|
+
experiment.enable_cohorting
|
420
|
+
expect(experiment.cohorting_disabled?).to eq false
|
421
|
+
end
|
422
|
+
|
423
|
+
it "returns false when nothing has been configured" do
|
424
|
+
experiment.disable_cohorting
|
425
|
+
expect(experiment.cohorting_disabled?).to eq true
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
373
429
|
describe 'changing an existing experiment' do
|
374
430
|
def same_but_different_alternative
|
375
431
|
Split::ExperimentCatalog.find_or_create('link_color', 'blue', 'yellow', 'orange')
|
@@ -392,6 +448,21 @@ describe Split::Experiment do
|
|
392
448
|
expect(same_experiment_again.version).to eq(1)
|
393
449
|
end
|
394
450
|
|
451
|
+
context "when metadata is changed" do
|
452
|
+
it "should increase version" do
|
453
|
+
experiment.save
|
454
|
+
experiment.metadata = { 'foo' => 'bar' }
|
455
|
+
|
456
|
+
expect { experiment.save }.to change { experiment.version }.by(1)
|
457
|
+
end
|
458
|
+
|
459
|
+
it "does not increase version" do
|
460
|
+
experiment.metadata = nil
|
461
|
+
experiment.save
|
462
|
+
expect { experiment.save }.to change { experiment.version }.by(0)
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
395
466
|
context 'when experiment configuration is changed' do
|
396
467
|
let(:reset_manually) { false }
|
397
468
|
|