split 3.2.0 → 4.0.5

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 (87) hide show
  1. checksums.yaml +5 -5
  2. data/.eslintrc +1 -1
  3. data/.github/FUNDING.yml +1 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
  5. data/.github/dependabot.yml +7 -0
  6. data/.github/workflows/ci.yml +63 -0
  7. data/.rspec +1 -0
  8. data/.rubocop.yml +67 -1043
  9. data/CHANGELOG.md +174 -0
  10. data/CODE_OF_CONDUCT.md +3 -3
  11. data/CONTRIBUTING.md +1 -1
  12. data/Gemfile +6 -1
  13. data/README.md +79 -33
  14. data/Rakefile +6 -5
  15. data/lib/split/algorithms/block_randomization.rb +7 -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 +25 -25
  20. data/lib/split/cache.rb +27 -0
  21. data/lib/split/combined_experiments_helper.rb +6 -5
  22. data/lib/split/configuration.rb +94 -91
  23. data/lib/split/dashboard/helpers.rb +9 -9
  24. data/lib/split/dashboard/pagination_helpers.rb +86 -0
  25. data/lib/split/dashboard/paginator.rb +17 -0
  26. data/lib/split/dashboard/public/dashboard.js +10 -0
  27. data/lib/split/dashboard/public/style.css +19 -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 +24 -5
  31. data/lib/split/dashboard/views/layout.erb +1 -1
  32. data/lib/split/dashboard.rb +47 -20
  33. data/lib/split/encapsulated_helper.rb +15 -8
  34. data/lib/split/engine.rb +7 -4
  35. data/lib/split/exceptions.rb +1 -0
  36. data/lib/split/experiment.rb +160 -122
  37. data/lib/split/experiment_catalog.rb +7 -8
  38. data/lib/split/extensions/string.rb +2 -1
  39. data/lib/split/goals_collection.rb +10 -10
  40. data/lib/split/helper.rb +56 -24
  41. data/lib/split/metric.rb +6 -6
  42. data/lib/split/persistence/cookie_adapter.rb +52 -15
  43. data/lib/split/persistence/dual_adapter.rb +53 -12
  44. data/lib/split/persistence/redis_adapter.rb +8 -4
  45. data/lib/split/persistence/session_adapter.rb +1 -2
  46. data/lib/split/persistence.rb +8 -6
  47. data/lib/split/redis_interface.rb +16 -31
  48. data/lib/split/trial.rb +48 -41
  49. data/lib/split/user.rb +30 -15
  50. data/lib/split/version.rb +2 -4
  51. data/lib/split/zscore.rb +2 -3
  52. data/lib/split.rb +39 -25
  53. data/spec/algorithms/block_randomization_spec.rb +6 -5
  54. data/spec/algorithms/weighted_sample_spec.rb +6 -5
  55. data/spec/algorithms/whiplash_spec.rb +4 -5
  56. data/spec/alternative_spec.rb +35 -36
  57. data/spec/cache_spec.rb +84 -0
  58. data/spec/combined_experiments_helper_spec.rb +18 -17
  59. data/spec/configuration_spec.rb +41 -45
  60. data/spec/dashboard/pagination_helpers_spec.rb +202 -0
  61. data/spec/dashboard/paginator_spec.rb +38 -0
  62. data/spec/dashboard_helpers_spec.rb +19 -18
  63. data/spec/dashboard_spec.rb +153 -48
  64. data/spec/encapsulated_helper_spec.rb +47 -23
  65. data/spec/experiment_catalog_spec.rb +14 -13
  66. data/spec/experiment_spec.rb +224 -111
  67. data/spec/goals_collection_spec.rb +18 -16
  68. data/spec/helper_spec.rb +539 -419
  69. data/spec/metric_spec.rb +14 -14
  70. data/spec/persistence/cookie_adapter_spec.rb +105 -27
  71. data/spec/persistence/dual_adapter_spec.rb +158 -66
  72. data/spec/persistence/redis_adapter_spec.rb +35 -27
  73. data/spec/persistence/session_adapter_spec.rb +2 -3
  74. data/spec/persistence_spec.rb +1 -2
  75. data/spec/redis_interface_spec.rb +25 -82
  76. data/spec/spec_helper.rb +38 -24
  77. data/spec/split_spec.rb +18 -18
  78. data/spec/support/cookies_mock.rb +1 -2
  79. data/spec/trial_spec.rb +117 -70
  80. data/spec/user_spec.rb +69 -27
  81. data/split.gemspec +26 -22
  82. metadata +85 -37
  83. data/.travis.yml +0 -41
  84. data/Appraisals +0 -13
  85. data/gemfiles/4.2.gemfile +0 -9
  86. data/gemfiles/5.0.gemfile +0 -10
  87. data/gemfiles/5.1.gemfile +0 -10
