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/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"
@@ -8,12 +9,12 @@ module Split
8
9
  def ab_test(metric_descriptor, control = nil, *alternatives)
9
10
  begin
10
11
  experiment = ExperimentCatalog.find_or_initialize(metric_descriptor, control, *alternatives)
11
- alternative = if Split.configuration.enabled
12
+ alternative = if Split.configuration.enabled && !exclude_visitor?
12
13
  experiment.save
13
14
  raise(Split::InvalidExperimentsFormatError) unless (Split.configuration.experiments || {}).fetch(experiment.name.to_sym, {})[:combined_experiments].nil?
14
- trial = Trial.new(:user => ab_user, :experiment => experiment,
15
- :override => override_alternative(experiment.name), :exclude => exclude_visitor?,
16
- :disabled => split_generically_disabled?)
15
+ trial = Trial.new(user: ab_user, experiment: experiment,
16
+ override: override_alternative(experiment.name), exclude: exclude_visitor?,
17
+ disabled: split_generically_disabled?)
17
18
  alt = trial.choose!(self)
18
19
  alt ? alt.name : nil
19
20
  else
@@ -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
@@ -43,16 +44,22 @@ module Split
43
44
  ab_user.delete(experiment.key)
44
45
  end
45
46
 
46
- def finish_experiment(experiment, options = {:reset => true})
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
- return true
52
+ 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)
@@ -62,14 +69,15 @@ module Split
62
69
  end
63
70
  end
64
71
 
65
- def ab_finished(metric_descriptor, options = {:reset => true})
72
+ def ab_finished(metric_descriptor, options = { reset: true })
66
73
  return if exclude_visitor? || Split.configuration.disabled?
67
74
  metric_descriptor, goals = normalize_metric(metric_descriptor)
68
75
  experiments = Metric.possible_experiments(metric_descriptor)
69
76
 
70
77
  if experiments.any?
71
78
  experiments.each do |experiment|
72
- finish_experiment(experiment, options.merge(:goals => goals))
79
+ next if override_present?(experiment.key)
80
+ finish_experiment(experiment, options.merge(goals: goals))
73
81
  end
74
82
  end
75
83
  rescue => e
@@ -78,8 +86,8 @@ module Split
78
86
  end
79
87
 
80
88
  def ab_record_extra_info(metric_descriptor, key, value = 1)
81
- return if exclude_visitor? || Split.configuration.disabled?
82
- metric_descriptor, goals = normalize_metric(metric_descriptor)
89
+ return if exclude_visitor? || Split.configuration.disabled? || value.nil?
90
+ metric_descriptor, _ = normalize_metric(metric_descriptor)
83
91
  experiments = Metric.possible_experiments(metric_descriptor)
84
92
 
85
93
  if experiments.any?
@@ -87,7 +95,7 @@ module Split
87
95
  alternative_name = ab_user[experiment.key]
88
96
 
89
97
  if alternative_name
90
- alternative = experiment.alternatives.find{|alt| alt.name == alternative_name}
98
+ alternative = experiment.alternatives.find { |alt| alt.name == alternative_name }
91
99
  alternative.record_extra_info(key, value) if alternative
92
100
  end
93
101
  end
@@ -97,24 +105,36 @@ module Split
97
105
  Split.configuration.db_failover_on_db_error.call(e)
98
106
  end
99
107
 
100
- def ab_active_experiments()
108
+ def ab_active_experiments
101
109
  ab_user.active_experiments
102
110
  rescue => e
103
111
  raise unless Split.configuration.db_failover
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)
113
- defined?(params) && params[OVERRIDE_PARAM_NAME] && params[OVERRIDE_PARAM_NAME][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)
124
+ params_present? && params[OVERRIDE_PARAM_NAME] && params[OVERRIDE_PARAM_NAME][experiment_name]
125
+ end
126
+
127
+ def override_alternative_by_cookies(experiment_name)
128
+ return unless request_present?
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
114
134
  end
115
135
 
116
136
  def split_generically_disabled?
117
- defined?(params) && params['SPLIT_DISABLE']
137
+ params_present? && params["SPLIT_DISABLE"]
118
138
  end
119
139
 
120
140
  def ab_user
@@ -122,22 +142,34 @@ module Split
122
142
  end
123
143
 
124
144
  def exclude_visitor?
125
- instance_eval(&Split.configuration.ignore_filter) || is_ignored_ip_address? || is_robot?
145
+ request_present? && (instance_exec(request, &Split.configuration.ignore_filter) || is_ignored_ip_address? || is_robot? || is_preview?)
126
146
  end
127
147
 
128
148
  def is_robot?
129
- defined?(request) && request.user_agent =~ Split.configuration.robot_regex
149
+ request_present? && request.user_agent =~ Split.configuration.robot_regex
150
+ end
151
+
152
+ def is_preview?
153
+ request_present? && defined?(request.headers) && request.headers["x-purpose"] == "preview"
130
154
  end
