split 3.3.2 → 4.0.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  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 +61 -0
  7. data/.rspec +1 -0
  8. data/.rubocop.yml +71 -1044
  9. data/.rubocop_todo.yml +226 -0
  10. data/Appraisals +1 -1
  11. data/CHANGELOG.md +62 -0
  12. data/CODE_OF_CONDUCT.md +3 -3
  13. data/Gemfile +2 -0
  14. data/README.md +40 -18
  15. data/Rakefile +2 -0
  16. data/gemfiles/6.0.gemfile +1 -1
  17. data/lib/split/algorithms/block_randomization.rb +2 -0
  18. data/lib/split/algorithms/weighted_sample.rb +2 -1
  19. data/lib/split/algorithms/whiplash.rb +3 -2
  20. data/lib/split/alternative.rb +4 -3
  21. data/lib/split/cache.rb +28 -0
  22. data/lib/split/combined_experiments_helper.rb +2 -1
  23. data/lib/split/configuration.rb +13 -14
  24. data/lib/split/dashboard/helpers.rb +1 -0
  25. data/lib/split/dashboard/pagination_helpers.rb +3 -3
  26. data/lib/split/dashboard/paginator.rb +1 -0
  27. data/lib/split/dashboard/public/dashboard.js +10 -0
  28. data/lib/split/dashboard/public/style.css +5 -0
  29. data/lib/split/dashboard/views/_controls.erb +13 -0
  30. data/lib/split/dashboard/views/layout.erb +1 -1
  31. data/lib/split/dashboard.rb +19 -1
  32. data/lib/split/encapsulated_helper.rb +3 -2
  33. data/lib/split/engine.rb +7 -4
  34. data/lib/split/exceptions.rb +1 -0
  35. data/lib/split/experiment.rb +98 -65
  36. data/lib/split/experiment_catalog.rb +1 -3
  37. data/lib/split/extensions/string.rb +1 -0
  38. data/lib/split/goals_collection.rb +2 -0
  39. data/lib/split/helper.rb +28 -8
  40. data/lib/split/metric.rb +2 -1
  41. data/lib/split/persistence/cookie_adapter.rb +6 -1
  42. data/lib/split/persistence/dual_adapter.rb +54 -12
  43. data/lib/split/persistence/redis_adapter.rb +5 -0
  44. data/lib/split/persistence/session_adapter.rb +1 -0
  45. data/lib/split/persistence.rb +4 -2
  46. data/lib/split/redis_interface.rb +9 -28
  47. data/lib/split/trial.rb +21 -11
  48. data/lib/split/user.rb +20 -4
  49. data/lib/split/version.rb +2 -4
  50. data/lib/split/zscore.rb +1 -0
  51. data/lib/split.rb +9 -3
  52. data/spec/alternative_spec.rb +1 -1
  53. data/spec/cache_spec.rb +88 -0
  54. data/spec/configuration_spec.rb +17 -15
  55. data/spec/dashboard/pagination_helpers_spec.rb +3 -1
  56. data/spec/dashboard_helpers_spec.rb +2 -2
  57. data/spec/dashboard_spec.rb +78 -17
  58. data/spec/encapsulated_helper_spec.rb +2 -2
  59. data/spec/experiment_spec.rb +116 -12
  60. data/spec/goals_collection_spec.rb +1 -1
  61. data/spec/helper_spec.rb +186 -112
  62. data/spec/persistence/cookie_adapter_spec.rb +1 -1
  63. data/spec/persistence/dual_adapter_spec.rb +160 -68
  64. data/spec/persistence/redis_adapter_spec.rb +9 -0
  65. data/spec/redis_interface_spec.rb +0 -69
  66. data/spec/spec_helper.rb +5 -6
  67. data/spec/trial_spec.rb +45 -19
  68. data/spec/user_spec.rb +45 -3
  69. data/split.gemspec +8 -9
  70. metadata +28 -36
  71. data/.travis.yml +0 -66
  72. 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 = trial ? trial.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(:user => ab_user, :experiment => experiment,
54
- :alternative => alternative_name)
55
- trial.complete!(options[:goals], self)
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, goals = normalize_metric(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
- override_alternative(experiment_name)
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
- extend Forwardable
9
- def_delegators :@adapter, :keys, :[], :[]=, :delete
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
- if logged_in.call(context)
26
- @adapter = logged_in_adapter.new(context)
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
- @adapter = logged_out_adapter.new(context)
41
+ @active_adapter.keys
29
42
  end
30
43
  end
31
44
 
32
- def self.with_config(options={})
33
- self.config.merge!(options)
34
- self
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 self.config
38
- @config ||= {}
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
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Split
3
4
  module Persistence
4
5
  class SessionAdapter
@@ -8,8 +8,10 @@ module Split
8
8
  require 'split/persistence/session_adapter'
9
9
 
10
10
  ADAPTERS = {
11
- :cookie => Split::Persistence::CookieAdapter,
12
- :session => Split::Persistence::SessionAdapter
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
- max_index = list_length(list_name) - 1
10
- list_values.each_with_index do |value, index|
11
- if index > max_index
12
- add_to_list(list_name, value)
13
- else
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
- def remove_last_item_from_list(list_name)
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) unless redis.sismember(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
- 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
@@ -33,7 +36,7 @@ module Split
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
@@ -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]
@@ -70,19 +74,25 @@ module Split
70
74
  else
71
75
  self.alternative = @user[@experiment.key]
72
76
  if alternative.nil?
73
- self.alternative = @experiment.next_alternative
77
+ if @experiment.cohorting_disabled?
78
+ self.alternative = @experiment.control
79
+ else
80
+ self.alternative = @experiment.next_alternative
74
81
 
75
- # Increment the number of participants since we are actually choosing a new alternative
76
- self.alternative.increment_participation
82
+ # Increment the number of participants since we are actually choosing a new alternative
83
+ self.alternative.increment_participation
77
84
 
78
- run_callback context, Split.configuration.on_trial_choose
85
+ run_callback context, Split.configuration.on_trial_choose
86
+ end
79
87
  end
80
88
  end
81
89
  end
82
90
 
83
- @user[@experiment.key] = alternative.name if !@experiment.has_winner? && should_store_alternative?
84
- @alternative_choosen = true
85
- 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
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
- 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,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
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Split
3
- MAJOR = 3
4
- MINOR = 3
5
- PATCH = 2
6
- VERSION = [MAJOR, MINOR, PATCH].join('.')
4
+ VERSION = "4.0.0.pre2"
7
5
  end
data/lib/split/zscore.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Split
3
4
  class Zscore
4
5
 
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(:url => server, :thread_safe => true)
40
+ Redis.new(url: server)
39
41
  elsif server.is_a?(Hash)
40
- Redis.new(server.merge(:thread_safe => true))
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
@@ -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
@@ -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
@@ -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
- expect(@config.redis).to eq("redis://localhost:6379")
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