split 4.0.1 → 4.0.3

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +8 -3
  3. data/.rubocop.yml +2 -5
  4. data/CHANGELOG.md +38 -0
  5. data/CONTRIBUTING.md +1 -1
  6. data/Gemfile +1 -1
  7. data/README.md +11 -3
  8. data/Rakefile +4 -5
  9. data/gemfiles/5.2.gemfile +1 -3
  10. data/gemfiles/6.0.gemfile +1 -3
  11. data/gemfiles/6.1.gemfile +1 -3
  12. data/gemfiles/7.0.gemfile +1 -3
  13. data/lib/split/algorithms/block_randomization.rb +5 -6
  14. data/lib/split/algorithms/whiplash.rb +16 -18
  15. data/lib/split/algorithms.rb +14 -0
  16. data/lib/split/alternative.rb +21 -22
  17. data/lib/split/cache.rb +0 -1
  18. data/lib/split/combined_experiments_helper.rb +4 -4
  19. data/lib/split/configuration.rb +83 -84
  20. data/lib/split/dashboard/helpers.rb +6 -7
  21. data/lib/split/dashboard/pagination_helpers.rb +53 -54
  22. data/lib/split/dashboard/public/style.css +5 -2
  23. data/lib/split/dashboard/views/_experiment.erb +2 -1
  24. data/lib/split/dashboard/views/index.erb +19 -4
  25. data/lib/split/dashboard.rb +29 -23
  26. data/lib/split/encapsulated_helper.rb +4 -6
  27. data/lib/split/experiment.rb +93 -88
  28. data/lib/split/experiment_catalog.rb +6 -5
  29. data/lib/split/extensions/string.rb +1 -1
  30. data/lib/split/goals_collection.rb +8 -10
  31. data/lib/split/helper.rb +20 -20
  32. data/lib/split/metric.rb +4 -5
  33. data/lib/split/persistence/cookie_adapter.rb +44 -47
  34. data/lib/split/persistence/dual_adapter.rb +7 -8
  35. data/lib/split/persistence/redis_adapter.rb +3 -4
  36. data/lib/split/persistence/session_adapter.rb +0 -2
  37. data/lib/split/persistence.rb +4 -4
  38. data/lib/split/redis_interface.rb +7 -1
  39. data/lib/split/trial.rb +23 -24
  40. data/lib/split/user.rb +12 -13
  41. data/lib/split/version.rb +1 -1
  42. data/lib/split/zscore.rb +1 -3
  43. data/lib/split.rb +26 -25
  44. data/spec/algorithms/block_randomization_spec.rb +6 -5
  45. data/spec/algorithms/weighted_sample_spec.rb +6 -5
  46. data/spec/algorithms/whiplash_spec.rb +4 -5
  47. data/spec/alternative_spec.rb +35 -36
  48. data/spec/cache_spec.rb +15 -19
  49. data/spec/combined_experiments_helper_spec.rb +18 -17
  50. data/spec/configuration_spec.rb +32 -38
  51. data/spec/dashboard/pagination_helpers_spec.rb +69 -67
  52. data/spec/dashboard/paginator_spec.rb +10 -9
  53. data/spec/dashboard_helpers_spec.rb +19 -18
  54. data/spec/dashboard_spec.rb +79 -35
  55. data/spec/encapsulated_helper_spec.rb +12 -14
  56. data/spec/experiment_catalog_spec.rb +14 -13
  57. data/spec/experiment_spec.rb +132 -123
  58. data/spec/goals_collection_spec.rb +17 -15
  59. data/spec/helper_spec.rb +415 -382
  60. data/spec/metric_spec.rb +14 -14
  61. data/spec/persistence/cookie_adapter_spec.rb +23 -8
  62. data/spec/persistence/dual_adapter_spec.rb +71 -71
  63. data/spec/persistence/redis_adapter_spec.rb +28 -29
  64. data/spec/persistence/session_adapter_spec.rb +2 -3
  65. data/spec/persistence_spec.rb +1 -2
  66. data/spec/redis_interface_spec.rb +26 -14
  67. data/spec/spec_helper.rb +16 -13
  68. data/spec/split_spec.rb +11 -11
  69. data/spec/support/cookies_mock.rb +1 -2
  70. data/spec/trial_spec.rb +61 -60
  71. data/spec/user_spec.rb +36 -36
  72. data/split.gemspec +21 -20
  73. metadata +25 -14
  74. data/.rubocop_todo.yml +0 -226
  75. data/Appraisals +0 -19
  76. data/gemfiles/5.0.gemfile +0 -9
  77. data/gemfiles/5.1.gemfile +0 -9
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rubystats'
4
-
5
3
  module Split
