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.
@@ -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
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --order rand
2
+ --color
3
+ --require spec_helper
4
+ --format doc
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in divisio.gemspec
4
+ gemspec
5
+
6
+ group :development, :test do
7
+ gem 'pry-plus'
8
+ gem 'mongoid'
9
+ end
10
+
11
+ group :test do
12
+ gem 'rspec'
13
+ end
@@ -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.
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,6 @@
1
+ class Divisio
2
+ module NoPersistenceAdapter
3
+ include BaseAdapter
4
+ extend self
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ class Divisio
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,8 @@
1
+ test:
2
+ sessions:
3
+ default:
4
+ database: divisio_test
5
+ hosts:
6
+ - localhost:27017
7
+ options:
8
+ <%= "safe: true" if Mongoid::VERSION =~ /^3/%>
@@ -0,0 +1,4 @@
1
+ describe Divisio::BaseAdapter do
2
+
3
+ it_behaves_like 'a base adapter'
4
+ end
@@ -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,4 @@
1
+ describe Divisio::NoPersistenceAdapter do
2
+
3
+ it_behaves_like 'a base adapter'
4
+ 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
@@ -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: