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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/dependabot.yml +7 -0
  4. data/.github/workflows/ci.yml +76 -0
  5. data/.rubocop.yml +177 -4
  6. data/CHANGELOG.md +87 -0
  7. data/CONTRIBUTING.md +1 -1
  8. data/Gemfile +2 -1
  9. data/README.md +37 -9
  10. data/Rakefile +5 -5
  11. data/gemfiles/5.2.gemfile +1 -3
  12. data/gemfiles/6.0.gemfile +1 -3
  13. data/gemfiles/{5.0.gemfile → 6.1.gemfile} +2 -4
  14. data/gemfiles/{5.1.gemfile → 7.0.gemfile} +2 -4
  15. data/lib/split/algorithms/block_randomization.rb +6 -6
  16. data/lib/split/algorithms/weighted_sample.rb +2 -1
  17. data/lib/split/algorithms/whiplash.rb +17 -18
  18. data/lib/split/algorithms.rb +14 -0
  19. data/lib/split/alternative.rb +22 -22
  20. data/lib/split/cache.rb +27 -0
  21. data/lib/split/combined_experiments_helper.rb +5 -4
  22. data/lib/split/configuration.rb +89 -94
  23. data/lib/split/dashboard/helpers.rb +7 -7
  24. data/lib/split/dashboard/pagination_helpers.rb +54 -54
  25. data/lib/split/dashboard/paginator.rb +1 -0
  26. data/lib/split/dashboard/public/dashboard.js +10 -0
  27. data/lib/split/dashboard/public/style.css +10 -2
  28. data/lib/split/dashboard/views/_controls.erb +13 -0
  29. data/lib/split/dashboard/views/_experiment.erb +2 -1
  30. data/lib/split/dashboard/views/index.erb +19 -4
  31. data/lib/split/dashboard.rb +42 -21
  32. data/lib/split/encapsulated_helper.rb +15 -8
  33. data/lib/split/engine.rb +1 -0
  34. data/lib/split/exceptions.rb +1 -0
  35. data/lib/split/experiment.rb +151 -124
  36. data/lib/split/experiment_catalog.rb +7 -8
  37. data/lib/split/extensions/string.rb +2 -1
  38. data/lib/split/goals_collection.rb +9 -10
  39. data/lib/split/helper.rb +50 -23
  40. data/lib/split/metric.rb +6 -6
  41. data/lib/split/persistence/cookie_adapter.rb +46 -44
  42. data/lib/split/persistence/dual_adapter.rb +7 -8
  43. data/lib/split/persistence/redis_adapter.rb +8 -4
  44. data/lib/split/persistence/session_adapter.rb +1 -2
  45. data/lib/split/persistence.rb +8 -6
  46. data/lib/split/redis_interface.rb +15 -29
  47. data/lib/split/trial.rb +43 -34
  48. data/lib/split/user.rb +25 -14
  49. data/lib/split/version.rb +2 -4
  50. data/lib/split/zscore.rb +2 -3
  51. data/lib/split.rb +34 -27
  52. data/spec/algorithms/block_randomization_spec.rb +6 -5
  53. data/spec/algorithms/weighted_sample_spec.rb +6 -5
  54. data/spec/algorithms/whiplash_spec.rb +4 -5
  55. data/spec/alternative_spec.rb +35 -36
  56. data/spec/cache_spec.rb +84 -0
  57. data/spec/combined_experiments_helper_spec.rb +18 -17
  58. data/spec/configuration_spec.rb +41 -45
  59. data/spec/dashboard/pagination_helpers_spec.rb +69 -67
  60. data/spec/dashboard/paginator_spec.rb +10 -9
  61. data/spec/dashboard_helpers_spec.rb +19 -18
  62. data/spec/dashboard_spec.rb +122 -38
  63. data/spec/encapsulated_helper_spec.rb +46 -22
  64. data/spec/experiment_catalog_spec.rb +14 -13
  65. data/spec/experiment_spec.rb +198 -118
  66. data/spec/goals_collection_spec.rb +18 -16
  67. data/spec/helper_spec.rb +454 -385
  68. data/spec/metric_spec.rb +14 -14
  69. data/spec/persistence/cookie_adapter_spec.rb +26 -11
  70. data/spec/persistence/dual_adapter_spec.rb +71 -71
  71. data/spec/persistence/redis_adapter_spec.rb +35 -27
  72. data/spec/persistence/session_adapter_spec.rb +2 -3
  73. data/spec/persistence_spec.rb +1 -2
  74. data/spec/redis_interface_spec.rb +25 -82
  75. data/spec/spec_helper.rb +35 -24
  76. data/spec/split_spec.rb +11 -11
  77. data/spec/support/cookies_mock.rb +1 -2
  78. data/spec/trial_spec.rb +102 -75
  79. data/spec/user_spec.rb +60 -29
  80. data/split.gemspec +22 -21
  81. metadata +43 -40
  82. data/.rubocop_todo.yml +0 -679
  83. data/.travis.yml +0 -60
  84. data/Appraisals +0 -19
  85. data/gemfiles/4.2.gemfile +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c51ebd6a368635471d5c3f820488065669ac7dc1ad7ed7eba61f2c4154ca42b7
4
- data.tar.gz: 7fabe9a63a07037b144b91a11c89675d48a6ec9a5883e47bf8193584c461d003
3
+ metadata.gz: 80d095f07432d336e773b30c3c5a873b554dd682f65ace33886d1a83697d447a
4
+ data.tar.gz: 9a504a3c9d4c67391528e1640c2c7eac3cfcafe91305cadb901e48541726f04c
5
5
  SHA512:
