divisio 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 +7 -0
- data/.gitignore +14 -0
- data/.rspec +4 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +120 -0
- data/divisio.gemspec +20 -0
- data/lib/divisio.rb +27 -0
- data/lib/divisio/base_adapter.rb +13 -0
- data/lib/divisio/modulo_algorithm.rb +30 -0
- data/lib/divisio/mongoid_adapter.rb +29 -0
- data/lib/divisio/mongoid_adapter/experiment.rb +19 -0
- data/lib/divisio/no_persistence_adapter.rb +6 -0
- data/lib/divisio/version.rb +3 -0
- data/mongoid.yml +8 -0
- data/spec/divisio/base_adapter_spec.rb +4 -0
- data/spec/divisio/modulo_algorithm_spec.rb +33 -0
- data/spec/divisio/mongoid_adapter/experiment_spec.rb +55 -0
- data/spec/divisio/mongoid_adapter_spec.rb +63 -0
- data/spec/divisio/no_persistence_adapter_spec.rb +4 -0
- data/spec/divisio_spec.rb +56 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/shared_examples_for_base_adapter.rb +17 -0
- metadata +75 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8aae102437166c67d6dc2dfe8277969afee58978
|
4
|
+
data.tar.gz: 34d89f2cf876329fa57d519c1ee76cdd17d6666f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: afa5878c8dae28f8848b3f095fe55240931c4b21711109bd033e3761d40934ae7c292f76d201c87ddb5b40809c2467fb973c071499eb8eacd48731e33936555c
|
7
|
+
data.tar.gz: 9822d1d7741be88a91cb1725be4af78eb343b34c5f2481559763fb1099ef5f6b5dcd326a57f16ff0f7bc7126133e2e42a2467c7fd494e0f200fcb2e6cc5c5771
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Simply Business
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
# Divisio
|
2
|
+
|
3
|
+
This gem assigns identities to a variant inside an experiment. It is similar to ab-tests framework.
|
4
|
+
|
5
|
+
A triplet - experiment, variants and identities will always produce the same variant outcome even if the test is deleted and rerun.
|
6
|
+
|
7
|
+
You can run multiple instances of the framework inside the same application for different adapters.
|
8
|
+
|
9
|
+
You can use it wherever you want however you want (you can make a global object for entire application or you can make individual little objects to be used inside methods).
|
10
|
+
It is up to you do decide!
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'divisio'
|
18
|
+
```
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
$ bundle
|
23
|
+
|
24
|
+
Or install it yourself as:
|
25
|
+
|
26
|
+
$ gem install divisio
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
### No persistence adapter
|
31
|
+
|
32
|
+
_This is the default adapter_
|
33
|
+
|
34
|
+
It returns the selected variant, but does not persist any information.
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
experiment_name = 'experiment1'
|
38
|
+
variants = [1,2,3]
|
39
|
+
identity = 'dragos'
|
40
|
+
|
41
|
+
Divisio.new.split(experiment_name, variants, identity) # ==>> 1
|
42
|
+
```
|
43
|
+
|
44
|
+
### Mongoid adaper
|
45
|
+
|
46
|
+
_Requires mongoid v4.0.0 or greater_
|
47
|
+
|
48
|
+
This adapter will persist the experiment name, identifier, and variant information in a MongoDb collection called `experiments`. Note: The variant returned will be cast to a string.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
experiment_name = 'experiment1'
|
52
|
+
variants = [1,2,3]
|
53
|
+
identity = 'dragos'
|
54
|
+
|
55
|
+
Divisio.new(adapter: Divisio::MongoidAdapter).split(experiment_name, variants, identity) # ==>> '1'
|
56
|
+
```
|
57
|
+
|
58
|
+
You can also specify the default adapter in your initializers as so:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
Divisio.default_adapter = Divisio::MongoidAdapter
|
62
|
+
```
|
63
|
+
|
64
|
+
then
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
Divisio.new.split(experiment_name, variants, identity) # ==>> '1'
|
68
|
+
```
|
69
|
+
|
70
|
+
### Weighted variants
|
71
|
+
|
72
|
+
Variants can be weighted by passing a hash of variants mapped to their relative weights, for example:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
experiment_name = 'experiment1'
|
76
|
+
variants = { a: 1, b: 2, c: 3 }
|
77
|
+
identity = 'dragos'
|
78
|
+
|
79
|
+
Divisio.new.split(experiment_name, variants, identity) # ==>> :c
|
80
|
+
```
|
81
|
+
|
82
|
+
There are three variants in this example: `a`, `b`, `c`
|
83
|
+
|
84
|
+
Variant `a` has a weight of 1, `b` has a weight of 2, `c` has a weight of 3.
|
85
|
+
|
86
|
+
Effectively, this means that the chances of getting the variant `b` are twice a likely as getting the variant `a`. The chances of getting the variant `c` are thrice as likely as getting the variant `a`.
|
87
|
+
|
88
|
+
### Further examples
|
89
|
+
|
90
|
+
You can use what it returns *directly*, for example if you want to render a partial you could do:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
partial = Divisio.new.split('amazing partial', ['partial1', 'partial2'], identity)
|
94
|
+
render(partial)
|
95
|
+
```
|
96
|
+
|
97
|
+
If we want to do complex logic based on some idiom, you need to do if/case statements:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
# this method could be in a global helper
|
101
|
+
def enable_partial_quotes
|
102
|
+
decision = Divisio.new.split('enable_partial_quotes', ['yes', 'no'], identity)
|
103
|
+
decision == 'yes'
|
104
|
+
end
|
105
|
+
|
106
|
+
# .....
|
107
|
+
# then somewhere else
|
108
|
+
# .....
|
109
|
+
if enable_partial_quotes
|
110
|
+
# do some stuff
|
111
|
+
else
|
112
|
+
```
|
113
|
+
|
114
|
+
## Contributing
|
115
|
+
|
116
|
+
1. Fork it ( https://github.com/[my-github-username]/divisio/fork )
|
117
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
118
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
119
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
120
|
+
5. Create a new Pull Request
|
data/divisio.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'divisio/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'divisio'
|
8
|
+
spec.version = Divisio::VERSION
|
9
|
+
spec.authors = ['Dragos Miron']
|
10
|
+
spec.email = ['tech@simplybusiness.co.uk']
|
11
|
+
spec.summary = %q{Provides a splitting framework similar to AB testing}
|
12
|
+
spec.description = %q{Provides a splitting framework similar to AB testing}
|
13
|
+
spec.homepage = 'http://www.simplybusiness.co.uk'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
end
|
data/lib/divisio.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'divisio/base_adapter'
|
2
|
+
require 'divisio/no_persistence_adapter'
|
3
|
+
require 'divisio/modulo_algorithm'
|
4
|
+
require 'divisio/mongoid_adapter' if defined? Mongoid
|
5
|
+
|
6
|
+
class Divisio
|
7
|
+
|
8
|
+
@default_adapter = Divisio::NoPersistenceAdapter
|
9
|
+
class << self
|
10
|
+
attr_accessor :default_adapter
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :adapter
|
14
|
+
private :adapter
|
15
|
+
|
16
|
+
def initialize(adapter: self.class.default_adapter)
|
17
|
+
@adapter = adapter
|
18
|
+
end
|
19
|
+
|
20
|
+
def split(experiment_name, variants, identity)
|
21
|
+
adapter.split(experiment_name, variants, identity)
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete_experiment_for_identity(identity, experiment_name)
|
25
|
+
adapter.delete_experiment_for_identity(identity, experiment_name)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Divisio
|
2
|
+
module BaseAdapter
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def split(experiment_name, variants, identity)
|
6
|
+
ModuloAlgorithm.new(experiment_name.to_s+identity.to_s, variants).calc
|
7
|
+
end
|
8
|
+
|
9
|
+
def delete_experiment_for_identity(identity, experiment_name)
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Divisio
|
2
|
+
class ModuloAlgorithm
|
3
|
+
|
4
|
+
attr_reader :key, :variants
|
5
|
+
private :key, :variants
|
6
|
+
|
7
|
+
def initialize(key, variants)
|
8
|
+
@key = key.to_s
|
9
|
+
@variants = variants_as_array(variants)
|
10
|
+
end
|
11
|
+
|
12
|
+
def calc
|
13
|
+
number_of_variants = variants.size
|
14
|
+
position = Digest::MD5.hexdigest(key).to_i(16) % number_of_variants
|
15
|
+
|
16
|
+
variants[position]
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def variants_as_array(variants)
|
22
|
+
return Array(variants) unless variants.is_a? Hash
|
23
|
+
flattened_variants = []
|
24
|
+
variants.each_pair do |name, weight|
|
25
|
+
weight.times { flattened_variants << name }
|
26
|
+
end
|
27
|
+
flattened_variants
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
%w(experiment).each do |f|
|
2
|
+
require_relative "mongoid_adapter/#{f}"
|
3
|
+
end
|
4
|
+
|
5
|
+
class Divisio
|
6
|
+
module MongoidAdapter
|
7
|
+
include BaseAdapter
|
8
|
+
extend self
|
9
|
+
|
10
|
+
def split(experiment_name, variants, identity)
|
11
|
+
experiment_object = Experiment.where(identifier: identity, name: experiment_name).first
|
12
|
+
return experiment_object.variant if experiment_object
|
13
|
+
|
14
|
+
variant_for_identity = super.to_s
|
15
|
+
experiment_object = Experiment.new(identifier: identity, name: experiment_name, variant: variant_for_identity)
|
16
|
+
|
17
|
+
return variant_for_identity if experiment_object.save
|
18
|
+
rescue Moped::Errors::OperationFailure
|
19
|
+
split(experiment_name, variants, identity)
|
20
|
+
end
|
21
|
+
|
22
|
+
def delete_experiment_for_identity(identity, experiment_name)
|
23
|
+
experiment_object = Experiment.where(identifier: identity, name: experiment_name).first
|
24
|
+
|
25
|
+
return experiment_object.destroy if experiment_object
|
26
|
+
return false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Divisio
|
2
|
+
module MongoidAdapter
|
3
|
+
class Experiment
|
4
|
+
|
5
|
+
include Mongoid::Document
|
6
|
+
include Mongoid::Timestamps
|
7
|
+
|
8
|
+
validates_presence_of :name, :variant, :identifier
|
9
|
+
validates_uniqueness_of :identifier, scope: :name
|
10
|
+
|
11
|
+
field :name, type: String
|
12
|
+
field :identifier, type: String
|
13
|
+
field :variant, type: String
|
14
|
+
|
15
|
+
index({ name: 1, identifier: 1}, { unique: true})
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/mongoid.yml
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
describe Divisio::ModuloAlgorithm do
|
2
|
+
describe '#calc' do
|
3
|
+
let(:variants) { }
|
4
|
+
|
5
|
+
context 'when there is one variant provided' do
|
6
|
+
let(:variants) { 1 }
|
7
|
+
|
8
|
+
it 'returns the variant' do
|
9
|
+
expect(Divisio::ModuloAlgorithm.new('key', variants).calc).to eq(1)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when there are multiple variants provided' do
|
14
|
+
let(:variants) { ['1', '2', '3'] }
|
15
|
+
|
16
|
+
{ blah8: '1', blah4: '2', blah0: '3'}.each_pair do |key, expected_variant|
|
17
|
+
it "returns the variant based on the key provided: #{key}" do
|
18
|
+
expect(Divisio::ModuloAlgorithm.new(key, variants).calc).to eq(expected_variant)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when there are multiple weighted variants provided' do
|
24
|
+
let(:variants) { { a: 1, b: 2, c: 3 } }
|
25
|
+
|
26
|
+
{ blah8: :a, blah4: :b, blah0: :c}.each_pair do |key, expected_variant|
|
27
|
+
it "returns the variant based on the key provided and weight: #{key}" do
|
28
|
+
expect(Divisio::ModuloAlgorithm.new(key, variants).calc).to eq(expected_variant)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
describe Divisio::MongoidAdapter::Experiment do
|
2
|
+
describe '#save' do
|
3
|
+
|
4
|
+
let(:required_fields) { {:name => 'experiment', :identifier => 'hash', :variant => '2'} }
|
5
|
+
subject { Divisio::MongoidAdapter::Experiment.new }
|
6
|
+
|
7
|
+
context 'all fields are present' do
|
8
|
+
it 'gets saved to the database' do
|
9
|
+
subject.attributes = required_fields
|
10
|
+
|
11
|
+
expect{ subject.save }.to change{ described_class.count }.from(0).to(1)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'missing fields' do
|
16
|
+
it 'does not get saved if identifier is missing' do
|
17
|
+
subject.attributes = required_fields.tap{ |h| h.delete(:identifier) }
|
18
|
+
expect(subject.save).to be_falsey
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'does not get saved if name is missing' do
|
22
|
+
subject.attributes = required_fields.tap{ |h| h.delete(:name) }
|
23
|
+
expect(subject.save).to be_falsey
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'does not get saved if variant is missing' do
|
27
|
+
subject.attributes = required_fields.tap{ |h| h.delete(:variant) }
|
28
|
+
expect(subject.save).to be_falsey
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'uniqueness' do
|
33
|
+
|
34
|
+
it 'does not save second object with the same name and identifier' do
|
35
|
+
subject.attributes = required_fields
|
36
|
+
subject.save
|
37
|
+
expect(described_class.count).to eq(1)
|
38
|
+
|
39
|
+
required_fields[:variant] = '3'
|
40
|
+
new_object = described_class.new(required_fields)
|
41
|
+
|
42
|
+
expect{ new_object.save }.to_not change(described_class, :count)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'does not save second object in case of race condition because of mongo index' do
|
46
|
+
described_class.create_indexes
|
47
|
+
Mongoid.default_session[:divisio_mongoid_adapter_experiments].insert(required_fields)
|
48
|
+
expect(described_class.count).to eq(1)
|
49
|
+
|
50
|
+
collection = described_class.collection
|
51
|
+
expect{collection.insert(required_fields)}.to raise_exception(Moped::Errors::OperationFailure)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
describe Divisio::MongoidAdapter do
|
2
|
+
let(:experiment) { 'experiment' }
|
3
|
+
let(:variants) { [1, 2, 3] }
|
4
|
+
let(:identity) { 'identity' }
|
5
|
+
|
6
|
+
describe '::split' do
|
7
|
+
subject{ described_class.split(experiment, variants, identity) }
|
8
|
+
|
9
|
+
it_behaves_like 'a base adapter'
|
10
|
+
|
11
|
+
context 'new record' do
|
12
|
+
|
13
|
+
it 'saves the experiment to the database' do
|
14
|
+
expect_any_instance_of(Divisio::MongoidAdapter::Experiment).to receive(:save)
|
15
|
+
subject
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'converts the variant to string' do
|
19
|
+
expect(subject).to be_a String
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'old record' do
|
24
|
+
|
25
|
+
before do
|
26
|
+
Divisio::MongoidAdapter::Experiment.create(name: experiment, identifier: identity, variant: 'random')
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'returns the variant from mongo if already exists' do
|
30
|
+
expect(subject).to eq('random')
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'does not re-save to the database' do
|
34
|
+
expect_any_instance_of(Divisio::MongoidAdapter::Experiment).to_not receive(:save)
|
35
|
+
subject
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '::delete_experiment_for_identity' do
|
41
|
+
subject{ described_class.delete_experiment_for_identity(identity, experiment) }
|
42
|
+
|
43
|
+
context 'record exists in the database' do
|
44
|
+
before do
|
45
|
+
Divisio::MongoidAdapter::Experiment.create(name: experiment, identifier: identity, variant: 'random')
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'deletes the record' do
|
49
|
+
expect{subject}.to change { Divisio::MongoidAdapter::Experiment.count }.from(1).to(0)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'returns true' do
|
53
|
+
expect(subject).to eq(true)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'record does not exist in the database' do
|
58
|
+
it 'returns false' do
|
59
|
+
expect(subject).to eq(false)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
describe Divisio do
|
2
|
+
describe '::default_adapter' do
|
3
|
+
it 'has a default adapter' do
|
4
|
+
expect(Divisio.default_adapter).to eq(Divisio::NoPersistenceAdapter)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '::default_adapter=' do
|
9
|
+
before do
|
10
|
+
@default_adapter = Divisio.default_adapter
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
Divisio.default_adapter = @default_adapter
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'overwrites the default adapter' do
|
18
|
+
Divisio.default_adapter = Divisio::MongoidAdapter
|
19
|
+
expect(Divisio.default_adapter).to eq(Divisio::MongoidAdapter)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '::new' do
|
24
|
+
context 'no adapter provided to initializer' do
|
25
|
+
it 'initializes an object with a default adapter' do
|
26
|
+
framework = Divisio.new
|
27
|
+
|
28
|
+
expect(framework.instance_variable_get('@adapter')).to eq(Divisio.default_adapter)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'adapter provided to initializer' do
|
33
|
+
it 'initializes an object with a supplied adapter' do
|
34
|
+
framework = Divisio.new(adapter: Divisio::MongoidAdapter)
|
35
|
+
|
36
|
+
expect(framework.instance_variable_get('@adapter')).to eq(Divisio::MongoidAdapter)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#split' do
|
42
|
+
it 'calls the same method on the adapter' do
|
43
|
+
expect(Divisio::MongoidAdapter).to receive(:split)
|
44
|
+
|
45
|
+
Divisio.new(adapter: Divisio::MongoidAdapter).split(anything, anything, anything)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#delete_experiment_for_identity' do
|
50
|
+
it 'calls the same method on the adapter' do
|
51
|
+
expect(Divisio::MongoidAdapter).to receive(:delete_experiment_for_identity)
|
52
|
+
|
53
|
+
Divisio.new(adapter: Divisio::MongoidAdapter).delete_experiment_for_identity(anything, anything)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
ENV['RACK_ENV'] = 'test'
|
2
|
+
|
3
|
+
Dir['./spec/support/**/*.rb'].sort.each { |f| require f}
|
4
|
+
|
5
|
+
require 'mongoid'
|
6
|
+
require 'divisio'
|
7
|
+
|
8
|
+
Mongoid.load!('mongoid.yml')
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.before(:each) do
|
12
|
+
Mongoid::Sessions.default.collections.each(&:drop)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
shared_examples_for 'a base adapter' do
|
2
|
+
|
3
|
+
describe '::split' do
|
4
|
+
let(:experiment) { 'experiment' }
|
5
|
+
let(:variants) { ['1', '2', '3'] }
|
6
|
+
let(:identity) { 'identity' }
|
7
|
+
subject{ described_class.split(experiment, variants, identity) }
|
8
|
+
|
9
|
+
it 'returns a variant for the given experiment and identity' do
|
10
|
+
expect(subject).to eq('3')
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'always assigns same variant for the same attributes' do
|
14
|
+
expect(described_class.split(experiment, variants, identity)).to eq(subject)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: divisio
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dragos Miron
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-13 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Provides a splitting framework similar to AB testing
|
14
|
+
email:
|
15
|
+
- tech@simplybusiness.co.uk
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".gitignore"
|
21
|
+
- ".rspec"
|
22
|
+
- Gemfile
|
23
|
+
- LICENSE.txt
|
24
|
+
- README.md
|
25
|
+
- divisio.gemspec
|
26
|
+
- lib/divisio.rb
|
27
|
+
- lib/divisio/base_adapter.rb
|
28
|
+
- lib/divisio/modulo_algorithm.rb
|
29
|
+
- lib/divisio/mongoid_adapter.rb
|
30
|
+
- lib/divisio/mongoid_adapter/experiment.rb
|
31
|
+
- lib/divisio/no_persistence_adapter.rb
|
32
|
+
- lib/divisio/version.rb
|
33
|
+
- mongoid.yml
|
34
|
+
- spec/divisio/base_adapter_spec.rb
|
35
|
+
- spec/divisio/modulo_algorithm_spec.rb
|
36
|
+
- spec/divisio/mongoid_adapter/experiment_spec.rb
|
37
|
+
- spec/divisio/mongoid_adapter_spec.rb
|
38
|
+
- spec/divisio/no_persistence_adapter_spec.rb
|
39
|
+
- spec/divisio_spec.rb
|
40
|
+
- spec/spec_helper.rb
|
41
|
+
- spec/support/shared_examples_for_base_adapter.rb
|
42
|
+
homepage: http://www.simplybusiness.co.uk
|
43
|
+
licenses:
|
44
|
+
- MIT
|
45
|
+
metadata: {}
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 2.0.14
|
63
|
+
signing_key:
|
64
|
+
specification_version: 4
|
65
|
+
summary: Provides a splitting framework similar to AB testing
|
66
|
+
test_files:
|
67
|
+
- spec/divisio/base_adapter_spec.rb
|
68
|
+
- spec/divisio/modulo_algorithm_spec.rb
|
69
|
+
- spec/divisio/mongoid_adapter/experiment_spec.rb
|
70
|
+
- spec/divisio/mongoid_adapter_spec.rb
|
71
|
+
- spec/divisio/no_persistence_adapter_spec.rb
|
72
|
+
- spec/divisio_spec.rb
|
73
|
+
- spec/spec_helper.rb
|
74
|
+
- spec/support/shared_examples_for_base_adapter.rb
|
75
|
+
has_rdoc:
|