data/lib/split/trial.rb CHANGED
@@ -1,18 +1,21 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Split
3
4
  class Trial
5
+ attr_accessor :goals
4
6
  attr_accessor :experiment
5
- attr_accessor :metadata
7
+ attr_writer :metadata
6
8
 
7
9
  def initialize(attrs = {})
8
10
  self.experiment = attrs.delete(:experiment)
9
11
  self.alternative = attrs.delete(:alternative)
10
12
  self.metadata = attrs.delete(:metadata)
13
+ self.goals = attrs.delete(:goals) || []
11
14
 
12
15
  @user = attrs.delete(:user)
13
16
  @options = attrs
14
17
 
15
- @alternative_choosen = false
18
+ @alternative_chosen = false
16
19
  end
17
20
 
18
21
  def metadata
@@ -21,24 +24,24 @@ module Split
21
24
 
22
25
  def alternative
23
26
  @alternative ||= if @experiment.has_winner?
24
- @experiment.winner
25
- end
27
+ @experiment.winner
28
+ end
26
29
  end
27
30
 
28
31
  def alternative=(alternative)
29
32
  @alternative = if alternative.kind_of?(Split::Alternative)
30
33
  alternative
31
34
  else
32
- @experiment.alternatives.find{|a| a.name == alternative }
35
+ @experiment.alternatives.find { |a| a.name == alternative }
33
36
  end
34
37
  end
35
38
 
36
- def complete!(goals=[], context = nil)
39
+ def complete!(context = nil)
37
40
  if alternative
38
41
  if Array(goals).empty?
39
42
  alternative.increment_completion
40
43
  else
41
- Array(goals).each {|g| alternative.increment_completion(g) }
44
+ Array(goals).each { |g| alternative.increment_completion(g) }
42
45
  end
43
46
 
44
47
  run_callback context, Split.configuration.on_trial_complete
@@ -51,8 +54,9 @@ module Split
51
54
  def choose!(context = nil)
52
55
  @user.cleanup_old_experiments!
53
56
  # Only run the process once
54
- return alternative if @alternative_choosen
57
+ return alternative if @alternative_chosen
55
58
 
59
+ new_participant = @user[@experiment.key].nil?
56
60
  if override_is_alternative?
57
61
  self.alternative = @options[:override]
58
62
  if should_store_alternative? && !@user[@experiment.key]
@@ -68,52 +72,55 @@ module Split
68
72
  if exclude_user?
69
73
  self.alternative = @experiment.control
70
74
  else
71
- value = @user[@experiment.key]
72
- if value
73
- self.alternative = value
74
- else
75
- self.alternative = @experiment.next_alternative
76
-
77
- # Increment the number of participants since we are actually choosing a new alternative
78
- self.alternative.increment_participation
79
-
80
- run_callback context, Split.configuration.on_trial_choose
75
+ self.alternative = @user[@experiment.key]
76
+ if alternative.nil?
77
+ if @experiment.cohorting_disabled?
78
+ self.alternative = @experiment.control
79
+ else
80
+ self.alternative = @experiment.next_alternative
81
+
82
+ # Increment the number of participants since we are actually choosing a new alternative
83
+ self.alternative.increment_participation
84
+
85
+ run_callback context, Split.configuration.on_trial_choose
86
+ end
81
87
  end
82
88
  end
83
89
  end
84
90
 
85
- @user[@experiment.key] = alternative.name if should_store_alternative?
86
- @alternative_choosen = true
87
- run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled?
91
+ new_participant_and_cohorting_disabled = new_participant && @experiment.cohorting_disabled?
92
+
93
+ @user[@experiment.key] = alternative.name unless @experiment.has_winner? || !should_store_alternative? || new_participant_and_cohorting_disabled
94
+ @alternative_chosen = true
95
+ run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled? || new_participant_and_cohorting_disabled
88
96
  alternative
