ab 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # OleryAb
1
+ # AB
2
2
 
3
- TODO: Write a gem description
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 'olery_ab'
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 olery_ab
94
+ $ gem install ab
18
95
 
19
96
  ## Usage
20
97
 
21
- TODO: Write usage instructions here
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
 
@@ -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)
@@ -7,7 +7,7 @@ module Ab
7
7
  chances = opts[:chances]
8
8
  salt = opts[:seed].to_s
9
9
 
10
- seed = Digest::SHA1.hexdigest(salt + value).to_i
10
+ seed = Digest::SHA1.hexdigest(salt + value).to_i(16)
11
11
  random_number = Randomizer.new(seed).rand(1..100)
12
12
 
13
13
  sum = 0
@@ -1,3 +1,3 @@
1
1
  module Ab
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -15,6 +15,5 @@ describe "Helper Method" do
15
15
  it "should take multiple options" do
16
16
  expect{view.ab(:options=>[1,2,3], :chance=>"1/2/2", :input=>50)}.to_not raise_error
17
17
  end
18
-
19
18
  end
20
19
 
@@ -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.1
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: 2013-02-21 00:00:00.000000000 Z
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: 1.8.24
73
+ rubygems_version: 2.4.5
78
74
  signing_key:
79
- specification_version: 3
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)