131
155
 
132
156
  def is_ignored_ip_address?
133
157
  return false if Split.configuration.ignore_ip_addresses.empty?
134
158
 
135
159
  Split.configuration.ignore_ip_addresses.each do |ip|
136
- return true if defined?(request) && (request.ip == ip || (ip.class == Regexp && request.ip =~ ip))
160
+ return true if request_present? && (request.ip == ip || (ip.class == Regexp && request.ip =~ ip))
137
161
  end
138
162
  false
139
163
  end
140
164
 
165
+ def params_present?
166
+ defined?(params) && params
167
+ end
168
+
169
+ def request_present?
170
+ defined?(request) && request
171
+ end
172
+
141
173
  def active_experiments
142
174
  ab_user.active_experiments
143
175
  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
@@ -15,13 +16,13 @@ module Split
15
16
  def self.load_from_redis(name)
16
17
  metric = Split.redis.hget(:metrics, name)
17
18
  if metric
18
- experiment_names = metric.split(',')
19
+ experiment_names = metric.split(",")
19
20
 
20
21
  experiments = experiment_names.collect do |experiment_name|
21
22
  Split::ExperimentCatalog.find(experiment_name)
22
23
  end
23
24
 
24
- Split::Metric.new(:name => name, :experiments => experiments)
25
+ Split::Metric.new(name: name, experiments: experiments)
25
26
  else
26
27
  nil
27
28
  end
@@ -30,7 +31,7 @@ module Split
30
31
  def self.load_from_configuration(name)
31
32
  metrics = Split.configuration.metrics
32
33
  if metrics && metrics[name]
33
- Split::Metric.new(:experiments => metrics[name], :name => name)
34
+ Split::Metric.new(experiments: metrics[name], name: name)
34
35
  else
35
36
  nil
36
37
  end
@@ -76,7 +77,7 @@ module Split
76
77
  end
77
78
 
78
79
  def save
79
- Split.redis.hset(:metrics, name, experiments.map(&:name).join(','))
80
+ Split.redis.hset(:metrics, name, experiments.map(&:name).join(","))
80
81
  end
81
82
 
82
83
  def complete!
@@ -96,6 +97,5 @@ module Split
96
97
  return metric_name, goals
97
98
  end
98
99
  private_class_method :normalize_metric
99
-
100
100
  end
101
101
  end
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "json"
3
4
 
4
5
  module Split
5
6
  module Persistence
6
7
  class CookieAdapter
7
-
8
8
  def initialize(context)
9
+ @context = context
9
10
  @request, @response = context.request, context.response
10
11
  @cookies = @request.cookies
11
12
  @expires = Time.now + cookie_length_config
@@ -28,20 +29,50 @@ module Split
28
29
  end
29
30
 
30
31
  private
32
+ def set_cookie(value = {})
33
+ cookie_key = :split.to_s
34
+ cookie_value = default_options.merge(value: JSON.generate(value))
35
+ if action_dispatch?
36
+ # The "send" is necessary when we call ab_test from the controller
37
+ # and thus @context is a rails controller, because then "cookies" is
38
+ # a private method.
39
+ @context.send(:cookies)[cookie_key] = cookie_value
40
+ else
41
+ set_cookie_via_rack(cookie_key, cookie_value)
42
+ end
43
+ end
31
44
 
32
- def set_cookie(value = {})
33
- @response.set_cookie :split.to_s, default_options.merge(value: JSON.generate(value))
34
- end
45
+ def default_options
46
+ { expires: @expires, path: "/", domain: cookie_domain_config }.compact
47
+ end
35
48
 
36
- def default_options
37
- { expires: @expires, path: '/' }
38
- end
49
+ def set_cookie_via_rack(key, value)
50
+ headers = @response.respond_to?(:header) ? @response.header : @response.headers
51
+ delete_cookie_header!(headers, key, value)
52
+ Rack::Utils.set_cookie_header!(headers, key, value)
53
+ end
39
54
 