89
97
  end
90
98
 
91
99
  private
100
+ def run_callback(context, callback_name)
101
+ context.send(callback_name, self) if callback_name && context.respond_to?(callback_name, true)
102
+ end
92
103
 
93
- def run_callback(context, callback_name)
94
- context.send(callback_name, self) if callback_name && context.respond_to?(callback_name, true)
95
- end
96
-
97
- def override_is_alternative?
98
- @experiment.alternatives.map(&:name).include?(@options[:override])
99
- end
104
+ def override_is_alternative?
105
+ @experiment.alternatives.map(&:name).include?(@options[:override])
106
+ end
100
107
 
101
- def should_store_alternative?
102
- if @options[:override] || @options[:disabled]
103
- Split.configuration.store_override
104
- else
105
- !exclude_user?
108
+ def should_store_alternative?
109
+ if @options[:override] || @options[:disabled]
110
+ Split.configuration.store_override
111
+ else
112
+ !exclude_user?
113
+ end
106
114
  end
107
- end
108
115
 
109
- def cleanup_old_versions
110
- if @experiment.version > 0
111
- @user.cleanup_old_versions!(@experiment)
116
+ def cleanup_old_versions
117
+ if @experiment.version > 0
118
+ @user.cleanup_old_versions!(@experiment)
119
+ end
112
120
  end
113
- end
114
121
 
115
- def exclude_user?
116
- @options[:exclude] || @experiment.start_time.nil? || @user.max_experiments_reached?(@experiment.key)
117
- end
122
+ def exclude_user?
123
+ @options[:exclude] || @experiment.start_time.nil? || @user.max_experiments_reached?(@experiment.key)
124
+ end
118
125
  end
119
126
  end
data/lib/split/user.rb CHANGED
@@ -1,4 +1,6 @@
1
- require 'forwardable'
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
2
4
 
3
5
  module Split
4
6
  class User
@@ -6,11 +8,13 @@ module Split
6
8
  def_delegators :@user, :keys, :[], :[]=, :delete
7
9
  attr_reader :user
8
10
 
9
- def initialize(context, adapter=nil)
11
+ def initialize(context, adapter = nil)
10
12
  @user = adapter || Split::Persistence.adapter.new(context)
13
+ @cleaned_up = false
11
14
  end
12
15
 
13
16
  def cleanup_old_experiments!
17
+ return if @cleaned_up
14
18
  keys_without_finished(user.keys).each do |key|
15
19
  experiment = ExperimentCatalog.find key_without_version(key)
16
20
  if experiment.nil? || experiment.has_winner? || experiment.start_time.nil?
@@ -18,12 +22,14 @@ module Split
18
22
  user.delete Experiment.finished_key(key)
19
23
  end
20
24
  end
25
+ @cleaned_up = true
21
26
  end
22
27
 
23
28
  def max_experiments_reached?(experiment_key)
24
- if Split.configuration.allow_multiple_experiments == 'control'
29
+ if Split.configuration.allow_multiple_experiments == "control"
25
30
  experiments = active_experiments
26
- count_control = experiments.count {|k,v| k == experiment_key || v == 'control'}
31
+ experiment_key_without_version = key_without_version(experiment_key)
32
+ count_control = experiments.count { |k, v| k == experiment_key_without_version || v == "control" }
27
33
  experiments.size > count_control
28
34
  else
29
35
  !Split.configuration.allow_multiple_experiments &&
@@ -32,13 +38,13 @@ module Split
32
38
  end
33
39
 
34
40
  def cleanup_old_versions!(experiment)
35
- keys = user.keys.select { |k| k.match(Regexp.new(experiment.name)) }
41
+ keys = user.keys.select { |k| key_without_version(k) == experiment.name }
36
42
  keys_without_experiment(keys, experiment.key).each { |key| user.delete(key) }
37
43
  end
38
44
 
39
45
  def active_experiments
40
46
  experiment_pairs = {}
41
- user.keys.each do |key|
47
+ keys_without_finished(user.keys).each do |key|
42
48
  Metric.possible_experiments(key_without_version(key)).each do |experiment|
