split 3.3.2 → 4.0.0.pre2
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/.eslintrc +1 -1
- data/.github/FUNDING.yml +1 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
- data/.github/dependabot.yml +7 -0
- data/.github/workflows/ci.yml +61 -0
- data/.rspec +1 -0
- data/.rubocop.yml +71 -1044
- data/.rubocop_todo.yml +226 -0
- data/Appraisals +1 -1
- data/CHANGELOG.md +62 -0
- data/CODE_OF_CONDUCT.md +3 -3
- data/Gemfile +2 -0
- data/README.md +40 -18
- data/Rakefile +2 -0
- data/gemfiles/6.0.gemfile +1 -1
- data/lib/split/algorithms/block_randomization.rb +2 -0
- data/lib/split/algorithms/weighted_sample.rb +2 -1
- data/lib/split/algorithms/whiplash.rb +3 -2
- data/lib/split/alternative.rb +4 -3
- data/lib/split/cache.rb +28 -0
- data/lib/split/combined_experiments_helper.rb +2 -1
- data/lib/split/configuration.rb +13 -14
- data/lib/split/dashboard/helpers.rb +1 -0
- data/lib/split/dashboard/pagination_helpers.rb +3 -3
- data/lib/split/dashboard/paginator.rb +1 -0
- data/lib/split/dashboard/public/dashboard.js +10 -0
- data/lib/split/dashboard/public/style.css +5 -0
- data/lib/split/dashboard/views/_controls.erb +13 -0
- data/lib/split/dashboard/views/layout.erb +1 -1
- data/lib/split/dashboard.rb +19 -1
- data/lib/split/encapsulated_helper.rb +3 -2
- data/lib/split/engine.rb +7 -4
- data/lib/split/exceptions.rb +1 -0
- data/lib/split/experiment.rb +98 -65
- data/lib/split/experiment_catalog.rb +1 -3
- data/lib/split/extensions/string.rb +1 -0
- data/lib/split/goals_collection.rb +2 -0
- data/lib/split/helper.rb +28 -8
- data/lib/split/metric.rb +2 -1
- data/lib/split/persistence/cookie_adapter.rb +6 -1
- data/lib/split/persistence/dual_adapter.rb +54 -12
- data/lib/split/persistence/redis_adapter.rb +5 -0
- data/lib/split/persistence/session_adapter.rb +1 -0
- data/lib/split/persistence.rb +4 -2
- data/lib/split/redis_interface.rb +9 -28
- data/lib/split/trial.rb +21 -11
- data/lib/split/user.rb +20 -4
- data/lib/split/version.rb +2 -4
- data/lib/split/zscore.rb +1 -0
- data/lib/split.rb +9 -3
- data/spec/alternative_spec.rb +1 -1
- data/spec/cache_spec.rb +88 -0
- data/spec/configuration_spec.rb +17 -15
- data/spec/dashboard/pagination_helpers_spec.rb +3 -1
- data/spec/dashboard_helpers_spec.rb +2 -2
- data/spec/dashboard_spec.rb +78 -17
- data/spec/encapsulated_helper_spec.rb +2 -2
- data/spec/experiment_spec.rb +116 -12
- data/spec/goals_collection_spec.rb +1 -1
- data/spec/helper_spec.rb +186 -112
- data/spec/persistence/cookie_adapter_spec.rb +1 -1
- data/spec/persistence/dual_adapter_spec.rb +160 -68
- data/spec/persistence/redis_adapter_spec.rb +9 -0
- data/spec/redis_interface_spec.rb +0 -69
- data/spec/spec_helper.rb +5 -6
- data/spec/trial_spec.rb +45 -19
- data/spec/user_spec.rb +45 -3
- data/split.gemspec +8 -9
- metadata +28 -36
- data/.travis.yml +0 -66
- data/gemfiles/4.2.gemfile +0 -9
data/lib/split/helper.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Split
|
3
4
|
module Helper
|
4
5
|
OVERRIDE_PARAM_NAME = "ab_test"
|
@@ -32,8 +33,8 @@ module Split
|
|
32
33
|
end
|
33
34
|
|
34
35
|
if block_given?
|
35
|
-
metadata =
|
36
|
-
yield(alternative, metadata)
|
36
|
+
metadata = experiment.metadata[alternative] if experiment.metadata
|
37
|
+
yield(alternative, metadata || {})
|
37
38
|
else
|
38
39
|
alternative
|
39
40
|
end
|
@@ -44,15 +45,21 @@ module Split
|
|
44
45
|
end
|
45
46
|
|
46
47
|
def finish_experiment(experiment, options = {:reset => true})
|
48
|
+
return false if active_experiments[experiment.name].nil?
|
47
49
|
return true if experiment.has_winner?
|
48
50
|
should_reset = experiment.resettable? && options[:reset]
|
49
51
|
if ab_user[experiment.finished_key] && !should_reset
|
50
52
|
return true
|
51
53
|
else
|
52
54
|
alternative_name = ab_user[experiment.key]
|
53
|
-
trial = Trial.new(
|
54
|
-
|
55
|
-
|
55
|
+
trial = Trial.new(
|
56
|
+
:user => ab_user,
|
57
|
+
:experiment => experiment,
|
58
|
+
:alternative => alternative_name,
|
59
|
+
:goals => options[:goals],
|
60
|
+
)
|
61
|
+
|
62
|
+
trial.complete!(self)
|
56
63
|
|
57
64
|
if should_reset
|
58
65
|
reset!(experiment)
|
@@ -69,6 +76,7 @@ module Split
|
|
69
76
|
|
70
77
|
if experiments.any?
|
71
78
|
experiments.each do |experiment|
|
79
|
+
next if override_present?(experiment.key)
|
72
80
|
finish_experiment(experiment, options.merge(:goals => goals))
|
73
81
|
end
|
74
82
|
end
|
@@ -79,7 +87,7 @@ module Split
|
|
79
87
|
|
80
88
|
def ab_record_extra_info(metric_descriptor, key, value = 1)
|
81
89
|
return if exclude_visitor? || Split.configuration.disabled?
|
82
|
-
metric_descriptor,
|
90
|
+
metric_descriptor, _ = normalize_metric(metric_descriptor)
|
83
91
|
experiments = Metric.possible_experiments(metric_descriptor)
|
84
92
|
|
85
93
|
if experiments.any?
|
@@ -104,15 +112,27 @@ module Split
|
|
104
112
|
Split.configuration.db_failover_on_db_error.call(e)
|
105
113
|
end
|
106
114
|
|
107
|
-
|
108
115
|
def override_present?(experiment_name)
|
109
|
-
|
116
|
+
override_alternative_by_params(experiment_name) || override_alternative_by_cookies(experiment_name)
|
110
117
|
end
|
111
118
|
|
112
119
|
def override_alternative(experiment_name)
|
120
|
+
override_alternative_by_params(experiment_name) || override_alternative_by_cookies(experiment_name)
|
121
|
+
end
|
122
|
+
|
123
|
+
def override_alternative_by_params(experiment_name)
|
113
124
|
defined?(params) && params[OVERRIDE_PARAM_NAME] && params[OVERRIDE_PARAM_NAME][experiment_name]
|
114
125
|
end
|
115
126
|
|
127
|
+
def override_alternative_by_cookies(experiment_name)
|
128
|
+
return unless defined?(request)
|
129
|
+
|
130
|
+
if request.cookies && request.cookies.key?('split_override')
|
131
|
+
experiments = JSON.parse(request.cookies['split_override']) rescue {}
|
132
|
+
experiments[experiment_name]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
116
136
|
def split_generically_disabled?
|
117
137
|
defined?(params) && params['SPLIT_DISABLE']
|
118
138
|
end
|
data/lib/split/metric.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Split
|
3
4
|
class Metric
|
4
5
|
attr_accessor :name
|
5
6
|
attr_accessor :experiments
|
6
7
|
|
7
8
|
def initialize(attrs = {})
|
8
|
-
attrs.each do |key,value|
|
9
|
+
attrs.each do |key, value|
|
9
10
|
if self.respond_to?("#{key}=")
|
10
11
|
self.send("#{key}=", value)
|
11
12
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "json"
|
3
4
|
|
4
5
|
module Split
|
@@ -44,7 +45,7 @@ module Split
|
|
44
45
|
end
|
45
46
|
|
46
47
|
def default_options
|
47
|
-
{ expires: @expires, path: '/' }
|
48
|
+
{ expires: @expires, path: '/', domain: cookie_domain_config }.compact
|
48
49
|
end
|
49
50
|
|
50
51
|
def set_cookie_via_rack(key, value)
|
@@ -86,6 +87,10 @@ module Split
|
|
86
87
|
Split.configuration.persistence_cookie_length
|
87
88
|
end
|
88
89
|
|
90
|
+
def cookie_domain_config
|
91
|
+
Split.configuration.persistence_cookie_domain
|
92
|
+
end
|
93
|
+
|
89
94
|
def action_dispatch?
|
90
95
|
defined?(Rails) && @response.is_a?(ActionDispatch::Response)
|
91
96
|
end
|
@@ -1,12 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'forwardable'
|
4
|
-
|
5
3
|
module Split
|
6
4
|
module Persistence
|
7
5
|
class DualAdapter
|
8
|
-
|
9
|
-
|
6
|
+
def self.with_config(options={})
|
7
|
+
self.config.merge!(options)
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.config
|
12
|
+
@config ||= {}
|
13
|
+
end
|
10
14
|
|
11
15
|
def initialize(context)
|
12
16
|
if logged_in = self.class.config[:logged_in]
|
@@ -22,22 +26,60 @@ module Split
|
|
22
26
|
raise "Please configure :logged_out_adapter"
|
23
27
|
end
|
24
28
|
|
25
|
-
|
26
|
-
|
29
|
+
@fallback_to_logged_out_adapter =
|
30
|
+
self.class.config[:fallback_to_logged_out_adapter] || false
|
31
|
+
@logged_in = logged_in.call(context)
|
32
|
+
@logged_in_adapter = logged_in_adapter.new(context)
|
33
|
+
@logged_out_adapter = logged_out_adapter.new(context)
|
34
|
+
@active_adapter = @logged_in ? @logged_in_adapter : @logged_out_adapter
|
35
|
+
end
|
36
|
+
|
37
|
+
def keys
|
38
|
+
if @fallback_to_logged_out_adapter
|
39
|
+
(@logged_in_adapter.keys + @logged_out_adapter.keys).uniq
|
27
40
|
else
|
28
|
-
@
|
41
|
+
@active_adapter.keys
|
29
42
|
end
|
30
43
|
end
|
31
44
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
45
|
+
def [](key)
|
46
|
+
if @fallback_to_logged_out_adapter
|
47
|
+
@logged_in && @logged_in_adapter[key] || @logged_out_adapter[key]
|
48
|
+
else
|
49
|
+
@active_adapter[key]
|
50
|
+
end
|
35
51
|
end
|
36
52
|
|
37
|
-
def
|
38
|
-
@
|
53
|
+
def []=(key, value)
|
54
|
+
if @fallback_to_logged_out_adapter
|
55
|
+
@logged_in_adapter[key] = value if @logged_in
|
56
|
+
old_value = @logged_out_adapter[key]
|
57
|
+
@logged_out_adapter[key] = value
|
58
|
+
|
59
|
+
decrement_participation(key, old_value) if decrement_participation?(old_value, value)
|
60
|
+
else
|
61
|
+
@active_adapter[key] = value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def delete(key)
|
66
|
+
if @fallback_to_logged_out_adapter
|
67
|
+
@logged_in_adapter.delete(key)
|
68
|
+
@logged_out_adapter.delete(key)
|
69
|
+
else
|
70
|
+
@active_adapter.delete(key)
|
71
|
+
end
|
39
72
|
end
|
40
73
|
|
74
|
+
private
|
75
|
+
|
76
|
+
def decrement_participation?(old_value, value)
|
77
|
+
!old_value.nil? && !value.nil? && old_value != value
|
78
|
+
end
|
79
|
+
|
80
|
+
def decrement_participation(key, value)
|
81
|
+
Split.redis.hincrby("#{key}:#{value}", 'participant_count', -1)
|
82
|
+
end
|
41
83
|
end
|
42
84
|
end
|
43
85
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Split
|
3
4
|
module Persistence
|
4
5
|
class RedisAdapter
|
@@ -39,6 +40,10 @@ module Split
|
|
39
40
|
Split.redis.hkeys(redis_key)
|
40
41
|
end
|
41
42
|
|
43
|
+
def self.find(user_id)
|
44
|
+
new(nil, user_id)
|
45
|
+
end
|
46
|
+
|
42
47
|
def self.with_config(options={})
|
43
48
|
self.config.merge!(options)
|
44
49
|
self
|
data/lib/split/persistence.rb
CHANGED
@@ -8,8 +8,10 @@ module Split
|
|
8
8
|
require 'split/persistence/session_adapter'
|
9
9
|
|
10
10
|
ADAPTERS = {
|
11
|
-
:
|
12
|
-
:
|
11
|
+
cookie: Split::Persistence::CookieAdapter,
|
12
|
+
session: Split::Persistence::SessionAdapter,
|
13
|
+
redis: Split::Persistence::RedisAdapter,
|
14
|
+
dual_adapter: Split::Persistence::DualAdapter
|
13
15
|
}.freeze
|
14
16
|
|
15
17
|
def self.adapter
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Split
|
2
4
|
# Simplifies the interface to Redis.
|
3
5
|
class RedisInterface
|
@@ -6,40 +8,19 @@ module Split
|
|
6
8
|
end
|
7
9
|
|
8
10
|
def persist_list(list_name, list_values)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
set_list_index(list_name, index, value)
|
11
|
+
if list_values.length > 0
|
12
|
+
redis.multi do |multi|
|
13
|
+
tmp_list = "#{list_name}_tmp"
|
14
|
+
multi.rpush(tmp_list, list_values)
|
15
|
+
multi.rename(tmp_list, list_name)
|
15
16
|
end
|
16
17
|
end
|
17
|
-
make_list_length(list_name, list_values.length)
|
18
|
-
list_values
|
19
|
-
end
|
20
|
-
|
21
|
-
def add_to_list(list_name, value)
|
22
|
-
redis.rpush(list_name, value)
|
23
|
-
end
|
24
|
-
|
25
|
-
def set_list_index(list_name, index, value)
|
26
|
-
redis.lset(list_name, index, value)
|
27
|
-
end
|
28
|
-
|
29
|
-
def list_length(list_name)
|
30
|
-
redis.llen(list_name)
|
31
|
-
end
|
32
18
|
|
33
|
-
|
34
|
-
redis.rpop(list_name)
|
35
|
-
end
|
36
|
-
|
37
|
-
def make_list_length(list_name, new_length)
|
38
|
-
redis.ltrim(list_name, 0, new_length - 1)
|
19
|
+
list_values
|
39
20
|
end
|
40
21
|
|
41
22
|
def add_to_set(set_name, value)
|
42
|
-
redis.sadd(set_name, value)
|
23
|
+
redis.sadd(set_name, value)
|
43
24
|
end
|
44
25
|
|
45
26
|
private
|
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
|
-
|
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
|
-
@
|
18
|
+
@alternative_chosen = false
|
16
19
|
end
|
17
20
|
|
18
21
|
def metadata
|
@@ -33,7 +36,7 @@ module Split
|
|
33
36
|
end
|
34
37
|
end
|
35
38
|
|
36
|
-
def complete!(
|
39
|
+
def complete!(context = nil)
|
37
40
|
if alternative
|
38
41
|
if Array(goals).empty?
|
39
42
|
alternative.increment_completion
|
@@ -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 @
|
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]
|
@@ -70,19 +74,25 @@ module Split
|
|
70
74
|
else
|
71
75
|
self.alternative = @user[@experiment.key]
|
72
76
|
if alternative.nil?
|
73
|
-
|
77
|
+
if @experiment.cohorting_disabled?
|
78
|
+
self.alternative = @experiment.control
|
79
|
+
else
|
80
|
+
self.alternative = @experiment.next_alternative
|
74
81
|
|
75
|
-
|
76
|
-
|
82
|
+
# Increment the number of participants since we are actually choosing a new alternative
|
83
|
+
self.alternative.increment_participation
|
77
84
|
|
78
|
-
|
85
|
+
run_callback context, Split.configuration.on_trial_choose
|
86
|
+
end
|
79
87
|
end
|
80
88
|
end
|
81
89
|
end
|
82
90
|
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
86
96
|
alternative
|
87
97
|
end
|
88
98
|
|
data/lib/split/user.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'forwardable'
|
2
4
|
|
3
5
|
module Split
|
@@ -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
29
|
if Split.configuration.allow_multiple_experiments == 'control'
|
25
30
|
experiments = active_experiments
|
26
|
-
|
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
|
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,6 +54,16 @@ module Split
|
|
48
54
|
experiment_pairs
|
49
55
|
end
|
50
56
|
|
57
|
+
def self.find(user_id, adapter)
|
58
|
+
adapter = adapter.is_a?(Symbol) ? Split::Persistence::ADAPTERS[adapter] : adapter
|
59
|
+
|
60
|
+
if adapter.respond_to?(:find)
|
61
|
+
User.new(nil, adapter.find(user_id))
|
62
|
+
else
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
51
67
|
private
|
52
68
|
|
53
69
|
def keys_without_experiment(keys, experiment_key)
|
data/lib/split/version.rb
CHANGED
data/lib/split/zscore.rb
CHANGED
data/lib/split.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'redis'
|
3
4
|
|
4
5
|
require 'split/algorithms/block_randomization'
|
5
6
|
require 'split/algorithms/weighted_sample'
|
6
7
|
require 'split/algorithms/whiplash'
|
7
8
|
require 'split/alternative'
|
9
|
+
require 'split/cache'
|
8
10
|
require 'split/configuration'
|
9
11
|
require 'split/encapsulated_helper'
|
10
12
|
require 'split/exceptions'
|
@@ -35,9 +37,9 @@ module Split
|
|
35
37
|
# `Redis::DistRedis`, or `Redis::Namespace`.
|
36
38
|
def redis=(server)
|
37
39
|
@redis = if server.is_a?(String)
|
38
|
-
Redis.new(:
|
40
|
+
Redis.new(url: server)
|
39
41
|
elsif server.is_a?(Hash)
|
40
|
-
Redis.new(server
|
42
|
+
Redis.new(server)
|
41
43
|
elsif server.respond_to?(:smembers)
|
42
44
|
server
|
43
45
|
else
|
@@ -64,11 +66,15 @@ module Split
|
|
64
66
|
self.configuration ||= Configuration.new
|
65
67
|
yield(configuration)
|
66
68
|
end
|
69
|
+
|
70
|
+
def cache(namespace, key, &block)
|
71
|
+
Split::Cache.fetch(namespace, key, &block)
|
72
|
+
end
|
67
73
|
end
|
68
74
|
|
69
75
|
# 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.
|
70
76
|
if defined?(::Rails)
|
71
|
-
class Railtie < Rails::Railtie
|
77
|
+
class Split::Railtie < Rails::Railtie
|
72
78
|
config.before_initialize { Split.configure {} }
|
73
79
|
end
|
74
80
|
else
|
data/spec/alternative_spec.rb
CHANGED
@@ -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
|
data/spec/cache_spec.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Split::Cache do
|
5
|
+
|
6
|
+
let(:namespace) { :test_namespace }
|
7
|
+
let(:key) { :test_key }
|
8
|
+
let(:now) { 1606189017 }
|
9
|
+
|
10
|
+
before { allow(Time).to receive(:now).and_return(now) }
|
11
|
+
|
12
|
+
describe 'clear' do
|
13
|
+
|
14
|
+
before { Split.configuration.cache = true }
|
15
|
+
|
16
|
+
it 'clears the cache' do
|
17
|
+
expect(Time).to receive(:now).and_return(now).exactly(2).times
|
18
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
19
|
+
Split::Cache.clear
|
20
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'clear_key' do
|
25
|
+
before { Split.configuration.cache = true }
|
26
|
+
|
27
|
+
it 'clears the cache' do
|
28
|
+
expect(Time).to receive(:now).and_return(now).exactly(3).times
|
29
|
+
Split::Cache.fetch(namespace, :key1) { Time.now }
|
30
|
+
Split::Cache.fetch(namespace, :key2) { Time.now }
|
31
|
+
Split::Cache.clear_key(:key1)
|
32
|
+
|
33
|
+
Split::Cache.fetch(namespace, :key1) { Time.now }
|
34
|
+
Split::Cache.fetch(namespace, :key2) { Time.now }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'fetch' do
|
39
|
+
|
40
|
+
subject { Split::Cache.fetch(namespace, key) { Time.now } }
|
41
|
+
|
42
|
+
context 'when cache disabled' do
|
43
|
+
|
44
|
+
before { Split.configuration.cache = false }
|
45
|
+
|
46
|
+
it 'returns the yield' do
|
47
|
+
expect(subject).to eql(now)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'yields every time' do
|
51
|
+
expect(Time).to receive(:now).and_return(now).exactly(2).times
|
52
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
53
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when cache enabled' do
|
58
|
+
|
59
|
+
before { Split.configuration.cache = true }
|
60
|
+
|
61
|
+
it 'returns the yield' do
|
62
|
+
expect(subject).to eql(now)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'yields once' do
|
66
|
+
expect(Time).to receive(:now).and_return(now).once
|
67
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
68
|
+
Split::Cache.fetch(namespace, key) { Time.now }
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'honors namespace' do
|
72
|
+
expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a)
|
73
|
+
expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b)
|
74
|
+
|
75
|
+
expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a)
|
76
|
+
expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'honors key' do
|
80
|
+
expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a)
|
81
|
+
expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b)
|
82
|
+
|
83
|
+
expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a)
|
84
|
+
expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/spec/configuration_spec.rb
CHANGED
@@ -212,23 +212,12 @@ describe Split::Configuration do
|
|
212
212
|
expect(@config.normalized_experiments).to eq({:my_experiment=>{:alternatives=>[{"control_opt"=>0.67}, [{"second_opt"=>0.1}, {"third_opt"=>0.23}]]}})
|
213
213
|
end
|
214
214
|
|
215
|
-
context 'redis_url configuration [DEPRECATED]' do
|
216
|
-
it 'should warn on set and assign to #redis' do
|
217
|
-
expect(@config).to receive(:warn).with(/\[DEPRECATED\]/) { nil }
|
218
|
-
@config.redis_url = 'example_url'
|
219
|
-
expect(@config.redis).to eq('example_url')
|
220
|
-
end
|
221
|
-
|
222
|
-
it 'should warn on get and return #redis' do
|
223
|
-
expect(@config).to receive(:warn).with(/\[DEPRECATED\]/) { nil }
|
224
|
-
@config.redis = 'example_url'
|
225
|
-
expect(@config.redis_url).to eq('example_url')
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
215
|
context "redis configuration" do
|
230
216
|
it "should default to local redis server" do
|
231
|
-
|
217
|
+
old_redis_url = ENV['REDIS_URL']
|
218
|
+
ENV.delete('REDIS_URL')
|
219
|
+
expect(Split::Configuration.new.redis).to eq("redis://localhost:6379")
|
220
|
+
ENV['REDIS_URL'] = old_redis_url
|
232
221
|
end
|
233
222
|
|
234
223
|
it "should allow for redis url to be configured" do
|
@@ -238,8 +227,10 @@ describe Split::Configuration do
|
|
238
227
|
|
239
228
|
context "provided REDIS_URL environment variable" do
|
240
229
|
it "should use the ENV variable" do
|
230
|
+
old_redis_url = ENV['REDIS_URL']
|
241
231
|
ENV['REDIS_URL'] = "env_redis_url"
|
242
232
|
expect(Split::Configuration.new.redis).to eq("env_redis_url")
|
233
|
+
ENV['REDIS_URL'] = old_redis_url
|
243
234
|
end
|
244
235
|
end
|
245
236
|
end
|
@@ -255,4 +246,15 @@ describe Split::Configuration do
|
|
255
246
|
end
|
256
247
|
end
|
257
248
|
|
249
|
+
context "persistence cookie domain" do
|
250
|
+
it "should default to nil" do
|
251
|
+
expect(@config.persistence_cookie_domain).to eq(nil)
|
252
|
+
end
|
253
|
+
|
254
|
+
it "should allow the persistence cookie domain to be configured" do
|
255
|
+
@config.persistence_cookie_domain = '.acme.com'
|
256
|
+
expect(@config.persistence_cookie_domain).to eq('.acme.com')
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
258
260
|
end
|
@@ -10,7 +10,9 @@ describe Split::DashboardPaginationHelpers do
|
|
10
10
|
context 'when params empty' do
|
11
11
|
let(:params) { Hash[] }
|
12
12
|
|
13
|
-
it 'returns 10' do
|
13
|
+
it 'returns the default (10)' do
|
14
|
+
default_per_page = Split.configuration.dashboard_pagination_default_per_page
|
15
|
+
expect(pagination_per).to eql default_per_page
|
14
16
|
expect(pagination_per).to eql 10
|
15
17
|
end
|
16
18
|
end
|