split 4.0.0.pre2 → 4.0.2

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +14 -1
  3. data/.rubocop.yml +2 -5
  4. data/CHANGELOG.md +26 -2
  5. data/CONTRIBUTING.md +1 -1
  6. data/Gemfile +2 -1
  7. data/README.md +4 -2
  8. data/Rakefile +4 -5
  9. data/gemfiles/5.2.gemfile +1 -3
  10. data/gemfiles/6.0.gemfile +1 -3
  11. data/gemfiles/{5.0.gemfile → 6.1.gemfile} +2 -4
  12. data/gemfiles/{5.1.gemfile → 7.0.gemfile} +3 -4
  13. data/lib/split/algorithms/block_randomization.rb +5 -6
  14. data/lib/split/algorithms/whiplash.rb +16 -18
  15. data/lib/split/algorithms.rb +22 -0
  16. data/lib/split/alternative.rb +21 -22
  17. data/lib/split/cache.rb +0 -1
  18. data/lib/split/combined_experiments_helper.rb +4 -4
  19. data/lib/split/configuration.rb +83 -84
  20. data/lib/split/dashboard/helpers.rb +6 -7
  21. data/lib/split/dashboard/pagination_helpers.rb +53 -54
  22. data/lib/split/dashboard/public/style.css +5 -2
  23. data/lib/split/dashboard/views/index.erb +19 -4
  24. data/lib/split/dashboard.rb +29 -23
  25. data/lib/split/encapsulated_helper.rb +4 -6
  26. data/lib/split/experiment.rb +84 -88
  27. data/lib/split/experiment_catalog.rb +6 -5
  28. data/lib/split/extensions/string.rb +1 -1
  29. data/lib/split/goals_collection.rb +8 -10
  30. data/lib/split/helper.rb +19 -19
  31. data/lib/split/metric.rb +4 -5
  32. data/lib/split/persistence/cookie_adapter.rb +44 -47
  33. data/lib/split/persistence/dual_adapter.rb +7 -8
  34. data/lib/split/persistence/redis_adapter.rb +2 -3
  35. data/lib/split/persistence/session_adapter.rb +0 -2
  36. data/lib/split/persistence.rb +4 -4
  37. data/lib/split/redis_interface.rb +1 -2
  38. data/lib/split/trial.rb +23 -24
  39. data/lib/split/user.rb +12 -13
  40. data/lib/split/version.rb +1 -1
  41. data/lib/split/zscore.rb +1 -3
  42. data/lib/split.rb +26 -25
  43. data/spec/algorithms/block_randomization_spec.rb +6 -5
  44. data/spec/algorithms/weighted_sample_spec.rb +6 -5
  45. data/spec/algorithms/whiplash_spec.rb +4 -5
  46. data/spec/alternative_spec.rb +35 -36
  47. data/spec/cache_spec.rb +15 -19
  48. data/spec/combined_experiments_helper_spec.rb +18 -17
  49. data/spec/configuration_spec.rb +32 -38
  50. data/spec/dashboard/pagination_helpers_spec.rb +69 -67
  51. data/spec/dashboard/paginator_spec.rb +10 -9
  52. data/spec/dashboard_helpers_spec.rb +19 -18
  53. data/spec/dashboard_spec.rb +67 -35
  54. data/spec/encapsulated_helper_spec.rb +12 -14
  55. data/spec/experiment_catalog_spec.rb +14 -13
  56. data/spec/experiment_spec.rb +121 -123
  57. data/spec/goals_collection_spec.rb +17 -15
  58. data/spec/helper_spec.rb +379 -382
  59. data/spec/metric_spec.rb +14 -14
  60. data/spec/persistence/cookie_adapter_spec.rb +23 -8
  61. data/spec/persistence/dual_adapter_spec.rb +71 -71
  62. data/spec/persistence/redis_adapter_spec.rb +25 -26
  63. data/spec/persistence/session_adapter_spec.rb +2 -3
  64. data/spec/persistence_spec.rb +1 -2
  65. data/spec/redis_interface_spec.rb +16 -14
  66. data/spec/spec_helper.rb +15 -13
  67. data/spec/split_spec.rb +11 -11
  68. data/spec/support/cookies_mock.rb +1 -2
  69. data/spec/trial_spec.rb +61 -60
  70. data/spec/user_spec.rb +36 -36
  71. data/split.gemspec +20 -20
  72. metadata +9 -10
  73. data/.rubocop_todo.yml +0 -226
  74. data/Appraisals +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8bc6c9cfc3470eb28463fe0d286124b8ad89132e458963cfea9035f82d2dc484
