split 4.0.0.pre2 → 4.0.2

Sign up to get free protection for your applications and to get access to all the features.
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