43
49
  if !experiment.has_winner?
44
50
  experiment_pairs[key_without_version(key)] = user[key]
@@ -48,18 +54,27 @@ module Split
48
54
  experiment_pairs
49
55
  end
50
56
 
51
- private
57
+ def self.find(user_id, adapter)
58
+ adapter = adapter.is_a?(Symbol) ? Split::Persistence::ADAPTERS[adapter] : adapter
52
59
 
53
- def keys_without_experiment(keys, experiment_key)
54
- keys.reject { |k| k.match(Regexp.new("^#{experiment_key}(:finished)?$")) }
60
+ if adapter.respond_to?(:find)
61
+ User.new(nil, adapter.find(user_id))
62
+ else
63
+ nil
64
+ end
55
65
  end
56
66
 
57
- def keys_without_finished(keys)
58
- keys.reject { |k| k.include?(":finished") }
59
- end
67
+ private
68
+ def keys_without_experiment(keys, experiment_key)
69
+ keys.reject { |k| k.match(Regexp.new("^#{experiment_key}(:finished)?$")) }
70
+ end
60
71
 
61
- def key_without_version(key)
62
- key.split(/\:\d(?!\:)/)[0]
63
- end
72
+ def keys_without_finished(keys)
73
+ keys.reject { |k| k.include?(":finished") }
74
+ end
75
+
76
+ def key_without_version(key)
77
+ key.split(/\:\d(?!\:)/)[0]
78
+ end
64
79
  end
65
80
  end
data/lib/split/version.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Split
3
- MAJOR = 3
4
- MINOR = 2
5
- PATCH = 0
6
- VERSION = [MAJOR, MINOR, PATCH].join('.')
4
+ VERSION = "4.0.5"
7
5
  end
data/lib/split/zscore.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Split
3
4
  class Zscore
4
-
5
5
  include Math
6
6
 
7
7
  def self.calculate(p1, n1, p2, n2)
@@ -50,8 +50,7 @@ module Split
50
50
  # Calculate z-score
51
51
  z_score = (p_1 - p_2)/(se)
52
52
 
53
- return z_score
54
-
53
+ z_score
55
54
  end
56
55
  end
57
56
  end
data/lib/split.rb CHANGED
@@ -1,27 +1,30 @@
1
1
  # frozen_string_literal: true
2
- require 'redis'
3
2
 
4
- require 'split/algorithms/block_randomization'
5
- require 'split/algorithms/weighted_sample'
6
- require 'split/algorithms/whiplash'
7
- require 'split/alternative'
8
- require 'split/configuration'
9
- require 'split/encapsulated_helper'
10
- require 'split/exceptions'
11
- require 'split/experiment'
12
- require 'split/experiment_catalog'
13
- require 'split/extensions/string'
14
- require 'split/goals_collection'
15
- require 'split/helper'
16
- require 'split/combined_experiments_helper'
17
- require 'split/metric'
18
- require 'split/persistence'
19
- require 'split/redis_interface'
20
- require 'split/trial'
21
- require 'split/user'
22
- require 'split/version'
23
- require 'split/zscore'
24
- require 'split/engine' if defined?(Rails)
3
+ require "redis"
4
+
5
+ require "split/algorithms"
6
+ require "split/algorithms/block_randomization"
7
+ require "split/algorithms/weighted_sample"
8
+ require "split/algorithms/whiplash"
9
+ require "split/alternative"
10
+ require "split/cache"
11
+ require "split/configuration"
12
+ require "split/encapsulated_helper"
13
+ require "split/exceptions"
14
+ require "split/experiment"
15
+ require "split/experiment_catalog"
16
+ require "split/extensions/string"
17
+ require "split/goals_collection"
18
+ require "split/helper"
19
+ require "split/combined_experiments_helper"
20
+ require "split/metric"
21
+ require "split/persistence"
22
+ require "split/redis_interface"
23
+ require "split/trial"
24
+ require "split/user"
25
+ require "split/version"
26
+ require "split/zscore"
27
+ require "split/engine" if defined?(::Rails::Engine)
25
28
 
26
29
  module Split
27
30
  extend self
@@ -35,9 +38,9 @@ module Split
35
38
  # `Redis::DistRedis`, or `Redis::Namespace`.