6
4
  class Experiment
7
5
  attr_accessor :name
@@ -13,7 +11,7 @@ module Split
13
11
  attr_reader :resettable
14
12
 
15
13
  DEFAULT_OPTIONS = {
16
- :resettable => true
14
+ resettable: true
17
15
  }
18
16
 
19
17
  def self.find(name)
@@ -52,7 +50,7 @@ module Split
52
50
 
53
51
  if alts.length == 1
54
52
  if alts[0].is_a? Hash
55
- alts = alts[0].map{|k, v| {k => v} }
53
+ alts = alts[0].map { |k, v| { k => v } }
56
54
  end
57
55
  end
58
56
 
@@ -87,7 +85,7 @@ module Split
87
85
  persist_experiment_configuration
88
86
  end
89
87
 
90
- redis.hmset(experiment_config_key, :resettable, resettable,
88
+ redis.hmset(experiment_config_key, :resettable, resettable.to_s,
91
89
  :algorithm, algorithm.to_s)
92
90
  self
93
91
  end
@@ -96,7 +94,7 @@ module Split
96
94
  if @alternatives.empty? && Split.configuration.experiment_for(@name).nil?
97
95
  raise ExperimentNotFound.new("Experiment #{@name} not found")
98
96
  end
99
- @alternatives.each {|a| a.validate! }
97
+ @alternatives.each { |a| a.validate! }
100
98
  goals_collection.validate!
101
99
  end
102
100
 
@@ -109,7 +107,7 @@ module Split
109
107
  end
110
108
 
111
109
  def [](name)
112
- alternatives.find{|a| a.name == name}
110
+ alternatives.find { |a| a.name == name }
113
111
  end
114
112
 
115
113
  def algorithm
@@ -121,7 +119,7 @@ module Split
121
119
  end
122
120
 
123
121
  def resettable=(resettable)
124
- @resettable = resettable.is_a?(String) ? resettable == 'true' : resettable
122
+ @resettable = resettable.is_a?(String) ? resettable == "true" : resettable
125
123
  end
126
124
 
127
125
  def alternatives=(alts)
@@ -157,7 +155,7 @@ module Split
157
155
  end
158
156
 
159
157
  def participant_count
160
- alternatives.inject(0){|sum, a| sum + a.participant_count}
158
+ alternatives.inject(0) { |sum, a| sum + a.participant_count }
161
159
  end
162
160
 
163
161
  def control
@@ -262,8 +260,8 @@ module Split
262
260
  exp_config = redis.hgetall(experiment_config_key)
263
261
 
264
262
  options = {
265
- resettable: exp_config['resettable'],
266
- algorithm: exp_config['algorithm'],
263
+ resettable: exp_config["resettable"],
264
+ algorithm: exp_config["algorithm"],
267
265
  alternatives: load_alternatives_from_redis,
268
266
  goals: Split::GoalsCollection.new(@name).load_from_redis,
269
267
  metadata: load_metadata_from_redis
@@ -272,7 +270,16 @@ module Split
272
270
  set_alternatives_and_options(options)
273
271
  end
274
272
 
273
+ def can_calculate_winning_alternatives?
274
+ self.alternatives.all? do |alternative|
275
+ alternative.participant_count >= 0 &&
276
+ (alternative.participant_count >= alternative.completed_count)
277
+ end
278
+ end
279
+
275
280
  def calc_winning_alternatives
281
+ return unless can_calculate_winning_alternatives?
282
+
276
283
  # Cache the winning alternatives so we recalculate them once per the specified interval.
277
284
  intervals_since_epoch =
278
285
  Time.now.utc.to_i / Split.configuration.winning_alternative_recalculation_interval
@@ -328,11 +335,11 @@ module Split
328
335
  winning_counts.each do |alternative, wins|
329
336
  alternative_probabilities[alternative] = wins / number_of_simulations.to_f
330
337
  end
331
- return alternative_probabilities
338
+ alternative_probabilities
332
339
  end
333
340
 
334
341
  def count_simulated_wins(winning_alternatives)
335
- # initialize a hash to keep track of winning alternative in simulations
342
+ # initialize a hash to keep track of winning alternative in simulations
336
343
  winning_counts = {}
337
344
  alternatives.each do |alternative|
338
345
  winning_counts[alternative] = 0
@@ -341,7 +348,7 @@ module Split
341
348
  winning_alternatives.each do |alternative|
342
349
  winning_counts[alternative] += 1
343
350
  end
344
- return winning_counts
351
+ winning_counts
345
352
  end
346
353
 
347
354
  def find_simulated_winner(simulated_cr_hash)
@@ -353,7 +360,7 @@ module Split
353
360
  end
354
361
  end
355
362
  winner = winning_pair[0]
356
- return winner
363
+ winner
357
364
  end
358
365
 
359
366
  def calc_simulated_conversion_rates(beta_params)
@@ -363,11 +370,11 @@ module Split
363
370
  beta_params.each do |alternative, params|
364
371
  alpha = params[0]
365
372
  beta = params[1]
366
- simulated_conversion_rate = Rubystats::BetaDistribution.new(alpha, beta).rng
373
+ simulated_conversion_rate = Split::Algorithms.beta_distribution_rng(alpha, beta)
367
374
  simulated_cr_hash[alternative] = simulated_conversion_rate
368
375
  end
369
376
 
370
- return simulated_cr_hash
377
+ simulated_cr_hash
371
378
  end
372
379
 
373
380
  def calc_beta_params(goal = nil)
@@ -381,7 +388,7 @@ module Split
381
388
 
382
389
  beta_params[alternative] = params
383
390
  end
384
- return beta_params
391
+ beta_params
385
392
  end
386
393
 
387
394
  def calc_time=(time)
@@ -394,11 +401,11 @@ module Split
394
401
 
395
402
  def jstring(goal = nil)
396
403
  js_id = if goal.nil?
397
- name
398
- else
399
- name + "-" + goal
400
- end
401
- js_id.gsub('/', '--')
404
+ name
405
+ else
406
+ name + "-" + goal
407
+ end
408
+ js_id.gsub("/", "--")
402
409
  end
403
410
 
404
411
  def cohorting_disabled?
@@ -410,95 +417,93 @@ module Split
410
417
 
411
418
  def disable_cohorting
412
419
  @cohorting_disabled = true
413
- redis.hset(experiment_config_key, :cohorting, true)
420
+ redis.hset(experiment_config_key, :cohorting, true.to_s)
414
421
  end
415
422
 
416
423
  def enable_cohorting
417
424
  @cohorting_disabled = false
418
- redis.hset(experiment_config_key, :cohorting, false)
425
+ redis.hset(experiment_config_key, :cohorting, false.to_s)
419
426
  end
420
427
 
421
428
  protected
429
+ def experiment_config_key
430
+ "experiment_configurations/#{@name}"
431
+ end
422
432
 
423
- def experiment_config_key
424
- "experiment_configurations/#{@name}"
425
- end
426
-
427
- def load_metadata_from_configuration
428
- Split.configuration.experiment_for(@name)[:metadata]
429
- end
433
+ def load_metadata_from_configuration
434
+ Split.configuration.experiment_for(@name)[:metadata]
435
+ end
430
436
 
431
- def load_metadata_from_redis
432
- meta = redis.get(metadata_key)
433
- JSON.parse(meta) unless meta.nil?
434
- end
437
+ def load_metadata_from_redis
438
+ meta = redis.get(metadata_key)
439
+ JSON.parse(meta) unless meta.nil?
440
+ end
435
441
 
436
- def load_alternatives_from_configuration
437
- alts = Split.configuration.experiment_for(@name)[:alternatives]
438
- raise ArgumentError, "Experiment configuration is missing :alternatives array" unless alts
439
- if alts.is_a?(Hash)
440
- alts.keys
441
- else
442
- alts.flatten
442
+ def load_alternatives_from_configuration
443
+ alts = Split.configuration.experiment_for(@name)[:alternatives]
444
+ raise ArgumentError, "Experiment configuration is missing :alternatives array" unless alts
445
+ if alts.is_a?(Hash)
446
+ alts.keys
447
+ else
448
+ alts.flatten
449
+ end
443
450
  end
444
- end
445
451
 
446
- def load_alternatives_from_redis
447
- alternatives = redis.lrange(@name, 0, -1)
448
- alternatives.map do |alt|
449
- alt = begin
450
- JSON.parse(alt)
451
- rescue
452
- alt
453
- end
454
- Split::Alternative.new(alt, @name)
452
+ def load_alternatives_from_redis
453
+ alternatives = redis.lrange(@name, 0, -1)
454
+ alternatives.map do |alt|
455
+ alt = begin
456
+ JSON.parse(alt)
457
+ rescue
458
+ alt
459
+ end
460
+ Split::Alternative.new(alt, @name)
461
+ end
455
462
  end
456
- end
457
463
 
458
464
  private
465
+ def redis
466
+ Split.redis
467
+ end
459
468
 
460
- def redis
461
- Split.redis
462
- end
469
+ def redis_interface
470
+ RedisInterface.new
471
+ end
463
472
 
464
- def redis_interface
465
- RedisInterface.new
466
- end
473
+ def persist_experiment_configuration
474
+ redis_interface.add_to_set(:experiments, name)
475
+ redis_interface.persist_list(name, @alternatives.map { |alt| { alt.name => alt.weight }.to_json })
476
+ goals_collection.save
467
477
 
468
- def persist_experiment_configuration
469
- redis_interface.add_to_set(:experiments, name)
470
- redis_interface.persist_list(name, @alternatives.map{|alt| {alt.name => alt.weight}.to_json})
471
- goals_collection.save
478
+ if @metadata
479
+ redis.set(metadata_key, @metadata.to_json)
480
+ else
481
+ delete_metadata
482
+ end
483
+ end
472
484
 
473
- if @metadata
474
- redis.set(metadata_key, @metadata.to_json)
475
- else
485
+ def remove_experiment_configuration
486
+ @alternatives.each(&:delete)
487
+ goals_collection.delete
476
488
  delete_metadata
489
+ redis.del(@name)
477
490
  end
478
- end
479
491
 
480
- def remove_experiment_configuration
481
- @alternatives.each(&:delete)
482
- goals_collection.delete
483
- delete_metadata
484
- redis.del(@name)
485
- end
486
-
487
- def experiment_configuration_has_changed?
488
- existing_experiment = Experiment.find(@name)
492
+ def experiment_configuration_has_changed?
493
+ existing_experiment = Experiment.find(@name)
489
494
 
490
- existing_experiment.alternatives.map(&:to_s) != @alternatives.map(&:to_s) ||
491
- existing_experiment.goals != @goals ||
492
- existing_experiment.metadata != @metadata
493
- end
495
+ existing_experiment.alternatives.map(&:to_s) != @alternatives.map(&:to_s) ||
496
+ existing_experiment.goals != @goals ||
497
+ existing_experiment.metadata != @metadata
498
+ end
494
499
 
495
- def goals_collection
496
- Split::GoalsCollection.new(@name, @goals)
497
- end
500
+ def goals_collection
501
+ Split::GoalsCollection.new(@name, @goals)
502
+ end
498
503
 
499
- def remove_experiment_cohorting
500
- @cohorting_disabled = false
501
- redis.hdel(experiment_config_key, :cohorting)
502
- end
504
+ def remove_experiment_cohorting
505
+ @cohorting_disabled = false
506
+ redis.hdel(experiment_config_key, :cohorting)
507
+ end
503
508
  end
504
509
  end
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Split
3
4
  class ExperimentCatalog
4
5
  # Return all experiments
5
6
  def self.all
6
7
  # Call compact to prevent nil experiments from being returned -- seems to happen during gem upgrades
7
- Split.redis.smembers(:experiments).map {|e| find(e)}.compact
8
+ Split.redis.smembers(:experiments).map { |e| find(e) }.compact
8
9
  end
9
10
 
10
11
  # Return experiments without a winner (considered "active") first
11
12
  def self.all_active_first
12
- all.partition{|e| not e.winner}.map{|es| es.sort_by(&:name)}.flatten
13
+ all.partition { |e| not e.winner }.map { |es| es.sort_by(&:name) }.flatten
13
14
  end
14
15
 
15
16
  def self.find(name)
@@ -19,14 +20,14 @@ module Split
19
20
  def self.find_or_initialize(metric_descriptor, control = nil, *alternatives)
20
21
  # Check if array is passed to ab_test
21
22
  # e.g. ab_test('name', ['Alt 1', 'Alt 2', 'Alt 3'])
22
- if control.is_a? Array and alternatives.length.zero?
23
+ if control.is_a?(Array) && alternatives.length.zero?
23
24
  control, alternatives = control.first, control[1..-1]
24
25
  end
25
26
 
26
27
  experiment_name_with_version, goals = normalize_experiment(metric_descriptor)
27
- experiment_name = experiment_name_with_version.to_s.split(':')[0]
28
+ experiment_name = experiment_name_with_version.to_s.split(":")[0]
28
29
  Split::Experiment.new(experiment_name,
29
- :alternatives => [control].compact + alternatives, :goals => goals)
30
+ alternatives: [control].compact + alternatives, goals: goals)
30
31
  end
31
32
 
32
33
  def self.find_or_create(metric_descriptor, control = nil, *alternatives)
@@ -4,7 +4,7 @@ class String
4
4
  # Constatntize is often provided by ActiveSupport, but ActiveSupport is not a dependency of Split.
5
5
  unless method_defined?(:constantize)
6
6
  def constantize
7
- names = self.split('::')
7
+ names = self.split("::")
8
8
  names.shift if names.empty? || names.first.empty?
9
9
 
10
10
  constant = Object
@@ -2,8 +2,7 @@
2
2
 
3
3
  module Split
4
4
  class GoalsCollection
5
-
6
- def initialize(experiment_name, goals=nil)
5
+ def initialize(experiment_name, goals = nil)
7
6
  @experiment_name = experiment_name
8
7
  @goals = goals
9
8
  end
@@ -15,10 +14,10 @@ module Split
15
14
  def load_from_configuration
16
15
  goals = Split.configuration.experiment_for(@experiment_name)[:goals]
17
16
 
18
- if goals.nil?
19
- goals = []
20
- else
17
+ if goals
21
18
  goals.flatten
19
+ else
20
+ []
22
21
  end
23
22
  end
24
23
 
@@ -29,7 +28,7 @@ module Split
29
28
 
30
29
  def validate!
31
30
  unless @goals.nil? || @goals.kind_of?(Array)
32
- raise ArgumentError, 'Goals must be an array'
31
+ raise ArgumentError, "Goals must be an array"
33
32
  end
34
33
  end
35
34
 
@@ -38,9 +37,8 @@ module Split
38
37
  end
39
38
 
40
39
  private
41
-
42
- def goals_key
43
- "#{@experiment_name}:goals"
44
- end
40
+ def goals_key
41
+ "#{@experiment_name}:goals"
42
+ end
45
43
  end
46
44
  end
data/lib/split/helper.rb CHANGED
@@ -12,9 +12,9 @@ module Split
12
12
  alternative = if Split.configuration.enabled && !exclude_visitor?
13
13
  experiment.save
14
14
  raise(Split::InvalidExperimentsFormatError) unless (Split.configuration.experiments || {}).fetch(experiment.name.to_sym, {})[:combined_experiments].nil?
15
- trial = Trial.new(:user => ab_user, :experiment => experiment,
16
- :override => override_alternative(experiment.name), :exclude => exclude_visitor?,
17
- :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?)
18
18
  alt = trial.choose!(self)
19
19
  alt ? alt.name : nil
20
20
  else
@@ -44,21 +44,21 @@ module Split
44
44
  ab_user.delete(experiment.key)
45
45
  end
46
46
 
47
- def finish_experiment(experiment, options = {:reset => true})
47
+ def finish_experiment(experiment, options = { reset: true })
48
48
  return false if active_experiments[experiment.name].nil?
49
49
  return true if experiment.has_winner?
50
50
  should_reset = experiment.resettable? && options[:reset]
51
51
  if ab_user[experiment.finished_key] && !should_reset
52
- return true
52
+ true
53
53
  else
54
54
  alternative_name = ab_user[experiment.key]
55
55
  trial = Trial.new(
56
- :user => ab_user,
57
- :experiment => experiment,
58
- :alternative => alternative_name,
59
- :goals => options[:goals],
60
- )
61
-
56
+ user: ab_user,
57
+ experiment: experiment,
58
+ alternative: alternative_name,
59
+ goals: options[:goals],
60
+ )
61
+
62
62
  trial.complete!(self)
