split 0.6.4 → 0.6.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.mdown +10 -0
- data/README.mdown +31 -4
- data/lib/split.rb +11 -1
- data/lib/split/persistence.rb +2 -2
- data/lib/split/persistence/redis_adapter.rb +52 -0
- data/lib/split/version.rb +1 -1
- data/spec/persistence/redis_adapter_spec.rb +81 -0
- data/split.gemspec +1 -0
- metadata +7 -3
data/CHANGELOG.mdown
CHANGED
data/README.mdown
CHANGED
@@ -191,11 +191,11 @@ end
|
|
191
191
|
|
192
192
|
### Experiment Persistence
|
193
193
|
|
194
|
-
Split comes with
|
194
|
+
Split comes with three built-in persistence adapters for storing users and the alternatives they've been given for each experiment.
|
195
195
|
|
196
196
|
By default Split will store the tests for each user in the session.
|
197
197
|
|
198
|
-
You can optionally configure Split to use a cookie or any custom adapter of your choosing.
|
198
|
+
You can optionally configure Split to use a cookie, Redis, or any custom adapter of your choosing.
|
199
199
|
|
200
200
|
#### Cookies
|
201
201
|
|
@@ -207,6 +207,22 @@ end
|
|
207
207
|
|
208
208
|
__Note:__ Using cookies depends on `ActionDispatch::Cookies` or any identical API
|
209
209
|
|
210
|
+
#### Redis
|
211
|
+
|
212
|
+
Using Redis will allow ab_users to persist across sessions or machines.
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
Split.configure do |config|
|
216
|
+
config.persistence = Split::Persistence::RedisAdapter.with_config(:lookup_by => proc { |context| context.current_user_id }
|
217
|
+
# Equivalent
|
218
|
+
# config.persistence = Split::Persistence::RedisAdapter.with_config(:lookup_by => :current_user_id }
|
219
|
+
end
|
220
|
+
```
|
221
|
+
|
222
|
+
Options:
|
223
|
+
* `lookup_by`: method to invoke per request for uniquely identifying ab_users (mandatory configuration)
|
224
|
+
* `namespace`: separate namespace to store these persisted values (default "persistence")
|
225
|
+
|
210
226
|
#### Custom Adapter
|
211
227
|
|
212
228
|
Your custom adapter needs to implement the same API as existing adapters.
|
@@ -554,12 +570,23 @@ end
|
|
554
570
|
|
555
571
|
## Algorithms
|
556
572
|
|
557
|
-
By default, Split ships with
|
573
|
+
By default, Split ships with `Split::Algorithms::WeightedSample` that randomly selects from possible alternatives for a traditional a/b test.
|
574
|
+
It is possible to specify static weights to favor certain alternatives.
|
558
575
|
|
559
|
-
|
576
|
+
`Split::Algorithms::Whiplash` is an implementation of a [multi-armed bandit algorithm](http://stevehanov.ca/blog/index.php?id=132).
|
577
|
+
This algorithm will automatically weight the alternatives based on their relative performance,
|
578
|
+
choosing the better-performing ones more often as trials are completed.
|
560
579
|
|
561
580
|
Users may also write their own algorithms. The default algorithm may be specified globally in the configuration file, or on a per experiment basis using the experiments hash of the configuration file.
|
562
581
|
|
582
|
+
To change the algorithm globally for all experiments, use the following in your initializer:
|
583
|
+
|
584
|
+
```ruby
|
585
|
+
Split.configure do |config|
|
586
|
+
config.algorithm = Split::Algorithms::Whiplash
|
587
|
+
end
|
588
|
+
```
|
589
|
+
|
563
590
|
## Extensions
|
564
591
|
|
565
592
|
- [Split::Export](http://github.com/andrew/split-export) - easily export ab test data out of Split
|
data/lib/split.rb
CHANGED
@@ -1,4 +1,14 @@
|
|
1
|
-
%w[algorithms
|
1
|
+
%w[algorithms
|
2
|
+
alternative
|
3
|
+
configuration
|
4
|
+
exceptions
|
5
|
+
experiment
|
6
|
+
extensions
|
7
|
+
helper
|
8
|
+
metric
|
9
|
+
persistence
|
10
|
+
trial
|
11
|
+
version].each do |f|
|
2
12
|
require "split/#{f}"
|
3
13
|
end
|
4
14
|
|
data/lib/split/persistence.rb
CHANGED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Split
|
2
|
+
module Persistence
|
3
|
+
class RedisAdapter
|
4
|
+
DEFAULT_CONFIG = {:namespace => 'persistence'}.freeze
|
5
|
+
|
6
|
+
attr_reader :redis_key
|
7
|
+
|
8
|
+
def initialize(context)
|
9
|
+
if lookup_by = self.class.config[:lookup_by]
|
10
|
+
if lookup_by.respond_to?(:call)
|
11
|
+
key_frag = lookup_by.call(context)
|
12
|
+
else
|
13
|
+
key_frag = context.send(lookup_by)
|
14
|
+
end
|
15
|
+
@redis_key = "#{self.class.config[:namespace]}:#{key_frag}"
|
16
|
+
else
|
17
|
+
raise "Please configure lookup_by"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](field)
|
22
|
+
Split.redis.hget(redis_key, field)
|
23
|
+
end
|
24
|
+
|
25
|
+
def []=(field, value)
|
26
|
+
Split.redis.hset(redis_key, field, value)
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete(field)
|
30
|
+
Split.redis.hdel(redis_key, field)
|
31
|
+
end
|
32
|
+
|
33
|
+
def keys
|
34
|
+
Split.redis.hkeys(redis_key)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.with_config(options={})
|
38
|
+
self.config.merge!(options)
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.config
|
43
|
+
@config ||= DEFAULT_CONFIG.dup
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.reset_config!
|
47
|
+
@config = DEFAULT_CONFIG.dup
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/split/version.rb
CHANGED
@@ -0,0 +1,81 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Split::Persistence::RedisAdapter do
|
4
|
+
|
5
|
+
let(:context) { double(:lookup => 'blah') }
|
6
|
+
|
7
|
+
subject { Split::Persistence::RedisAdapter.new(context) }
|
8
|
+
|
9
|
+
describe '#redis_key' do
|
10
|
+
before { Split::Persistence::RedisAdapter.reset_config! }
|
11
|
+
|
12
|
+
context 'default' do
|
13
|
+
it 'should raise error with prompt to set lookup_by' do
|
14
|
+
expect{Split::Persistence::RedisAdapter.new(context)
|
15
|
+
}.to raise_error
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'config with lookup_by = proc { "block" }' do
|
20
|
+
before { Split::Persistence::RedisAdapter.with_config(:lookup_by => proc{'block'}) }
|
21
|
+
|
22
|
+
it 'should be "persistence:block"' do
|
23
|
+
subject.redis_key.should == 'persistence:block'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'config with lookup_by = proc { |context| context.test }' do
|
28
|
+
before { Split::Persistence::RedisAdapter.with_config(:lookup_by => proc{'block'}) }
|
29
|
+
let(:context) { double(:test => 'block') }
|
30
|
+
|
31
|
+
it 'should be "persistence:block"' do
|
32
|
+
subject.redis_key.should == 'persistence:block'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'config with lookup_by = "method_name"' do
|
37
|
+
before { Split::Persistence::RedisAdapter.with_config(:lookup_by => 'method_name') }
|
38
|
+
let(:context) { double(:method_name => 'val') }
|
39
|
+
|
40
|
+
it 'should be "persistence:bar"' do
|
41
|
+
subject.redis_key.should == 'persistence:val'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'config with namespace and lookup_by' do
|
46
|
+
before { Split::Persistence::RedisAdapter.with_config(:lookup_by => proc{'frag'}, :namespace => 'namer') }
|
47
|
+
|
48
|
+
it 'should be "namer"' do
|
49
|
+
subject.redis_key.should == 'namer:frag'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'functional tests' do
|
55
|
+
before { Split::Persistence::RedisAdapter.with_config(:lookup_by => 'lookup') }
|
56
|
+
|
57
|
+
describe "#[] and #[]=" do
|
58
|
+
it "should set and return the value for given key" do
|
59
|
+
subject["my_key"] = "my_value"
|
60
|
+
subject["my_key"].should eq("my_value")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#delete" do
|
65
|
+
it "should delete the given key" do
|
66
|
+
subject["my_key"] = "my_value"
|
67
|
+
subject.delete("my_key")
|
68
|
+
subject["my_key"].should be_nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "#keys" do
|
73
|
+
it "should return an array of the user's stored keys" do
|
74
|
+
subject["my_key"] = "my_value"
|
75
|
+
subject["my_second_key"] = "my_second_value"
|
76
|
+
subject.keys.should =~ ["my_key", "my_second_key"]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
data/split.gemspec
CHANGED
@@ -7,6 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = Split::VERSION
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
9
|
s.authors = ["Andrew Nesbitt"]
|
10
|
+
s.licenses = ['MIT']
|
10
11
|
s.email = ["andrewnez@gmail.com"]
|
11
12
|
s.homepage = "https://github.com/andrew/split"
|
12
13
|
s.summary = %q{Rack based split testing framework}
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: split
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-08-
|
12
|
+
date: 2013-08-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
@@ -204,6 +204,7 @@ files:
|
|
204
204
|
- lib/split/metric.rb
|
205
205
|
- lib/split/persistence.rb
|
206
206
|
- lib/split/persistence/cookie_adapter.rb
|
207
|
+
- lib/split/persistence/redis_adapter.rb
|
207
208
|
- lib/split/persistence/session_adapter.rb
|
208
209
|
- lib/split/trial.rb
|
209
210
|
- lib/split/version.rb
|
@@ -217,6 +218,7 @@ files:
|
|
217
218
|
- spec/helper_spec.rb
|
218
219
|
- spec/metric_spec.rb
|
219
220
|
- spec/persistence/cookie_adapter_spec.rb
|
221
|
+
- spec/persistence/redis_adapter_spec.rb
|
220
222
|
- spec/persistence/session_adapter_spec.rb
|
221
223
|
- spec/persistence_spec.rb
|
222
224
|
- spec/spec_helper.rb
|
@@ -224,7 +226,8 @@ files:
|
|
224
226
|
- spec/trial_spec.rb
|
225
227
|
- split.gemspec
|
226
228
|
homepage: https://github.com/andrew/split
|
227
|
-
licenses:
|
229
|
+
licenses:
|
230
|
+
- MIT
|
228
231
|
post_install_message:
|
229
232
|
rdoc_options: []
|
230
233
|
require_paths:
|
@@ -258,6 +261,7 @@ test_files:
|
|
258
261
|
- spec/helper_spec.rb
|
259
262
|
- spec/metric_spec.rb
|
260
263
|
- spec/persistence/cookie_adapter_spec.rb
|
264
|
+
- spec/persistence/redis_adapter_spec.rb
|
261
265
|
- spec/persistence/session_adapter_spec.rb
|
262
266
|
- spec/persistence_spec.rb
|
263
267
|
- spec/spec_helper.rb
|