36
39
  def redis=(server)
37
40
  @redis = if server.is_a?(String)
38
- Redis.new(:url => server, :thread_safe => true)
41
+ Redis.new(url: server)
39
42
  elsif server.is_a?(Hash)
40
- Redis.new(server.merge(:thread_safe => true))
43
+ Redis.new(server)
41
44
  elsif server.respond_to?(:smembers)
42
45
  server
43
46
  else
@@ -64,6 +67,17 @@ module Split
64
67
  self.configuration ||= Configuration.new
65
68
  yield(configuration)
66
69
  end
70
+
71
+ def cache(namespace, key, &block)
72
+ Split::Cache.fetch(namespace, key, &block)
73
+ end
67
74
  end
68
75
 
69
- Split.configure {}
76
+ # Check to see if being run in a Rails application. If so, wait until before_initialize to run configuration so Gems that create ENV variables have the chance to initialize first.
77
+ if defined?(::Rails::Railtie)
78
+ class Split::Railtie < Rails::Railtie
79
+ config.before_initialize { Split.configure { } }
80
+ end
81
+ else
82
+ Split.configure { }
83
+ end
@@ -1,11 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "spec_helper"
2
4
 
3
5
  describe Split::Algorithms::BlockRandomization do
4
-
5
- let(:experiment) { Split::Experiment.new 'experiment' }
6
- let(:alternative_A) { Split::Alternative.new 'A', 'experiment' }
7
- let(:alternative_B) { Split::Alternative.new 'B', 'experiment' }
8
- let(:alternative_C) { Split::Alternative.new 'C', 'experiment' }
6
+ let(:experiment) { Split::Experiment.new "experiment" }
7
+ let(:alternative_A) { Split::Alternative.new "A", "experiment" }
8
+ let(:alternative_B) { Split::Alternative.new "B", "experiment" }
9
+ let(:alternative_C) { Split::Alternative.new "C", "experiment" }
9
10
 
10
11
  before :each do
11
12
  allow(experiment).to receive(:alternatives) { [alternative_A, alternative_B, alternative_C] }
@@ -1,19 +1,20 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "spec_helper"
3
4
 
4
5
  describe Split::Algorithms::WeightedSample do
5
6
  it "should return an alternative" do
6
- experiment = Split::ExperimentCatalog.find_or_create('link_color', {'blue' => 100}, {'red' => 0 })
7
+ experiment = Split::ExperimentCatalog.find_or_create("link_color", { "blue" => 100 }, { "red" => 0 })
7
8
  expect(Split::Algorithms::WeightedSample.choose_alternative(experiment).class).to eq(Split::Alternative)
8
9
  end
9
10
 
10
11
  it "should always return a heavily weighted option" do
11
- experiment = Split::ExperimentCatalog.find_or_create('link_color', {'blue' => 100}, {'red' => 0 })
12
- expect(Split::Algorithms::WeightedSample.choose_alternative(experiment).name).to eq('blue')
12
+ experiment = Split::ExperimentCatalog.find_or_create("link_color", { "blue" => 100 }, { "red" => 0 })
13
+ expect(Split::Algorithms::WeightedSample.choose_alternative(experiment).name).to eq("blue")
13
14
  end
14
15
 
15
16
  it "should return one of the results" do
16
- experiment = Split::ExperimentCatalog.find_or_create('link_color', {'blue' => 1}, {'red' => 1 })
17
- expect(['red', 'blue']).to include Split::Algorithms::WeightedSample.choose_alternative(experiment).name
17
+ experiment = Split::ExperimentCatalog.find_or_create("link_color", { "blue" => 1 }, { "red" => 1 })
18
+ expect(["red", "blue"]).to include Split::Algorithms::WeightedSample.choose_alternative(experiment).name
18
19
  end
19
20
  end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "spec_helper"
3
4
 
4
5
  describe Split::Algorithms::Whiplash do
5
-
6
6
  it "should return an algorithm" do
7
- experiment = Split::ExperimentCatalog.find_or_create('link_color', {'blue' => 1}, {'red' => 1 })
7
+ experiment = Split::ExperimentCatalog.find_or_create("link_color", { "blue" => 1 }, { "red" => 1 })
8
8
  expect(Split::Algorithms::Whiplash.choose_alternative(experiment).class).to eq(Split::Alternative)