40
- def hash
41
- @hash ||= begin
42
- if cookies = @cookies[:split.to_s]
55
+ # Use Rack::Utils#make_delete_cookie_header after Rack 2.0.0
56
+ def delete_cookie_header!(header, key, value)
57
+ cookie_header = header["Set-Cookie"]
58
+ case cookie_header
59
+ when nil, ""
60
+ cookies = []
61
+ when String
62
+ cookies = cookie_header.split("\n")
63
+ when Array
64
+ cookies = cookie_header
65
+ end
66
+
67
+ cookies.reject! { |cookie| cookie =~ /\A#{Rack::Utils.escape(key)}=/ }
68
+ header["Set-Cookie"] = cookies.join("\n")
69
+ end
70
+
71
+ def hash
72
+ @hash ||= if cookies = @cookies[:split.to_s]
43
73
  begin
44
- JSON.parse(cookies)
74
+ parsed = JSON.parse(cookies)
75
+ parsed.is_a?(Hash) ? parsed : {}
45
76
  rescue JSON::ParserError
46
77
  {}
47
78
  end
@@ -49,12 +80,18 @@ module Split
49
80
  {}
50
81
  end
51
82
  end
52
- end
53
83
 
54
- def cookie_length_config
55
- Split.configuration.persistence_cookie_length
56
- end
84
+ def cookie_length_config
85
+ Split.configuration.persistence_cookie_length
86
+ end
87
+
88
+ def cookie_domain_config
89
+ Split.configuration.persistence_cookie_domain
90
+ end
57
91
 
92
+ def action_dispatch?
93
+ defined?(Rails) && @response.is_a?(ActionDispatch::Response)
94
+ end
58
95
  end
59
96
  end
60
97
  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,59 @@ 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
39
63
  end
40
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
72
+ end
73
+
74
+ private
75
+ def decrement_participation?(old_value, value)
76
+ !old_value.nil? && !value.nil? && old_value != value
77
+ end
78
+
79
+ def decrement_participation(key, value)
80
+ Split.redis.hincrby("#{key}:#{value}", "participant_count", -1)
81
+ end
41
82
  end
42
83
  end
43
84
  end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Split
3
4
  module Persistence
4
5
  class RedisAdapter
5
- DEFAULT_CONFIG = {:namespace => 'persistence'}.freeze
6
+ DEFAULT_CONFIG = { namespace: "persistence" }.freeze
6
7
 
7
8
  attr_reader :redis_key
8
9
 
@@ -26,7 +27,7 @@ module Split
26
27
  end
27
28
 
28
29
  def []=(field, value)
29
- Split.redis.hset(redis_key, field, value)
30
+ Split.redis.hset(redis_key, field, value.to_s)
30
31
  expire_seconds = self.class.config[:expire_seconds]
31
32
  Split.redis.expire(redis_key, expire_seconds) if expire_seconds
32
33
  end
@@ -39,7 +40,11 @@ module Split
39
40
  Split.redis.hkeys(redis_key)
40
41
  end
41
42
 
42
- def self.with_config(options={})
43
+ def self.find(user_id)
44
+ new(nil, user_id)
45
+ end
46
+
47
+ def self.with_config(options = {})
43
48
  self.config.merge!(options)
44
49
  self
45
50
  end
@@ -51,7 +56,6 @@ module Split
51
56
  def self.reset_config!
52
57
  @config = DEFAULT_CONFIG.dup
53
58
  end
54
-
55
59
  end
56
60
  end
57
61
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Split
3
4
  module Persistence
4
5
  class SessionAdapter
5
-
6
6
  def initialize(context)
7
7
  @session = context.session
8
8
  @session[:split] ||= {}
@@ -23,7 +23,6 @@ module Split
23
23
  def keys
24
24
  @session[:split].keys
25
25
  end
26
-
27
26
  end
28
27
  end
29
28
  end
@@ -2,14 +2,16 @@
2
2
 
3
3
  module Split
4
4
  module Persistence
5
- require 'split/persistence/cookie_adapter'
6
- require 'split/persistence/dual_adapter'
7
- require 'split/persistence/redis_adapter'
8
- require 'split/persistence/session_adapter'
5
+ require "split/persistence/cookie_adapter"
6
+ require "split/persistence/dual_adapter"
7
+ require "split/persistence/redis_adapter"
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,46 +8,29 @@ 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
+ tmp_list += redis_namespace_used? ? "{#{Split.redis.namespace}:#{list_name}}" : "{#{list_name}}"
15
+ multi.rpush(tmp_list, list_values)
16
+ multi.rename(tmp_list, list_name)
15
17
  end
16
18
  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
19
 
29
- def list_length(list_name)
30
- redis.llen(list_name)
31
- end
32
-
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
- while list_length(list_name) > new_length
39
- remove_last_item_from_list(list_name)
40
- end
20
+ list_values
41
21
  end
42
22
 
43
23
  def add_to_set(set_name, value)
44
- redis.sadd(set_name, value) unless redis.sismember(set_name, value)
24
+ return redis.sadd?(set_name, value) if redis.respond_to?(:sadd?)
25
+
26
+ redis.sadd(set_name, value)
45
27
  end
46
28
 
47
29
  private
30
+ attr_accessor :redis
48
31
 
49
- attr_accessor :redis
32
+ def redis_namespace_used?
33
+ defined?(Redis::Namespace) && Split.redis.is_a?(Redis::Namespace)
34
+ end
50
35
  end
51
36
  end