4
- data.tar.gz: 5cacccff1115b056ecafcea231ea27fff5d564ee9630b4279d8ce1fa5d8282ca
3
+ metadata.gz: f1c97063d4d1ccf4c2cd5dfd2e83b98b3f55a934b9dfa9b5862ede3ee5b8c58c
4
+ data.tar.gz: 28e30003a6d2059baf91482f5ac9a97b0b7d2e37c61a425710cbf1adf21a4a83
5
5
  SHA512:
6
- metadata.gz: bca228077d8dbecf7e06f21f411070effcc520c64d09b055c41fea4da7d786bedb81dbdd4f389a20f9166e08d9abf26ff75ea60bd1daf43c2555f5621adbb1e0
7
- data.tar.gz: ad2aca522d8d7f7bad54ee256c8626dd7747ae6c65aaf6da40d0de4c56d78f2bddd34af4c22be9d9fc943538446e523bf1f6ae7bf43e59505eddd350507b168d
6
+ metadata.gz: 988f115f0f96870188221552cca45f6a7207f548500dbf2a5446abaa47ac7365571644a5a231443ca7616182b68c3139f8d62fdcf18d1593fca8898092a332e2
7
+ data.tar.gz: b6a668159d8b6fe9529bae5bc650f120b9de1bdf593bc89d0d949a2e07e8cfda7c96b4c12e5e4615696cf31b845215f95ce60b4abcb9ff3d7ef056669a4ae51d
@@ -28,6 +28,16 @@ jobs:
28
28
  - gemfile: 6.0.gemfile
29
29
  ruby: '3.0'
30
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
+
31
41
  runs-on: ubuntu-latest
32
42
 
33
43
  services:
@@ -41,7 +51,7 @@ jobs:
41
51
  --health-retries 5
42
52
 
43
53
  steps:
44
- - uses: actions/checkout@v2
54
+ - uses: actions/checkout@v3
45
55
 
46
56
  - uses: ruby/setup-ruby@v1
47
57
  with:
@@ -59,3 +69,6 @@ jobs:
59
69
  run: bundle exec rspec
60
70
  env:
61
71
  REDIS_URL: redis:6379
72
+
73
+ - name: Rubocop
74
+ run: bundle exec rubocop
data/.rubocop.yml CHANGED
@@ -1,12 +1,9 @@
1
- inherit_from: .rubocop_todo.yml
2
-
3
1
  AllCops:
4
2
  TargetRubyVersion: 2.5
5
3
  DisabledByDefault: true
4
+ SuggestExtensions: false
6
5
  Exclude:
7
- - 'Appraisals'
8
6
  - 'gemfiles/**/*'
9
- - 'spec/**/*.rb'
10
7
 
11
8
  Style/AndOr:
12
9
  Enabled: true
@@ -114,7 +111,7 @@ Layout/SpaceInsideParens:
114
111
  Enabled: true
115
112
 
116
113
  Style/StringLiterals:
117
- Enabled: false
114
+ Enabled: true
118
115
  EnforcedStyle: double_quotes
119
116
 
120
117
  Layout/IndentationStyle:
data/CHANGELOG.md CHANGED
@@ -1,4 +1,27 @@
1
- ## 4.0.0.pre2
1
+ # 4.0.2 (December 2nd, 2022)
2
+
3
+ Bugfixes:
4
+ - Stop crashing on non-hash json (@knarewski, #697)
5
+ - Handle when Rails is partially loaded as a Gem (@TSMMark, #687)
6
+
7
+ Features:
8
+ - Add support for redis-client, which does not automatically cast types to strings (@knarewski, #696)
9
+ - Add ability to initialize experiments (@robin-phung, #673)
10
+
11
+ Misc:
12
+ - Fix default branch name and gem metadata indentation (@ursm, #693)
13
+ - Update actions/checkout to v3 (@andrehjr, #683)
14
+ - Enforce double quotes (@andrehjr, #682)
15
+ - Fix Rubocop Style/* Offenses (@andrehjr, #681)
16
+ - Enable rubocop on Github Actions (@andrehjr, #680)
17
+ - Fix all Layout issues on the project (@andrehjr, #679)
18
+ - Fix Style/HashSyntax offenses (@andrehjr, #678)
19
+ - Remove usage of deprecated implicit block expectation from specs (@andrehjr, #677)
20
+ - Remove appraisals configuration (@andrehjr, #676)
21
+ - Add Ruby 3.1 (@andrehjr, #675)
22
+ - Encapsulate Split::Algorithms at our own module to avoid explicit calling rubystats everywhere (@andrehjr, #674)
23
+
24
+ ## 4.0.1 (December 30th, 2021)
2
25
 
3
26
  Bugfixes:
4
27
  - ab_test must return metadata on error or if split is disabled/excluded user (@andrehjr, #622)
@@ -7,7 +30,7 @@ Bugfixes:
7
30
  - Respect experiment defaults when loading experiments in initializer. (@mattwd7, #599)
8
31
  - Removes metadata key when it updated to nil (@andrehjr, #633)
9
32
  - Force experiment does not count for metrics (@andrehjr, #637)
10
- - Fix cleanup_old_versions! misbehaviour (@serggi, #661)
33
+ - Fix cleanup_old_versions! misbehaviour (@serggl, #661)
11
34
 
12
35
  Features:
13
36
  - Make goals accessible via on_trial_complete callbacks (@robin-phung, #625)
@@ -32,6 +55,7 @@ Misc:
32
55
  - Remove 'set' parsing for alternatives. Sets were used as storage and deprecated on 0.x (@andrehjr, #639)
33
56
  - Adding documentation related to what is stored on cookies. (@andrehjr, #634)
34
57
  - Keep railtie defined under the Split gem namespace (@avit, #666)
58
+ - Update RSpec helper to support block syntax (@clowder, #665)
35
59
 
36
60
  ## 3.4.1 (November 12th, 2019)
37
61
 
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
@@ -4,5 +4,6 @@ source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
6
 
7
- gem "appraisal"
7
+ gem "rubocop", require: false
8
+ gem "matrix"
8
9
  gem "codeclimate-test-reporter"
data/README.md CHANGED
@@ -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
 
data/Rakefile CHANGED
@@ -1,10 +1,9 @@
1
1
  #!/usr/bin/env rake
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'bundler/gem_tasks'
5
- require 'rspec/core/rake_task'
6
- require 'appraisal'
4
+ require "bundler/gem_tasks"
5
+ require "rspec/core/rake_task"
7
6
 
8
- RSpec::Core::RakeTask.new('spec')
7
+ RSpec::Core::RakeTask.new("spec")
9
8
 
10
- task :default => :spec
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,8 @@
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"
6
+ gem "matrix"
8
7
 
9
8
  gemspec path: "../"
@@ -12,12 +12,11 @@ module Split
12
12
  end
13
13
 
14
14
  private
15
-
16
- def minimum_participant_alternatives(alternatives)
17
- alternatives_by_count = alternatives.group_by(&:participant_count)
18
- min_group = alternatives_by_count.min_by { |k, v| k }
19
- min_group.last
20
- 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
21
20
  end
22
21
  end
23
22
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  # A multi-armed bandit implementation inspired by
4
4
  # @aaronsw and victorykit/whiplash
5
- require 'rubystats'
6
5
 
7
6
  module Split
8
7
  module Algorithms
@@ -13,26 +12,25 @@ module Split
13
12
  end
14
13
 
15
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
16
20
 
17
- def arm_guess(participants, completions)
18
- a = [participants, 0].max
19
- b = [participants-completions, 0].max
20
- Rubystats::BetaDistribution.new(a+fairness_constant, b+fairness_constant).rng
21
- end
22
-
23
- def best_guess(alternatives)
24
- guesses = {}
25
- alternatives.each do |alternative|
26
- 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
27
29
  end
28
- gmax = guesses.values.max
29
- best = guesses.keys.select { |name| guesses[name] == gmax }
30
- best.sample
31
- end
32
30
 
33
- def fairness_constant
34
- 7
35
- end
31
+ def fairness_constant
32
+ 7
33
+ end
36
34
  end
37
35
  end
38
36
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "matrix"
5
+ rescue LoadError => error
6
+ if error.message.match?(/matrix/)
7
+ $stderr.puts "You don't have matrix installed in your application. Please add it to your Gemfile and run bundle install"
8
+ raise
9
+ end
10
+ end
11
+
12
+ require "rubystats"
13
+
14
+ module Split
15
+ module Algorithms
16
+ class << self
17
+ def beta_distribution_rng(a, b)
18
+ Rubystats::BetaDistribution.new(a, b).rng
19
+ end
20
+ end
21
+ end
22
+ end
@@ -38,11 +38,11 @@ module Split
38
38
  end
39
39
 
40
40
  def participant_count
41
- Split.redis.hget(key, 'participant_count').to_i
41
+ Split.redis.hget(key, "participant_count").to_i
42
42
  end
43
43
 
44
44
  def participant_count=(count)
45
- Split.redis.hset(key, 'participant_count', count.to_i)
45
+ Split.redis.hset(key, "participant_count", count.to_i)
46
46
  end
47
47
 
48
48
  def completed_count(goal = nil)
@@ -67,13 +67,13 @@ module Split
67
67
  def set_field(goal)
68
68
  field = "completed_count"
69
69
  field += ":" + goal unless goal.nil?
70
- return field
70
+ field
71
71
  end
72
72
 
73
73
  def set_prob_field(goal)
74
74
  field = "p_winner"
75
75
  field += ":" + goal unless goal.nil?
76
- return field
76
+ field
77
77
  end
78
78
 
79
79
  def set_completed_count(count, goal = nil)
@@ -82,7 +82,7 @@ module Split
82
82
  end
83
83
 
84
84
  def increment_participation
85
- Split.redis.hincrby key, 'participant_count', 1
85
+ Split.redis.hincrby key, "participant_count", 1
86
86
  end
87
87
 
88
88
  def increment_completion(goal = nil)
@@ -112,7 +112,7 @@ module Split
112
112
  control = experiment.control
113
113
  alternative = self
114
114
 
115
- return 'N/A' if control.name == alternative.name
115
+ return "N/A" if control.name == alternative.name
116
116
 
117
117
  p_a = alternative.conversion_rate(goal)
118
118
  p_c = control.conversion_rate(goal)
@@ -121,13 +121,13 @@ module Split
121
121
  n_c = control.participant_count
122
122
 
123
123
  # can't calculate zscore for P(x) > 1
124
- return 'N/A' if p_a > 1 || p_c > 1
124
+ return "N/A" if p_a > 1 || p_c > 1
125
125
 
126
126
  Split::Zscore.calculate(p_a, n_a, p_c, n_c)
127
127
  end
128
128
 
129
129
  def extra_info
130
- data = Split.redis.hget(key, 'recorded_info')
130
+ data = Split.redis.hget(key, "recorded_info")
131
131
  if data && data.length > 1
132
132
  begin
133
133
  JSON.parse(data)
@@ -149,24 +149,24 @@ module Split
149
149
  @recorded_info[k] = value
150
150
  end
151
151
 
152
- Split.redis.hset key, 'recorded_info', (@recorded_info || {}).to_json
152
+ Split.redis.hset key, "recorded_info", (@recorded_info || {}).to_json
153
153
  end
154
154
 
155
155
  def save
156
- Split.redis.hsetnx key, 'participant_count', 0
157
- Split.redis.hsetnx key, 'completed_count', 0
158
- Split.redis.hsetnx key, 'p_winner', p_winner
159
- Split.redis.hsetnx key, 'recorded_info', (@recorded_info || {}).to_json
156
+ Split.redis.hsetnx key, "participant_count", 0
157
+ Split.redis.hsetnx key, "completed_count", 0
158
+ Split.redis.hsetnx key, "p_winner", p_winner
159
+ Split.redis.hsetnx key, "recorded_info", (@recorded_info || {}).to_json
160
160
  end
161
161
 
162
162
  def validate!
163
163
  unless String === @name || hash_with_correct_values?(@name)
164
- raise ArgumentError, 'Alternative must be a string'
164
+ raise ArgumentError, "Alternative must be a string"
165
165
  end
166
166
  end
167
167
 
168
168
  def reset
169
- Split.redis.hmset key, 'participant_count', 0, 'completed_count', 0, 'recorded_info', nil
169
+ Split.redis.hmset key, "participant_count", 0, "completed_count", 0, "recorded_info", ""
170
170
  unless goals.empty?
171
171
  goals.each do |g|
172
172
  field = "completed_count:#{g}"
@@ -180,13 +180,12 @@ module Split
180
180
  end
181
181
 
182
182
  private
183
+ def hash_with_correct_values?(name)
184
+ Hash === name && String === name.keys.first && Float(name.values.first) rescue false
185
+ end
183
186
 
184
- def hash_with_correct_values?(name)
185
- Hash === name && String === name.keys.first && Float(name.values.first) rescue false
186
- end
187
-
188
- def key
189
- "#{experiment_name}:#{name}"
190
- end
187
+ def key
188
+ "#{experiment_name}:#{name}"
189
+ end
191
190
  end
192
191
  end
data/lib/split/cache.rb CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Split
4
4
  class Cache
5
-
6
5
  def self.clear
7
6
  @cache = nil
8
7
  end
@@ -29,10 +29,10 @@ module Split
29
29
  end
30
30
 
31
31
  def find_combined_experiment(metric_descriptor)
32
- raise(Split::InvalidExperimentsFormatError, 'Invalid descriptor class (String or Symbol required)') unless metric_descriptor.class == String || metric_descriptor.class == Symbol
33
- raise(Split::InvalidExperimentsFormatError, 'Enable configuration') unless Split.configuration.enabled
34
- raise(Split::InvalidExperimentsFormatError, 'Enable `allow_multiple_experiments`') unless Split.configuration.allow_multiple_experiments
35
- Split::configuration.experiments[metric_descriptor.to_sym]
32
+ raise(Split::InvalidExperimentsFormatError, "Invalid descriptor class (String or Symbol required)") unless metric_descriptor.class == String || metric_descriptor.class == Symbol
33
+ raise(Split::InvalidExperimentsFormatError, "Enable configuration") unless Split.configuration.enabled
34
+ raise(Split::InvalidExperimentsFormatError, "Enable `allow_multiple_experiments`") unless Split.configuration.allow_multiple_experiments
35
+ Split.configuration.experiments[metric_descriptor.to_sym]
36
36
  end
37
37
  end
38
38
  end