9
9
  end
10
10
 
11
11
  it "should return one of the results" do
12
- experiment = Split::ExperimentCatalog.find_or_create('link_color', {'blue' => 1}, {'red' => 1 })
13
- expect(['red', 'blue']).to include Split::Algorithms::Whiplash.choose_alternative(experiment).name
12
+ experiment = Split::ExperimentCatalog.find_or_create("link_color", { "blue" => 1 }, { "red" => 1 })
13
+ expect(["red", "blue"]).to include Split::Algorithms::Whiplash.choose_alternative(experiment).name
14
14
  end
15
15
 
16
16
  it "should guess floats" do
@@ -20,5 +20,4 @@ describe Split::Algorithms::Whiplash do
20
20
  expect(Split::Algorithms::Whiplash.send(:arm_guess, 1000, 5).class).to eq(Float)
21
21
  expect(Split::Algorithms::Whiplash.send(:arm_guess, 10, -2).class).to eq(Float)
22
22
  end
23
-
24
23
  end
@@ -1,19 +1,19 @@
1
1
  # frozen_string_literal: true
2
- require 'spec_helper'
3
- require 'split/alternative'
4
2
 
5
- describe Split::Alternative do
3
+ require "spec_helper"
4
+ require "split/alternative"
6
5
 
6
+ describe Split::Alternative do
7
7
  let(:alternative) {
8
- Split::Alternative.new('Basket', 'basket_text')
8
+ Split::Alternative.new("Basket", "basket_text")
9
9
  }
10
10
 
11
11
  let(:alternative2) {
12
- Split::Alternative.new('Cart', 'basket_text')
12
+ Split::Alternative.new("Cart", "basket_text")
13
13
  }
14
14
 
15
15
  let!(:experiment) {
16
- Split::ExperimentCatalog.find_or_create({"basket_text" => ["purchase", "refund"]}, "Basket", "Cart")
16
+ Split::ExperimentCatalog.find_or_create({ "basket_text" => ["purchase", "refund"] }, "Basket", "Cart")
17
17
  }
18
18
 
19
19
  let(:goal1) { "purchase" }
@@ -24,48 +24,48 @@ describe Split::Alternative do
24
24
  end
25
25
 
26
26
  it "should have and only return the name" do
27
- expect(alternative.name).to eq('Basket')
27
+ expect(alternative.name).to eq("Basket")
28
28
  end
29
29
 
30
- describe 'weights' do
30
+ describe "weights" do
31
31
  it "should set the weights" do
32
- experiment = Split::Experiment.new('basket_text', :alternatives => [{'Basket' => 0.6}, {"Cart" => 0.4}])
32
+ experiment = Split::Experiment.new("basket_text", alternatives: [{ "Basket" => 0.6 }, { "Cart" => 0.4 }])
33
33
  first = experiment.alternatives[0]
34
- expect(first.name).to eq('Basket')
34
+ expect(first.name).to eq("Basket")
35
35
  expect(first.weight).to eq(0.6)
36
36
 
37
37
  second = experiment.alternatives[1]
38
- expect(second.name).to eq('Cart')
38
+ expect(second.name).to eq("Cart")
39
39
  expect(second.weight).to eq(0.4)
40
40
  end
41
41
 
42
42
  it "accepts probability on alternatives" do