63
63
 
64
64
  if should_reset
@@ -69,7 +69,7 @@ module Split
69
69
  end
70
70
  end
71
71
 
72
- def ab_finished(metric_descriptor, options = {:reset => true})
72
+ def ab_finished(metric_descriptor, options = { reset: true })
73
73
  return if exclude_visitor? || Split.configuration.disabled?
74
74
  metric_descriptor, goals = normalize_metric(metric_descriptor)
75
75
  experiments = Metric.possible_experiments(metric_descriptor)
@@ -77,7 +77,7 @@ module Split
77
77
  if experiments.any?
78
78
  experiments.each do |experiment|
79
79
  next if override_present?(experiment.key)
80
- finish_experiment(experiment, options.merge(:goals => goals))
80
+ finish_experiment(experiment, options.merge(goals: goals))
81
81
  end
82
82
  end
83
83
  rescue => e
@@ -86,7 +86,7 @@ module Split
86
86
  end
87
87
 
88
88
  def ab_record_extra_info(metric_descriptor, key, value = 1)
89
- return if exclude_visitor? || Split.configuration.disabled?
89
+ return if exclude_visitor? || Split.configuration.disabled? || value.nil?
90
90
  metric_descriptor, _ = normalize_metric(metric_descriptor)
91
91
  experiments = Metric.possible_experiments(metric_descriptor)
