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 CHANGED
@@ -1,3 +1,13 @@
1
+ ## 0.6.5 (August 23, 2013)
2
+
3
+ Features:
4
+
5
+ - Added Redis adapter for persisting experiments across sessions (@fengb, #203)
6
+
7
+ Misc:
8
+
9
+ - Expand upon algorithms section in README (@swrobel, #200)
10
+
1
11
  ## 0.6.4 (August 8, 2013)
2
12
 
3
13
  Features:
data/README.mdown CHANGED
@@ -191,11 +191,11 @@ end
191
191
 
192
192
  ### Experiment Persistence
193
193
 
194
- Split comes with two built-in persistence adapters for storing users and the alternatives they've been given for each experiment.
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 an algorithm that randomly selects from possible alternatives for a traditional a/b test.
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
- An implementation of a bandit algorithm is also provided.
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 extensions metric trial experiment alternative helper version configuration persistence exceptions].each do |f|
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
 
@@ -1,4 +1,4 @@
1
- %w[session_adapter cookie_adapter].each do |f|
1
+ %w[session_adapter cookie_adapter redis_adapter].each do |f|
2
2
  require "split/persistence/#{f}"
3
3
  end
4
4
 
@@ -25,4 +25,4 @@ module Split
25
25
  Split.configuration.persistence
26
26
  end
27
27
  end
28
- end
28
+ end
@@ -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
@@ -1,6 +1,6 @@
1
1
  module Split
2
2
  MAJOR = 0
3
3
  MINOR = 6
4
- PATCH = 4
4
+ PATCH = 5
5
5
  VERSION = [MAJOR, MINOR, PATCH].join('.')
6
6
  end
@@ -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
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-08 00:00:00.000000000 Z
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