simple_split 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 10bbe495f89c5d80168e05747bbdf32cc0ed8911
4
+ data.tar.gz: 64f86c88ead59e49ad0b5fc46f0dd3fc22cc98b2
5
+ SHA512:
6
+ metadata.gz: eec5fe8cf16d9d28f84e62a3c99436aafe17909b5e54ec3bdce9fc30b1272fdbdede0fa36048dbff718a58633255b510c3868282747759fc5b044d4d6bb845d4
7
+ data.tar.gz: 8bb9f98d5a22fa4bab376e41e96ebb5cc225543f35997735c12b665b792ebfa75ba084f2132ef4ce56acc1c8c4ec5fd0dfd8703aa2ddedb1eb22d95ddfd1c86b
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rubocop.yml ADDED
@@ -0,0 +1,25 @@
1
+ Encoding:
2
+ Enabled: false
3
+
4
+ Documentation:
5
+ Enabled: false
6
+
7
+ ClassAndModuleChildren:
8
+ Enabled: false
9
+
10
+ ClassLength:
11
+ Enabled: false
12
+
13
+ MethodLength:
14
+ Enabled: false
15
+
16
+ DoubleNegation:
17
+ Enabled: false
18
+
19
+ AllCops:
20
+ Exclude:
21
+ - "bin/**/*"
22
+ - "db/**/*"
23
+ - "config/**/*"
24
+ - "Rakefile"
25
+ - "spec/i18n_spec.rb"
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in simple_split.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+
2
+ # simple_split – dead simple A/B testing on Rails
3
+
4
+ The `simple_split` gem provides the absolute simplest possible support for split
5
+ testing with Rails that could possibly exist. More feature-complete solutions
6
+ like [`split`][split] provide more bells and whistles, but they also do far
7
+ more than necessary for simple split testing with good analytics services.
8
+
9
+ Instead, `simple_split` is bare-bones while still supporting flexible testing.
10
+
11
+ - Supports weighted variations
12
+ - Does not require the use of any data store
13
+ - Variations already seen by users are tracked via cookies
14
+
15
+ ## Quick Start
16
+
17
+ Add `gem 'simple_split'` to your Gemfile, then run `bundle`. That's it. Now you
18
+ can start adding split testing support to your project.
19
+
20
+ All classes that inherit from `ActionView` or `ActionController` have access to
21
+ the `ab_test` method. It accepts an experiment name and a list of variations.
22
+
23
+ ```ruby
24
+ ab_test 'experiment_name', 'variation_a', 'variation_b', 'variation_c'
25
+ ```
26
+
27
+ The result of a call to `ab_test` is a randomly selected variation. If a user
28
+ has already seen a particular variation, that variation will always be returned
29
+ instead of randomly selecting one.
30
+
31
+ Weights can be specified using a hash.
32
+
33
+ ```ruby
34
+ ab_test 'experiment_name', { 'variation_a' => 1.0, 'variation_b' => 0.2 }
35
+ ```
36
+
37
+ ## Credit
38
+
39
+ This was inspired by the [`simple_abs`][simple_abs] gem, which was already quite
40
+ stripped-down, but it didn't quite suit my needs.
41
+
42
+ [split]: https://github.com/splitrb/split
43
+ [simple_abs]: https://github.com/n8/simple_abs
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'spec'
6
+ t.pattern = 'spec/**/*_spec.rb'
7
+ end
data/bin/bundler ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'bundler' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('bundler', 'bundler')
data/bin/console ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'console' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('simple_split', 'console')
data/bin/rake ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rake' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rake', 'rake')
data/bin/setup ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'setup' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('simple_split', 'setup')
@@ -0,0 +1,3 @@
1
+ module SimpleSplit
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,61 @@
1
+ require 'active_support/all'
2
+ require 'weighted_randomizer'
3
+
4
+ module SimpleSplit
5
+ class TestSelector
6
+ def initialize(get_test, set_test)
7
+ @get_test = get_test
8
+ @set_test = set_test
9
+ end
10
+
11
+ def get_test(name, tests)
12
+ get_existing_test_value(name, tests) || assign_test(name, tests)
13
+ end
14
+
15
+ private
16
+
17
+ def get_existing_test_value(name, tests)
18
+ test_value = @get_test.call name
19
+ test_value if test_value.present? && tests.include?(test_value)
20
+ end
21
+
22
+ def assign_test(name, tests)
23
+ selected_test = select_test tests
24
+ @set_test.call name, selected_test
25
+ selected_test
26
+ end
27
+
28
+ def select_test(tests)
29
+ WeightedRandomizer.new(normalize_tests tests).sample
30
+ end
31
+
32
+ def normalize_tests(tests)
33
+ tests.flat_map do |test|
34
+ if test.is_a? Hash
35
+ test
36
+ elsif test.is_a? Array
37
+ normalize_tests(test)
38
+ else
39
+ { test => 1.0 }
40
+ end.to_a
41
+ end.to_h
42
+ end
43
+ end
44
+
45
+ module TestHelper
46
+ def ab_test(name, *tests)
47
+ TestSelector.new(->(k) { cookies[k] },
48
+ ->(k, v) { cookies[k] = v })
49
+ .get_test name, tests
50
+ end
51
+ end
52
+
53
+ if defined? Rails
54
+ class Railtie < Rails::Railtie
55
+ initializer 'simple_split.initialize' do
56
+ ActionController::Base.send :include, SimpleSplit::TestHelper
57
+ ActionView::Base.send :include, SimpleSplit::TestHelper
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'simple_split/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'simple_split'
8
+ spec.version = SimpleSplit::VERSION
9
+ spec.authors = ['Alexis King']
10
+ spec.email = ['alexis@gophilosophie.com']
11
+
12
+ spec.summary = 'Dead simple, database-free AB testing in Rails'
13
+ spec.homepage = 'https://github.com/lexi-lambda/simple_split'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_development_dependency 'bundler', '~> 1.10'
21
+ spec.add_development_dependency 'rake', '~> 10.0'
22
+ spec.add_development_dependency 'minitest', '~> 5.7.0'
23
+
24
+ spec.add_runtime_dependency 'activesupport', '>= 3.0'
25
+ spec.add_runtime_dependency 'weighted_randomizer', '>= 0.1.2'
26
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe SimpleSplit::TestSelector do
4
+ let(:data_store) { Hash.new }
5
+ let(:selector) do
6
+ SimpleSplit::TestSelector.new(->(k) { data_store[k] },
7
+ ->(k, v) { data_store[k] = v })
8
+ end
9
+
10
+ it 'uses an existing test if one is already set' do
11
+ data_store[:example_experiment] = :test_a
12
+ _(selector.get_test :example_experiment, [:test_a, :test_b])
13
+ .must_equal :test_a
14
+ end
15
+
16
+ it 'must save the chosen test if one does not yet exist' do
17
+ chosen_test = selector.get_test :example_experiment, [:test_a, :test_b]
18
+ _(data_store[:example_experiment]).must_equal chosen_test
19
+ end
20
+
21
+ it 'must select tests proportionally to their weights' do
22
+ non_storing_selector =
23
+ SimpleSplit::TestSelector.new(->(_) { nil }, ->(_, _) {})
24
+ tests = [{ test_a: 1.0, test_b: 2.0 }]
25
+
26
+ number_of_trials = 1_000
27
+ test_results = number_of_trials.times.map do
28
+ non_storing_selector.get_test(:example_experiment, tests) == :test_a
29
+ end
30
+ test_a_frequency = test_results.count(true).to_f / number_of_trials
31
+
32
+ _(test_a_frequency).must_be_close_to 0.3, 0.1
33
+ end
34
+ end
@@ -0,0 +1,8 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'simple_split'
5
+
6
+ require 'minitest/spec'
7
+ require 'minitest/autorun'
8
+ require 'minitest/pride'
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple_split
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alexis King
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 5.7.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 5.7.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: weighted_randomizer
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 0.1.2
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 0.1.2
83
+ description:
84
+ email:
85
+ - alexis@gophilosophie.com
86
+ executables:
87
+ - bundler
88
+ - console
89
+ - rake
90
+ - setup
91
+ extensions: []
92
+ extra_rdoc_files: []
93
+ files:
94
+ - ".gitignore"
95
+ - ".rubocop.yml"
96
+ - Gemfile
97
+ - README.md
98
+ - Rakefile
99
+ - bin/bundler
100
+ - bin/console
101
+ - bin/rake
102
+ - bin/setup
103
+ - lib/simple_split.rb
104
+ - lib/simple_split/version.rb
105
+ - simple_split.gemspec
106
+ - spec/lib/simple_split_spec.rb
107
+ - spec/spec_helper.rb
108
+ homepage: https://github.com/lexi-lambda/simple_split
109
+ licenses: []
110
+ metadata: {}
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 2.4.5
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: Dead simple, database-free AB testing in Rails
131
+ test_files:
132
+ - spec/lib/simple_split_spec.rb
133
+ - spec/spec_helper.rb