92
92
 
@@ -95,7 +95,7 @@ module Split
95
95
  alternative_name = ab_user[experiment.key]
96
96
 
97
97
  if alternative_name
98
- alternative = experiment.alternatives.find{|alt| alt.name == alternative_name}
98
+ alternative = experiment.alternatives.find { |alt| alt.name == alternative_name }
99
99
  alternative.record_extra_info(key, value) if alternative
100
100
  end
101
101
  end
@@ -105,7 +105,7 @@ module Split
105
105
  Split.configuration.db_failover_on_db_error.call(e)
106
106
  end
107
107
 
108
- def ab_active_experiments()
108
+ def ab_active_experiments
109
109
  ab_user.active_experiments
110
110
  rescue => e
111
111
  raise unless Split.configuration.db_failover
@@ -127,14 +127,14 @@ module Split
127
127
  def override_alternative_by_cookies(experiment_name)
128
128
  return unless defined?(request)
129
129
 
130
- if request.cookies && request.cookies.key?('split_override')
131
- experiments = JSON.parse(request.cookies['split_override']) rescue {}
130
+ if request.cookies && request.cookies.key?("split_override")
131
+ experiments = JSON.parse(request.cookies["split_override"]) rescue {}
132
132
  experiments[experiment_name]
133
133
  end
