split 1.4.0 → 1.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +9 -5
- data/lib/split.rb +1 -0
- data/lib/split/configuration.rb +4 -0
- data/lib/split/experiment.rb +19 -32
- data/lib/split/goals_collection.rb +44 -0
- data/lib/split/trial.rb +1 -1
- data/lib/split/version.rb +1 -1
- data/spec/experiment_catalog_spec.rb +15 -0
- data/spec/experiment_spec.rb +43 -32
- data/spec/goals_collection_spec.rb +80 -0
- data/split.gemspec +1 -0
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07f7886845453ce92ac81c33d32927c4c0528d39
|
4
|
+
data.tar.gz: 26a46a18c8a8e743ea532be9f9e2e3c63f516f4e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1beb35f67693fa2073877c112cda7ba7abec3dfeff05699fdf13275d79ba0b424c982cc7c2811fdde826fcbced38f4e6b34d1e8db56786bf43aa7edff6d0bb92
|
7
|
+
data.tar.gz: d5956789a67b6d92d46e86e55b340900f806951a3dfea7419e1889df6844e21ef20a6777420fbb1362a8001f15c30e9114eb6020ec0de8b3bb508c8c58dfc6af
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
## 1.4.1 (April 21st, 2016)
|
2
|
+
|
3
|
+
Bugfixes:
|
4
|
+
|
5
|
+
- respect manual start configuration after an experiment has been deleted (@mtyeh411, #372)
|
6
|
+
|
7
|
+
Misc:
|
8
|
+
|
9
|
+
- Introduce goals collection to reduce complexity of Experiment#save (@pakallis, #365)
|
10
|
+
- Revise specs according to http://betterspecs.org/ (@hkliya, #369)
|
11
|
+
|
1
12
|
## 1.4.0 (April 2nd, 2016)
|
2
13
|
|
3
14
|
Features:
|
data/README.md
CHANGED
@@ -151,7 +151,7 @@ It is not required to send `SPLIT_DISABLE=false` to activate Split.
|
|
151
151
|
By default new AB tests will be active right after deployment. In case you would like to start new test a while after
|
152
152
|
the deploy, you can do it by setting the `start_manually` configuration option to `true`.
|
153
153
|
|
154
|
-
After choosing this option tests won't be started right after deploy, but after pressing the `Start` button in Split admin dashboard.
|
154
|
+
After choosing this option tests won't be started right after deploy, but after pressing the `Start` button in Split admin dashboard. If a test is deleted from the Split dashboard, then it can only be started after pressing the `Start` button whenever being re-initialized.
|
155
155
|
|
156
156
|
### Reset after completion
|
157
157
|
|
@@ -284,8 +284,12 @@ For example:
|
|
284
284
|
|
285
285
|
``` ruby
|
286
286
|
Split.configure do |config|
|
287
|
+
# after experiment reset or deleted
|
287
288
|
config.on_experiment_reset = -> (example) { # Do something on reset }
|
288
289
|
config.on_experiment_delete = -> (experiment) { # Do something else on delete }
|
290
|
+
# before experiment reset or deleted
|
291
|
+
config.on_before_experiment_reset = -> (example) { # Do something on reset }
|
292
|
+
config.on_before_experiment_delete = -> (experiment) { # Do something else on delete }
|
289
293
|
end
|
290
294
|
```
|
291
295
|
|
@@ -445,9 +449,9 @@ Split.configure do |config|
|
|
445
449
|
alternatives: ["a", "b"],
|
446
450
|
metadata: {
|
447
451
|
"a" => {"text" => "Have a fantastic day"},
|
448
|
-
"b" => {"text" => "Don't get hit by a bus"}
|
452
|
+
"b" => {"text" => "Don't get hit by a bus"}
|
449
453
|
}
|
450
|
-
}
|
454
|
+
}
|
451
455
|
}
|
452
456
|
end
|
453
457
|
```
|
@@ -493,7 +497,7 @@ Split.configure do |config|
|
|
493
497
|
config.experiments = {
|
494
498
|
my_first_experiment: {
|
495
499
|
alternatives: ["a", "b"],
|
496
|
-
metric: :my_metric
|
500
|
+
metric: :my_metric
|
497
501
|
}
|
498
502
|
}
|
499
503
|
end
|
@@ -628,7 +632,7 @@ trial.choose!
|
|
628
632
|
trial.alternative.name
|
629
633
|
|
630
634
|
# if the goal has been achieved, increment the successful completions for this alternative.
|
631
|
-
if
|
635
|
+
if goal_achieved?
|
632
636
|
trial.complete!
|
633
637
|
end
|
634
638
|
|
data/lib/split.rb
CHANGED
data/lib/split/configuration.rb
CHANGED
@@ -19,6 +19,8 @@ module Split
|
|
19
19
|
attr_accessor :on_trial_complete
|
20
20
|
attr_accessor :on_experiment_reset
|
21
21
|
attr_accessor :on_experiment_delete
|
22
|
+
attr_accessor :on_before_experiment_reset
|
23
|
+
attr_accessor :on_before_experiment_delete
|
22
24
|
attr_accessor :include_rails_helper
|
23
25
|
attr_accessor :beta_probability_simulations
|
24
26
|
attr_accessor :redis_url
|
@@ -199,6 +201,8 @@ module Split
|
|
199
201
|
@db_failover_on_db_error = proc{|error|} # e.g. use Rails logger here
|
200
202
|
@on_experiment_reset = proc{|experiment|}
|
201
203
|
@on_experiment_delete = proc{|experiment|}
|
204
|
+
@on_before_experiment_reset = proc{|experiment|}
|
205
|
+
@on_before_experiment_delete = proc{|experiment|}
|
202
206
|
@db_failover_allow_parameter_override = false
|
203
207
|
@allow_multiple_experiments = false
|
204
208
|
@enabled = true
|
data/lib/split/experiment.rb
CHANGED
@@ -23,7 +23,7 @@ module Split
|
|
23
23
|
if alternatives.empty? && (exp_config = Split.configuration.experiment_for(name))
|
24
24
|
set_alternatives_and_options(
|
25
25
|
alternatives: load_alternatives_from_configuration,
|
26
|
-
goals:
|
26
|
+
goals: Split::GoalsCollection.new(@name).load_from_configuration,
|
27
27
|
metadata: load_metadata_from_configuration,
|
28
28
|
resettable: exp_config[:resettable],
|
29
29
|
algorithm: exp_config[:algorithm]
|
@@ -60,7 +60,7 @@ module Split
|
|
60
60
|
exp_config = Split.configuration.experiment_for(name)
|
61
61
|
if exp_config
|
62
62
|
alts = load_alternatives_from_configuration
|
63
|
-
options[:goals] =
|
63
|
+
options[:goals] = Split::GoalsCollection.new(@name).load_from_configuration
|
64
64
|
options[:metadata] = load_metadata_from_configuration
|
65
65
|
options[:resettable] = exp_config[:resettable]
|
66
66
|
options[:algorithm] = exp_config[:algorithm]
|
@@ -84,21 +84,21 @@ module Split
|
|
84
84
|
Split.redis.sadd(:experiments, name)
|
85
85
|
start unless Split.configuration.start_manually
|
86
86
|
@alternatives.reverse.each {|a| Split.redis.lpush(name, a.name)}
|
87
|
-
|
87
|
+
goals_collection.save
|
88
88
|
save_metadata
|
89
89
|
Split.redis.set(metadata_key, @metadata.to_json) unless @metadata.nil?
|
90
90
|
else
|
91
91
|
existing_alternatives = load_alternatives_from_redis
|
92
|
-
existing_goals =
|
92
|
+
existing_goals = Split::GoalsCollection.new(@name).load_from_redis
|
93
93
|
existing_metadata = load_metadata_from_redis
|
94
94
|
unless existing_alternatives == @alternatives.map(&:name) && existing_goals == @goals && existing_metadata == @metadata
|
95
95
|
reset
|
96
96
|
@alternatives.each(&:delete)
|
97
|
-
|
97
|
+
goals_collection.delete
|
98
98
|
delete_metadata
|
99
99
|
Split.redis.del(@name)
|
100
100
|
@alternatives.reverse.each {|a| Split.redis.lpush(name, a.name)}
|
101
|
-
|
101
|
+
goals_collection.save
|
102
102
|
save_metadata
|
103
103
|
end
|
104
104
|
end
|
@@ -113,9 +113,7 @@ module Split
|
|
113
113
|
raise ExperimentNotFound.new("Experiment #{@name} not found")
|
114
114
|
end
|
115
115
|
@alternatives.each {|a| a.validate! }
|
116
|
-
|
117
|
-
raise ArgumentError, 'Goals must be an array'
|
118
|
-
end
|
116
|
+
goals_collection.validate!
|
119
117
|
end
|
120
118
|
|
121
119
|
def new_record?
|
@@ -241,6 +239,7 @@ module Split
|
|
241
239
|
end
|
242
240
|
|
243
241
|
def reset
|
242
|
+
Split.configuration.on_before_experiment_reset.call(self)
|
244
243
|
alternatives.each(&:reset)
|
245
244
|
reset_winner
|
246
245
|
Split.configuration.on_experiment_reset.call(self)
|
@@ -248,20 +247,20 @@ module Split
|
|
248
247
|
end
|
249
248
|
|
250
249
|
def delete
|
250
|
+
Split.configuration.on_before_experiment_delete.call(self)
|
251
|
+
if Split.configuration.start_manually
|
252
|
+
Split.redis.hdel(:experiment_start_times, @name)
|
253
|
+
end
|
251
254
|
alternatives.each(&:delete)
|
252
255
|
reset_winner
|
253
256
|
Split.redis.srem(:experiments, name)
|
254
257
|
Split.redis.del(name)
|
255
|
-
|
258
|
+
goals_collection.delete
|
256
259
|
delete_metadata
|
257
260
|
Split.configuration.on_experiment_delete.call(self)
|
258
261
|
increment_version
|
259
262
|
end
|
260
263
|
|
261
|
-
def delete_goals
|
262
|
-
Split.redis.del(goals_key)
|
263
|
-
end
|
264
|
-
|
265
264
|
def delete_metadata
|
266
265
|
Split.redis.del(metadata_key)
|
267
266
|
end
|
@@ -271,7 +270,7 @@ module Split
|
|
271
270
|
self.resettable = exp_config['resettable']
|
272
271
|
self.algorithm = exp_config['algorithm']
|
273
272
|
self.alternatives = load_alternatives_from_redis
|
274
|
-
self.goals =
|
273
|
+
self.goals = Split::GoalsCollection.new(@name).load_from_redis
|
275
274
|
self.metadata = load_metadata_from_redis
|
276
275
|
end
|
277
276
|
|
@@ -419,19 +418,6 @@ module Split
|
|
419
418
|
metadata = Split.configuration.experiment_for(@name)[:metadata]
|
420
419
|
end
|
421
420
|
|
422
|
-
def load_goals_from_configuration
|
423
|
-
goals = Split.configuration.experiment_for(@name)[:goals]
|
424
|
-
if goals.nil?
|
425
|
-
goals = []
|
426
|
-
else
|
427
|
-
goals.flatten
|
428
|
-
end
|
429
|
-
end
|
430
|
-
|
431
|
-
def load_goals_from_redis
|
432
|
-
Split.redis.lrange(goals_key, 0, -1)
|
433
|
-
end
|
434
|
-
|
435
421
|
def load_metadata_from_redis
|
436
422
|
meta = Split.redis.get(metadata_key)
|
437
423
|
JSON.parse(meta) unless meta.nil?
|
@@ -459,13 +445,14 @@ module Split
|
|
459
445
|
end
|
460
446
|
end
|
461
447
|
|
462
|
-
def save_goals
|
463
|
-
@goals.reverse.each {|a| Split.redis.lpush(goals_key, a)} unless @goals.nil?
|
464
|
-
end
|
465
|
-
|
466
448
|
def save_metadata
|
467
449
|
Split.redis.set(metadata_key, @metadata.to_json) unless @metadata.nil?
|
468
450
|
end
|
469
451
|
|
452
|
+
private
|
453
|
+
|
454
|
+
def goals_collection
|
455
|
+
Split::GoalsCollection.new(@name, @goals)
|
456
|
+
end
|
470
457
|
end
|
471
458
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Split
|
2
|
+
class GoalsCollection
|
3
|
+
|
4
|
+
def initialize(experiment_name, goals=nil)
|
5
|
+
@experiment_name = experiment_name
|
6
|
+
@goals = goals
|
7
|
+
end
|
8
|
+
|
9
|
+
def load_from_redis
|
10
|
+
Split.redis.lrange(goals_key, 0, -1)
|
11
|
+
end
|
12
|
+
|
13
|
+
def load_from_configuration
|
14
|
+
goals = Split.configuration.experiment_for(@experiment_name)[:goals]
|
15
|
+
|
16
|
+
if goals.nil?
|
17
|
+
goals = []
|
18
|
+
else
|
19
|
+
goals.flatten
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def save
|
24
|
+
return false if @goals.nil?
|
25
|
+
@goals.reverse.each { |goal| Split.redis.lpush(goals_key, goal) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def validate!
|
29
|
+
unless @goals.nil? || @goals.kind_of?(Array)
|
30
|
+
raise ArgumentError, 'Goals must be an array'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete
|
35
|
+
Split.redis.del(goals_key)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def goals_key
|
41
|
+
"#{@experiment_name}:goals"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/split/trial.rb
CHANGED
@@ -55,7 +55,7 @@ module Split
|
|
55
55
|
|
56
56
|
if @options[:override]
|
57
57
|
self.alternative = @options[:override]
|
58
|
-
elsif @options[:disabled] ||
|
58
|
+
elsif @options[:disabled] || Split.configuration.disabled?
|
59
59
|
self.alternative = @experiment.control
|
60
60
|
elsif @experiment.has_winner?
|
61
61
|
self.alternative = @experiment.winner
|
data/lib/split/version.rb
CHANGED
@@ -35,4 +35,19 @@ describe Split::ExperimentCatalog do
|
|
35
35
|
expect(subject.find_or_create('my_exp', 'control me').control.to_s).to eq('control me')
|
36
36
|
end
|
37
37
|
end
|
38
|
+
|
39
|
+
describe '.find' do
|
40
|
+
it "should return an existing experiment" do
|
41
|
+
experiment = Split::Experiment.new('basket_text', alternatives: ['blue', 'red', 'green'])
|
42
|
+
experiment.save
|
43
|
+
|
44
|
+
experiment = subject.find('basket_text')
|
45
|
+
|
46
|
+
expect(experiment.name).to eq('basket_text')
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should return nil if experiment not exist" do
|
50
|
+
expect(subject.find('non_existent_experiment')).to be_nil
|
51
|
+
end
|
52
|
+
end
|
38
53
|
end
|
data/spec/experiment_spec.rb
CHANGED
@@ -104,18 +104,6 @@ describe Split::Experiment do
|
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
107
|
-
describe 'find' do
|
108
|
-
it "should return an existing experiment" do
|
109
|
-
experiment.save
|
110
|
-
experiment = Split::ExperimentCatalog.find('basket_text')
|
111
|
-
expect(experiment.name).to eq('basket_text')
|
112
|
-
end
|
113
|
-
|
114
|
-
it "should return an existing experiment" do
|
115
|
-
expect(Split::ExperimentCatalog.find('non_existent_experiment')).to be_nil
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
107
|
describe 'control' do
|
120
108
|
it 'should be the first alternative' do
|
121
109
|
experiment.save
|
@@ -210,6 +198,18 @@ describe Split::Experiment do
|
|
210
198
|
expect(Split.configuration.on_experiment_delete).to receive(:call)
|
211
199
|
experiment.delete
|
212
200
|
end
|
201
|
+
|
202
|
+
it "should call the on_before_experiment_delete hook" do
|
203
|
+
expect(Split.configuration.on_before_experiment_delete).to receive(:call)
|
204
|
+
experiment.delete
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'should reset the start time if the experiment should be manually started' do
|
208
|
+
Split.configuration.start_manually = true
|
209
|
+
experiment.start
|
210
|
+
experiment.delete
|
211
|
+
expect(experiment.start_time).to be_nil
|
212
|
+
end
|
213
213
|
end
|
214
214
|
|
215
215
|
|
@@ -276,6 +276,11 @@ describe Split::Experiment do
|
|
276
276
|
expect(Split.configuration.on_experiment_reset).to receive(:call)
|
277
277
|
experiment.reset
|
278
278
|
end
|
279
|
+
|
280
|
+
it "should call the on_before_experiment_reset hook" do
|
281
|
+
expect(Split.configuration.on_before_experiment_reset).to receive(:call)
|
282
|
+
experiment.reset
|
283
|
+
end
|
279
284
|
end
|
280
285
|
|
281
286
|
describe 'algorithm' do
|
@@ -291,31 +296,38 @@ describe Split::Experiment do
|
|
291
296
|
end
|
292
297
|
end
|
293
298
|
|
294
|
-
describe 'next_alternative' do
|
295
|
-
|
299
|
+
describe '#next_alternative' do
|
300
|
+
context 'with multiple alternatives' do
|
301
|
+
let(:experiment) { Split::ExperimentCatalog.find_or_create('link_color', 'blue', 'red', 'green') }
|
296
302
|
|
297
|
-
|
298
|
-
|
299
|
-
|
303
|
+
context 'with winner' do
|
304
|
+
it "should always return the winner" do
|
305
|
+
green = Split::Alternative.new('green', 'link_color')
|
306
|
+
experiment.winner = 'green'
|
300
307
|
|
301
|
-
|
302
|
-
|
308
|
+
expect(experiment.next_alternative.name).to eq('green')
|
309
|
+
green.increment_participation
|
303
310
|
|
304
|
-
|
305
|
-
|
311
|
+
expect(experiment.next_alternative.name).to eq('green')
|
312
|
+
end
|
313
|
+
end
|
306
314
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
315
|
+
context 'without winner' do
|
316
|
+
it "should use the specified algorithm" do
|
317
|
+
experiment.algorithm = Split::Algorithms::Whiplash
|
318
|
+
expect(experiment.algorithm).to receive(:choose_alternative).and_return(Split::Alternative.new('green', 'link_color'))
|
319
|
+
expect(experiment.next_alternative.name).to eq('green')
|
320
|
+
end
|
321
|
+
end
|
311
322
|
end
|
312
|
-
end
|
313
323
|
|
314
|
-
|
315
|
-
|
324
|
+
context 'with single alternative' do
|
325
|
+
let(:experiment) { Split::ExperimentCatalog.find_or_create('link_color', 'blue') }
|
316
326
|
|
317
|
-
|
318
|
-
|
327
|
+
it "should always return the only alternative" do
|
328
|
+
expect(experiment.next_alternative.name).to eq('blue')
|
329
|
+
expect(experiment.next_alternative.name).to eq('blue')
|
330
|
+
end
|
319
331
|
end
|
320
332
|
end
|
321
333
|
|
@@ -432,8 +444,7 @@ describe Split::Experiment do
|
|
432
444
|
|
433
445
|
it "should return nil and not re-calculate probabilities if they have already been calculated today" do
|
434
446
|
experiment = Split::ExperimentCatalog.find_or_create({'link_color3' => ["purchase", "refund"]}, 'blue', 'red', 'green')
|
435
|
-
|
436
|
-
experiment.calc_time = experiment_calc_time
|
447
|
+
expect(experiment.calc_winning_alternatives).not_to be nil
|
437
448
|
expect(experiment.calc_winning_alternatives).to be nil
|
438
449
|
end
|
439
450
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'split/goals_collection'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
describe Split::GoalsCollection do
|
6
|
+
let(:experiment_name) { 'experiment_name' }
|
7
|
+
|
8
|
+
describe 'initialization' do
|
9
|
+
let(:goals_collection) {
|
10
|
+
Split::GoalsCollection.new('experiment_name', ['goal1', 'goal2'])
|
11
|
+
}
|
12
|
+
|
13
|
+
it "should have an experiment_name" do
|
14
|
+
expect(goals_collection.instance_variable_get(:@experiment_name)).
|
15
|
+
to eq('experiment_name')
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should have a list of goals" do
|
19
|
+
expect(goals_collection.instance_variable_get(:@goals)).
|
20
|
+
to eq(['goal1', 'goal2'])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#validate!" do
|
25
|
+
it "should't raise ArgumentError if @goals is nil?" do
|
26
|
+
goals_collection = Split::GoalsCollection.new('experiment_name')
|
27
|
+
expect { goals_collection.validate! }.not_to raise_error(ArgumentError)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should raise ArgumentError if @goals is not an Array" do
|
31
|
+
goals_collection = Split::GoalsCollection.
|
32
|
+
new('experiment_name', 'not an array')
|
33
|
+
expect { goals_collection.validate! }.to raise_error(ArgumentError)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should't raise ArgumentError if @goals is an array" do
|
37
|
+
goals_collection = Split::GoalsCollection.
|
38
|
+
new('experiment_name', ['an array'])
|
39
|
+
expect { goals_collection.validate! }.not_to raise_error(ArgumentError)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#delete" do
|
44
|
+
let(:goals_key) { "#{experiment_name}:goals" }
|
45
|
+
|
46
|
+
it "should delete goals from redis" do
|
47
|
+
goals_collection = Split::GoalsCollection.new(experiment_name, ['goal1'])
|
48
|
+
goals_collection.save
|
49
|
+
|
50
|
+
goals_collection.delete
|
51
|
+
expect(Split.redis.exists(goals_key)).to be false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#save" do
|
56
|
+
let(:goals_key) { "#{experiment_name}:goals" }
|
57
|
+
|
58
|
+
it "should return false if @goals is nil" do
|
59
|
+
goals_collection = Split::GoalsCollection.
|
60
|
+
new(experiment_name, nil)
|
61
|
+
|
62
|
+
expect(goals_collection.save).to be false
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should save goals to redis if @goals is valid" do
|
66
|
+
goals = ['valid goal 1', 'valid goal 2']
|
67
|
+
collection = Split::GoalsCollection.new(experiment_name, goals)
|
68
|
+
collection.save
|
69
|
+
|
70
|
+
expect(Split.redis.lrange(goals_key, 0, -1)).to eq goals
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should return @goals if @goals is valid" do
|
74
|
+
goals_collection = Split::GoalsCollection.
|
75
|
+
new(experiment_name, ['valid goal'])
|
76
|
+
|
77
|
+
expect(goals_collection.save).to eq(['valid goal'])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/split.gemspec
CHANGED
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: 1.4.
|
4
|
+
version: 1.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Nesbitt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-04-
|
11
|
+
date: 2016-04-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -136,6 +136,20 @@ dependencies:
|
|
136
136
|
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '3.4'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: pry
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0.10'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0.10'
|
139
153
|
description:
|
140
154
|
email:
|
141
155
|
- andrewnez@gmail.com
|
@@ -181,6 +195,7 @@ files:
|
|
181
195
|
- lib/split/extensions.rb
|
182
196
|
- lib/split/extensions/array.rb
|
183
197
|
- lib/split/extensions/string.rb
|
198
|
+
- lib/split/goals_collection.rb
|
184
199
|
- lib/split/helper.rb
|
185
200
|
- lib/split/metric.rb
|
186
201
|
- lib/split/persistence.rb
|
@@ -199,6 +214,7 @@ files:
|
|
199
214
|
- spec/encapsulated_helper_spec.rb
|
200
215
|
- spec/experiment_catalog_spec.rb
|
201
216
|
- spec/experiment_spec.rb
|
217
|
+
- spec/goals_collection_spec.rb
|
202
218
|
- spec/helper_spec.rb
|
203
219
|
- spec/metric_spec.rb
|
204
220
|
- spec/persistence/cookie_adapter_spec.rb
|
@@ -243,6 +259,7 @@ test_files:
|
|
243
259
|
- spec/encapsulated_helper_spec.rb
|
244
260
|
- spec/experiment_catalog_spec.rb
|
245
261
|
- spec/experiment_spec.rb
|
262
|
+
- spec/goals_collection_spec.rb
|
246
263
|
- spec/helper_spec.rb
|
247
264
|
- spec/metric_spec.rb
|
248
265
|
- spec/persistence/cookie_adapter_spec.rb
|