vinted-ab 0.0.3 → 0.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3566b6e9c6dc50f3ca1e435644b7e3bb1eab9fd1
4
- data.tar.gz: 062d9118532a050d1155981c7ca4633911365421
3
+ metadata.gz: 45ab4518594015ccbca8ebd4d441f835e103e22c
4
+ data.tar.gz: 80fa73e5144373f439208f7773b82c76c4949de4
5
5
  SHA512:
6
- metadata.gz: 3a23e68efe7ebd7e8442f088feb526f1718338b4b41e94375de361deca94b865ad526634a4fd267999b6aebed3d4729d419a7a0a1bbc6159480e38e2b4516e42
7
- data.tar.gz: 5f40b68ced5a8adf1f9eed2a34066e68ca94d7716419694bcba22004b3df738509a079d9eb37aeb3987bfecafd279c40114c7dbb7e03c8d9813acf89e90ec903
6
+ metadata.gz: 467ebec6807d536330430ebd111cff5fcb7d43935b43fa01866d370d61a5db9afa934b662836d5c025cd44395737270a1d029209ed4b5a8a01c58b953530f58c
7
+ data.tar.gz: a6a561c9a611031fd09d75da86ad36d6b3714fed4980e46cdae5a863aa1c7c45a25c48135134a5e87a1f183dd78e847c07cb0f45def7bf2495ad136c6ef918fa
data/README.md CHANGED
@@ -82,11 +82,11 @@ For this lib to work, it requires a configuration json, which looks like this:
82
82
 