43
43
  Split.configuration.experiments = {
44
- :my_experiment => {
45
- :alternatives => [
46
- { :name => "control_opt", :percent => 67 },
47
- { :name => "second_opt", :percent => 10 },
48
- { :name => "third_opt", :percent => 23 },
44
+ my_experiment: {
45
+ alternatives: [
46
+ { name: "control_opt", percent: 67 },
47
+ { name: "second_opt", percent: 10 },
48
+ { name: "third_opt", percent: 23 },
49
49
  ]
50
50
  }
51
51
  }
52
52
  experiment = Split::Experiment.new(:my_experiment)
53
53
  first = experiment.alternatives[0]
54
- expect(first.name).to eq('control_opt')
54
+ expect(first.name).to eq("control_opt")
55
55
  expect(first.weight).to eq(0.67)
56
56
 
57
57
  second = experiment.alternatives[1]
58
- expect(second.name).to eq('second_opt')
58
+ expect(second.name).to eq("second_opt")
59
59
  expect(second.weight).to eq(0.1)
60
60
  end
61
61
 
62
62
  it "accepts probability on some alternatives" do
63
63
  Split.configuration.experiments = {
64
- :my_experiment => {
65
- :alternatives => [
66
- { :name => "control_opt", :percent => 34 },
64
+ my_experiment: {
65
+ alternatives: [
66
+ { name: "control_opt", percent: 34 },
67
67
  "second_opt",
68
- { :name => "third_opt", :percent => 23 },
68
+ { name: "third_opt", percent: 23 },
69
69
  "fourth_opt",
70
70
  ],
71
71
  }
@@ -87,11 +87,11 @@ describe Split::Alternative do
87
87
  #
88
88
  it "allows name param without probability" do
89
89
  Split.configuration.experiments = {
90
- :my_experiment => {
91
- :alternatives => [
92
- { :name => "control_opt" },
90
+ my_experiment: {
91
+ alternatives: [
92
+ { name: "control_opt" },
93
93
  "second_opt",
94
- { :name => "third_opt", :percent => 64 },
94
+ { name: "third_opt", percent: 64 },
95
95
  ],
96
96
  }
97
97
  }
@@ -126,7 +126,7 @@ describe Split::Alternative do
126
126
 
127
127
  it "should save to redis" do
128
128
  alternative.save
129
- expect(Split.redis.exists('basket_text:Basket')).to be true
129
+ expect(Split.redis.exists?("basket_text:Basket")).to be true
130
130
  end
131
131
 
132
132
  it "should increment participation count" do
@@ -166,7 +166,7 @@ describe Split::Alternative do
166
166
  expect(alternative2.control?).to be_falsey
167
167
  end
168
168
 
169
- describe 'unfinished_count' do
169
+ describe "unfinished_count" do
170
170
  it "should be difference between participant and completed counts" do
171
171
  alternative.increment_participation
172
172
  expect(alternative.unfinished_count).to eq(alternative.participant_count)
@@ -182,7 +182,7 @@ describe Split::Alternative do
182
182
  end
183
183
  end
184
184
 
185
- describe 'conversion rate' do
185
+ describe "conversion rate" do
186
186
  it "should be 0 if there are no conversions" do
187
187
  expect(alternative.completed_count).to eq(0)
188
188
  expect(alternative.conversion_rate).to eq(0)
@@ -225,8 +225,7 @@ describe Split::Alternative do
225
225
  end
226
226
  end
227
227
 
228
- describe 'z score' do
229
-
228
+ describe "z score" do
230
229
  it "should return an error string when the control has 0 people" do
231
230
  expect(alternative2.z_score).to eq("Needs 30+ participants.")
232
231
  expect(alternative2.z_score(goal1)).to eq("Needs 30+ participants.")
@@ -269,9 +268,9 @@ describe Split::Alternative do
269
268
 
270
269
  it "should be N/A for the control" do
271
270
  control = experiment.control
272
- expect(control.z_score).to eq('N/A')
273
- expect(control.z_score(goal1)).to eq('N/A')
274
- expect(control.z_score(goal2)).to eq('N/A')
271
+ expect(control.z_score).to eq("N/A")
272
+ expect(control.z_score(goal1)).to eq("N/A")
273
+ expect(control.z_score(goal2)).to eq("N/A")
275
274
  end
276
275
 
277
276
  it "should not blow up for Conversion Rates > 1" do
@@ -289,8 +288,8 @@ describe Split::Alternative do
289
288
 
290
289
  describe "extra_info" do
291
290
  it "reads saved value of recorded_info in redis" do
292
- saved_recorded_info = {"key_1" => 1, "key_2" => "2"}
293
- Split.redis.hset "#{alternative.experiment_name}:#{alternative.name}", 'recorded_info', saved_recorded_info.to_json
291
+ saved_recorded_info = { "key_1" => 1, "key_2" => "2" }
292
+ Split.redis.hset "#{alternative.experiment_name}:#{alternative.name}", "recorded_info", saved_recorded_info.to_json
294
293
  extra_info = alternative.extra_info
295
294
 
296
295
  expect(extra_info).to eql(saved_recorded_info)