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.
@@ -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)