134
134
  end
135
135
 
136
136
  def split_generically_disabled?
137
- defined?(params) && params['SPLIT_DISABLE']
137
+ defined?(params) && params["SPLIT_DISABLE"]
138
138
  end
139
139
 
140
140
  def ab_user
@@ -150,7 +150,7 @@ module Split
150
150
  end
151
151
 
152
152
  def is_preview?
153
- defined?(request) && defined?(request.headers) && request.headers['x-purpose'] == 'preview'
153
+ defined?(request) && defined?(request.headers) && request.headers["x-purpose"] == "preview"
154
154
  end
155
155
 
156
156
  def is_ignored_ip_address?
data/lib/split/metric.rb CHANGED
@@ -16,13 +16,13 @@ module Split
16
16
  def self.load_from_redis(name)
17
17
  metric = Split.redis.hget(:metrics, name)
18
18
  if metric
19
- experiment_names = metric.split(',')
19
+ experiment_names = metric.split(",")
20
20
 
21
21
  experiments = experiment_names.collect do |experiment_name|
22
22
  Split::ExperimentCatalog.find(experiment_name)
23
23
  end
24
24
 
25
- Split::Metric.new(:name => name, :experiments => experiments)
25
+ Split::Metric.new(name: name, experiments: experiments)
26
26
  else
27
27
  nil
28
28
  end
@@ -31,7 +31,7 @@ module Split
31
31
  def self.load_from_configuration(name)
32
32
  metrics = Split.configuration.metrics
33
33
  if metrics && metrics[name]
34
- Split::Metric.new(:experiments => metrics[name], :name => name)
34
+ Split::Metric.new(experiments: metrics[name], name: name)
35
35
  else
36
36
  nil
37
37
  end
@@ -77,7 +77,7 @@ module Split
77
77
  end
78
78
 
79
79
  def save
80
- Split.redis.hset(:metrics, name, experiments.map(&:name).join(','))
80
+ Split.redis.hset(:metrics, name, experiments.map(&:name).join(","))
81
81
  end
82
82
 
83
83
  def complete!
@@ -97,6 +97,5 @@ module Split
97
97
  return metric_name, goals
98
98
  end
99
99
  private_class_method :normalize_metric
100
-
101
100
  end
102
101
  end