ab 0.0.1 → 0.0.2
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 +7 -0
- data/README.md +117 -5
- data/lib/ab/chance.rb +15 -1
- data/lib/ab/indexer.rb +1 -1
- data/lib/ab/version.rb +1 -1
- data/spec/ab_test_spec.rb +0 -1
- data/spec/tester_spec.rb +29 -1
- metadata +10 -14
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0a6dc32389b41a3d6dedd61190c68cab39bff8cc
|
4
|
+
data.tar.gz: 48dc84a73213f270443884816a6e0cdb6213a5f7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ce5b523e9213a127e8b84339f0c9672175dfb82c2e37ecea1c19289b647bba9c22d43e80023dbb82198cd14ccb8506c3e936361557eab497c389090777239f04
|
7
|
+
data.tar.gz: d6b4f577c2ff9aaf7c205c0f66157c37f4bb690c1e885bf1613905b602e29cd753fe7d1a08a45d167d7939a3ce337a9d16c977d8cc32ce92674aa879532e9ecc
|
data/README.md
CHANGED
@@ -1,12 +1,89 @@
|
|
1
|
-
#
|
1
|
+
# AB
|
2
2
|
|
3
|
-
|
3
|
+
This gem provides you with a way to do AB testing in a deterministic way
|
4
|
+
without the need for Redis or any other service.
|
5
|
+
|
6
|
+
** NOTICE **
|
7
|
+
|
8
|
+
*The gem is in very early development*
|
9
|
+
|
10
|
+
Since other gems need all kinds of extra services to work, and we didn't want
|
11
|
+
any extra dependencies, we created a gem that will serve your users an A/B/C or
|
12
|
+
whatever version based on for example a user-id
|
13
|
+
|
14
|
+
You can then use e.g. a tool like Mixpanel to track which user saw what, and
|
15
|
+
make your AB testing decisions.
|
16
|
+
|
17
|
+
The gem works in a Rails and non rails environment. It's tested on ruby 1.8.7, 1.9.3, 2.1.5, 2.2.2, jruby-1.7.8 and rubinius-2.4.0.
|
18
|
+
|
19
|
+
# Examples
|
20
|
+
|
21
|
+
## Simple view usage
|
22
|
+
In your view (haml):
|
23
|
+
|
24
|
+
```haml
|
25
|
+
-ab(:options=>["dude","your royal highness"], :chances=>"20/80",:name=>"greeting").for(@current_user.id) do |option|
|
26
|
+
="Well hello #{option}"
|
27
|
+
```
|
28
|
+
|
29
|
+
## Defining tests in a controller
|
30
|
+
You can also create a tester, for example in your ```app/controllers/application_controller.rb```
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
class ApplicationController
|
34
|
+
before_filter :ab_tests
|
35
|
+
|
36
|
+
def ab_tests
|
37
|
+
@greeting_test = Ab::Tester.new(:options=>["Yo", "Hello"], :chances=>"50/50", :name=>"greeter")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
Then in your view (haml style):
|
43
|
+
|
44
|
+
```haml
|
45
|
+
-@greeting_test.for(@current_user) do |option|
|
46
|
+
="#{option} Bro!"
|
47
|
+
```
|
48
|
+
|
49
|
+
## Using a Tracker
|
50
|
+
|
51
|
+
Let's say you have a hypothetical MixPanel Tracker you want to use for AB
|
52
|
+
testing. You can for example implement it like this:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
class ApplicationController
|
56
|
+
before_filter :ab_tests
|
57
|
+
after_filter :track_ab_choices
|
58
|
+
|
59
|
+
def ab_tests
|
60
|
+
@greeting_test = Ab::Tester.new(:options=>["Yo", "Hello"], :chances=>"50/50", :name=>"greeter")
|
61
|
+
end
|
62
|
+
|
63
|
+
def track_ab_choices
|
64
|
+
MixpanelTracker.track(@current_user, "experiment:greeting", :version=>@greeting_test.call(@current_user.id))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
## What a chance!
|
70
|
+
|
71
|
+
The chances option of the ab view helper as well as in ```Ab::Tester``` takes a
|
72
|
+
string as an argument. With a format using ```/```. It normalized the chances
|
73
|
+
afterwards, so the following chances result in the exact same chance
|
74
|
+
distribution:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
Ab::Tester.new(:chances=>"10/60/30")
|
78
|
+
Ab::Tester.new(:chances=>"1/6/3")
|
79
|
+
Ab::Tester.new(:chances=>"35/210/105")
|
80
|
+
```
|
4
81
|
|
5
82
|
## Installation
|
6
83
|
|
7
84
|
Add this line to your application's Gemfile:
|
8
85
|
|
9
|
-
gem '
|
86
|
+
gem 'ab'
|
10
87
|
|
11
88
|
And then execute:
|
12
89
|
|
@@ -14,11 +91,46 @@ And then execute:
|
|
14
91
|
|
15
92
|
Or install it yourself as:
|
16
93
|
|
17
|
-
$ gem install
|
94
|
+
$ gem install ab
|
18
95
|
|
19
96
|
## Usage
|
20
97
|
|
21
|
-
|
98
|
+
The main class is the ```Ab::Tester``` class which takes several options as arguments:
|
99
|
+
|
100
|
+
## General Usage:
|
101
|
+
```ruby
|
102
|
+
tester = Ab::Tester.new(:name=>"test_name", :options=>[true, false], :chances=>"50/50")
|
103
|
+
tester.for(user.id) # Will return true or false, depending on the user.id
|
104
|
+
```
|
105
|
+
|
106
|
+
```Ab::Tester``` options:
|
107
|
+
|
108
|
+
* ```name```: Name of the tester, used as a seed to the random number generator
|
109
|
+
* ```options```: (required) Array of options to choose from in the test, the options can be anything
|
110
|
+
* ```chances```: (required) String representing the chances of the options e.g. ```"10/20/70"```
|
111
|
+
* ```indexer```: A class that responds a class level ```call(opts={})``` which contains the logic on how to get from a value, seed and chances to a choice. See the source code of the basic Indexer.
|
112
|
+
|
113
|
+
### If you use rails
|
114
|
+
|
115
|
+
You can also use the view helper ```ab``` with the exact same options as above
|
116
|
+
|
117
|
+
option 1: using the output directy
|
118
|
+
|
119
|
+
```erb
|
120
|
+
Hello there <%=ab(:options=>["stranger", "you"], :chances=>"80/20").for(@current_user.id) %>
|
121
|
+
```
|
122
|
+
|
123
|
+
option 2: using a block:
|
124
|
+
|
125
|
+
```erb
|
126
|
+
<%=ab(:options=>[:stranger, :you], :chances=>"80/20").for(@current_user.id) do |option| %>
|
127
|
+
<% if option == :stranger %>
|
128
|
+
Hello there stranger!
|
129
|
+
<% elsif options == :you %>
|
130
|
+
Could you please introduce yourself?
|
131
|
+
<% end %>
|
132
|
+
<% end %>
|
133
|
+
```
|
22
134
|
|
23
135
|
## Contributing
|
24
136
|
|
data/lib/ab/chance.rb
CHANGED
@@ -12,7 +12,21 @@ module Ab
|
|
12
12
|
def self.normalize(array)
|
13
13
|
sum = array.reduce(&:+)
|
14
14
|
factor = 100.to_f / sum
|
15
|
-
array.map{|e| e * factor}.map(&:to_i)
|
15
|
+
normalized_chances = array.map{|e| e * factor}.map(&:to_i)
|
16
|
+
normalized_sum = normalized_chances.reduce(&:+)
|
17
|
+
|
18
|
+
# Check if sum of chances is 100.
|
19
|
+
# If it is not, add +1 to each chance (from last to first)
|
20
|
+
# until it reaches 100.
|
21
|
+
unless normalized_sum == 100
|
22
|
+
index = array.count - 1
|
23
|
+
(100 - normalized_sum).times do
|
24
|
+
normalized_chances[index] += 1
|
25
|
+
index = index < 0 ? array.count - 1 : index - 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
return normalized_chances
|
16
30
|
end
|
17
31
|
|
18
32
|
def self.split_chance(string)
|
data/lib/ab/indexer.rb
CHANGED
data/lib/ab/version.rb
CHANGED
data/spec/ab_test_spec.rb
CHANGED
data/spec/tester_spec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
2
3
|
|
3
4
|
describe Ab::Tester do
|
4
5
|
|
@@ -37,6 +38,12 @@ describe Ab::Tester do
|
|
37
38
|
Tester.new(:chances=>"0/1/0", :options=>[1,2,3]).call(1235).should eql(2)
|
38
39
|
Tester.new(:chances=>"0/0/1", :options=>[1,2,3]).call(1235).should eql(3)
|
39
40
|
end
|
41
|
+
|
42
|
+
it "returns same result for same input" do
|
43
|
+
100.times do
|
44
|
+
Tester.new(:name => "Test", :options=>[1,2,3], :chances => "1/1/1").call(54321).should eql(3)
|
45
|
+
end
|
46
|
+
end
|
40
47
|
|
41
48
|
it "should take a block and provide the result to the block" do
|
42
49
|
expect do |b|
|
@@ -44,5 +51,26 @@ describe Ab::Tester do
|
|
44
51
|
.call(1235, &b)
|
45
52
|
end.to yield_with_args(1)
|
46
53
|
end
|
47
|
-
|
54
|
+
|
55
|
+
context 'distribution of results' do
|
56
|
+
it 'distributes almost equally in 1000 iterations 50/50 chance' do
|
57
|
+
result = []
|
58
|
+
1000.times do
|
59
|
+
random = SecureRandom.random_number(1000000000)
|
60
|
+
result << Tester.new(:name => "Test", :chances => "1/1").call(random)
|
61
|
+
end
|
62
|
+
|
63
|
+
result.count(true).should be_within(50).of(500)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'distributes almost equally in 1000 iterations 33/33/33 chance' do
|
67
|
+
result = []
|
68
|
+
1000.times do
|
69
|
+
random = SecureRandom.random_number(1000000000)
|
70
|
+
result << Tester.new(:name => "Test", :options=>[1,2,3], :chances => "1/1/1").call(random)
|
71
|
+
end
|
72
|
+
|
73
|
+
result.count(1).should be_within(50).of(333)
|
74
|
+
end
|
75
|
+
end
|
48
76
|
end
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ab
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.2
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- sparkboxx
|
@@ -10,22 +9,20 @@ authors:
|
|
10
9
|
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date:
|
12
|
+
date: 2015-04-19 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: rspec
|
17
16
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
17
|
requirements:
|
20
|
-
- -
|
18
|
+
- - ">="
|
21
19
|
- !ruby/object:Gem::Version
|
22
20
|
version: '0'
|
23
21
|
type: :development
|
24
22
|
prerelease: false
|
25
23
|
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
24
|
requirements:
|
28
|
-
- -
|
25
|
+
- - ">="
|
29
26
|
- !ruby/object:Gem::Version
|
30
27
|
version: '0'
|
31
28
|
description: Basic AB testing gem
|
@@ -36,7 +33,7 @@ executables: []
|
|
36
33
|
extensions: []
|
37
34
|
extra_rdoc_files: []
|
38
35
|
files:
|
39
|
-
- .gitignore
|
36
|
+
- ".gitignore"
|
40
37
|
- Gemfile
|
41
38
|
- LICENSE.txt
|
42
39
|
- README.md
|
@@ -56,27 +53,26 @@ files:
|
|
56
53
|
- spec/tester_spec.rb
|
57
54
|
homepage: http://github.com/olery/ab
|
58
55
|
licenses: []
|
56
|
+
metadata: {}
|
59
57
|
post_install_message:
|
60
58
|
rdoc_options: []
|
61
59
|
require_paths:
|
62
60
|
- lib
|
63
61
|
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
-
none: false
|
65
62
|
requirements:
|
66
|
-
- -
|
63
|
+
- - ">="
|
67
64
|
- !ruby/object:Gem::Version
|
68
65
|
version: '0'
|
69
66
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
-
none: false
|
71
67
|
requirements:
|
72
|
-
- -
|
68
|
+
- - ">="
|
73
69
|
- !ruby/object:Gem::Version
|
74
70
|
version: '0'
|
75
71
|
requirements: []
|
76
72
|
rubyforge_project:
|
77
|
-
rubygems_version:
|
73
|
+
rubygems_version: 2.4.5
|
78
74
|
signing_key:
|
79
|
-
specification_version:
|
75
|
+
specification_version: 4
|
80
76
|
summary: The gem provides a basic class Tester or view helper function ab that allows
|
81
77
|
you to deterministically show a certain option (a/b/c/etc) to a user based on a
|
82
78
|
certain chance (e.g. 1/5)
|