83
83
  ```ruby
84
84
  configuration = retrieve_from_svc_abs
85
- ab = Ab::Experiments.new(configuration, user_id)
85
+ ab = Ab::Tests.new(configuration, user_id)
86
86
 
87
87
  # defining callbacks
88
- Ab::Experiments.before_picking_variant { |experiment| puts 'magic' }
89
- Ab::Experiments.after_picking_variant { |experiment, variant| puts "#{variant_name}" }
88
+ Ab::Tests.before_picking_variant { |test| puts 'magic' }
89
+ Ab::Tests.after_picking_variant { |test, variant| puts "#{variant_name}" }
90
90
 
91
91
  # ab.experiment never returns nil
92
92
  # but if you don't belong to any of the buckets, variant will be nil
@@ -0,0 +1,54 @@
1
+ module Ab
2
+ class AssignedTest
3
+ include Hooks
4
+ define_hooks :before_picking_variant, :after_picking_variant
5
+
6
+ def initialize(test, id)
7
+ @test, @id = test, id
8
+ @test.variants.map(&:name).each do |name|
9
+ define_singleton_method("#{name}?") { name == variant }
10
+ end
11
+ end
12
+
13
+ def variant
14
+ @variant ||= begin
15
+ return unless part_of_test?
16
+ return unless running?
17
+
18
+ run_hook :before_picking_variant, @test.name
19
+ picked_variant = @test.variants.find { |v| v.accumulated_chance_weight > weight_id }
20
+
21
+ result = picked_variant.name if picked_variant
22
+ run_hook :after_picking_variant, @test.name, result
23
+ result
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def part_of_test?
30
+ @test.buckets == 'all' || @test.buckets.include?(bucket_id)
31
+ end
32
+
33
+ def bucket_id
34
+ @bucket_id ||= digest(@test.salt + @id.to_s) % @test.bucket_count
35
+ end
36
+
37
+ def running?
38
+ now = DateTime.now
39
+ now.between?(@test.start_at, @test.end_at)
40
+ end
41
+
42
+ def weight_id
43
+ @variant_digest ||= digest("#{@test.seed}#{@id}") % positive_weight_sum
44
+ end
45
+
46
+ def positive_weight_sum
47
+ @test.weight_sum > 0 ? @test.weight_sum : 1
48
+ end
49
+
50
+ def digest(string)
51
+ Digest::SHA256.hexdigest(string).to_i(16)
52
+ end
53
+ end
54
+ end
@@ -1,4 +1,4 @@
1
- class NullExperiment
1
+ class NullTest
2
2
  def variant
3
3
  nil
4
4
  end
@@ -1,5 +1,5 @@
1
1
  module Ab
2
- class Experiment < Struct.new(:hash, :salt, :bucket_count)
2
+ class Test < Struct.new(:hash, :salt, :bucket_count)
3
3
  def buckets
4
4
  hash['buckets']
5
5
  end
data/lib/ab/tests.rb ADDED
@@ -0,0 +1,41 @@
1
+ module Ab
2
+ class Tests
3
+ include Hooks
4
+ define_hooks :before_picking_variant, :after_picking_variant
5
+
6
+ Ab::AssignedTest.before_picking_variant do |test|
7
+ Ab::Tests.run_hook :before_picking_variant, test
8
+ end
9
+ Ab::AssignedTest.after_picking_variant do |test, variant|
10
+ Ab::Tests.run_hook :after_picking_variant, test, variant
11
+ end
12
+
13
+ def initialize(config, id)
14
+ @assigned_tests ||= {}
15
+
16
+ (config['ab_tests'] || []).each do |test|
17
+ name = test['name']
18
+ @assigned_tests[name] = nil
19
+ define_singleton_method(name) do
20
+ test = Test.new(test, config['salt'], config['bucket_count'])
21
+ @assigned_tests[name] ||= AssignedTest.new(test, id)
22
+ end
23
+ end
24
+ end
25
+
26
+ def all
27
+ result = @assigned_tests.keys.map do |name|
28
+ [name, send(name).variant]
29
+ end
30
+ Hash[result]
31
+ end
32
+
33
+ def method_missing(meth, *args, &block)
34
+ @null_test ||= NullTest.new
35
+ end
36
+
37
+ def respond_to?(meth)
38
+ true
39
+ end
40
+ end
41
+ end
data/lib/ab/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ab
2
- VERSION = '0.0.3'
2
+ VERSION = '0.1.0'
3
3
  end
data/lib/ab.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'hooks'
2
2
  require 'ab/version'
3
- require 'ab/null_experiment'
3
+ require 'ab/null_test'
4
4
  require 'ab/variant'
5
- require 'ab/experiment'
6
- require 'ab/assigned_experiment'
7
- require 'ab/experiments'
5
+ require 'ab/test'
6
+ require 'ab/assigned_test'
7
+ require 'ab/tests'
@@ -1,9 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  module Ab
4
- describe AssignedExperiment do
5
- let(:assigned_experiment) { AssignedExperiment.new(experiment, id) }
6
- let(:experiment) { Experiment.new(hash, 'e131bfcfcab06c65d633d0266c7e62a4918', 1000) }
4
+ describe AssignedTest do
5
+ let(:assigned_test) { AssignedTest.new(test, id) }
6
+ let(:test) { Test.new(hash, 'e131bfcfcab06c65d633d0266c7e62a4918', 1000) }
7
7
  let(:hash) {
8
8
  {
9
9
  'name' => name,
@@ -20,10 +20,10 @@ module Ab
20
20
  let(:end_at) { DateTime.now.next_year.to_s }
21
21
  let(:seed) { 'cccc8888' }
22
22
  let(:buckets) { 'all' }
23
- let(:thousand_variants) { 1.upto(1000).map { |i| AssignedExperiment.new(experiment, i).variant } }
23
+ let(:thousand_variants) { 1.upto(1000).map { |i| AssignedTest.new(test, i).variant } }
24
24
 
25
25
  describe '#variant' do
26
- subject { assigned_experiment.variant }
26
+ subject { assigned_test.variant }
27
27
 
28
28
  context 'single variant' do
29
29
  let(:variants) { [{ 'name' => 'enabled', 'chance_weight' => chance_weight }] }
@@ -54,14 +54,14 @@ module Ab
54
54
  end
55
55
  end
56
56
 
57
- context 'experiment that has not started yet' do
57
+ context 'test that has not started yet' do
58
58
  let(:start_at) { DateTime.now.next_year.to_s }
59
59
  let(:buckets) { [1, 2, 3] }
60
60
  let(:variants) { [{ 'name' => 'enabled', 'chance_weight' => 1 }] }
61
61
  it { should be_nil }
62
62
  end
63
63
 
64
- context 'experiment that has already ended' do
64
+ context 'test that has already ended' do
65
65
  let(:end_at) { DateTime.now.prev_year.to_s }
66
66
  let(:buckets) { [1, 2, 3] }
67
67
  let(:variants) { [{ 'name' => 'enabled', 'chance_weight' => 1 }] }
@@ -1,8 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  module Ab
4
- describe NullExperiment do
5
- subject { NullExperiment.new }
4
+ describe NullTest do
5
+ subject { NullTest.new }
6
6
 
7
7
  specify 'does not raise for method ending in question mark' do
8
8
  expect{ subject.bla? }.to_not raise_error
data/spec/test_spec.rb ADDED
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ module Ab
4
+ describe Test do
5
+ let(:test) { Test.new(test_hash, '4321', 100) }
6
+
7
+ context '#buckets' do
8
+ subject { test.buckets }
9
+ let(:test_hash) { { 'buckets' => [1, 2, 3] } }
10
+ it { should == [1, 2, 3] }
11
+ end
12
+
13
+ context '#name' do
14
+ subject { test.name }
15
+ let(:test_hash) { { 'name' => 'test' } }
16
+ it { should == 'test' }
17
+ end
18
+
19
+ context '#start_at' do
20
+ subject { test.start_at }
21
+ let(:test_hash) { { 'start_at' => '2014-05-27T11:56:25+03:00' } }
22
+ it { should == DateTime.new(2014, 5, 27, 11, 56, 25, '+3') }
23
+ end
24
+
25
+ context '#end_at' do
26
+ subject { test.end_at }
27
+
28
+ context 'nil' do
29
+ let(:test_hash) { {} }
30
+ it { should > DateTime.new(2020) }
31
+ end
32
+
33
+ context 'april fools' do
34
+ let(:test_hash) { { 'end_at' => '2014-04-01T12:00:00+00:00' } }
35
+ it { should == DateTime.new(2014, 4, 1, 12) }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,9 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  module Ab
4
- describe Experiments do
4
+ describe Tests do
5
5
  describe '.new' do
6
- subject { Experiments.new(config, id) }
6
+ subject { Tests.new(config, id) }
7
7
  let(:id) { 1 }
8
8
 
9
9
  context 'empty config' do
@@ -30,7 +30,7 @@ module Ab
30
30
  }]
31
31
  }
32
32
  }
33
- its(:feed) { should be_kind_of AssignedExperiment }
33
+ its(:feed) { should be_kind_of AssignedTest }
34
34
  its(:all) { should == { 'feed' => 'enabled' } }
35
35
  end
36
36
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vinted-ab
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mindaugas Mozūras
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-06-02 00:00:00.000000000 Z
12
+ date: 2014-06-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: hooks
@@ -100,17 +100,17 @@ files:
100
100
  - Rakefile
101
101
  - ab.gemspec
102
102
  - lib/ab.rb
103
- - lib/ab/assigned_experiment.rb
104
- - lib/ab/experiment.rb
105
- - lib/ab/experiments.rb
106
- - lib/ab/null_experiment.rb
103
+ - lib/ab/assigned_test.rb
104
+ - lib/ab/null_test.rb
105
+ - lib/ab/test.rb
106
+ - lib/ab/tests.rb
107
107
  - lib/ab/variant.rb
108
108
  - lib/ab/version.rb
109
- - spec/assigned_experiment_spec.rb
110
- - spec/experiment_spec.rb
111
- - spec/experiments_spec.rb
112
- - spec/null_experiment_spec.rb
109
+ - spec/assigned_test_spec.rb
110
+ - spec/null_test_spec.rb
113
111
  - spec/spec_helper.rb
112
+ - spec/test_spec.rb
113
+ - spec/tests_spec.rb
114
114
  homepage: https://github.com/vinted/ab
115
115
  licenses:
116
116
  - MIT
@@ -1,54 +0,0 @@
1
- module Ab
2
- class AssignedExperiment
3
- include Hooks
4
- define_hooks :before_picking_variant, :after_picking_variant
5
-
6
- def initialize(experiment, id)
7
- @experiment, @id = experiment, id
8
- @experiment.variants.map(&:name).each do |name|
9
- define_singleton_method("#{name}?") { name == variant }
10
- end
11
- end
12
-
13
- def variant
14
- @variant ||= begin
15
- return unless part_of_experiment?
16
- return unless running?
17
-
18
- run_hook :before_picking_variant, @experiment.name
19
- picked_variant = @experiment.variants.find { |v| v.accumulated_chance_weight > weight_id }
20
-
21
- result = picked_variant.name if picked_variant
22
- run_hook :after_picking_variant, @experiment.name, result
23
- result
24
- end
25
- end
26
-
27
- private
28
-
29
- def part_of_experiment?
30
- @experiment.buckets == 'all' || @experiment.buckets.include?(bucket_id)
31
- end
32
-
33
- def bucket_id
34
- @bucket_id ||= digest(@experiment.salt + @id.to_s) % @experiment.bucket_count
35
- end
36
-
37
- def running?
38
- now = DateTime.now
39
- now.between?(@experiment.start_at, @experiment.end_at)
40
- end
41
-
42
- def weight_id
43
- @variant_digest ||= digest("#{@experiment.seed}#{@id}") % positive_weight_sum
44
- end
45
-
46
- def positive_weight_sum
47
- @experiment.weight_sum > 0 ? @experiment.weight_sum : 1
48
- end
49
-
50
- def digest(string)
51
- Digest::SHA256.hexdigest(string).to_i(16)
52
- end
53
- end
54
- end
@@ -1,41 +0,0 @@
1
- module Ab
2
- class Experiments
3
- include Hooks
4
- define_hooks :before_picking_variant, :after_picking_variant
5
-
6
- Ab::AssignedExperiment.before_picking_variant do |experiment|
7
- Ab::Experiments.run_hook :before_picking_variant, experiment
8
- end
9
- Ab::AssignedExperiment.after_picking_variant do |experiment, variant|
10
- Ab::Experiments.run_hook :after_picking_variant, experiment, variant
11
- end
12
-
13
- def initialize(config, id)
14
- @assigned_experiments ||= {}
15
-
16
- (config['ab_tests'] || []).each do |experiment|
17
- name = experiment['name']
18
- @assigned_experiments[name] = nil
19
- define_singleton_method(name) do
20
- experiment = Experiment.new(experiment, config['salt'], config['bucket_count'])
21
- @assigned_experiments[name] ||= AssignedExperiment.new(experiment, id)
22
- end
23
- end
24
- end
25
-
26
- def all
27
- result = @assigned_experiments.keys.map do |name|
28
- [name, send(name).variant]
29
- end
30
- Hash[result]
31
- end
32
-
33
- def method_missing(meth, *args, &block)
34
- @null_experiment ||= NullExperiment.new
35
- end
36
-
37
- def respond_to?(meth)
38
- true
39
- end
40
- end
41
- end
@@ -1,39 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Ab
4
- describe Experiment do
5
- let(:experiment) { Experiment.new(experiment_hash, '4321', 100) }
6
-
7
- context '#buckets' do
8
- subject { experiment.buckets }
9
- let(:experiment_hash) { { 'buckets' => [1, 2, 3] } }
10
- it { should == [1, 2, 3] }
11
- end
12
-
13
- context '#name' do
14
- subject { experiment.name }
15
- let(:experiment_hash) { { 'name' => 'test' } }
16
- it { should == 'test' }
17
- end
18
-
19
- context '#start_at' do
20
- subject { experiment.start_at }
21
- let(:experiment_hash) { { 'start_at' => '2014-05-27T11:56:25+03:00' } }
22
- it { should == DateTime.new(2014, 5, 27, 11, 56, 25, '+3') }
23
- end
24
-
25
- context '#end_at' do
26
- subject { experiment.end_at }
27
-
28
- context 'nil' do
29
- let(:experiment_hash) { {} }
30
- it { should > DateTime.new(2020) }
31
- end
32
-
33
- context 'april fools' do
34
- let(:experiment_hash) { { 'end_at' => '2014-04-01T12:00:00+00:00' } }
35
- it { should == DateTime.new(2014, 4, 1, 12) }
36
- end
37
- end
38
- end
39
- end