6
- metadata.gz: 49c32cc9c769184b79716956046604e8f63de0fba0a87036f6cf2809803e74c376887db47e3a472d9b5acfb5172eb5d0bcea1c700bf74f799a5d259e0cf3dd49
7
- data.tar.gz: b930240fd33d85f594fef51f756100f925581fc1ead0dd910a1a51417362c68ab03aea6adbb1b50076db78114a1bc842fd2cd2d4a9462b9d50aa43c25b166df8
6
+ metadata.gz: 68e5919618103f315fa9f4f0d18384392c02c01c680c13d50a77cfa0507bd19a1c01fb5c1db66619329c1ef38013c3ebe90e880f6af5c9bac9ba2d1a76b3963a
7
+ data.tar.gz: 1ae5fe0187e16b4bb3efc14a0e0ded4df98527fbc530dab4a89c544d133b1a7e8cd0adc6579f0f43ea3c84aa6899a2d123e97c5af105ce158e804a5ac8286728
@@ -0,0 +1 @@
1
+ open_collective: split
@@ -0,0 +1,7 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ open-pull-requests-limit: 10
@@ -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
- - 'spec/**/*.rb'
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/master/README.md).
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
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  source "https://rubygems.org"
3
4
 
4
5
  gemspec
5
6
 
6
- gem "appraisal"
7
+ gem "rubocop", require: false
7
8
  gem "codeclimate-test-reporter"
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # [Split](https://libraries.io/rubygems/split)
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/split.svg)](http://badge.fury.io/rb/split)
4
- [![Build Status](https://secure.travis-ci.org/splitrb/split.svg?branch=master)](https://travis-ci.org/splitrb/split)
4
+ ![Build status](https://github.com/splitrb/split/actions/workflows/ci.yml/badge.svg?branch=main)
5
5
  [![Code Climate](https://codeclimate.com/github/splitrb/split/badges/gpa.svg)](https://codeclimate.com/github/splitrb/split)
6
6
  [![Test Coverage](https://codeclimate.com/github/splitrb/split/badges/coverage.svg)](https://codeclimate.com/github/splitrb/split/coverage)
7
7
  [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](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 currently requires Ruby 1.9.3 or higher. If your project requires compatibility with Ruby 1.8.x and Rails 2.3, please use v0.8.0.
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 2.0 or greater.
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
- By default, cookies will expire in 1 year. To change that, set the `persistence_cookie_length` in the configuration (unit of time in seconds).
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 you can define them in a configuration file:
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
- intializer):
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(:experiment => experiment)
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
- RSpec::Core::RakeTask.new('spec')
4
+ require "bundler/gem_tasks"
5
+ require "rspec/core/rake_task"
8
6
 
9
- task :default => :spec
7
+ RSpec::Core::RakeTask.new("spec")
8
+
9
+ task default: :spec
data/gemfiles/5.2.gemfile CHANGED
@@ -1,8 +1,6 @@
1
- # This file was generated by Appraisal
2
-
3
1
  source "https://rubygems.org"
4
2
 
5
- gem "appraisal"
3
+ gem "rubocop", require: false
6
4
  gem "codeclimate-test-reporter"
7
5
  gem "rails", "~> 5.2"
8
6
 
data/gemfiles/6.0.gemfile CHANGED
@@ -1,8 +1,6 @@
1
- # This file was generated by Appraisal
2
-
3
1
  source "https://rubygems.org"
4
2
 
5
- gem "appraisal"
3
+ gem "rubocop", require: false
6
4
  gem "codeclimate-test-reporter"
7
5
  gem "rails", "~> 6.0"
8
6
 
@@ -1,9 +1,7 @@
1
- # This file was generated by Appraisal
2
-
3
1
  source "https://rubygems.org"
4
2
 
5
- gem "appraisal"
3
+ gem "rubocop", require: false
6
4
  gem "codeclimate-test-reporter"
7
- gem "rails", "~> 5.0"
5
+ gem "rails", "~> 6.1"
8
6
 
9
7
  gemspec path: "../"
@@ -1,9 +1,7 @@
1
- # This file was generated by Appraisal
2
-
3
1
  source "https://rubygems.org"
4
2
 
5
- gem "appraisal"
3
+ gem "rubocop", require: false
6
4
  gem "codeclimate-test-reporter"
7
- gem "rails", "~> 5.1"
5
+ gem "rails", "~> 7.0"
8
6
 
9
7
  gemspec path: "../"
@@ -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
- 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
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
- def arm_guess(participants, completions)
17
- a = [participants, 0].max
18
- b = [participants-completions, 0].max
19
- s = SimpleRandom.new; s.set_seed; s.beta(a+fairness_constant, b+fairness_constant)
20
- end
21
-
22
- def best_guess(alternatives)
23
- guesses = {}
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
- def fairness_constant
33
- 7
34
- end
31
+ def fairness_constant
32
+ 7
33
+ end
35
34
  end
36
35
  end
37
36
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "matrix"
4
+ require "rubystats"
5
+
6
+ module Split
7
+ module Algorithms
8
+ class << self
9
+ def beta_distribution_rng(a, b)
10
+ Rubystats::BetaDistribution.new(a, b).rng
11
+ end
12
+ end
13
+ end
14
+ end