split 3.4.1 → 4.0.4
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 +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.github/dependabot.yml +7 -0
- data/.github/workflows/ci.yml +76 -0
- data/.rubocop.yml +177 -4
- data/CHANGELOG.md +87 -0
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +2 -1
- data/README.md +37 -9
- data/Rakefile +5 -5
- data/gemfiles/5.2.gemfile +1 -3
- data/gemfiles/6.0.gemfile +1 -3
- data/gemfiles/{5.0.gemfile → 6.1.gemfile} +2 -4
- data/gemfiles/{5.1.gemfile → 7.0.gemfile} +2 -4
- data/lib/split/algorithms/block_randomization.rb +6 -6
- data/lib/split/algorithms/weighted_sample.rb +2 -1
- data/lib/split/algorithms/whiplash.rb +17 -18
- data/lib/split/algorithms.rb +14 -0
- data/lib/split/alternative.rb +22 -22
- data/lib/split/cache.rb +27 -0
- data/lib/split/combined_experiments_helper.rb +5 -4
- data/lib/split/configuration.rb +89 -94
- data/lib/split/dashboard/helpers.rb +7 -7
- data/lib/split/dashboard/pagination_helpers.rb +54 -54
- data/lib/split/dashboard/paginator.rb +1 -0
- data/lib/split/dashboard/public/dashboard.js +10 -0
- data/lib/split/dashboard/public/style.css +10 -2
- data/lib/split/dashboard/views/_controls.erb +13 -0
- data/lib/split/dashboard/views/_experiment.erb +2 -1
- data/lib/split/dashboard/views/index.erb +19 -4
- data/lib/split/dashboard.rb +42 -21
- data/lib/split/encapsulated_helper.rb +15 -8
- data/lib/split/engine.rb +1 -0
- data/lib/split/exceptions.rb +1 -0
- data/lib/split/experiment.rb +151 -124
- data/lib/split/experiment_catalog.rb +7 -8
- data/lib/split/extensions/string.rb +2 -1
- data/lib/split/goals_collection.rb +9 -10
- data/lib/split/helper.rb +50 -23
- data/lib/split/metric.rb +6 -6
- data/lib/split/persistence/cookie_adapter.rb +46 -44
- data/lib/split/persistence/dual_adapter.rb +7 -8
- data/lib/split/persistence/redis_adapter.rb +8 -4
- data/lib/split/persistence/session_adapter.rb +1 -2
- data/lib/split/persistence.rb +8 -6
- data/lib/split/redis_interface.rb +15 -29
- data/lib/split/trial.rb +43 -34
- data/lib/split/user.rb +25 -14
- data/lib/split/version.rb +2 -4
- data/lib/split/zscore.rb +2 -3
- data/lib/split.rb +34 -27
- data/spec/algorithms/block_randomization_spec.rb +6 -5
- data/spec/algorithms/weighted_sample_spec.rb +6 -5
- data/spec/algorithms/whiplash_spec.rb +4 -5
- data/spec/alternative_spec.rb +35 -36
- data/spec/cache_spec.rb +84 -0
- data/spec/combined_experiments_helper_spec.rb +18 -17
- data/spec/configuration_spec.rb +41 -45
- data/spec/dashboard/pagination_helpers_spec.rb +69 -67
- data/spec/dashboard/paginator_spec.rb +10 -9
- data/spec/dashboard_helpers_spec.rb +19 -18
- data/spec/dashboard_spec.rb +122 -38
- data/spec/encapsulated_helper_spec.rb +46 -22
- data/spec/experiment_catalog_spec.rb +14 -13
- data/spec/experiment_spec.rb +198 -118
- data/spec/goals_collection_spec.rb +18 -16
- data/spec/helper_spec.rb +454 -385
- data/spec/metric_spec.rb +14 -14
- data/spec/persistence/cookie_adapter_spec.rb +26 -11
- data/spec/persistence/dual_adapter_spec.rb +71 -71
- data/spec/persistence/redis_adapter_spec.rb +35 -27
- data/spec/persistence/session_adapter_spec.rb +2 -3
- data/spec/persistence_spec.rb +1 -2
- data/spec/redis_interface_spec.rb +25 -82
- data/spec/spec_helper.rb +35 -24
- data/spec/split_spec.rb +11 -11
- data/spec/support/cookies_mock.rb +1 -2
- data/spec/trial_spec.rb +102 -75
- data/spec/user_spec.rb +60 -29
- data/split.gemspec +22 -21
- metadata +43 -40
- data/.rubocop_todo.yml +0 -679
- data/.travis.yml +0 -60
- data/Appraisals +0 -19
- data/gemfiles/4.2.gemfile +0 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 80d095f07432d336e773b30c3c5a873b554dd682f65ace33886d1a83697d447a
|
|
4
|
+
data.tar.gz: 9a504a3c9d4c67391528e1640c2c7eac3cfcafe91305cadb901e48541726f04c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 68e5919618103f315fa9f4f0d18384392c02c01c680c13d50a77cfa0507bd19a1c01fb5c1db66619329c1ef38013c3ebe90e880f6af5c9bac9ba2d1a76b3963a
|
|
7
|
+
data.tar.gz: 1ae5fe0187e16b4bb3efc14a0e0ded4df98527fbc530dab4a89c544d133b1a7e8cd0adc6579f0f43ea3c84aa6899a2d123e97c5af105ce158e804a5ac8286728
|
data/.github/FUNDING.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
open_collective: split
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
name: split
|
|
2
|
+
|
|
3
|
+
on: [push]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
strategy:
|
|
8
|
+
matrix:
|
|
9
|
+
include:
|
|
10
|
+
- gemfile: 5.2.gemfile
|
|
11
|
+
ruby: 2.5
|
|
12
|
+
|
|
13
|
+
- gemfile: 5.2.gemfile
|
|
14
|
+
ruby: 2.6
|
|
15
|
+
|
|
16
|
+
- gemfile: 5.2.gemfile
|
|
17
|
+
ruby: 2.7
|
|
18
|
+
|
|
19
|
+
- gemfile: 6.0.gemfile
|
|
20
|
+
ruby: 2.5
|
|
21
|
+
|
|
22
|
+
- gemfile: 6.0.gemfile
|
|
23
|
+
ruby: 2.6
|
|
24
|
+
|
|
25
|
+
- gemfile: 6.0.gemfile
|
|
26
|
+
ruby: 2.7
|
|
27
|
+
|
|
28
|
+
- gemfile: 6.0.gemfile
|
|
29
|
+
ruby: '3.0'
|
|
30
|
+
|
|
31
|
+
- gemfile: 6.1.gemfile
|
|
32
|
+
ruby: '3.0'
|
|
33
|
+
|
|
34
|
+
- gemfile: 7.0.gemfile
|
|
35
|
+
ruby: '3.0'
|
|
36
|
+
|
|
37
|
+
- gemfile: 7.0.gemfile
|
|
38
|
+
ruby: '3.1'
|
|
39
|
+
|
|
40
|
+
- gemfile: 7.0.gemfile
|
|
41
|
+
ruby: '3.2'
|
|
42
|
+
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
|
|
45
|
+
services:
|
|
46
|
+
redis:
|
|
47
|
+
image: redis
|
|
48
|
+
ports: ['6379:6379']
|
|
49
|
+
options: >-
|
|
50
|
+
--health-cmd "redis-cli ping"
|
|
51
|
+
--health-interval 10s
|
|
52
|
+
--health-timeout 5s
|
|
53
|
+
--health-retries 5
|
|
54
|
+
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/checkout@v4
|
|
57
|
+
|
|
58
|
+
- uses: ruby/setup-ruby@v1
|
|
59
|
+
with:
|
|
60
|
+
ruby-version: ${{ matrix.ruby }}
|
|
61
|
+
|
|
62
|
+
- name: Install dependencies
|
|
63
|
+
run: |
|
|
64
|
+
bundle config set gemfile "${GITHUB_WORKSPACE}/gemfiles/${{ matrix.gemfile }}"
|
|
65
|
+
bundle install --jobs 4 --retry 3
|
|
66
|
+
|
|
67
|
+
- name: Display Ruby version
|
|
68
|
+
run: ruby -v
|
|
69
|
+
|
|
70
|
+
- name: Test
|
|
71
|
+
run: bundle exec rspec
|
|
72
|
+
env:
|
|
73
|
+
REDIS_URL: redis:6379
|
|
74
|
+
|
|
75
|
+
- name: Rubocop
|
|
76
|
+
run: bundle exec rubocop
|
data/.rubocop.yml
CHANGED
|
@@ -1,7 +1,180 @@
|
|
|
1
|
-
inherit_from: .rubocop_todo.yml
|
|
2
|
-
|
|
3
1
|
AllCops:
|
|
2
|
+
TargetRubyVersion: 2.5
|
|
3
|
+
DisabledByDefault: true
|
|
4
|
+
SuggestExtensions: false
|
|
4
5
|
Exclude:
|
|
5
|
-
- 'Appraisals'
|
|
6
6
|
- 'gemfiles/**/*'
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
Style/AndOr:
|
|
9
|
+
Enabled: true
|
|
10
|
+
|
|
11
|
+
Layout/CaseIndentation:
|
|
12
|
+
Enabled: true
|
|
13
|
+
|
|
14
|
+
Layout/ClosingHeredocIndentation:
|
|
15
|
+
Enabled: true
|
|
16
|
+
|
|
17
|
+
Layout/CommentIndentation:
|
|
18
|
+
Enabled: true
|
|
19
|
+
|
|
20
|
+
Layout/ElseAlignment:
|
|
21
|
+
Enabled: true
|
|
22
|
+
|
|
23
|
+
Layout/EndAlignment:
|
|
24
|
+
Enabled: true
|
|
25
|
+
EnforcedStyleAlignWith: variable
|
|
26
|
+
AutoCorrect: true
|
|
27
|
+
|
|
28
|
+
Layout/EmptyLineAfterMagicComment:
|
|
29
|
+
Enabled: true
|
|
30
|
+
|
|
31
|
+
Layout/EmptyLinesAroundAccessModifier:
|
|
32
|
+
Enabled: true
|
|
33
|
+
EnforcedStyle: only_before
|
|
34
|
+
|
|
35
|
+
Layout/EmptyLinesAroundBlockBody:
|
|
36
|
+
Enabled: true
|
|
37
|
+
|
|
38
|
+
Layout/EmptyLinesAroundClassBody:
|
|
39
|
+
Enabled: true
|
|
40
|
+
|
|
41
|
+
Layout/EmptyLinesAroundMethodBody:
|
|
42
|
+
Enabled: true
|
|
43
|
+
|
|
44
|
+
Layout/EmptyLinesAroundModuleBody:
|
|
45
|
+
Enabled: true
|
|
46
|
+
|
|
47
|
+
Style/HashSyntax:
|
|
48
|
+
Enabled: true
|
|
49
|
+
|
|
50
|
+
Layout/FirstArgumentIndentation:
|
|
51
|
+
Enabled: true
|
|
52
|
+
|
|
53
|
+
Layout/IndentationConsistency:
|
|
54
|
+
Enabled: true
|
|
55
|
+
EnforcedStyle: indented_internal_methods
|
|
56
|
+
|
|
57
|
+
Layout/IndentationWidth:
|
|
58
|
+
Enabled: true
|
|
59
|
+
|
|
60
|
+
Layout/LeadingCommentSpace:
|
|
61
|
+
Enabled: true
|
|
62
|
+
|
|
63
|
+
Layout/SpaceAfterColon:
|
|
64
|
+
Enabled: true
|
|
65
|
+
|
|
66
|
+
Layout/SpaceAfterComma:
|
|
67
|
+
Enabled: true
|
|
68
|
+
|
|
69
|
+
Layout/SpaceAfterSemicolon:
|
|
70
|
+
Enabled: true
|
|
71
|
+
|
|
72
|
+
Layout/SpaceAroundEqualsInParameterDefault:
|
|
73
|
+
Enabled: true
|
|
74
|
+
|
|
75
|
+
Layout/SpaceAroundKeyword:
|
|
76
|
+
Enabled: true
|
|
77
|
+
|
|
78
|
+
Layout/SpaceBeforeComma:
|
|
79
|
+
Enabled: true
|
|
80
|
+
|
|
81
|
+
Layout/SpaceBeforeComment:
|
|
82
|
+
Enabled: true
|
|
83
|
+
|
|
84
|
+
Layout/SpaceBeforeFirstArg:
|
|
85
|
+
Enabled: true
|
|
86
|
+
|
|
87
|
+
Style/DefWithParentheses:
|
|
88
|
+
Enabled: true
|
|
89
|
+
|
|
90
|
+
Style/MethodDefParentheses:
|
|
91
|
+
Enabled: true
|
|
92
|
+
|
|
93
|
+
Style/FrozenStringLiteralComment:
|
|
94
|
+
Enabled: true
|
|
95
|
+
EnforcedStyle: always
|
|
96
|
+
|
|
97
|
+
Style/RedundantFreeze:
|
|
98
|
+
Enabled: true
|
|
99
|
+
|
|
100
|
+
Layout/SpaceBeforeBlockBraces:
|
|
101
|
+
Enabled: true
|
|
102
|
+
|
|
103
|
+
Layout/SpaceInsideBlockBraces:
|
|
104
|
+
Enabled: true
|
|
105
|
+
EnforcedStyleForEmptyBraces: space
|
|
106
|
+
|
|
107
|
+
Layout/SpaceInsideHashLiteralBraces:
|
|
108
|
+
Enabled: true
|
|
109
|
+
|
|
110
|
+
Layout/SpaceInsideParens:
|
|
111
|
+
Enabled: true
|
|
112
|
+
|
|
113
|
+
Style/StringLiterals:
|
|
114
|
+
Enabled: true
|
|
115
|
+
EnforcedStyle: double_quotes
|
|
116
|
+
|
|
117
|
+
Layout/IndentationStyle:
|
|
118
|
+
Enabled: true
|
|
119
|
+
|
|
120
|
+
Layout/TrailingEmptyLines:
|
|
121
|
+
Enabled: true
|
|
122
|
+
|
|
123
|
+
Layout/TrailingWhitespace:
|
|
124
|
+
Enabled: true
|
|
125
|
+
|
|
126
|
+
Style/RedundantPercentQ:
|
|
127
|
+
Enabled: true
|
|
128
|
+
|
|
129
|
+
Lint/AmbiguousOperator:
|
|
130
|
+
Enabled: true
|
|
131
|
+
|
|
132
|
+
Lint/AmbiguousRegexpLiteral:
|
|
133
|
+
Enabled: true
|
|
134
|
+
|
|
135
|
+
Lint/ErbNewArguments:
|
|
136
|
+
Enabled: true
|
|
137
|
+
|
|
138
|
+
Lint/RequireParentheses:
|
|
139
|
+
Enabled: true
|
|
140
|
+
|
|
141
|
+
Lint/ShadowingOuterLocalVariable:
|
|
142
|
+
Enabled: true
|
|
143
|
+
|
|
144
|
+
Lint/RedundantStringCoercion:
|
|
145
|
+
Enabled: true
|
|
146
|
+
|
|
147
|
+
Lint/UriEscapeUnescape:
|
|
148
|
+
Enabled: true
|
|
149
|
+
|
|
150
|
+
Lint/UselessAssignment:
|
|
151
|
+
Enabled: true
|
|
152
|
+
|
|
153
|
+
Lint/DeprecatedClassMethods:
|
|
154
|
+
Enabled: true
|
|
155
|
+
|
|
156
|
+
Style/ParenthesesAroundCondition:
|
|
157
|
+
Enabled: true
|
|
158
|
+
|
|
159
|
+
Style/HashTransformKeys:
|
|
160
|
+
Enabled: true
|
|
161
|
+
|
|
162
|
+
Style/HashTransformValues:
|
|
163
|
+
Enabled: true
|
|
164
|
+
|
|
165
|
+
Style/RedundantBegin:
|
|
166
|
+
Enabled: true
|
|
167
|
+
|
|
168
|
+
Style/RedundantReturn:
|
|
169
|
+
Enabled: true
|
|
170
|
+
AllowMultipleReturnValues: true
|
|
171
|
+
|
|
172
|
+
Style/Semicolon:
|
|
173
|
+
Enabled: true
|
|
174
|
+
AllowAsExpressionSeparator: true
|
|
175
|
+
|
|
176
|
+
Style/ColonMethodCall:
|
|
177
|
+
Enabled: true
|
|
178
|
+
|
|
179
|
+
Style/TrivialAccessors:
|
|
180
|
+
Enabled: true
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,90 @@
|
|
|
1
|
+
# 4.0.4 (March 3rd, 2024)
|
|
2
|
+
|
|
3
|
+
Bugfixes:
|
|
4
|
+
- Better integration for EncapsulatedHelper when needing params/request info (@henrique-ft, #721 and #723)
|
|
5
|
+
|
|
6
|
+
Misc:
|
|
7
|
+
- Make specs compatible with newer Rack versions (@andrehjr, #722)
|
|
8
|
+
|
|
9
|
+
# 4.0.3 (November 15th, 2023)
|
|
10
|
+
|
|
11
|
+
Bugfixes:
|
|
12
|
+
- Do not throw error if alternativas have data that can lead to negative numbers for probability calculation (@andrehjr, #703)
|
|
13
|
+
- Do not persist invalid extra_info on ab_record_extra_info. (@trostli @andrehjr, #717)
|
|
14
|
+
- CROSSSLOT keys issue fix when using redis cluster (@naveen-chidhambaram, #710)
|
|
15
|
+
- Convert value to string before saving it in RedisAdapter (@Jealrock, #714)
|
|
16
|
+
- Fix deprecation warning with Redis 4.8.0 (@martingregoire, #701)
|
|
17
|
+
|
|
18
|
+
Misc:
|
|
19
|
+
- Add matrix as a default dependency (@andrehjr, #705)
|
|
20
|
+
- Add Ruby 3.2 to Github Actions (@andrehjr, #702)
|
|
21
|
+
- Update documentation regarding finding users outside a web session (@andrehjr, #716)
|
|
22
|
+
- Update actions/checkout to v4 (@andrehjr, #718)
|
|
23
|
+
|
|
24
|
+
# 4.0.2 (December 2nd, 2022)
|
|
25
|
+
|
|
26
|
+
Bugfixes:
|
|
27
|
+
- Stop crashing on non-hash json (@knarewski, #697)
|
|
28
|
+
- Handle when Rails is partially loaded as a Gem (@TSMMark, #687)
|
|
29
|
+
|
|
30
|
+
Features:
|
|
31
|
+
- Add support for redis-client, which does not automatically cast types to strings (@knarewski, #696)
|
|
32
|
+
- Add ability to initialize experiments (@robin-phung, #673)
|
|
33
|
+
|
|
34
|
+
Misc:
|
|
35
|
+
- Fix default branch name and gem metadata indentation (@ursm, #693)
|
|
36
|
+
- Update actions/checkout to v3 (@andrehjr, #683)
|
|
37
|
+
- Enforce double quotes (@andrehjr, #682)
|
|
38
|
+
- Fix Rubocop Style/* Offenses (@andrehjr, #681)
|
|
39
|
+
- Enable rubocop on Github Actions (@andrehjr, #680)
|
|
40
|
+
- Fix all Layout issues on the project (@andrehjr, #679)
|
|
41
|
+
- Fix Style/HashSyntax offenses (@andrehjr, #678)
|
|
42
|
+
- Remove usage of deprecated implicit block expectation from specs (@andrehjr, #677)
|
|
43
|
+
- Remove appraisals configuration (@andrehjr, #676)
|
|
44
|
+
- Add Ruby 3.1 (@andrehjr, #675)
|
|
45
|
+
- Encapsulate Split::Algorithms at our own module to avoid explicit calling rubystats everywhere (@andrehjr, #674)
|
|
46
|
+
|
|
47
|
+
## 4.0.1 (December 30th, 2021)
|
|
48
|
+
|
|
49
|
+
Bugfixes:
|
|
50
|
+
- ab_test must return metadata on error or if split is disabled/excluded user (@andrehjr, #622)
|
|
51
|
+
- Fix versioned experiments when used with allow_multiple_experiments=control (@andrehjr, #613)
|
|
52
|
+
- Only block Pinterest bot (@huoxito, #606)
|
|
53
|
+
- Respect experiment defaults when loading experiments in initializer. (@mattwd7, #599)
|
|
54
|
+
- Removes metadata key when it updated to nil (@andrehjr, #633)
|
|
55
|
+
- Force experiment does not count for metrics (@andrehjr, #637)
|
|
56
|
+
- Fix cleanup_old_versions! misbehaviour (@serggl, #661)
|
|
57
|
+
|
|
58
|
+
Features:
|
|
59
|
+
- Make goals accessible via on_trial_complete callbacks (@robin-phung, #625)
|
|
60
|
+
- Replace usage of SimpleRandom with RubyStats(Used for Beta Distribution RNG) (@andrehjr, #616)
|
|
61
|
+
- Introduce enable/disable experiment cohorting (@robin-phung, #615)
|
|
62
|
+
- Add on_experiment_winner_choose callback (@GenaMinenkov, #574)
|
|
63
|
+
- Add Split::Cache to reduce load on Redis (@rdh, #648)
|
|
64
|
+
- Caching based optimization in the experiment#save path (@amangup, #652)
|
|
65
|
+
- Adds config option for cookie domain (@joedelia, #664)
|
|
66
|
+
|
|
67
|
+
Misc:
|
|
68
|
+
- Drop support for Ruby < 2.5 (@andrehjr, #627)
|
|
69
|
+
- Drop support for Rails < 5 (@andrehjr, #607)
|
|
70
|
+
- Bump minimum required redis to 4.2 (@andrehjr, #628)
|
|
71
|
+
- Removed repeated loading from config (@robin-phung, #619)
|
|
72
|
+
- Simplify RedisInterface usage when persisting Experiment alternatives (@andrehjr, #632)
|
|
73
|
+
- Remove redis_url impl. Deprecated on version 2.2 (@andrehjr, #631)
|
|
74
|
+
- Remove thread_safe config as redis-rb is thread_safe by default (@andrehjr, #630)
|
|
75
|
+
- Fix typo of in `Split::Trial` class variable (TomasBarry, #644)
|
|
76
|
+
- Single HSET to update values, instead of multiple ones (@andrehjr, #640)
|
|
77
|
+
- Use Redis#hmset to keep compatibility with Redis < 4.0 (@andrehjr, #659)
|
|
78
|
+
- Remove 'set' parsing for alternatives. Sets were used as storage and deprecated on 0.x (@andrehjr, #639)
|
|
79
|
+
- Adding documentation related to what is stored on cookies. (@andrehjr, #634)
|
|
80
|
+
- Keep railtie defined under the Split gem namespace (@avit, #666)
|
|
81
|
+
- Update RSpec helper to support block syntax (@clowder, #665)
|
|
82
|
+
|
|
83
|
+
## 3.4.1 (November 12th, 2019)
|
|
84
|
+
|
|
85
|
+
Bugfixes:
|
|
86
|
+
- Reference ActionController directly when including split helpers, to avoid breaking Rails API Controllers (@andrehjr, #602)
|
|
87
|
+
|
|
1
88
|
## 3.4.0 (November 9th, 2019)
|
|
2
89
|
|
|
3
90
|
Features:
|
data/CONTRIBUTING.md
CHANGED
|
@@ -25,7 +25,7 @@ Want to contribute to Split? That's great! Here are a couple of guidelines that
|
|
|
25
25
|
|
|
26
26
|
## Setup instructions
|
|
27
27
|
|
|
28
|
-
You can find in-depth instructions to install in our [README](https://github.com/splitrb/split/blob/
|
|
28
|
+
You can find in-depth instructions to install in our [README](https://github.com/splitrb/split/blob/main/README.md).
|
|
29
29
|
|
|
30
30
|
*Note*: Split requires Ruby 1.9.2 or higher.
|
|
31
31
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# [Split](https://libraries.io/rubygems/split)
|
|
2
2
|
|
|
3
3
|
[](http://badge.fury.io/rb/split)
|
|
4
|
-
|
|
4
|
+

|
|
5
5
|
[](https://codeclimate.com/github/splitrb/split)
|
|
6
6
|
[](https://codeclimate.com/github/splitrb/split/coverage)
|
|
7
7
|
[](https://github.com/RichardLitt/standard-readme)
|
|
@@ -19,11 +19,13 @@ Split is designed to be hacker friendly, allowing for maximum customisation and
|
|
|
19
19
|
|
|
20
20
|
### Requirements
|
|
21
21
|
|
|
22
|
-
Split
|
|
22
|
+
Split v4.0+ is currently tested with Ruby >= 2.5 and Rails >= 5.2.
|
|
23
|
+
|
|
24
|
+
If your project requires compatibility with Ruby 2.4.x or older Rails versions. You can try v3.0 or v0.8.0(for Ruby 1.9.3)
|
|
23
25
|
|
|
24
26
|
Split uses Redis as a datastore.
|
|
25
27
|
|
|
26
|
-
Split only supports Redis
|
|
28
|
+
Split only supports Redis 4.0 or greater.
|
|
27
29
|
|
|
28
30
|
If you're on OS X, Homebrew is the simplest way to install Redis:
|
|
29
31
|
|
|
@@ -175,8 +177,10 @@ module SplitHelper
|
|
|
175
177
|
# use_ab_test(signup_form: "single_page", pricing: "show_enterprise_prices")
|
|
176
178
|
#
|
|
177
179
|
def use_ab_test(alternatives_by_experiment)
|
|
178
|
-
allow_any_instance_of(Split::Helper).to receive(:ab_test) do |_receiver, experiment|
|
|
179
|
-
alternatives_by_experiment.fetch(experiment) { |key| raise "Unknown experiment '#{key}'" }
|
|
180
|
+
allow_any_instance_of(Split::Helper).to receive(:ab_test) do |_receiver, experiment, &block|
|
|
181
|
+
variant = alternatives_by_experiment.fetch(experiment) { |key| raise "Unknown experiment '#{key}'" }
|
|
182
|
+
block.call(variant) unless block.nil?
|
|
183
|
+
variant
|
|
180
184
|
end
|
|
181
185
|
end
|
|
182
186
|
end
|
|
@@ -263,7 +267,7 @@ Split.configure do |config|
|
|
|
263
267
|
end
|
|
264
268
|
```
|
|
265
269
|
|
|
266
|
-
|
|
270
|
+
When using the cookie persistence, Split stores data into an anonymous tracking cookie named 'split', which expires in 1 year. To change that, set the `persistence_cookie_length` in the configuration (unit of time in seconds).
|
|
267
271
|
|
|
268
272
|
```ruby
|
|
269
273
|
Split.configure do |config|
|
|
@@ -272,6 +276,8 @@ Split.configure do |config|
|
|
|
272
276
|
end
|
|
273
277
|
```
|
|
274
278
|
|
|
279
|
+
The data stored consists of the experiment name and the variants the user is in. Example: { "experiment_name" => "variant_a" }
|
|
280
|
+
|
|
275
281
|
__Note:__ Using cookies depends on `ActionDispatch::Cookies` or any identical API
|
|
276
282
|
|
|
277
283
|
#### Redis
|
|
@@ -386,6 +392,8 @@ Split.configure do |config|
|
|
|
386
392
|
# before experiment reset or deleted
|
|
387
393
|
config.on_before_experiment_reset = -> (example) { # Do something on reset }
|
|
388
394
|
config.on_before_experiment_delete = -> (experiment) { # Do something else on delete }
|
|
395
|
+
# after experiment winner had been set
|
|
396
|
+
config.on_experiment_winner_choose = -> (experiment) { # Do something on winner choose }
|
|
389
397
|
end
|
|
390
398
|
```
|
|
391
399
|
|
|
@@ -644,7 +652,7 @@ The API to define goals for an experiment is this:
|
|
|
644
652
|
ab_test({link_color: ["purchase", "refund"]}, "red", "blue")
|
|
645
653
|
```
|
|
646
654
|
|
|
647
|
-
or you can
|
|
655
|
+
or you can define them in a configuration file:
|
|
648
656
|
|
|
649
657
|
```ruby
|
|
650
658
|
Split.configure do |config|
|
|
@@ -752,6 +760,20 @@ split_config = YAML.load_file(Rails.root.join('config', 'split.yml'))
|
|
|
752
760
|
Split.redis = split_config[Rails.env]
|
|
753
761
|
```
|
|
754
762
|
|
|
763
|
+
### Redis Caching (v4.0+)
|
|
764
|
+
|
|
765
|
+
In some high-volume usage scenarios, Redis load can be incurred by repeated
|
|
766
|
+
fetches for fairly static data. Enabling caching will reduce this load.
|
|
767
|
+
|
|
768
|
+
```ruby
|
|
769
|
+
Split.configuration.cache = true
|
|
770
|
+
````
|
|
771
|
+
|
|
772
|
+
This currently caches:
|
|
773
|
+
- `Split::ExperimentCatalog.find`
|
|
774
|
+
- `Split::Experiment.start_time`
|
|
775
|
+
- `Split::Experiment.winner`
|
|
776
|
+
|
|
755
777
|
## Namespaces
|
|
756
778
|
|
|
757
779
|
If you're running multiple, separate instances of Split you may want
|
|
@@ -768,7 +790,7 @@ library. To configure Split to use `Redis::Namespace`, do the following:
|
|
|
768
790
|
```
|
|
769
791
|
|
|
770
792
|
2. Configure `Split.redis` to use a `Redis::Namespace` instance (possible in an
|
|
771
|
-
|
|
793
|
+
initializer):
|
|
772
794
|
|
|
773
795
|
```ruby
|
|
774
796
|
redis = Redis.new(url: ENV['REDIS_URL']) # or whatever config you want
|
|
@@ -785,10 +807,16 @@ conduct experiments that are not tied to a web session.
|
|
|
785
807
|
```ruby
|
|
786
808
|
# create a new experiment
|
|
787
809
|
experiment = Split::ExperimentCatalog.find_or_create('color', 'red', 'blue')
|
|
810
|
+
|
|
811
|
+
# find the user
|
|
812
|
+
user = Split::User.find(user_id, :redis)
|
|
813
|
+
|
|
788
814
|
# create a new trial
|
|
789
|
-
trial = Split::Trial.new(:
|
|
815
|
+
trial = Split::Trial.new(user: user, experiment: experiment)
|
|
816
|
+
|
|
790
817
|
# run trial
|
|
791
818
|
trial.choose!
|
|
819
|
+
|
|
792
820
|
# get the result, returns either red or blue
|
|
793
821
|
trial.alternative.name
|
|
794
822
|
|
data/Rakefile
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env rake
|
|
2
2
|
# frozen_string_literal: true
|
|
3
|
-
require 'bundler/gem_tasks'
|
|
4
|
-
require 'rspec/core/rake_task'
|
|
5
|
-
require 'appraisal'
|
|
6
3
|
|
|
7
|
-
|
|
4
|
+
require "bundler/gem_tasks"
|
|
5
|
+
require "rspec/core/rake_task"
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
RSpec::Core::RakeTask.new("spec")
|
|
8
|
+
|
|
9
|
+
task default: :spec
|
data/gemfiles/5.2.gemfile
CHANGED
data/gemfiles/6.0.gemfile
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
# Selects alternative with minimum count of participants
|
|
3
4
|
# If all counts are even (i.e. all are minimum), samples from all possible alternatives
|
|
4
5
|
|
|
@@ -11,12 +12,11 @@ module Split
|
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
private
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
end
|
|
15
|
+
def minimum_participant_alternatives(alternatives)
|
|
16
|
+
alternatives_by_count = alternatives.group_by(&:participant_count)
|
|
17
|
+
min_group = alternatives_by_count.min_by { |k, v| k }
|
|
18
|
+
min_group.last
|
|
19
|
+
end
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
module Split
|
|
3
4
|
module Algorithms
|
|
4
5
|
module WeightedSample
|
|
@@ -8,7 +9,7 @@ module Split
|
|
|
8
9
|
total = weights.inject(:+)
|
|
9
10
|
point = rand * total
|
|
10
11
|
|
|
11
|
-
experiment.alternatives.zip(weights).each do |n,w|
|
|
12
|
+
experiment.alternatives.zip(weights).each do |n, w|
|
|
12
13
|
return n if w >= point
|
|
13
14
|
point -= w
|
|
14
15
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
# A multi-armed bandit implementation inspired by
|
|
3
4
|
# @aaronsw and victorykit/whiplash
|
|
4
|
-
require 'simple-random'
|
|
5
5
|
|
|
6
6
|
module Split
|
|
7
7
|
module Algorithms
|
|
@@ -12,26 +12,25 @@ module Split
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
private
|
|
15
|
+
def arm_guess(participants, completions)
|
|
16
|
+
a = [participants, 0].max
|
|
17
|
+
b = [participants-completions, 0].max
|
|
18
|
+
Split::Algorithms.beta_distribution_rng(a + fairness_constant, b + fairness_constant)
|
|
19
|
+
end
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
alternatives.each do |alternative|
|
|
25
|
-
guesses[alternative.name] = arm_guess(alternative.participant_count, alternative.all_completed_count)
|
|
21
|
+
def best_guess(alternatives)
|
|
22
|
+
guesses = {}
|
|
23
|
+
alternatives.each do |alternative|
|
|
24
|
+
guesses[alternative.name] = arm_guess(alternative.participant_count, alternative.all_completed_count)
|
|
25
|
+
end
|
|
26
|
+
gmax = guesses.values.max
|
|
27
|
+
best = guesses.keys.select { |name| guesses[name] == gmax }
|
|
28
|
+
best.sample
|
|
26
29
|
end
|
|
27
|
-
gmax = guesses.values.max
|
|
28
|
-
best = guesses.keys.select { |name| guesses[name] == gmax }
|
|
29
|
-
best.sample
|
|
30
|
-
end
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
def fairness_constant
|
|
32
|
+
7
|
|
33
|
+
end
|
|
35
34
|
end
|
|
36
35
|
end
|
|
37
36
|
end
|