From c6a0d76638fb3134b75aa7f368800709af00b50f Mon Sep 17 00:00:00 2001 From: Gennady Minenkov Date: Wed, 26 Jun 2019 11:55:29 +0300 Subject: [PATCH 001/136] Add on_winner_choose hook --- README.md | 2 ++ lib/split/configuration.rb | 2 ++ lib/split/experiment.rb | 1 + spec/experiment_spec.rb | 11 ++++++++--- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 836c2057..ef9015a9 100644 --- a/README.md +++ b/README.md @@ -386,6 +386,8 @@ Split.configure do |config| # before experiment reset or deleted config.on_before_experiment_reset = -> (example) { # Do something on reset } config.on_before_experiment_delete = -> (experiment) { # Do something else on delete } + # after experiment winner had been set + config.on_winner_choose = -> (experiment) { # Do something on winner choose } end ``` diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index 5647fa49..e66b2d81 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -20,6 +20,7 @@ class Configuration attr_accessor :on_experiment_reset attr_accessor :on_experiment_delete attr_accessor :on_before_experiment_reset + attr_accessor :on_winner_choose attr_accessor :on_before_experiment_delete attr_accessor :include_rails_helper attr_accessor :beta_probability_simulations @@ -216,6 +217,7 @@ def initialize @on_experiment_delete = proc{|experiment|} @on_before_experiment_reset = proc{|experiment|} @on_before_experiment_delete = proc{|experiment|} + @on_winner_choose = proc{|experiment|} @db_failover_allow_parameter_override = false @allow_multiple_experiments = false @enabled = true diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index b9c5fac7..8ae1f775 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -155,6 +155,7 @@ def has_winner? def winner=(winner_name) redis.hset(:experiment_winner, name, winner_name.to_s) @has_winner = true + Split.configuration.on_winner_choose.call(self) end def participant_count diff --git a/spec/experiment_spec.rb b/spec/experiment_spec.rb index fe7ec44a..ee92e3a7 100644 --- a/spec/experiment_spec.rb +++ b/spec/experiment_spec.rb @@ -118,7 +118,7 @@ def alternative(color) experiment = Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :resettable => false) expect(experiment.resettable).to be_falsey end - + context 'from configuration' do let(:experiment_name) { :my_experiment } let(:experiments) do @@ -130,7 +130,7 @@ def alternative(color) end before { Split.configuration.experiments = experiments } - + it 'assigns default values to the experiment' do expect(Split::Experiment.new(experiment_name).resettable).to eq(true) end @@ -233,12 +233,17 @@ def alternative(color) end describe 'winner=' do - it "should allow you to specify a winner" do + it 'should allow you to specify a winner' do experiment.save experiment.winner = 'red' expect(experiment.winner.name).to eq('red') end + it 'should call the on_winner_choose hook' do + expect(Split.configuration.on_winner_choose).to receive(:call) + experiment.winner = 'green' + end + context 'when has_winner state is memoized' do before { expect(experiment).to_not have_winner } From ce0e62bde72993f264efbdcc447a73f66168b65b Mon Sep 17 00:00:00 2001 From: Gennady Minenkov Date: Sat, 11 Apr 2020 22:48:51 +0300 Subject: [PATCH 002/136] Update winner choose hook name --- README.md | 2 +- lib/split/configuration.rb | 4 ++-- lib/split/experiment.rb | 2 +- spec/experiment_spec.rb | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ef9015a9..7b13c95f 100644 --- a/README.md +++ b/README.md @@ -387,7 +387,7 @@ Split.configure do |config| config.on_before_experiment_reset = -> (example) { # Do something on reset } config.on_before_experiment_delete = -> (experiment) { # Do something else on delete } # after experiment winner had been set - config.on_winner_choose = -> (experiment) { # Do something on winner choose } + config.on_experiment_winner_choose = -> (experiment) { # Do something on winner choose } end ``` diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index e66b2d81..e36cbca3 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -20,7 +20,7 @@ class Configuration attr_accessor :on_experiment_reset attr_accessor :on_experiment_delete attr_accessor :on_before_experiment_reset - attr_accessor :on_winner_choose + attr_accessor :on_experiment_winner_choose attr_accessor :on_before_experiment_delete attr_accessor :include_rails_helper attr_accessor :beta_probability_simulations @@ -217,7 +217,7 @@ def initialize @on_experiment_delete = proc{|experiment|} @on_before_experiment_reset = proc{|experiment|} @on_before_experiment_delete = proc{|experiment|} - @on_winner_choose = proc{|experiment|} + @on_experiment_winner_choose = proc{|experiment|} @db_failover_allow_parameter_override = false @allow_multiple_experiments = false @enabled = true diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 8ae1f775..36a7a26f 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -155,7 +155,7 @@ def has_winner? def winner=(winner_name) redis.hset(:experiment_winner, name, winner_name.to_s) @has_winner = true - Split.configuration.on_winner_choose.call(self) + Split.configuration.on_experiment_winner_choose.call(self) end def participant_count diff --git a/spec/experiment_spec.rb b/spec/experiment_spec.rb index ee92e3a7..c90f865d 100644 --- a/spec/experiment_spec.rb +++ b/spec/experiment_spec.rb @@ -239,8 +239,8 @@ def alternative(color) expect(experiment.winner.name).to eq('red') end - it 'should call the on_winner_choose hook' do - expect(Split.configuration.on_winner_choose).to receive(:call) + it 'should call the on_experiment_winner_choose hook' do + expect(Split.configuration.on_experiment_winner_choose).to receive(:call) experiment.winner = 'green' end From 0d2178e90c7b3948eb08d7ffcd21e587b5de4d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sat, 2 May 2020 18:45:57 -0300 Subject: [PATCH 003/136] Fix #max_experiments_reached? when using allow_multiple_experiments=control When using allow_multiple_experiments=control Split only allows one experiment with an alternative other than 'control'. Split::User#max_experiments_reached? checks for that looping through all experiments. As Split::User#active_experiments drops the experiment version we also need to do that here to be able to check properly. Fixes #612 --- lib/split/user.rb | 3 ++- spec/helper_spec.rb | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/split/user.rb b/lib/split/user.rb index 1f479071..a073bf40 100644 --- a/lib/split/user.rb +++ b/lib/split/user.rb @@ -27,7 +27,8 @@ def cleanup_old_experiments! def max_experiments_reached?(experiment_key) if Split.configuration.allow_multiple_experiments == 'control' experiments = active_experiments - count_control = experiments.count {|k,v| k == experiment_key || v == 'control'} + experiment_key_without_version = key_without_version(experiment_key) + count_control = experiments.count {|k,v| k == experiment_key_without_version || v == 'control'} experiments.size > count_control else !Split.configuration.allow_multiple_experiments && diff --git a/spec/helper_spec.rb b/spec/helper_spec.rb index b921c5d3..2ac23a11 100755 --- a/spec/helper_spec.rb +++ b/spec/helper_spec.rb @@ -229,13 +229,15 @@ context "when user already has experiment" do let(:mock_user){ Split::User.new(self, {'test_0' => 'test-alt'}) } - before{ + + before do Split.configure do |config| config.allow_multiple_experiments = 'control' end + Split::ExperimentCatalog.find_or_initialize('test_0', 'control', 'test-alt').save Split::ExperimentCatalog.find_or_initialize('test_1', 'control', 'test-alt').save - } + end it "should restore previously selected alternative" do expect(ab_user.active_experiments.size).to eq 1 @@ -243,6 +245,16 @@ expect(ab_test(:test_0, {'control' => 1}, {"test-alt" => 100})).to eq 'test-alt' end + it "should select the correct alternatives after experiment resets" do + experiment = Split::ExperimentCatalog.find(:test_0) + experiment.reset + mock_user[experiment.key] = 'test-alt' + + expect(ab_user.active_experiments.size).to eq 1 + expect(ab_test(:test_0, {'control' => 100}, {"test-alt" => 1})).to eq 'test-alt' + expect(ab_test(:test_0, {'control' => 0}, {"test-alt" => 100})).to eq 'test-alt' + end + it "lets override existing choice" do pending "this requires user store reset on first call not depending on whelther it is current trial" @params = { 'ab_test' => { 'test_1' => 'test-alt' } } From 036374ee5f41572238c1ae010e66f66ef67c50a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Thu, 7 May 2020 18:59:11 -0300 Subject: [PATCH 004/136] Replace usage of SimpleRandom with RubyStats --- lib/split/algorithms/whiplash.rb | 4 ++-- lib/split/experiment.rb | 9 ++++----- split.gemspec | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/split/algorithms/whiplash.rb b/lib/split/algorithms/whiplash.rb index b9d844f6..5373cada 100644 --- a/lib/split/algorithms/whiplash.rb +++ b/lib/split/algorithms/whiplash.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # A multi-armed bandit implementation inspired by # @aaronsw and victorykit/whiplash -require 'simple-random' +require 'rubystats' module Split module Algorithms @@ -16,7 +16,7 @@ def choose_alternative(experiment) def arm_guess(participants, completions) a = [participants, 0].max b = [participants-completions, 0].max - s = SimpleRandom.new; s.set_seed; s.beta(a+fairness_constant, b+fairness_constant) + Rubystats::BetaDistribution.new(a+fairness_constant, b+fairness_constant).rng end def best_guess(alternatives) diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 36a7a26f..c167538f 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -1,4 +1,7 @@ # frozen_string_literal: true + +require 'rubystats' + module Split class Experiment attr_accessor :name @@ -354,17 +357,13 @@ def find_simulated_winner(simulated_cr_hash) end def calc_simulated_conversion_rates(beta_params) - # initialize a random variable (from which to simulate conversion rates ~beta-distributed) - rand = SimpleRandom.new - rand.set_seed - simulated_cr_hash = {} # create a hash which has the conversion rate pulled from each alternative's beta distribution beta_params.each do |alternative, params| alpha = params[0] beta = params[1] - simulated_conversion_rate = rand.beta(alpha, beta) + simulated_conversion_rate = Rubystats::BetaDistribution.new(alpha, beta).rng simulated_cr_hash[alternative] = simulated_conversion_rate end diff --git a/split.gemspec b/split.gemspec index d8f969c4..f16e7df8 100644 --- a/split.gemspec +++ b/split.gemspec @@ -31,7 +31,7 @@ Gem::Specification.new do |s| s.add_dependency 'redis', '>= 2.1' s.add_dependency 'sinatra', '>= 1.2.6' - s.add_dependency 'simple-random', '>= 0.9.3' + s.add_dependency 'rubystats', '>= 0.3.0' s.add_development_dependency 'bundler', '>= 1.17' s.add_development_dependency 'simplecov', '~> 0.15' From 648576af0ae0ef0c6e5c475d74b3daf6f4d485c6 Mon Sep 17 00:00:00 2001 From: Robin Phung Date: Tue, 12 May 2020 17:23:31 -0700 Subject: [PATCH 005/136] removed unreached loading from config --- lib/split/experiment.rb | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 36a7a26f..73b11bd7 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -18,21 +18,7 @@ def initialize(name, options = {}) @name = name.to_s - alternatives = extract_alternatives_from_options(options) - - if alternatives.empty? && (exp_config = Split.configuration.experiment_for(name)) - options = { - alternatives: load_alternatives_from_configuration, - goals: Split::GoalsCollection.new(@name).load_from_configuration, - metadata: load_metadata_from_configuration, - resettable: exp_config[:resettable], - algorithm: exp_config[:algorithm] - } - else - options[:alternatives] = alternatives - end - - set_alternatives_and_options(options) + extract_alternatives_from_options(options) end def self.finished_key(key) From de61579d63d06ca2d320011753d99b4f38b74f1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Jr?= Date: Wed, 20 May 2020 19:37:14 -0300 Subject: [PATCH 006/136] ab_test must return metadata on error or if split is disabled --- lib/split/helper.rb | 4 +- spec/encapsulated_helper_spec.rb | 2 +- spec/helper_spec.rb | 72 ++++++++++++++++++++++---------- 3 files changed, 54 insertions(+), 24 deletions(-) diff --git a/lib/split/helper.rb b/lib/split/helper.rb index d4529e44..b8ddfc2a 100644 --- a/lib/split/helper.rb +++ b/lib/split/helper.rb @@ -32,8 +32,8 @@ def ab_test(metric_descriptor, control = nil, *alternatives) end if block_given? - metadata = trial ? trial.metadata : {} - yield(alternative, metadata) + metadata = experiment.metadata[alternative] if experiment.metadata + yield(alternative, metadata || {}) else alternative end diff --git a/spec/encapsulated_helper_spec.rb b/spec/encapsulated_helper_spec.rb index cb51c26a..a39d98c9 100644 --- a/spec/encapsulated_helper_spec.rb +++ b/spec/encapsulated_helper_spec.rb @@ -21,7 +21,7 @@ def params end it "calls the block with selected alternative" do - expect{|block| ab_test('link_color', 'red', 'red', &block) }.to yield_with_args('red', nil) + expect{|block| ab_test('link_color', 'red', 'red', &block) }.to yield_with_args('red', {}) end context "inside a view" do diff --git a/spec/helper_spec.rb b/spec/helper_spec.rb index 2ac23a11..95eb32ee 100755 --- a/spec/helper_spec.rb +++ b/spec/helper_spec.rb @@ -277,33 +277,63 @@ end describe 'metadata' do - before do - Split.configuration.experiments = { - :my_experiment => { - :alternatives => ["one", "two"], - :resettable => false, - :metadata => { 'one' => 'Meta1', 'two' => 'Meta2' } + context 'is defined' do + before do + Split.configuration.experiments = { + :my_experiment => { + :alternatives => ["one", "two"], + :resettable => false, + :metadata => { 'one' => 'Meta1', 'two' => 'Meta2' } + } } - } - end + end + + it 'should be passed to helper block' do + @params = { 'ab_test' => { 'my_experiment' => 'two' } } + expect(ab_test('my_experiment')).to eq 'two' + expect(ab_test('my_experiment') do |alternative, meta| + meta + end).to eq('Meta2') + end + + it 'should pass control metadata helper block if library disabled' do + Split.configure do |config| + config.enabled = false + end - it 'should be passed to helper block' do - @params = { 'ab_test' => { 'my_experiment' => 'one' } } - expect(ab_test('my_experiment')).to eq 'one' - expect(ab_test('my_experiment') do |alternative, meta| - meta - end).to eq('Meta1') + expect(ab_test('my_experiment')).to eq 'one' + expect(ab_test('my_experiment') do |_, meta| + meta + end).to eq('Meta1') + end end - it 'should pass empty hash to helper block if library disabled' do - Split.configure do |config| - config.enabled = false + context 'is not defined' do + before do + Split.configuration.experiments = { + :my_experiment => { + :alternatives => ["one", "two"], + :resettable => false, + :metadata => nil + } + } end - expect(ab_test('my_experiment')).to eq 'one' - expect(ab_test('my_experiment') do |_, meta| - meta - end).to eq({}) + it 'should be passed to helper block' do + expect(ab_test('my_experiment') do |alternative, meta| + meta + end).to eq({}) + end + + it 'should pass control metadata helper block if library disabled' do + Split.configure do |config| + config.enabled = false + end + + expect(ab_test('my_experiment') do |_, meta| + meta + end).to eq({}) + end end end From f9fc878405d4c811f1abe54349f1b2f5532ee8f7 Mon Sep 17 00:00:00 2001 From: Robin Phung Date: Thu, 7 May 2020 15:59:11 -0700 Subject: [PATCH 007/136] Introduce enable/disable experiment cohorting --- lib/split/dashboard.rb | 11 ++++++++ lib/split/dashboard/public/dashboard.js | 10 +++++++ lib/split/dashboard/public/style.css | 5 ++++ lib/split/dashboard/views/_controls.erb | 12 +++++++++ lib/split/experiment.rb | 14 ++++++++++ lib/split/trial.rb | 17 +++++++----- spec/dashboard_spec.rb | 22 +++++++++++++++ spec/experiment_spec.rb | 36 ++++++++++++++++++++++++- spec/trial_spec.rb | 27 +++++++++++++++++++ 9 files changed, 147 insertions(+), 7 deletions(-) diff --git a/lib/split/dashboard.rb b/lib/split/dashboard.rb index b6257839..3c0f82e5 100755 --- a/lib/split/dashboard.rb +++ b/lib/split/dashboard.rb @@ -65,6 +65,17 @@ class Dashboard < Sinatra::Base redirect url('/') end + post '/update_cohorting' do + @experiment = Split::ExperimentCatalog.find(params[:experiment]) + case params[:cohorting_action].downcase + when "enable" + @experiment.enable_cohorting + when "disable" + @experiment.disable_cohorting + end + redirect url('/') + end + delete '/experiment' do @experiment = Split::ExperimentCatalog.find(params[:experiment]) @experiment.delete diff --git a/lib/split/dashboard/public/dashboard.js b/lib/split/dashboard/public/dashboard.js index 0f779046..ebfa28c4 100644 --- a/lib/split/dashboard/public/dashboard.js +++ b/lib/split/dashboard/public/dashboard.js @@ -22,3 +22,13 @@ function confirmReopen() { var agree = confirm("This will reopen the experiment. Are you sure?"); return agree ? true : false; } + +function confirmEnableCohorting(){ + var agree = confirm("This will enable the cohorting of the experiment. Are you sure?"); + return agree ? true : false; +} + +function confirmDisableCohorting(){ + var agree = confirm("This will disable the cohorting of the experiment. Note: Existing participants will continue to receive their alternative and may continue to convert. Are you sure?"); + return agree ? true : false; +} diff --git a/lib/split/dashboard/public/style.css b/lib/split/dashboard/public/style.css index cd7c5b1b..3c2c640e 100644 --- a/lib/split/dashboard/public/style.css +++ b/lib/split/dashboard/public/style.css @@ -326,3 +326,8 @@ a.button.green:focus, button.green:focus, input[type="submit"].green:focus { display: inline-block; padding: 5px; } + +.divider { + display: inline-block; + margin-left: 10px; +} diff --git a/lib/split/dashboard/views/_controls.erb b/lib/split/dashboard/views/_controls.erb index 1ec32ded..e8e5ec45 100644 --- a/lib/split/dashboard/views/_controls.erb +++ b/lib/split/dashboard/views/_controls.erb @@ -1,3 +1,15 @@ +<% if experiment.cohorting_disabled? %> +
" method='post' onclick="return confirmEnableCohorting()"> + + +
+<% else %> +
" method='post' onclick="return confirmDisableCohorting()"> + + +
+<% end %> +| <% if experiment.has_winner? %>
" method='post' onclick="return confirmReopen()"> diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 4c97bead..998c4903 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -235,6 +235,7 @@ def delete end reset_winner redis.srem(:experiments, name) + redis.hdel(experiment_config_key, :cohorting) remove_experiment_configuration Split.configuration.on_experiment_delete.call(self) increment_version @@ -387,6 +388,19 @@ def jstring(goal = nil) js_id.gsub('/', '--') end + def cohorting_disabled? + value = redis.hget(experiment_config_key, :cohorting) + value.nil? ? false : value.downcase == "true" + end + + def disable_cohorting + redis.hset(experiment_config_key, :cohorting, true) + end + + def enable_cohorting + redis.hset(experiment_config_key, :cohorting, false) + end + protected def experiment_config_key diff --git a/lib/split/trial.rb b/lib/split/trial.rb index 527a1b60..589738fa 100644 --- a/lib/split/trial.rb +++ b/lib/split/trial.rb @@ -53,6 +53,7 @@ def choose!(context = nil) # Only run the process once return alternative if @alternative_choosen + new_participant = @user[@experiment.key].nil? if override_is_alternative? self.alternative = @options[:override] if should_store_alternative? && !@user[@experiment.key] @@ -70,19 +71,23 @@ def choose!(context = nil) else self.alternative = @user[@experiment.key] if alternative.nil? - self.alternative = @experiment.next_alternative + if @experiment.cohorting_disabled? + self.alternative = @experiment.control + else + self.alternative = @experiment.next_alternative - # Increment the number of participants since we are actually choosing a new alternative - self.alternative.increment_participation + # Increment the number of participants since we are actually choosing a new alternative + self.alternative.increment_participation - run_callback context, Split.configuration.on_trial_choose + run_callback context, Split.configuration.on_trial_choose + end end end end - @user[@experiment.key] = alternative.name if !@experiment.has_winner? && should_store_alternative? + @user[@experiment.key] = alternative.name unless @experiment.has_winner? || !should_store_alternative? || (new_participant && @experiment.cohorting_disabled?) @alternative_choosen = true - run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled? + run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled? || (new_participant && @experiment.cohorting_disabled?) alternative end diff --git a/spec/dashboard_spec.rb b/spec/dashboard_spec.rb index b47de7bd..ce3fbe6c 100644 --- a/spec/dashboard_spec.rb +++ b/spec/dashboard_spec.rb @@ -161,6 +161,28 @@ def link(color) end end + describe "update cohorting" do + it "calls enable of cohorting when action is enable" do + post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "enable" } + + expect(experiment.cohorting_disabled?).to eq false + end + + it "calls disable of cohorting when action is disable" do + post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "disable" } + + expect(experiment.cohorting_disabled?).to eq true + end + + it "calls neither enable or disable cohorting when passed invalid action" do + previous_value = experiment.cohorting_disabled? + + post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "other" } + + expect(experiment.cohorting_disabled?).to eq previous_value + end + end + it "should reset an experiment" do red_link.participant_count = 5 blue_link.participant_count = 7 diff --git a/spec/experiment_spec.rb b/spec/experiment_spec.rb index c90f865d..bace4418 100644 --- a/spec/experiment_spec.rb +++ b/spec/experiment_spec.rb @@ -223,8 +223,14 @@ def alternative(color) experiment.delete expect(experiment.start_time).to be_nil end - end + it "should default cohorting back to false" do + experiment.disable_cohorting + expect(experiment.cohorting_disabled?).to eq(true) + experiment.delete + expect(experiment.cohorting_disabled?).to eq(false) + end + end describe 'winner' do it "should have no winner initially" do @@ -392,6 +398,34 @@ def alternative(color) end end + describe "#cohorting_disabled?" do + it "returns false when nothing has been configured" do + expect(experiment.cohorting_disabled?).to eq false + end + + it "returns true when enable_cohorting is performed" do + experiment.enable_cohorting + expect(experiment.cohorting_disabled?).to eq false + end + + it "returns false when nothing has been configured" do + experiment.disable_cohorting + expect(experiment.cohorting_disabled?).to eq true + end + end + + describe "#disable_cohorting" do + it "saves a new key in redis" do + expect(experiment.disable_cohorting).to eq true + end + end + + describe "#enable_cohorting" do + it "saves a new key in redis" do + expect(experiment.enable_cohorting).to eq true + end + end + describe 'changing an existing experiment' do def same_but_different_alternative Split::ExperimentCatalog.find_or_create('link_color', 'blue', 'yellow', 'orange') diff --git a/spec/trial_spec.rb b/spec/trial_spec.rb index b16164a7..5abc05bb 100644 --- a/spec/trial_spec.rb +++ b/spec/trial_spec.rb @@ -198,6 +198,33 @@ def expect_alternative(trial, alternative_name) expect(trial.alternative.name).to_not be_empty Split.configuration.on_trial_choose = nil end + + it "assigns user to an alternative" do + trial.choose! context + + expect(alternatives).to include(user[experiment.name]) + end + + context "when cohorting is disabled" do + before(:each) { allow(experiment).to receive(:cohorting_disabled?).and_return(true) } + + it "picks the control and does not run on_trial callbacks" do + Split.configuration.on_trial = :on_trial_callback + + expect(experiment).to_not receive(:next_alternative) + expect(context).not_to receive(:on_trial_callback) + expect_alternative(trial, 'basket') + + Split.configuration.enabled = true + Split.configuration.on_trial = nil + end + + it "user is not assigned an alternative" do + trial.choose! context + + expect(user[experiment]).to eq(nil) + end + end end end From 0dcc853e8d185d510bb7c3d57defe7c04f50d976 Mon Sep 17 00:00:00 2001 From: Robin Phung Date: Wed, 27 May 2020 11:27:42 -0700 Subject: [PATCH 008/136] disable cohorting revision --- lib/split/dashboard/views/_controls.erb | 25 +++++++++++++------------ lib/split/experiment.rb | 15 ++++++++++++--- lib/split/trial.rb | 6 ++++-- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/lib/split/dashboard/views/_controls.erb b/lib/split/dashboard/views/_controls.erb index e8e5ec45..2472b9ee 100644 --- a/lib/split/dashboard/views/_controls.erb +++ b/lib/split/dashboard/views/_controls.erb @@ -1,20 +1,21 @@ -<% if experiment.cohorting_disabled? %> - " method='post' onclick="return confirmEnableCohorting()"> - - - -<% else %> -
" method='post' onclick="return confirmDisableCohorting()"> - - -
-<% end %> -| <% if experiment.has_winner? %>
" method='post' onclick="return confirmReopen()">
+<% else %> + <% if experiment.cohorting_disabled? %> +
" method='post' onclick="return confirmEnableCohorting()"> + + +
+ <% else %> +
" method='post' onclick="return confirmDisableCohorting()"> + + +
+ <% end %> <% end %> +| <% if experiment.start_time %>
" method='post' onclick="return confirmReset()"> diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 998c4903..e50d0d90 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -235,7 +235,7 @@ def delete end reset_winner redis.srem(:experiments, name) - redis.hdel(experiment_config_key, :cohorting) + remove_experiment_cohorting remove_experiment_configuration Split.configuration.on_experiment_delete.call(self) increment_version @@ -389,15 +389,19 @@ def jstring(goal = nil) end def cohorting_disabled? - value = redis.hget(experiment_config_key, :cohorting) - value.nil? ? false : value.downcase == "true" + @cohorting_disabled ||= begin + value = redis.hget(experiment_config_key, :cohorting) + value.nil? ? false : value.downcase == "true" + end end def disable_cohorting + @cohorting_disabled = true redis.hset(experiment_config_key, :cohorting, true) end def enable_cohorting + @cohorting_disabled = false redis.hset(experiment_config_key, :cohorting, false) end @@ -482,5 +486,10 @@ def experiment_configuration_has_changed? def goals_collection Split::GoalsCollection.new(@name, @goals) end + + def remove_experiment_cohorting + @cohorting_disabled = false + redis.hdel(experiment_config_key, :cohorting) + end end end diff --git a/lib/split/trial.rb b/lib/split/trial.rb index 589738fa..cce7e347 100644 --- a/lib/split/trial.rb +++ b/lib/split/trial.rb @@ -85,9 +85,11 @@ def choose!(context = nil) end end - @user[@experiment.key] = alternative.name unless @experiment.has_winner? || !should_store_alternative? || (new_participant && @experiment.cohorting_disabled?) + new_participant_and_cohorting_disabled = new_participant && @experiment.cohorting_disabled? + + @user[@experiment.key] = alternative.name unless @experiment.has_winner? || !should_store_alternative? || new_participant_and_cohorting_disabled @alternative_choosen = true - run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled? || (new_participant && @experiment.cohorting_disabled?) + run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled? || new_participant_and_cohorting_disabled alternative end From 00748fd590caf929ed2561aeadbf42fa0ac9dee3 Mon Sep 17 00:00:00 2001 From: Robin Phung Date: Tue, 2 Jun 2020 22:14:48 -0700 Subject: [PATCH 009/136] Update trial to init with goals instead --- lib/split/helper.rb | 11 ++++++++--- lib/split/trial.rb | 4 +++- spec/helper_spec.rb | 12 +++--------- spec/trial_spec.rb | 37 ++++++++++++++++++------------------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/lib/split/helper.rb b/lib/split/helper.rb index b8ddfc2a..63a0464e 100644 --- a/lib/split/helper.rb +++ b/lib/split/helper.rb @@ -51,9 +51,14 @@ def finish_experiment(experiment, options = {:reset => true}) return true else alternative_name = ab_user[experiment.key] - trial = Trial.new(:user => ab_user, :experiment => experiment, - :alternative => alternative_name) - trial.complete!(options[:goals], self) + trial = Trial.new( + :user => ab_user, + :experiment => experiment, + :alternative => alternative_name, + :goals => options[:goals], + ) + + trial.complete!(self) if should_reset reset!(experiment) diff --git a/lib/split/trial.rb b/lib/split/trial.rb index cce7e347..6a20703a 100644 --- a/lib/split/trial.rb +++ b/lib/split/trial.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true module Split class Trial + attr_accessor :goals attr_accessor :experiment attr_writer :metadata @@ -8,6 +9,7 @@ def initialize(attrs = {}) self.experiment = attrs.delete(:experiment) self.alternative = attrs.delete(:alternative) self.metadata = attrs.delete(:metadata) + self.goals = attrs.delete(:goals) || [] @user = attrs.delete(:user) @options = attrs @@ -33,7 +35,7 @@ def alternative=(alternative) end end - def complete!(goals=[], context = nil) + def complete!(context = nil) if alternative if Array(goals).empty? alternative.increment_completion diff --git a/spec/helper_spec.rb b/spec/helper_spec.rb index 95eb32ee..0e0b4fc7 100755 --- a/spec/helper_spec.rb +++ b/spec/helper_spec.rb @@ -1138,15 +1138,9 @@ def should_finish_experiment(experiment_name, should_finish=true) end it "should increment the counter for the specified-goal completed alternative" do - expect(lambda { - expect(lambda { - ab_finished({"link_color" => ["purchase"]}) - }).not_to change { - Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal2) - } - }).to change { - Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal1) - }.by(1) + expect{ ab_finished({"link_color" => ["purchase"]}) } + .to change{ Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal2) }.by(0) + .and change{ Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal1) }.by(1) end end end diff --git a/spec/trial_spec.rb b/spec/trial_spec.rb index 5abc05bb..557f1e59 100644 --- a/spec/trial_spec.rb +++ b/spec/trial_spec.rb @@ -229,38 +229,37 @@ def expect_alternative(trial, alternative_name) end describe "#complete!" do - let(:trial) { Split::Trial.new(:user => user, :experiment => experiment) } context 'when there are no goals' do + let(:trial) { Split::Trial.new(:user => user, :experiment => experiment) } it 'should complete the trial' do trial.choose! old_completed_count = trial.alternative.completed_count trial.complete! - expect(trial.alternative.completed_count).to be(old_completed_count+1) + expect(trial.alternative.completed_count).to eq(old_completed_count + 1) end end - context 'when there are many goals' do - let(:goals) { ['first', 'second'] } + context "when there are many goals" do + let(:goals) { [ "goal1", "second" ] } let(:trial) { Split::Trial.new(:user => user, :experiment => experiment, :goals => goals) } - shared_examples_for "goal completion" do - it 'should not complete the trial' do - trial.choose! - old_completed_count = trial.alternative.completed_count - trial.complete!(goal) - expect(trial.alternative.completed_count).to_not be(old_completed_count+1) - end - end - describe 'Array of Goals' do - let(:goal) { [goals.first] } - it_behaves_like 'goal completion' + it "increments the conmpleted count corresponding to the goals" do + trial.choose! + old_completed_counts = goals.map{ |goal| [goal, trial.alternative.completed_count(goal)] }.to_h + trial.complete! + goals.each { | goal | expect(trial.alternative.completed_count(goal)).to eq(old_completed_counts[goal] + 1) } end + end - describe 'String of Goal' do - let(:goal) { goals.first } - it_behaves_like 'goal completion' + context "when there is 1 goal of type string" do + let(:goal) { "first" } + let(:trial) { Split::Trial.new(:user => user, :experiment => experiment, :goals => goal) } + it "increments the conmpleted count corresponding to the goal" do + trial.choose! + old_completed_count = trial.alternative.completed_count(goal) + trial.complete! + expect(trial.alternative.completed_count(goal)).to eq(old_completed_count + 1) end - end end From 18622a5d3ded990277b3d142d7fa59ba60e5033c Mon Sep 17 00:00:00 2001 From: Robin Phung Date: Thu, 4 Jun 2020 10:55:47 -0700 Subject: [PATCH 010/136] spec copy change --- spec/trial_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/trial_spec.rb b/spec/trial_spec.rb index 557f1e59..c171944b 100644 --- a/spec/trial_spec.rb +++ b/spec/trial_spec.rb @@ -240,10 +240,10 @@ def expect_alternative(trial, alternative_name) end context "when there are many goals" do - let(:goals) { [ "goal1", "second" ] } + let(:goals) { [ "goal1", "goal2" ] } let(:trial) { Split::Trial.new(:user => user, :experiment => experiment, :goals => goals) } - it "increments the conmpleted count corresponding to the goals" do + it "increments the completed count corresponding to the goals" do trial.choose! old_completed_counts = goals.map{ |goal| [goal, trial.alternative.completed_count(goal)] }.to_h trial.complete! @@ -252,9 +252,9 @@ def expect_alternative(trial, alternative_name) end context "when there is 1 goal of type string" do - let(:goal) { "first" } + let(:goal) { "goal" } let(:trial) { Split::Trial.new(:user => user, :experiment => experiment, :goals => goal) } - it "increments the conmpleted count corresponding to the goal" do + it "increments the completed count corresponding to the goal" do trial.choose! old_completed_count = trial.alternative.completed_count(goal) trial.complete! From 4b388f7e542039299f6121407f9b00e92123ec06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Thu, 18 Jun 2020 00:14:07 -0300 Subject: [PATCH 011/136] Only suport ruby 2.5+ --- .travis.yml | 12 ------------ split.gemspec | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index c06d4c08..cf71746f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,5 @@ language: ruby rvm: - - 2.2.2 - - 2.3.8 - - 2.4.9 - 2.5.7 - 2.6.5 @@ -12,15 +9,6 @@ gemfile: - gemfiles/5.2.gemfile - gemfiles/6.0.gemfile -matrix: - exclude: - - rvm: 2.2.2 - gemfile: gemfiles/6.0.gemfile - - rvm: 2.3.8 - gemfile: gemfiles/6.0.gemfile - - rvm: 2.4.9 - gemfile: gemfiles/6.0.gemfile - before_install: - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true - gem install bundler --version=1.17.3 diff --git a/split.gemspec b/split.gemspec index f16e7df8..4db1cd34 100644 --- a/split.gemspec +++ b/split.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| "mailing_list_uri" => "https://groups.google.com/d/forum/split-ruby" } - s.required_ruby_version = '>= 2.2.2' + s.required_ruby_version = '>= 2.5.0' s.required_rubygems_version = '>= 2.0.0' s.files = `git ls-files`.split("\n") From 9ae76100285fc43f98a5bc565d3685c66a0cff4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Thu, 18 Jun 2020 00:14:45 -0300 Subject: [PATCH 012/136] Add ruby 2.7 to the matrix --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cf71746f..b2a21923 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: ruby rvm: - 2.5.7 - - 2.6.5 + - 2.6.6 + - 2.7.1 gemfile: - gemfiles/5.0.gemfile From 12225656da44bb748c2ed9c67f63846dc5f2593d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Izurieta?= Date: Tue, 9 Jun 2020 12:41:24 -0500 Subject: [PATCH 013/136] Change redis.exists to redis.exists? to avoid problems with redis 4.3 --- lib/split/experiment.rb | 2 +- lib/split/experiment_catalog.rb | 2 +- spec/alternative_spec.rb | 2 +- spec/experiment_spec.rb | 6 +++--- spec/goals_collection_spec.rb | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index e50d0d90..7f7a5ced 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -94,7 +94,7 @@ def validate! end def new_record? - !redis.exists(name) + !redis.exists?(name) end def ==(obj) diff --git a/lib/split/experiment_catalog.rb b/lib/split/experiment_catalog.rb index e5652f31..3a1bbace 100644 --- a/lib/split/experiment_catalog.rb +++ b/lib/split/experiment_catalog.rb @@ -13,7 +13,7 @@ def self.all_active_first end def self.find(name) - return unless Split.redis.exists(name) + return unless Split.redis.exists?(name) Experiment.new(name).tap { |exp| exp.load_from_redis } end diff --git a/spec/alternative_spec.rb b/spec/alternative_spec.rb index 1a9b02eb..5e6d3704 100644 --- a/spec/alternative_spec.rb +++ b/spec/alternative_spec.rb @@ -126,7 +126,7 @@ it "should save to redis" do alternative.save - expect(Split.redis.exists('basket_text:Basket')).to be true + expect(Split.redis.exists?('basket_text:Basket')).to be true end it "should increment participation count" do diff --git a/spec/experiment_spec.rb b/spec/experiment_spec.rb index bace4418..ba956ef5 100644 --- a/spec/experiment_spec.rb +++ b/spec/experiment_spec.rb @@ -37,7 +37,7 @@ def alternative(color) it "should save to redis" do experiment.save - expect(Split.redis.exists('basket_text')).to be true + expect(Split.redis.exists?('basket_text')).to be true end it "should save the start time to redis" do @@ -85,7 +85,7 @@ def alternative(color) it "should not create duplicates when saving multiple times" do experiment.save experiment.save - expect(Split.redis.exists('basket_text')).to be true + expect(Split.redis.exists?('basket_text')).to be true expect(Split.redis.lrange('basket_text', 0, -1)).to eq(['{"Basket":1}', '{"Cart":1}']) end @@ -197,7 +197,7 @@ def alternative(color) experiment.save experiment.delete - expect(Split.redis.exists('link_color')).to be false + expect(Split.redis.exists?('link_color')).to be false expect(Split::ExperimentCatalog.find('link_color')).to be_nil end diff --git a/spec/goals_collection_spec.rb b/spec/goals_collection_spec.rb index 6c5807dd..a5d746ab 100644 --- a/spec/goals_collection_spec.rb +++ b/spec/goals_collection_spec.rb @@ -48,7 +48,7 @@ goals_collection.save goals_collection.delete - expect(Split.redis.exists(goals_key)).to be false + expect(Split.redis.exists?(goals_key)).to be false end end From 1f5cb04494f55c09aa9fa0bc2c27a08d5467d99f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Tue, 16 Jun 2020 20:21:51 -0300 Subject: [PATCH 014/136] Bump minimum required redis version --- split.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/split.gemspec b/split.gemspec index 4db1cd34..e6b9d585 100644 --- a/split.gemspec +++ b/split.gemspec @@ -29,7 +29,7 @@ Gem::Specification.new do |s| s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.require_paths = ["lib"] - s.add_dependency 'redis', '>= 2.1' + s.add_dependency 'redis', '>= 4.2' s.add_dependency 'sinatra', '>= 1.2.6' s.add_dependency 'rubystats', '>= 0.3.0' From 01116947d28032c174715cdc2fc71603d269c47a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Tue, 16 Jun 2020 20:22:39 -0300 Subject: [PATCH 015/136] hset behavior changed on redis-rb, removing redundant specs --- spec/experiment_spec.rb | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/spec/experiment_spec.rb b/spec/experiment_spec.rb index ba956ef5..5990df8b 100644 --- a/spec/experiment_spec.rb +++ b/spec/experiment_spec.rb @@ -414,18 +414,6 @@ def alternative(color) end end - describe "#disable_cohorting" do - it "saves a new key in redis" do - expect(experiment.disable_cohorting).to eq true - end - end - - describe "#enable_cohorting" do - it "saves a new key in redis" do - expect(experiment.enable_cohorting).to eq true - end - end - describe 'changing an existing experiment' do def same_but_different_alternative Split::ExperimentCatalog.find_or_create('link_color', 'blue', 'yellow', 'orange') From b93a485c173dee75f4693cc07f3dcb22a021eed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Wed, 17 Jun 2020 23:43:03 -0300 Subject: [PATCH 016/136] Use a real redis-server on CI --- .travis.yml | 3 +++ spec/configuration_spec.rb | 1 + spec/spec_helper.rb | 10 ++++------ split.gemspec | 1 - 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index b2a21923..300bb929 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,9 @@ rvm: - 2.6.6 - 2.7.1 +services: + - redis-server + gemfile: - gemfiles/5.0.gemfile - gemfiles/5.1.gemfile diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb index 26397009..a1c353cc 100644 --- a/spec/configuration_spec.rb +++ b/spec/configuration_spec.rb @@ -240,6 +240,7 @@ it "should use the ENV variable" do ENV['REDIS_URL'] = "env_redis_url" expect(Split::Configuration.new.redis).to eq("env_redis_url") + ENV.delete('REDIS_URL') end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6951489f..178b420f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,17 +13,15 @@ Dir['./spec/support/*.rb'].each { |f| require f } -require "fakeredis" - -G_fakeredis = Redis.new - module GlobalSharedContext extend RSpec::SharedContext let(:mock_user){ Split::User.new(double(session: {})) } + before(:each) do Split.configuration = Split::Configuration.new - Split.redis = G_fakeredis - Split.redis.flushall + Split.redis = Redis.new + Split.redis.select(10) + Split.redis.flushdb @ab_user = mock_user params = nil end diff --git a/split.gemspec b/split.gemspec index e6b9d585..d63bc2d0 100644 --- a/split.gemspec +++ b/split.gemspec @@ -39,6 +39,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'rake', '~> 13' s.add_development_dependency 'rspec', '~> 3.7' s.add_development_dependency 'pry', '~> 0.10' - s.add_development_dependency 'fakeredis', '~> 0.7' s.add_development_dependency 'rails', '>= 5.0' end From eefce9abaf5c13c1dc4fc879bccfb1cdf8d2982f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Thu, 18 Jun 2020 23:11:32 -0300 Subject: [PATCH 017/136] Update CHANGELOG.md --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 030e3311..b2ef72c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## Unreleased 4.0.0 + +Bugfixes: +- ab_test must return metadata on error or if split is disabled/excluded user (@andrehjr, #622) +- Fix versioned experiments when used with allow_multiple_experiments=control (@andrehjr, #613) +- Only block Pinterest bot (@huoxito, #606) +- Respect experiment defaults when loading experiments in initializer. (@mattwd7, #599) + +Features: +- Make goals accessible via on_trial_complete callbacks (@robin-phung, #625) +- Replace usage of SimpleRandom with RubyStats(Used for Beta Distribution RNG) (@andrehjr, #616) +- Introduce enable/disable experiment cohorting (@robin-phung, #615) +- Add on_experiment_winner_choose callback (@GenaMinenkov, #574) + +Misc: +- Drop support for Ruby < 2.5 (@andrehjr, #627) +- Drop support for Rails < 5 (@andrehkr, #607) +- Bump minimum required redis to 4.2 (@andrehjr, #628) +- Removed repeated loading from config (@robin-phung, #619) + ## 3.4.1 (November 12th, 2019) Bugfixes: From 41502b2ed94945b53681751db2318b830774f9cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 21 Jun 2020 21:57:35 -0300 Subject: [PATCH 018/136] Remove thread_safe config as redis-rb is thread_safe by default since 2.2 --- lib/split.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/split.rb b/lib/split.rb index 176023fa..736a4244 100755 --- a/lib/split.rb +++ b/lib/split.rb @@ -35,9 +35,9 @@ module Split # `Redis::DistRedis`, or `Redis::Namespace`. def redis=(server) @redis = if server.is_a?(String) - Redis.new(:url => server, :thread_safe => true) + Redis.new(url: server) elsif server.is_a?(Hash) - Redis.new(server.merge(:thread_safe => true)) + Redis.new(server) elsif server.respond_to?(:smembers) server else From cc816b3c9c0ca34fbcce2020246be3a5e60052ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 21 Jun 2020 22:07:08 -0300 Subject: [PATCH 019/136] Remove redis_url impl. Deprecated on ~2.2 --- lib/split/configuration.rb | 10 ---------- spec/configuration_spec.rb | 14 -------------- 2 files changed, 24 deletions(-) diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index e36cbca3..c0879565 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -232,16 +232,6 @@ def initialize @dashboard_pagination_default_per_page = 10 end - def redis_url=(value) - warn '[DEPRECATED] `redis_url=` is deprecated in favor of `redis=`' - self.redis = value - end - - def redis_url - warn '[DEPRECATED] `redis_url` is deprecated in favor of `redis`' - self.redis - end - private def value_for(hash, key) diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb index a1c353cc..4001f6e6 100644 --- a/spec/configuration_spec.rb +++ b/spec/configuration_spec.rb @@ -212,20 +212,6 @@ expect(@config.normalized_experiments).to eq({:my_experiment=>{:alternatives=>[{"control_opt"=>0.67}, [{"second_opt"=>0.1}, {"third_opt"=>0.23}]]}}) end - context 'redis_url configuration [DEPRECATED]' do - it 'should warn on set and assign to #redis' do - expect(@config).to receive(:warn).with(/\[DEPRECATED\]/) { nil } - @config.redis_url = 'example_url' - expect(@config.redis).to eq('example_url') - end - - it 'should warn on get and return #redis' do - expect(@config).to receive(:warn).with(/\[DEPRECATED\]/) { nil } - @config.redis = 'example_url' - expect(@config.redis_url).to eq('example_url') - end - end - context "redis configuration" do it "should default to local redis server" do expect(@config.redis).to eq("redis://localhost:6379") From fcd51dc8957ed4de5e78dc45e6649e2428daac0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 21 Jun 2020 22:21:46 -0300 Subject: [PATCH 020/136] Remove unused RedisInterface#remove_last_item_from_list --- lib/split/redis_interface.rb | 4 ---- spec/redis_interface_spec.rb | 14 -------------- 2 files changed, 18 deletions(-) diff --git a/lib/split/redis_interface.rb b/lib/split/redis_interface.rb index 1796a980..9eb8851c 100644 --- a/lib/split/redis_interface.rb +++ b/lib/split/redis_interface.rb @@ -31,10 +31,6 @@ def list_length(list_name) redis.llen(list_name) end - def remove_last_item_from_list(list_name) - redis.rpop(list_name) - end - def make_list_length(list_name, new_length) redis.ltrim(list_name, 0, new_length - 1) end diff --git a/spec/redis_interface_spec.rb b/spec/redis_interface_spec.rb index 42c4e43c..27845429 100644 --- a/spec/redis_interface_spec.rb +++ b/spec/redis_interface_spec.rb @@ -70,20 +70,6 @@ end end - describe '#remove_last_item_from_list' do - subject(:remove_last_item_from_list) do - interface.add_to_list(list_name, 'y') - interface.add_to_list(list_name, 'z') - interface.remove_last_item_from_list(list_name) - end - - specify do - remove_last_item_from_list - expect(Split.redis.lindex(list_name, 0)).to eq 'y' - expect(Split.redis.llen(list_name)).to eq 1 - end - end - describe '#make_list_length' do subject(:make_list_length) do interface.add_to_list(list_name, 'y') From 593a8d00f5a0d53f12b84b22d923339a501c4afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 21 Jun 2020 22:28:09 -0300 Subject: [PATCH 021/136] Simplify list persistence on RedisInterface --- lib/split/redis_interface.rb | 29 ++++--------------- spec/redis_interface_spec.rb | 55 ------------------------------------ 2 files changed, 6 insertions(+), 78 deletions(-) diff --git a/lib/split/redis_interface.rb b/lib/split/redis_interface.rb index 9eb8851c..87425004 100644 --- a/lib/split/redis_interface.rb +++ b/lib/split/redis_interface.rb @@ -7,32 +7,15 @@ def initialize end def persist_list(list_name, list_values) - max_index = list_length(list_name) - 1 - list_values.each_with_index do |value, index| - if index > max_index - add_to_list(list_name, value) - else - set_list_index(list_name, index, value) + if list_values.length > 0 + redis.multi do |multi| + tmp_list = "#{list_name}_tmp" + multi.rpush(tmp_list, list_values) + multi.rename(tmp_list, list_name) end end - make_list_length(list_name, list_values.length) - list_values - end - def add_to_list(list_name, value) - redis.rpush(list_name, value) - end - - def set_list_index(list_name, index, value) - redis.lset(list_name, index, value) - end - - def list_length(list_name) - redis.llen(list_name) - end - - def make_list_length(list_name, new_length) - redis.ltrim(list_name, 0, new_length - 1) + list_values end def add_to_set(set_name, value) diff --git a/spec/redis_interface_spec.rb b/spec/redis_interface_spec.rb index 27845429..c37c3c28 100644 --- a/spec/redis_interface_spec.rb +++ b/spec/redis_interface_spec.rb @@ -29,61 +29,6 @@ end end - describe '#add_to_list' do - subject(:add_to_list) do - interface.add_to_list(list_name, 'y') - interface.add_to_list(list_name, 'z') - end - - specify do - add_to_list - expect(Split.redis.lindex(list_name, 0)).to eq 'y' - expect(Split.redis.lindex(list_name, 1)).to eq 'z' - expect(Split.redis.llen(list_name)).to eq 2 - end - end - - describe '#set_list_index' do - subject(:set_list_index) do - interface.add_to_list(list_name, 'y') - interface.add_to_list(list_name, 'z') - interface.set_list_index(list_name, 0, 'a') - end - - specify do - set_list_index - expect(Split.redis.lindex(list_name, 0)).to eq 'a' - expect(Split.redis.lindex(list_name, 1)).to eq 'z' - expect(Split.redis.llen(list_name)).to eq 2 - end - end - - describe '#list_length' do - subject(:list_length) do - interface.add_to_list(list_name, 'y') - interface.add_to_list(list_name, 'z') - interface.list_length(list_name) - end - - specify do - expect(list_length).to eq 2 - end - end - - describe '#make_list_length' do - subject(:make_list_length) do - interface.add_to_list(list_name, 'y') - interface.add_to_list(list_name, 'z') - interface.make_list_length(list_name, 1) - end - - specify do - make_list_length - expect(Split.redis.lindex(list_name, 0)).to eq 'y' - expect(Split.redis.llen(list_name)).to eq 1 - end - end - describe '#add_to_set' do subject(:add_to_set) do interface.add_to_set(set_name, 'something') From 7b9caf5ef0bfd1c5db6726b729cba3431bb9cac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 21 Jun 2020 22:20:29 -0300 Subject: [PATCH 022/136] Remove extra SISMEMBER check when calling RedisInterface#add_to_set --- lib/split/redis_interface.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/split/redis_interface.rb b/lib/split/redis_interface.rb index 87425004..a6f39614 100644 --- a/lib/split/redis_interface.rb +++ b/lib/split/redis_interface.rb @@ -19,7 +19,7 @@ def persist_list(list_name, list_values) end def add_to_set(set_name, value) - redis.sadd(set_name, value) unless redis.sismember(set_name, value) + redis.sadd(set_name, value) end private From f062deff6f001cbec100667bec315f3e4fbebaa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 21 Jun 2020 22:55:45 -0300 Subject: [PATCH 023/136] update rubocop_todo.yml --- .rubocop_todo.yml | 156 ++++++++++++++++++++++++++++------------------ 1 file changed, 97 insertions(+), 59 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7a554e3a..b56902ea 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,12 +1,12 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2019-10-21 18:09:14 -0300 using RuboCop version 0.75.1. +# on 2020-06-21 22:54:51 -0300 using RuboCop version 0.85.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 4 +# Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: TreatCommentsAsGroupSeparators, Include. # Include: **/*.gemspec @@ -16,7 +16,7 @@ Gemspec/OrderedDependencies: # Offense count: 1 # Configuration parameters: Include. -# Include: **/*.gemspec, +# Include: **/*.gemspec Gemspec/RequiredRubyVersion: Exclude: - 'split.gemspec' @@ -25,21 +25,11 @@ Gemspec/RequiredRubyVersion: # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: with_first_argument, with_fixed_indentation -Layout/AlignArguments: +Layout/ArgumentAlignment: Exclude: - 'lib/split.rb' - 'lib/split/experiment_catalog.rb' -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. -# SupportedHashRocketStyles: key, separator, table -# SupportedColonStyles: key, separator, table -# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit -Layout/AlignHash: - Exclude: - - 'lib/split/helper.rb' - # Offense count: 1 # Cop supports --auto-correct. Layout/CommentIndentation: @@ -69,7 +59,7 @@ Layout/EmptyLineAfterGuardClause: - 'lib/split/helper.rb' - 'lib/split/user.rb' -# Offense count: 25 +# Offense count: 30 # Cop supports --auto-correct. Layout/EmptyLineAfterMagicComment: Enabled: false @@ -140,10 +130,20 @@ Layout/ExtraSpacing: # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: special_inside_parentheses, consistent, align_braces -Layout/IndentFirstHashElement: +Layout/FirstHashElementIndentation: Exclude: - 'split.gemspec' +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. +# SupportedHashRocketStyles: key, separator, table +# SupportedColonStyles: key, separator, table +# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit +Layout/HashAlignment: + Exclude: + - 'lib/split/helper.rb' + # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: Width, IgnoredPatterns. @@ -164,7 +164,7 @@ Layout/SpaceAfterComma: - 'lib/split/metric.rb' - 'lib/split/user.rb' -# Offense count: 5 +# Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: space, no_space @@ -173,12 +173,12 @@ Layout/SpaceAroundEqualsInParameterDefault: - 'lib/split/goals_collection.rb' - 'lib/split/persistence/dual_adapter.rb' - 'lib/split/persistence/redis_adapter.rb' - - 'lib/split/trial.rb' - 'lib/split/user.rb' # Offense count: 24 # Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment. +# Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator. +# SupportedStylesForExponentOperator: space, no_space Layout/SpaceAroundOperators: Exclude: - 'lib/split/algorithms/whiplash.rb' @@ -187,7 +187,7 @@ Layout/SpaceAroundOperators: - 'lib/split/trial.rb' - 'lib/split/zscore.rb' -# Offense count: 14 +# Offense count: 15 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. # SupportedStyles: space, no_space @@ -209,7 +209,7 @@ Layout/SpaceInsideArrayLiteralBrackets: Exclude: - 'lib/split/dashboard/helpers.rb' -# Offense count: 31 +# Offense count: 33 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. # SupportedStyles: space, no_space @@ -238,10 +238,17 @@ Layout/SpaceInsideHashLiteralBraces: # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: final_newline, final_blank_line -Layout/TrailingBlankLines: +Layout/TrailingEmptyLines: Exclude: - 'Rakefile' +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: AllowInHeredoc. +Layout/TrailingWhitespace: + Exclude: + - 'lib/split/helper.rb' + # Offense count: 8 # Configuration parameters: AllowSafeAssignment. Lint/AssignmentInCondition: @@ -252,7 +259,13 @@ Lint/AssignmentInCondition: - 'lib/split/persistence/dual_adapter.rb' - 'lib/split/persistence/redis_adapter.rb' -# Offense count: 4 +# Offense count: 2 +# Cop supports --auto-correct. +Lint/SendWithMixinArgument: + Exclude: + - 'lib/split/engine.rb' + +# Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: @@ -260,11 +273,12 @@ Lint/UnusedBlockArgument: - 'lib/split/algorithms/block_randomization.rb' - 'lib/split/configuration.rb' - 'lib/split/engine.rb' + - 'lib/split/experiment.rb' - 'lib/split/metric.rb' # Offense count: 2 # Cop supports --auto-correct. -# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods. Lint/UnusedMethodArgument: Exclude: - 'lib/split/dashboard/helpers.rb' @@ -276,19 +290,26 @@ Lint/UselessAssignment: - 'lib/split/goals_collection.rb' # Offense count: 19 +# Configuration parameters: IgnoredMethods. Metrics/AbcSize: - Max: 47 + Max: 54 + +# Offense count: 1 +# Configuration parameters: CountBlocks. +Metrics/BlockNesting: + Max: 4 # Offense count: 3 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 377 + Max: 386 # Offense count: 6 +# Configuration parameters: IgnoredMethods. Metrics/CyclomaticComplexity: - Max: 14 + Max: 18 -# Offense count: 23 +# Offense count: 22 # Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: Max: 66 @@ -296,11 +317,12 @@ Metrics/MethodLength: # Offense count: 1 # Configuration parameters: CountComments. Metrics/ModuleLength: - Max: 134 + Max: 138 # Offense count: 8 +# Configuration parameters: IgnoredMethods. Metrics/PerceivedComplexity: - Max: 16 + Max: 21 # Offense count: 4 Naming/AccessorMethodName: @@ -314,11 +336,19 @@ Naming/BinaryOperatorParameterName: Exclude: - 'lib/split/experiment.rb' +# Offense count: 5 +# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. +# AllowedNames: io, id, to, by, on, in, at, ip, db, os, pp +Naming/MethodParameterName: + Exclude: + - 'lib/split/alternative.rb' + - 'lib/split/zscore.rb' + # Offense count: 4 -# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist, MethodDefinitionMacros. +# Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros. # NamePrefix: is_, has_, have_ -# NamePrefixBlacklist: is_, has_, have_ -# NameWhitelist: is_a? +# ForbiddenPrefixes: is_, has_, have_ +# AllowedMethods: is_a? # MethodDefinitionMacros: define_method, define_singleton_method Naming/PredicateName: Exclude: @@ -326,14 +356,6 @@ Naming/PredicateName: - 'lib/split/experiment.rb' - 'lib/split/helper.rb' -# Offense count: 5 -# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. -# AllowedNames: io, id, to, by, on, in, at, ip, db -Naming/UncommunicativeMethodParamName: - Exclude: - - 'lib/split/alternative.rb' - - 'lib/split/zscore.rb' - # Offense count: 6 # Configuration parameters: EnforcedStyle. # SupportedStyles: snake_case, normalcase, non_integer @@ -364,6 +386,7 @@ Style/BarePercentLiterals: - 'lib/split/dashboard/pagination_helpers.rb' # Offense count: 8 +# Configuration parameters: AllowOnConstant. Style/CaseEquality: Exclude: - 'lib/split/alternative.rb' @@ -449,14 +472,13 @@ Style/GuardClause: - 'lib/split/persistence/dual_adapter.rb' - 'lib/split/trial.rb' -# Offense count: 25 +# Offense count: 23 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. # SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys Style/HashSyntax: Exclude: - 'Rakefile' - - 'lib/split.rb' - 'lib/split/experiment.rb' - 'lib/split/experiment_catalog.rb' - 'lib/split/helper.rb' @@ -493,7 +515,7 @@ Style/MethodDefParentheses: # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, Autocorrect. -# SupportedStyles: module_function, extend_self +# SupportedStyles: module_function, extend_self, forbidden Style/ModuleFunction: Exclude: - 'lib/split.rb' @@ -575,7 +597,7 @@ Style/RedundantParentheses: - 'lib/split/alternative.rb' - 'lib/split/zscore.rb' -# Offense count: 11 +# Offense count: 12 # Cop supports --auto-correct. # Configuration parameters: AllowMultipleReturnValues. Style/RedundantReturn: @@ -587,7 +609,7 @@ Style/RedundantReturn: - 'lib/split/metric.rb' - 'lib/split/zscore.rb' -# Offense count: 21 +# Offense count: 20 # Cop supports --auto-correct. Style/RedundantSelf: Exclude: @@ -620,20 +642,13 @@ Style/RescueStandardError: # Offense count: 2 # Cop supports --auto-correct. -# Configuration parameters: ConvertCodeThatCanStartToReturnNil, Whitelist. -# Whitelist: present?, blank?, presence, try, try! +# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. +# AllowedMethods: present?, blank?, presence, try, try! Style/SafeNavigation: Exclude: - 'lib/split/configuration.rb' - 'lib/split/helper.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: AllowAsExpressionSeparator. -Style/Semicolon: - Exclude: - - 'lib/split/algorithms/whiplash.rb' - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: . @@ -641,12 +656,27 @@ Style/Semicolon: Style/SpecialGlobalVars: EnforcedStyle: use_perl_names -# Offense count: 86 +# Offense count: 46 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes Style/StringLiterals: - Enabled: false + Exclude: + - 'Gemfile' + - 'lib/split.rb' + - 'lib/split/alternative.rb' + - 'lib/split/dashboard.rb' + - 'lib/split/dashboard/helpers.rb' + - 'lib/split/encapsulated_helper.rb' + - 'lib/split/engine.rb' + - 'lib/split/experiment.rb' + - 'lib/split/helper.rb' + - 'lib/split/persistence/cookie_adapter.rb' + - 'lib/split/persistence/dual_adapter.rb' + - 'lib/split/persistence/redis_adapter.rb' + - 'lib/split/user.rb' + - 'lib/split/zscore.rb' + - 'split.gemspec' # Offense count: 3 # Cop supports --auto-correct. @@ -658,6 +688,14 @@ Style/SymbolProc: - 'lib/split/experiment_catalog.rb' - 'lib/split/metric.rb' +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInArguments: + Exclude: + - 'lib/split/helper.rb' + # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: AllowNamedUnderscoreVariables. @@ -671,9 +709,9 @@ Style/ZeroLengthPredicate: Exclude: - 'lib/split/user.rb' -# Offense count: 74 +# Offense count: 9 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https -Metrics/LineLength: +Layout/LineLength: Max: 183 From bb995682c355f1f5297e8549ae8ab2e3296b932b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 21 Jun 2020 22:59:00 -0300 Subject: [PATCH 024/136] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2ef72c1..a1d5c739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ Misc: - Drop support for Rails < 5 (@andrehkr, #607) - Bump minimum required redis to 4.2 (@andrehjr, #628) - Removed repeated loading from config (@robin-phung, #619) +- Simplify RedisInterface usage when persisting Experiment alternatives (@andrehjr, #632) +- Remove redis_url impl. Deprecated on version 2.2 (@andrehjr, #631) +- Remove thread_safe config as redis-rb is thread_safe by default (@andrehjr, #630) ## 3.4.1 (November 12th, 2019) From 50e7359baa17e6783492be9fcef5d199bec777d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sat, 4 Jul 2020 22:27:38 -0300 Subject: [PATCH 025/136] Recreate rubocop configuration --- .rubocop.yml | 178 ++++++++++++++- .rubocop_todo.yml | 555 ++++------------------------------------------ 2 files changed, 217 insertions(+), 516 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 900eec30..4e74759b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,7 +1,183 @@ inherit_from: .rubocop_todo.yml AllCops: + TargetRubyVersion: 2.5 + DisabledByDefault: true Exclude: - 'Appraisals' - 'gemfiles/**/*' - - 'spec/**/*.rb' \ No newline at end of file + - 'spec/**/*.rb' + +Style/AndOr: + Enabled: true + +Layout/CaseIndentation: + Enabled: true + +Layout/ClosingHeredocIndentation: + Enabled: true + +Layout/CommentIndentation: + Enabled: true + +Layout/ElseAlignment: + Enabled: true + +Layout/EndAlignment: + Enabled: true + EnforcedStyleAlignWith: variable + AutoCorrect: true + +Layout/EmptyLineAfterMagicComment: + Enabled: false + +Layout/EmptyLinesAroundAccessModifier: + Enabled: true + EnforcedStyle: only_before + +Layout/EmptyLinesAroundBlockBody: + Enabled: true + +Layout/EmptyLinesAroundClassBody: + Enabled: true + +Layout/EmptyLinesAroundMethodBody: + Enabled: true + +Layout/EmptyLinesAroundModuleBody: + Enabled: true + +Style/HashSyntax: + Enabled: true + +Layout/FirstArgumentIndentation: + Enabled: true + +Layout/IndentationConsistency: + Enabled: true + EnforcedStyle: indented_internal_methods + +Layout/IndentationWidth: + Enabled: true + +Layout/LeadingCommentSpace: + Enabled: true + +Layout/SpaceAfterColon: + Enabled: true + +Layout/SpaceAfterComma: + Enabled: true + +Layout/SpaceAfterSemicolon: + Enabled: true + +Layout/SpaceAroundEqualsInParameterDefault: + Enabled: true + +Layout/SpaceAroundKeyword: + Enabled: true + +Layout/SpaceBeforeComma: + Enabled: true + +Layout/SpaceBeforeComment: + Enabled: true + +Layout/SpaceBeforeFirstArg: + Enabled: true + +Style/DefWithParentheses: + Enabled: true + +Style/MethodDefParentheses: + Enabled: true + +Style/FrozenStringLiteralComment: + Enabled: true + EnforcedStyle: always + +Style/RedundantFreeze: + Enabled: true + +Layout/SpaceBeforeBlockBraces: + Enabled: true + +Layout/SpaceInsideBlockBraces: + Enabled: true + EnforcedStyleForEmptyBraces: space + +Layout/SpaceInsideHashLiteralBraces: + Enabled: true + +Layout/SpaceInsideParens: + Enabled: true + +Style/StringLiterals: + Enabled: false + EnforcedStyle: double_quotes + +Layout/IndentationStyle: + Enabled: true + +Layout/TrailingEmptyLines: + Enabled: true + +Layout/TrailingWhitespace: + Enabled: true + +Style/RedundantPercentQ: + Enabled: true + +Lint/AmbiguousOperator: + Enabled: true + +Lint/AmbiguousRegexpLiteral: + Enabled: true + +Lint/ErbNewArguments: + Enabled: true + +Lint/RequireParentheses: + Enabled: true + +Lint/ShadowingOuterLocalVariable: + Enabled: true + +Lint/RedundantStringCoercion: + Enabled: true + +Lint/UriEscapeUnescape: + Enabled: true + +Lint/UselessAssignment: + Enabled: true + +Lint/DeprecatedClassMethods: + Enabled: true + +Style/ParenthesesAroundCondition: + Enabled: true + +Style/HashTransformKeys: + Enabled: true + +Style/HashTransformValues: + Enabled: true + +Style/RedundantBegin: + Enabled: true + +Style/RedundantReturn: + Enabled: true + AllowMultipleReturnValues: true + +Style/Semicolon: + Enabled: true + AllowAsExpressionSeparator: true + +Style/ColonMethodCall: + Enabled: true + +Style/TrivialAccessors: + Enabled: true diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b56902ea..7bbe705b 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,81 +1,47 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2020-06-21 22:54:51 -0300 using RuboCop version 0.85.1. +# on 2020-07-05 01:43:26 UTC using RuboCop version 0.86.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: TreatCommentsAsGroupSeparators, Include. -# Include: **/*.gemspec -Gemspec/OrderedDependencies: - Exclude: - - 'split.gemspec' - -# Offense count: 1 -# Configuration parameters: Include. -# Include: **/*.gemspec -Gemspec/RequiredRubyVersion: - Exclude: - - 'split.gemspec' - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: with_first_argument, with_fixed_indentation -Layout/ArgumentAlignment: - Exclude: - - 'lib/split.rb' - - 'lib/split/experiment_catalog.rb' - # Offense count: 1 # Cop supports --auto-correct. Layout/CommentIndentation: Exclude: - 'lib/split/experiment.rb' -# Offense count: 5 +# Offense count: 1 # Cop supports --auto-correct. Layout/ElseAlignment: Exclude: - - 'lib/split.rb' - - 'lib/split/helper.rb' - - 'lib/split/trial.rb' - -# Offense count: 20 -# Cop supports --auto-correct. -Layout/EmptyLineAfterGuardClause: - Exclude: - - 'lib/split.rb' - - 'lib/split/algorithms/weighted_sample.rb' - - 'lib/split/alternative.rb' - - 'lib/split/combined_experiments_helper.rb' - - 'lib/split/configuration.rb' - 'lib/split/experiment.rb' - - 'lib/split/experiment_catalog.rb' - - 'lib/split/goals_collection.rb' - - 'lib/split/helper.rb' - - 'lib/split/user.rb' # Offense count: 30 # Cop supports --auto-correct. Layout/EmptyLineAfterMagicComment: Enabled: false -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: AllowAdjacentOneLineDefs, NumberOfEmptyLines. -Layout/EmptyLineBetweenDefs: - Exclude: - - 'lib/split/helper.rb' - -# Offense count: 1 +# Offense count: 14 # Cop supports --auto-correct. -Layout/EmptyLines: +# Configuration parameters: EnforcedStyle. +# SupportedStyles: around, only_before +Layout/EmptyLinesAroundAccessModifier: Exclude: - - 'lib/split/helper.rb' + - 'lib/split/algorithms/block_randomization.rb' + - 'lib/split/algorithms/whiplash.rb' + - 'lib/split/alternative.rb' + - 'lib/split/configuration.rb' + - 'lib/split/dashboard/pagination_helpers.rb' + - 'lib/split/encapsulated_helper.rb' + - 'lib/split/experiment.rb' + - 'lib/split/goals_collection.rb' + - 'lib/split/persistence/cookie_adapter.rb' + - 'lib/split/persistence/dual_adapter.rb' + - 'lib/split/redis_interface.rb' + - 'lib/split/trial.rb' + - 'lib/split/user.rb' # Offense count: 8 # Cop supports --auto-correct. @@ -106,52 +72,34 @@ Layout/EmptyLinesAroundModuleBody: Exclude: - 'lib/split/encapsulated_helper.rb' -# Offense count: 3 +# Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyleAlignWith, AutoCorrect, Severity. # SupportedStylesAlignWith: keyword, variable, start_of_line Layout/EndAlignment: Exclude: - - 'lib/split.rb' - - 'lib/split/helper.rb' - - 'lib/split/trial.rb' - -# Offense count: 4 -# Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. -Layout/ExtraSpacing: - Exclude: - - 'lib/split/algorithms/whiplash.rb' - - 'lib/split/dashboard.rb' - - 'lib/split/metric.rb' + - 'lib/split/configuration.rb' + - 'lib/split/experiment.rb' - 'lib/split/trial.rb' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: special_inside_parentheses, consistent, align_braces -Layout/FirstHashElementIndentation: - Exclude: - - 'split.gemspec' - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. -# SupportedHashRocketStyles: key, separator, table -# SupportedColonStyles: key, separator, table -# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit -Layout/HashAlignment: - Exclude: - - 'lib/split/helper.rb' - -# Offense count: 3 +# Offense count: 17 # Cop supports --auto-correct. # Configuration parameters: Width, IgnoredPatterns. Layout/IndentationWidth: Exclude: - - 'lib/split.rb' - - 'lib/split/helper.rb' + - 'lib/split/algorithms/block_randomization.rb' + - 'lib/split/algorithms/whiplash.rb' + - 'lib/split/alternative.rb' + - 'lib/split/configuration.rb' + - 'lib/split/dashboard/pagination_helpers.rb' + - 'lib/split/encapsulated_helper.rb' + - 'lib/split/experiment.rb' + - 'lib/split/goals_collection.rb' + - 'lib/split/persistence/cookie_adapter.rb' + - 'lib/split/persistence/dual_adapter.rb' + - 'lib/split/redis_interface.rb' - 'lib/split/trial.rb' + - 'lib/split/user.rb' # Offense count: 10 # Cop supports --auto-correct. @@ -175,18 +123,6 @@ Layout/SpaceAroundEqualsInParameterDefault: - 'lib/split/persistence/redis_adapter.rb' - 'lib/split/user.rb' -# Offense count: 24 -# Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator. -# SupportedStylesForExponentOperator: space, no_space -Layout/SpaceAroundOperators: - Exclude: - - 'lib/split/algorithms/whiplash.rb' - - 'lib/split/alternative.rb' - - 'lib/split/metric.rb' - - 'lib/split/trial.rb' - - 'lib/split/zscore.rb' - # Offense count: 15 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. @@ -200,22 +136,14 @@ Layout/SpaceBeforeBlockBraces: - 'lib/split/helper.rb' - 'lib/split/trial.rb' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets. -# SupportedStyles: space, no_space, compact -# SupportedStylesForEmptyBrackets: space, no_space -Layout/SpaceInsideArrayLiteralBrackets: - Exclude: - - 'lib/split/dashboard/helpers.rb' - -# Offense count: 33 +# Offense count: 35 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. # SupportedStyles: space, no_space # SupportedStylesForEmptyBraces: space, no_space Layout/SpaceInsideBlockBraces: Exclude: + - 'lib/split.rb' - 'lib/split/configuration.rb' - 'lib/split/experiment.rb' - 'lib/split/experiment_catalog.rb' @@ -249,120 +177,11 @@ Layout/TrailingWhitespace: Exclude: - 'lib/split/helper.rb' -# Offense count: 8 -# Configuration parameters: AllowSafeAssignment. -Lint/AssignmentInCondition: - Exclude: - - 'lib/split/combined_experiments_helper.rb' - - 'lib/split/configuration.rb' - - 'lib/split/persistence/cookie_adapter.rb' - - 'lib/split/persistence/dual_adapter.rb' - - 'lib/split/persistence/redis_adapter.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -Lint/SendWithMixinArgument: - Exclude: - - 'lib/split/engine.rb' - -# Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. -Lint/UnusedBlockArgument: - Exclude: - - 'lib/split/algorithms/block_randomization.rb' - - 'lib/split/configuration.rb' - - 'lib/split/engine.rb' - - 'lib/split/experiment.rb' - - 'lib/split/metric.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods. -Lint/UnusedMethodArgument: - Exclude: - - 'lib/split/dashboard/helpers.rb' - - 'lib/split/persistence/cookie_adapter.rb' - # Offense count: 1 Lint/UselessAssignment: Exclude: - 'lib/split/goals_collection.rb' -# Offense count: 19 -# Configuration parameters: IgnoredMethods. -Metrics/AbcSize: - Max: 54 - -# Offense count: 1 -# Configuration parameters: CountBlocks. -Metrics/BlockNesting: - Max: 4 - -# Offense count: 3 -# Configuration parameters: CountComments. -Metrics/ClassLength: - Max: 386 - -# Offense count: 6 -# Configuration parameters: IgnoredMethods. -Metrics/CyclomaticComplexity: - Max: 18 - -# Offense count: 22 -# Configuration parameters: CountComments, ExcludedMethods. -Metrics/MethodLength: - Max: 66 - -# Offense count: 1 -# Configuration parameters: CountComments. -Metrics/ModuleLength: - Max: 138 - -# Offense count: 8 -# Configuration parameters: IgnoredMethods. -Metrics/PerceivedComplexity: - Max: 21 - -# Offense count: 4 -Naming/AccessorMethodName: - Exclude: - - 'lib/split/alternative.rb' - - 'lib/split/experiment.rb' - - 'lib/split/persistence/cookie_adapter.rb' - -# Offense count: 1 -Naming/BinaryOperatorParameterName: - Exclude: - - 'lib/split/experiment.rb' - -# Offense count: 5 -# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. -# AllowedNames: io, id, to, by, on, in, at, ip, db, os, pp -Naming/MethodParameterName: - Exclude: - - 'lib/split/alternative.rb' - - 'lib/split/zscore.rb' - -# Offense count: 4 -# Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros. -# NamePrefix: is_, has_, have_ -# ForbiddenPrefixes: is_, has_, have_ -# AllowedMethods: is_a? -# MethodDefinitionMacros: define_method, define_singleton_method -Naming/PredicateName: - Exclude: - - 'spec/**/*' - - 'lib/split/experiment.rb' - - 'lib/split/helper.rb' - -# Offense count: 6 -# Configuration parameters: EnforcedStyle. -# SupportedStyles: snake_case, normalcase, non_integer -Naming/VariableNumber: - Exclude: - - 'lib/split/zscore.rb' - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. @@ -371,107 +190,18 @@ Style/AndOr: Exclude: - 'lib/split/experiment_catalog.rb' -# Offense count: 2 -# Configuration parameters: AllowedChars. -Style/AsciiComments: - Exclude: - - 'lib/split/zscore.rb' - -# Offense count: 4 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: percent_q, bare_percent -Style/BarePercentLiterals: - Exclude: - - 'lib/split/dashboard/pagination_helpers.rb' - -# Offense count: 8 -# Configuration parameters: AllowOnConstant. -Style/CaseEquality: - Exclude: - - 'lib/split/alternative.rb' - - 'lib/split/experiment_catalog.rb' - - 'lib/split/helper.rb' - - 'lib/split/metric.rb' - -# Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: is_a?, kind_of? -Style/ClassCheck: - Exclude: - - 'lib/split/alternative.rb' - - 'lib/split/configuration.rb' - - 'lib/split/experiment.rb' - - 'lib/split/goals_collection.rb' - - 'lib/split/trial.rb' - # Offense count: 1 # Cop supports --auto-correct. Style/ColonMethodCall: Exclude: - 'lib/split/combined_experiments_helper.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: Keywords. -# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW -Style/CommentAnnotation: - Exclude: - - 'lib/split/configuration.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. -# SupportedStyles: assign_to_condition, assign_inside_condition -Style/ConditionalAssignment: - Exclude: - - 'lib/split/dashboard.rb' - - 'lib/split/persistence/redis_adapter.rb' - # Offense count: 1 # Cop supports --auto-correct. Style/DefWithParentheses: Exclude: - 'lib/split/helper.rb' -# Offense count: 29 -Style/Documentation: - Enabled: false - -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: empty, nil, both -Style/EmptyElse: - Exclude: - - 'lib/split/experiment.rb' - - 'lib/split/metric.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -Style/Encoding: - Exclude: - - 'split.gemspec' - -# Offense count: 1 -# Cop supports --auto-correct. -Style/ExpandPathArguments: - Exclude: - - 'split.gemspec' - -# Offense count: 13 -# Configuration parameters: MinBodyLength. -Style/GuardClause: - Exclude: - - 'lib/split/alternative.rb' - - 'lib/split/configuration.rb' - - 'lib/split/experiment.rb' - - 'lib/split/goals_collection.rb' - - 'lib/split/helper.rb' - - 'lib/split/persistence/dual_adapter.rb' - - 'lib/split/trial.rb' - # Offense count: 23 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. @@ -486,24 +216,6 @@ Style/HashSyntax: - 'lib/split/persistence.rb' - 'lib/split/persistence/redis_adapter.rb' -# Offense count: 4 -Style/IdenticalConditionalBranches: - Exclude: - - 'lib/split/configuration.rb' - - 'lib/split/experiment.rb' - -# Offense count: 14 -# Cop supports --auto-correct. -Style/IfUnlessModifier: - Exclude: - - 'lib/split/alternative.rb' - - 'lib/split/experiment.rb' - - 'lib/split/experiment_catalog.rb' - - 'lib/split/goals_collection.rb' - - 'lib/split/metric.rb' - - 'lib/split/trial.rb' - - 'lib/split/user.rb' - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. @@ -512,206 +224,19 @@ Style/MethodDefParentheses: Exclude: - 'lib/split/configuration.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, Autocorrect. -# SupportedStyles: module_function, extend_self, forbidden -Style/ModuleFunction: - Exclude: - - 'lib/split.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: literals, strict -Style/MutableConstant: - Exclude: - - 'lib/split/experiment.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: both, prefix, postfix -Style/NegatedIf: - Exclude: - - 'lib/split/user.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: IncludeSemanticChanges. -Style/NonNilCheck: - Exclude: - - 'lib/split/configuration.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -Style/Not: - Exclude: - - 'lib/split/experiment_catalog.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: Strict. -Style/NumericLiterals: - MinDigits: 9 - -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods. -# SupportedStyles: predicate, comparison -Style/NumericPredicate: - Exclude: - - 'spec/**/*' - - 'lib/split/experiment.rb' - - 'lib/split/trial.rb' - - 'lib/split/user.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -Style/ParallelAssignment: - Exclude: - - 'lib/split/experiment_catalog.rb' - - 'lib/split/persistence/cookie_adapter.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: short, verbose -Style/PreferredHashMethods: - Exclude: - - 'lib/split/configuration.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: compact, exploded -Style/RaiseArgs: - Exclude: - - 'lib/split/configuration.rb' - - 'lib/split/experiment.rb' - -# Offense count: 6 -# Cop supports --auto-correct. -Style/RedundantParentheses: - Exclude: - - 'lib/split/alternative.rb' - - 'lib/split/zscore.rb' - -# Offense count: 12 +# Offense count: 9 # Cop supports --auto-correct. # Configuration parameters: AllowMultipleReturnValues. Style/RedundantReturn: Exclude: - 'lib/split/alternative.rb' - 'lib/split/experiment.rb' - - 'lib/split/experiment_catalog.rb' - 'lib/split/helper.rb' - - 'lib/split/metric.rb' - 'lib/split/zscore.rb' -# Offense count: 20 -# Cop supports --auto-correct. -Style/RedundantSelf: - Exclude: - - 'lib/split.rb' - - 'lib/split/alternative.rb' - - 'lib/split/configuration.rb' - - 'lib/split/experiment.rb' - - 'lib/split/extensions/string.rb' - - 'lib/split/metric.rb' - - 'lib/split/persistence/dual_adapter.rb' - - 'lib/split/persistence/redis_adapter.rb' - - 'lib/split/trial.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -Style/RescueModifier: - Exclude: - - 'lib/split/alternative.rb' - - 'lib/split/configuration.rb' - -# Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: implicit, explicit -Style/RescueStandardError: - Exclude: - - 'lib/split/alternative.rb' - - 'lib/split/experiment.rb' - - 'lib/split/helper.rb' - -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. -# AllowedMethods: present?, blank?, presence, try, try! -Style/SafeNavigation: - Exclude: - - 'lib/split/configuration.rb' - - 'lib/split/helper.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: . -# SupportedStyles: use_perl_names, use_english_names -Style/SpecialGlobalVars: - EnforcedStyle: use_perl_names - -# Offense count: 46 +# Offense count: 258 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes Style/StringLiterals: - Exclude: - - 'Gemfile' - - 'lib/split.rb' - - 'lib/split/alternative.rb' - - 'lib/split/dashboard.rb' - - 'lib/split/dashboard/helpers.rb' - - 'lib/split/encapsulated_helper.rb' - - 'lib/split/engine.rb' - - 'lib/split/experiment.rb' - - 'lib/split/helper.rb' - - 'lib/split/persistence/cookie_adapter.rb' - - 'lib/split/persistence/dual_adapter.rb' - - 'lib/split/persistence/redis_adapter.rb' - - 'lib/split/user.rb' - - 'lib/split/zscore.rb' - - 'split.gemspec' - -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: IgnoredMethods. -# IgnoredMethods: respond_to, define_method -Style/SymbolProc: - Exclude: - - 'lib/split/experiment.rb' - - 'lib/split/experiment_catalog.rb' - - 'lib/split/metric.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, no_comma -Style/TrailingCommaInArguments: - Exclude: - - 'lib/split/helper.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: AllowNamedUnderscoreVariables. -Style/TrailingUnderscoreVariable: - Exclude: - - 'lib/split/helper.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -Style/ZeroLengthPredicate: - Exclude: - - 'lib/split/user.rb' - -# Offense count: 9 -# Cop supports --auto-correct. -# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. -# URISchemes: http, https -Layout/LineLength: - Max: 183 + Enabled: false From 2c151af24e9076096fe843a55b847a20e186f1c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 5 Jul 2020 00:24:27 -0300 Subject: [PATCH 026/136] Removes metadata key when it updated to nil --- lib/split/experiment.rb | 7 ++++++- spec/experiment_spec.rb | 31 +++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 7f7a5ced..758cedbe 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -464,7 +464,12 @@ def persist_experiment_configuration redis_interface.add_to_set(:experiments, name) redis_interface.persist_list(name, @alternatives.map{|alt| {alt.name => alt.weight}.to_json}) goals_collection.save - redis.set(metadata_key, @metadata.to_json) unless @metadata.nil? + + if @metadata + redis.set(metadata_key, @metadata.to_json) + else + delete_metadata + end end def remove_experiment_configuration diff --git a/spec/experiment_spec.rb b/spec/experiment_spec.rb index 5990df8b..98699e4c 100644 --- a/spec/experiment_spec.rb +++ b/spec/experiment_spec.rb @@ -151,10 +151,23 @@ def alternative(color) describe '#metadata' do let(:experiment) { Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :algorithm => Split::Algorithms::Whiplash, :metadata => meta) } + let(:meta) { { a: 'b' }} + + before do + experiment.save + end + + it "should delete the key when metadata is removed" do + experiment.metadata = nil + experiment.save + + expect(Split.redis.exists?(experiment.metadata_key)).to be_falsey + end + context 'simple hash' do let(:meta) { { 'basket' => 'a', 'cart' => 'b' } } + it "should persist metadata in redis" do - experiment.save e = Split::ExperimentCatalog.find('basket_text') expect(e).to eq(experiment) expect(e.metadata).to eq(meta) @@ -164,7 +177,6 @@ def alternative(color) context 'nested hash' do let(:meta) { { 'basket' => { 'one' => 'two' }, 'cart' => 'b' } } it "should persist metadata in redis" do - experiment.save e = Split::ExperimentCatalog.find('basket_text') expect(e).to eq(experiment) expect(e.metadata).to eq(meta) @@ -436,6 +448,21 @@ def same_but_different_alternative expect(same_experiment_again.version).to eq(1) end + context "when metadata is changed" do + it "should increase version" do + experiment.save + experiment.metadata = { 'foo' => 'bar' } + + expect { experiment.save }.to change { experiment.version }.by(1) + end + + it "does not increase version" do + experiment.metadata = nil + experiment.save + expect { experiment.save }.to change { experiment.version }.by(0) + end + end + context 'when experiment configuration is changed' do let(:reset_manually) { false } From d3e0f4043daa6fcbd8af3904a4149969fce77c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 5 Jul 2020 12:50:18 -0300 Subject: [PATCH 027/136] Fix Rubocop offense Layout/EmptyLineAfterMagicComment --- .rubocop.yml | 2 +- .rubocop_todo.yml | 5 ----- Gemfile | 1 + Rakefile | 1 + lib/split.rb | 1 + lib/split/algorithms/block_randomization.rb | 1 + lib/split/algorithms/weighted_sample.rb | 1 + lib/split/algorithms/whiplash.rb | 1 + lib/split/alternative.rb | 1 + lib/split/combined_experiments_helper.rb | 1 + lib/split/configuration.rb | 1 + lib/split/dashboard.rb | 1 + lib/split/dashboard/helpers.rb | 1 + lib/split/dashboard/pagination_helpers.rb | 1 + lib/split/dashboard/paginator.rb | 1 + lib/split/encapsulated_helper.rb | 1 + lib/split/engine.rb | 1 + lib/split/exceptions.rb | 1 + lib/split/experiment_catalog.rb | 1 + lib/split/extensions/string.rb | 1 + lib/split/goals_collection.rb | 1 + lib/split/helper.rb | 1 + lib/split/metric.rb | 1 + lib/split/persistence/cookie_adapter.rb | 1 + lib/split/persistence/redis_adapter.rb | 1 + lib/split/persistence/session_adapter.rb | 1 + lib/split/redis_interface.rb | 1 + lib/split/trial.rb | 1 + lib/split/user.rb | 1 + lib/split/version.rb | 1 + lib/split/zscore.rb | 1 + split.gemspec | 1 + 32 files changed, 31 insertions(+), 6 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 4e74759b..08f75e8f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -29,7 +29,7 @@ Layout/EndAlignment: AutoCorrect: true Layout/EmptyLineAfterMagicComment: - Enabled: false + Enabled: true Layout/EmptyLinesAroundAccessModifier: Enabled: true diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7bbe705b..97c4fd43 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -18,11 +18,6 @@ Layout/ElseAlignment: Exclude: - 'lib/split/experiment.rb' -# Offense count: 30 -# Cop supports --auto-correct. -Layout/EmptyLineAfterMagicComment: - Enabled: false - # Offense count: 14 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/Gemfile b/Gemfile index 6e50cfec..479b7d8a 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,5 @@ # frozen_string_literal: true + source "https://rubygems.org" gemspec diff --git a/Rakefile b/Rakefile index 1fc677bd..4444c77f 100755 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,6 @@ #!/usr/bin/env rake # frozen_string_literal: true + require 'bundler/gem_tasks' require 'rspec/core/rake_task' require 'appraisal' diff --git a/lib/split.rb b/lib/split.rb index 736a4244..94160d87 100755 --- a/lib/split.rb +++ b/lib/split.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'redis' require 'split/algorithms/block_randomization' diff --git a/lib/split/algorithms/block_randomization.rb b/lib/split/algorithms/block_randomization.rb index de2bc3db..a69cc25a 100644 --- a/lib/split/algorithms/block_randomization.rb +++ b/lib/split/algorithms/block_randomization.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # Selects alternative with minimum count of participants # If all counts are even (i.e. all are minimum), samples from all possible alternatives diff --git a/lib/split/algorithms/weighted_sample.rb b/lib/split/algorithms/weighted_sample.rb index cbed66d3..7b8daca1 100644 --- a/lib/split/algorithms/weighted_sample.rb +++ b/lib/split/algorithms/weighted_sample.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split module Algorithms module WeightedSample diff --git a/lib/split/algorithms/whiplash.rb b/lib/split/algorithms/whiplash.rb index 5373cada..6f9d6540 100644 --- a/lib/split/algorithms/whiplash.rb +++ b/lib/split/algorithms/whiplash.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # A multi-armed bandit implementation inspired by # @aaronsw and victorykit/whiplash require 'rubystats' diff --git a/lib/split/alternative.rb b/lib/split/alternative.rb index 80de5099..43c38773 100644 --- a/lib/split/alternative.rb +++ b/lib/split/alternative.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split class Alternative attr_accessor :name diff --git a/lib/split/combined_experiments_helper.rb b/lib/split/combined_experiments_helper.rb index af5b2884..b026c665 100644 --- a/lib/split/combined_experiments_helper.rb +++ b/lib/split/combined_experiments_helper.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split module CombinedExperimentsHelper def ab_combined_test(metric_descriptor, control = nil, *alternatives) diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index c0879565..ffbe3352 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split class Configuration attr_accessor :ignore_ip_addresses diff --git a/lib/split/dashboard.rb b/lib/split/dashboard.rb index 3c0f82e5..ed264679 100755 --- a/lib/split/dashboard.rb +++ b/lib/split/dashboard.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'sinatra/base' require 'split' require 'bigdecimal' diff --git a/lib/split/dashboard/helpers.rb b/lib/split/dashboard/helpers.rb index 0c05481b..c3b1b8ee 100644 --- a/lib/split/dashboard/helpers.rb +++ b/lib/split/dashboard/helpers.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split module DashboardHelpers def h(text) diff --git a/lib/split/dashboard/pagination_helpers.rb b/lib/split/dashboard/pagination_helpers.rb index 3386879a..a003543d 100644 --- a/lib/split/dashboard/pagination_helpers.rb +++ b/lib/split/dashboard/pagination_helpers.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'split/dashboard/paginator' module Split diff --git a/lib/split/dashboard/paginator.rb b/lib/split/dashboard/paginator.rb index c76f9f70..b55578cf 100644 --- a/lib/split/dashboard/paginator.rb +++ b/lib/split/dashboard/paginator.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split class DashboardPaginator def initialize(collection, page_number, per) diff --git a/lib/split/encapsulated_helper.rb b/lib/split/encapsulated_helper.rb index e41380f2..a2581b95 100644 --- a/lib/split/encapsulated_helper.rb +++ b/lib/split/encapsulated_helper.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "split/helper" # Split's helper exposes all kinds of methods we don't want to diff --git a/lib/split/engine.rb b/lib/split/engine.rb index 3663cc94..0d31bcd9 100644 --- a/lib/split/engine.rb +++ b/lib/split/engine.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split class Engine < ::Rails::Engine initializer "split" do |app| diff --git a/lib/split/exceptions.rb b/lib/split/exceptions.rb index 3330f836..7b0d98b2 100644 --- a/lib/split/exceptions.rb +++ b/lib/split/exceptions.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split class InvalidPersistenceAdapterError < StandardError; end class ExperimentNotFound < StandardError; end diff --git a/lib/split/experiment_catalog.rb b/lib/split/experiment_catalog.rb index 3a1bbace..2b18cf61 100644 --- a/lib/split/experiment_catalog.rb +++ b/lib/split/experiment_catalog.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split class ExperimentCatalog # Return all experiments diff --git a/lib/split/extensions/string.rb b/lib/split/extensions/string.rb index fbad4784..8491c85a 100644 --- a/lib/split/extensions/string.rb +++ b/lib/split/extensions/string.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + class String # Constatntize is often provided by ActiveSupport, but ActiveSupport is not a dependency of Split. unless method_defined?(:constantize) diff --git a/lib/split/goals_collection.rb b/lib/split/goals_collection.rb index 12507c0d..85634383 100644 --- a/lib/split/goals_collection.rb +++ b/lib/split/goals_collection.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split class GoalsCollection diff --git a/lib/split/helper.rb b/lib/split/helper.rb index 63a0464e..2e5f18ca 100644 --- a/lib/split/helper.rb +++ b/lib/split/helper.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split module Helper OVERRIDE_PARAM_NAME = "ab_test" diff --git a/lib/split/metric.rb b/lib/split/metric.rb index 79b3d1f0..266d2e8f 100644 --- a/lib/split/metric.rb +++ b/lib/split/metric.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split class Metric attr_accessor :name diff --git a/lib/split/persistence/cookie_adapter.rb b/lib/split/persistence/cookie_adapter.rb index ffe7be65..b3e693db 100644 --- a/lib/split/persistence/cookie_adapter.rb +++ b/lib/split/persistence/cookie_adapter.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "json" module Split diff --git a/lib/split/persistence/redis_adapter.rb b/lib/split/persistence/redis_adapter.rb index 3fe367b9..26e03eab 100644 --- a/lib/split/persistence/redis_adapter.rb +++ b/lib/split/persistence/redis_adapter.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split module Persistence class RedisAdapter diff --git a/lib/split/persistence/session_adapter.rb b/lib/split/persistence/session_adapter.rb index 6722db5c..a196d820 100644 --- a/lib/split/persistence/session_adapter.rb +++ b/lib/split/persistence/session_adapter.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split module Persistence class SessionAdapter diff --git a/lib/split/redis_interface.rb b/lib/split/redis_interface.rb index a6f39614..4668a1c0 100644 --- a/lib/split/redis_interface.rb +++ b/lib/split/redis_interface.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split # Simplifies the interface to Redis. class RedisInterface diff --git a/lib/split/trial.rb b/lib/split/trial.rb index 6a20703a..d7018e46 100644 --- a/lib/split/trial.rb +++ b/lib/split/trial.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split class Trial attr_accessor :goals diff --git a/lib/split/user.rb b/lib/split/user.rb index a073bf40..181b224b 100644 --- a/lib/split/user.rb +++ b/lib/split/user.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'forwardable' module Split diff --git a/lib/split/version.rb b/lib/split/version.rb index 3e2d5737..f281a7e6 100644 --- a/lib/split/version.rb +++ b/lib/split/version.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split MAJOR = 3 MINOR = 4 diff --git a/lib/split/zscore.rb b/lib/split/zscore.rb index fbd84873..dd9a97d1 100644 --- a/lib/split/zscore.rb +++ b/lib/split/zscore.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split class Zscore diff --git a/split.gemspec b/split.gemspec index d63bc2d0..626d5586 100644 --- a/split.gemspec +++ b/split.gemspec @@ -1,5 +1,6 @@ # -*- encoding: utf-8 -*- # frozen_string_literal: true + $:.push File.expand_path("../lib", __FILE__) require "split/version" From a5a32851157dd348074a801bc804972d576baf48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 5 Jul 2020 13:46:53 -0300 Subject: [PATCH 028/136] Add description to what split stores in cookies --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b13c95f..a2e3552b 100644 --- a/README.md +++ b/README.md @@ -263,7 +263,7 @@ Split.configure do |config| end ``` -By default, cookies will expire in 1 year. To change that, set the `persistence_cookie_length` in the configuration (unit of time in seconds). +When using the cookie persistence, Split stores data into an anonymous tracking cookie named 'split', which expires in 1 year. To change that, set the `persistence_cookie_length` in the configuration (unit of time in seconds). ```ruby Split.configure do |config| @@ -272,6 +272,8 @@ Split.configure do |config| end ``` +The data stored consists of the experiment name and the variants the user is in. Example: { "experiment_name" => "variant_a" } + __Note:__ Using cookies depends on `ActionDispatch::Cookies` or any identical API #### Redis From 12390b8cee2486e05f22c27fb43f4b7d36af43ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 7 Aug 2020 21:43:51 -0300 Subject: [PATCH 029/136] Force experiment does not count for metrics When using the admin dashboard and forcing an experment, this should not count on metrics and should be only used for testing. --- lib/split/dashboard.rb | 7 +++++-- lib/split/helper.rb | 17 +++++++++++++++-- spec/dashboard_spec.rb | 28 +++++++++++++++++++++++----- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/lib/split/dashboard.rb b/lib/split/dashboard.rb index ed264679..ba7e1ab0 100755 --- a/lib/split/dashboard.rb +++ b/lib/split/dashboard.rb @@ -36,8 +36,11 @@ class Dashboard < Sinatra::Base post '/force_alternative' do experiment = Split::ExperimentCatalog.find(params[:experiment]) alternative = Split::Alternative.new(params[:alternative], experiment.name) - alternative.increment_participation - Split::User.new(self)[experiment.key] = alternative.name + + cookies = JSON.parse(request.cookies['split_override']) rescue {} + cookies[experiment.name] = alternative.name + response.set_cookie('split_override', { value: cookies.to_json, path: '/' }) + redirect url('/') end diff --git a/lib/split/helper.rb b/lib/split/helper.rb index 2e5f18ca..5494ca42 100644 --- a/lib/split/helper.rb +++ b/lib/split/helper.rb @@ -76,6 +76,7 @@ def ab_finished(metric_descriptor, options = {:reset => true}) if experiments.any? experiments.each do |experiment| + next if override_present?(experiment.key) finish_experiment(experiment, options.merge(:goals => goals)) end end @@ -111,15 +112,27 @@ def ab_active_experiments() Split.configuration.db_failover_on_db_error.call(e) end - def override_present?(experiment_name) - override_alternative(experiment_name) + override_alternative_by_params(experiment_name) || override_alternative_by_cookies(experiment_name) end def override_alternative(experiment_name) + override_alternative_by_params(experiment_name) || override_alternative_by_cookies(experiment_name) + end + + def override_alternative_by_params(experiment_name) defined?(params) && params[OVERRIDE_PARAM_NAME] && params[OVERRIDE_PARAM_NAME][experiment_name] end + def override_alternative_by_cookies(experiment_name) + return unless defined?(request) + + if request.cookies && request.cookies.key?('split_override') + experiments = JSON.parse(request.cookies['split_override']) rescue {} + experiments[experiment_name] + end + end + def split_generically_disabled? defined?(params) && params['SPLIT_DISABLE'] end diff --git a/spec/dashboard_spec.rb b/spec/dashboard_spec.rb index ce3fbe6c..dcc7732d 100644 --- a/spec/dashboard_spec.rb +++ b/spec/dashboard_spec.rb @@ -6,8 +6,16 @@ describe Split::Dashboard do include Rack::Test::Methods + class TestDashboard < Split::Dashboard + include Split::Helper + + get '/my_experiment' do + ab_test(params[:experiment], 'blue', 'red') + end + end + def app - @app ||= Split::Dashboard + @app ||= TestDashboard end def link(color) @@ -90,8 +98,17 @@ def link(color) it "should set current user's alternative" do blue_link.participant_count = 7 post "/force_alternative?experiment=#{experiment.name}", alternative: "blue" - expect(user[experiment.key]).to eq("blue") - expect(blue_link.participant_count).to eq(8) + + get "/my_experiment?experiment=#{experiment.name}" + expect(last_response.body).to include("blue") + end + + it "should not modify an existing user" do + blue_link.participant_count = 7 + post "/force_alternative?experiment=#{experiment.name}", alternative: "blue" + + expect(user[experiment.key]).to eq("red") + expect(blue_link.participant_count).to eq(7) end end @@ -108,8 +125,9 @@ def link(color) it "should set current user's alternative" do blue_link.participant_count = 7 post "/force_alternative?experiment=#{experiment.name}", alternative: "blue" - expect(user[experiment.key]).to eq("blue") - expect(blue_link.participant_count).to eq(8) + + get "/my_experiment?experiment=#{experiment.name}" + expect(last_response.body).to include("blue") end end end From 3a9da748c46245d1291eb17d6c8015faa9a5819f Mon Sep 17 00:00:00 2001 From: Paulo Casaretto Date: Wed, 3 Apr 2019 16:32:02 -0300 Subject: [PATCH 030/136] Remove Sinatra Dependency for Split Dashboard --- lib/split/dashboard.rb | 42 ++++++-------- lib/split/dashboard/action.rb | 31 ++++++++++ lib/split/dashboard/helpers.rb | 2 +- lib/split/dashboard/pagination_helpers.rb | 4 +- lib/split/dashboard/rendering.rb | 48 +++++++++++++++ lib/split/dashboard/routing.rb | 58 +++++++++++++++++++ lib/split/dashboard/views/_experiment.erb | 2 +- .../views/_experiment_with_goal_header.erb | 2 +- lib/split/dashboard/views/index.erb | 6 +- spec/dashboard/pagination_helpers_spec.rb | 38 ++++++------ spec/dashboard_helpers_spec.rb | 1 + spec/dashboard_spec.rb | 15 +++-- split.gemspec | 1 - 13 files changed, 189 insertions(+), 61 deletions(-) create mode 100644 lib/split/dashboard/action.rb create mode 100644 lib/split/dashboard/rendering.rb create mode 100644 lib/split/dashboard/routing.rb diff --git a/lib/split/dashboard.rb b/lib/split/dashboard.rb index ba7e1ab0..08ce7771 100755 --- a/lib/split/dashboard.rb +++ b/lib/split/dashboard.rb @@ -1,22 +1,15 @@ # frozen_string_literal: true -require 'sinatra/base' require 'split' require 'bigdecimal' -require 'split/dashboard/helpers' -require 'split/dashboard/pagination_helpers' +require 'split/dashboard/routing' +require 'split/dashboard/rendering' +require 'split/dashboard/action' module Split - class Dashboard < Sinatra::Base - dir = File.dirname(File.expand_path(__FILE__)) - - set :views, "#{dir}/dashboard/views" - set :public_folder, "#{dir}/dashboard/public" - set :static, true - set :method_override, true - - helpers Split::DashboardHelpers - helpers Split::DashboardPaginationHelpers + class Dashboard + include Routing + include Rendering get '/' do # Display experiments without a winner at the top of the dashboard @@ -30,48 +23,47 @@ class Dashboard < Sinatra::Base else @current_env = "Rack: #{Rack.version}" end - erb :index + render_erb :index end post '/force_alternative' do - experiment = Split::ExperimentCatalog.find(params[:experiment]) - alternative = Split::Alternative.new(params[:alternative], experiment.name) + experiment = Split::ExperimentCatalog.find(params['experiment']) + alternative = Split::Alternative.new(params['alternative'], experiment.name) cookies = JSON.parse(request.cookies['split_override']) rescue {} cookies[experiment.name] = alternative.name response.set_cookie('split_override', { value: cookies.to_json, path: '/' }) - redirect url('/') end post '/experiment' do - @experiment = Split::ExperimentCatalog.find(params[:experiment]) - @alternative = Split::Alternative.new(params[:alternative], params[:experiment]) + @experiment = Split::ExperimentCatalog.find(params['experiment']) + @alternative = Split::Alternative.new(params['alternative'], params['experiment']) @experiment.winner = @alternative.name redirect url('/') end post '/start' do - @experiment = Split::ExperimentCatalog.find(params[:experiment]) + @experiment = Split::ExperimentCatalog.find(params['experiment']) @experiment.start redirect url('/') end post '/reset' do - @experiment = Split::ExperimentCatalog.find(params[:experiment]) + @experiment = Split::ExperimentCatalog.find(params['experiment']) @experiment.reset redirect url('/') end post '/reopen' do - @experiment = Split::ExperimentCatalog.find(params[:experiment]) + @experiment = Split::ExperimentCatalog.find(params['experiment']) @experiment.reset_winner redirect url('/') end post '/update_cohorting' do - @experiment = Split::ExperimentCatalog.find(params[:experiment]) - case params[:cohorting_action].downcase + @experiment = Split::ExperimentCatalog.find(params['experiment']) + case params['cohorting_action'].downcase when "enable" @experiment.enable_cohorting when "disable" @@ -81,7 +73,7 @@ class Dashboard < Sinatra::Base end delete '/experiment' do - @experiment = Split::ExperimentCatalog.find(params[:experiment]) + @experiment = Split::ExperimentCatalog.find(params['experiment']) @experiment.delete redirect url('/') end diff --git a/lib/split/dashboard/action.rb b/lib/split/dashboard/action.rb new file mode 100644 index 00000000..66d6b899 --- /dev/null +++ b/lib/split/dashboard/action.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Split + class Dashboard + class Action + include Rendering + include Split::Helper + + def initialize(env, block) + @action = block + @env = env + end + + def call + instance_eval(&@action) + end + + def request + @request ||= Rack::Request.new(@env) + end + + def response + @response ||= Rack::Response.new + end + + def params + request.params + end + end + end +end diff --git a/lib/split/dashboard/helpers.rb b/lib/split/dashboard/helpers.rb index c3b1b8ee..c11418fb 100644 --- a/lib/split/dashboard/helpers.rb +++ b/lib/split/dashboard/helpers.rb @@ -11,7 +11,7 @@ def url(*path_parts) end def path_prefix - request.env['SCRIPT_NAME'] + request.script_name end def number_to_percentage(number, precision = 2) diff --git a/lib/split/dashboard/pagination_helpers.rb b/lib/split/dashboard/pagination_helpers.rb index a003543d..1aa99450 100644 --- a/lib/split/dashboard/pagination_helpers.rb +++ b/lib/split/dashboard/pagination_helpers.rb @@ -6,11 +6,11 @@ module Split module DashboardPaginationHelpers def pagination_per default_per_page = Split.configuration.dashboard_pagination_default_per_page - @pagination_per ||= (params[:per] || default_per_page).to_i + @pagination_per ||= (params['per'] || default_per_page).to_i end def page_number - @page_number ||= (params[:page] || 1).to_i + @page_number ||= (params['page'] || 1).to_i end def paginated(collection) diff --git a/lib/split/dashboard/rendering.rb b/lib/split/dashboard/rendering.rb new file mode 100644 index 00000000..063602d8 --- /dev/null +++ b/lib/split/dashboard/rendering.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'split/dashboard/helpers' +require 'split/dashboard/pagination_helpers' + +module Split + class Dashboard + module Rendering + VIEWS_PATH = File.join( + File.dirname(File.expand_path(__FILE__)), + 'views' + ) + + def render_erb(view) + [200, {}, [erb(view)]] + end + + def erb(view, opts = {}) + render_layout do + partial(view, opts) + end + end + + def partial(view, opts = {}) + filename = view.to_s + '.erb' + file_path = File.join(VIEWS_PATH, filename) + raw = File.read(file_path) + b = binding + (opts[:locals] || {}).each do |k, v| + b.local_variable_set(k, v) + end + ERB.new(raw).result(b) + end + + def render_layout(&block) + partial(:layout, &block) + end + + def redirect(url) + response.redirect(url) + response.finish + end + + include Split::DashboardHelpers + include Split::DashboardPaginationHelpers + end + end +end diff --git a/lib/split/dashboard/routing.rb b/lib/split/dashboard/routing.rb new file mode 100644 index 00000000..86ee659a --- /dev/null +++ b/lib/split/dashboard/routing.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Split + class Dashboard + module Routing + STATIC_RACK = Rack::File.new( + File.join( + File.dirname(File.expand_path(__FILE__)), + 'public' + ) + ) + + def self.included(mod) + mod.extend(ClassMethods) + end + + def call(env) + self.class.call(env) + end + + module ClassMethods + def call(env) + route = route_for(env["REQUEST_METHOD"], env["PATH_INFO"]) + + if route + Action.new(env, route).call + else + STATIC_RACK.call(env) + end + end + + def get(path, &block) + key = ['GET', path] + routes[key] = block + end + + def post(path, &block) + key = ['POST', path] + routes[key] = block + end + + def delete(path, &block) + key = ['DELETE', path] + routes[key] = block + end + + def routes + @routes ||= Hash.new + end + + def route_for(method, path) + key = [method, path] + routes[key] + end + end + end + end +end diff --git a/lib/split/dashboard/views/_experiment.erb b/lib/split/dashboard/views/_experiment.erb index 2f2459ad..211fd7c8 100644 --- a/lib/split/dashboard/views/_experiment.erb +++ b/lib/split/dashboard/views/_experiment.erb @@ -40,7 +40,7 @@ <% if goal.nil? %>
<%= experiment.start_time ? experiment.start_time.strftime('%Y-%m-%d') : 'Unknown' %> - <%= erb :_controls, :locals => {:experiment => experiment} %> + <%= partial :_controls, :locals => {:experiment => experiment} %>
<% end %> diff --git a/lib/split/dashboard/views/_experiment_with_goal_header.erb b/lib/split/dashboard/views/_experiment_with_goal_header.erb index 3149966d..b50d2af8 100644 --- a/lib/split/dashboard/views/_experiment_with_goal_header.erb +++ b/lib/split/dashboard/views/_experiment_with_goal_header.erb @@ -2,7 +2,7 @@
<%= experiment.start_time ? experiment.start_time.strftime('%Y-%m-%d') : 'Unknown' %> - <%= erb :_controls, :locals => {:experiment => experiment} %> + <%= partial :_controls, locals: {:experiment => experiment} %>
diff --git a/lib/split/dashboard/views/index.erb b/lib/split/dashboard/views/index.erb index 2bdbb708..eeb82339 100644 --- a/lib/split/dashboard/views/index.erb +++ b/lib/split/dashboard/views/index.erb @@ -8,11 +8,11 @@ <% paginated(@experiments).each do |experiment| %> <% if experiment.goals.empty? %> - <%= erb :_experiment, :locals => {:goal => nil, :experiment => experiment} %> + <%= partial :_experiment, locals: {:goal => nil, :experiment => experiment} %> <% else %> - <%= erb :_experiment_with_goal_header, :locals => {:experiment => experiment} %> + <%= partial :_experiment_with_goal_header, locals: {:experiment => experiment} %> <% experiment.goals.each do |g| %> - <%= erb :_experiment, :locals => {:goal => g, :experiment => experiment} %> + <%= partial :_experiment, locals: {:goal => g, :experiment => experiment} %> <% end %> <% end %> <% end %> diff --git a/spec/dashboard/pagination_helpers_spec.rb b/spec/dashboard/pagination_helpers_spec.rb index 315595e0..de42c337 100644 --- a/spec/dashboard/pagination_helpers_spec.rb +++ b/spec/dashboard/pagination_helpers_spec.rb @@ -8,7 +8,7 @@ describe '#pagination_per' do context 'when params empty' do - let(:params) { Hash[] } + let(:params) { { } } it 'returns the default (10)' do default_per_page = Split.configuration.dashboard_pagination_default_per_page @@ -18,7 +18,7 @@ end context 'when params[:per] is 5' do - let(:params) { Hash[per: 5] } + let(:params) { { 'per' => '5' } } it 'returns 5' do expect(pagination_per).to eql 5 @@ -28,7 +28,7 @@ describe '#page_number' do context 'when params empty' do - let(:params) { Hash[] } + let(:params) { { } } it 'returns 1' do expect(page_number).to eql 1 @@ -36,7 +36,7 @@ end context 'when params[:page] is "2"' do - let(:params) { Hash[page: '2'] } + let(:params) { { 'page' => '2' } } it 'returns 2' do expect(page_number).to eql 2 @@ -46,7 +46,7 @@ describe '#paginated' do let(:collection) { (1..20).to_a } - let(:params) { Hash[per: '5', page: '3'] } + let(:params) { { 'per' => '5', 'page' => '3' } } it { expect(paginated(collection)).to eql [11, 12, 13, 14, 15] } end @@ -57,7 +57,7 @@ end context 'when page is 3' do - let(:params) { Hash[page: '3'] } + let(:params) { { 'page' => '3' } } it { expect(show_first_page_tag?).to be true } end end @@ -72,7 +72,7 @@ end context 'when page is 4' do - let(:params) { Hash[page: '4'] } + let(:params) { { 'page' => '4' } } it { expect(show_first_ellipsis_tag?).to be true } end end @@ -87,14 +87,14 @@ end context 'when page is 2' do - let(:params) { Hash[page: '2'] } + let(:params) { { 'page' => '2' } } it { expect(show_prev_page_tag?).to be true } end end describe '#prev_page_tag' do context 'when page is 2' do - let(:params) { Hash[page: '2'] } + let(:params) { { 'page' => '2' } } it do expect(prev_page_tag).to eql '1' @@ -102,7 +102,7 @@ end context 'when page is 3' do - let(:params) { Hash[page: '3'] } + let(:params) { { 'page' => '3' } } it do expect(prev_page_tag).to eql '2' @@ -116,26 +116,26 @@ end context 'when page is 2' do - let(:params) { Hash[page: '2'] } + let(:params) { { 'page' => '2' } } it { expect(show_prev_page_tag?).to be true } end end describe '#current_page_tag' do context 'when page is 1' do - let(:params) { Hash[page: '1'] } + let(:params) { { 'page' => '1' } } it { expect(current_page_tag).to eql '1' } end context 'when page is 2' do - let(:params) { Hash[page: '2'] } + let(:params) { { 'page' => '2' } } it { expect(current_page_tag).to eql '2' } end end describe '#show_next_page_tag?' do context 'when page is 2' do - let(:params) { Hash[page: '2'] } + let(:params) { { 'page' => '2' } } context 'when collection length is 20' do let(:collection) { (1..20).to_a } @@ -151,12 +151,12 @@ describe '#next_page_tag' do context 'when page is 1' do - let(:params) { Hash[page: '1'] } + let(:params) { { 'page' => '1' } } it { expect(next_page_tag).to eql '2' } end context 'when page is 2' do - let(:params) { Hash[page: '2'] } + let(:params) { { 'page' => '2' } } it { expect(next_page_tag).to eql '3' } end end @@ -175,7 +175,7 @@ describe '#show_last_ellipsis_tag?' do let(:collection) { (1..30).to_a } - let(:params) { Hash[per: '5', page: '2'] } + let(:params) { { 'per' => '5', 'page' => '2' } } it { expect(show_last_ellipsis_tag?(collection)).to be true } end @@ -183,12 +183,12 @@ let(:collection) { (1..30).to_a } context 'when page is 5/6' do - let(:params) { Hash[per: '5', page: '5'] } + let(:params) { { 'per' => '5', 'page' => '5' } } it { expect(show_last_page_tag?(collection)).to be false } end context 'when page is 4/6' do - let(:params) { Hash[per: '5', page: '4'] } + let(:params) { { 'per' => '5', 'page' => '4' } } it { expect(show_last_page_tag?(collection)).to be true } end end diff --git a/spec/dashboard_helpers_spec.rb b/spec/dashboard_helpers_spec.rb index 3643c232..7b608ead 100644 --- a/spec/dashboard_helpers_spec.rb +++ b/spec/dashboard_helpers_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spec_helper' require 'split/dashboard/helpers' diff --git a/spec/dashboard_spec.rb b/spec/dashboard_spec.rb index dcc7732d..9cc44d63 100644 --- a/spec/dashboard_spec.rb +++ b/spec/dashboard_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spec_helper' require 'rack/test' require 'split/dashboard' @@ -6,16 +7,14 @@ describe Split::Dashboard do include Rack::Test::Methods - class TestDashboard < Split::Dashboard - include Split::Helper - + class Split::Dashboard get '/my_experiment' do - ab_test(params[:experiment], 'blue', 'red') + [200, {}, [ ab_test(params['experiment'], 'blue', 'red') ]] end end def app - @app ||= TestDashboard + @app ||= Split::Dashboard end def link(color) @@ -27,7 +26,7 @@ def link(color) } let(:experiment_with_goals) { - Split::ExperimentCatalog.find_or_create({"link_color" => ["goal_1", "goal_2"]}, "blue", "red") + Split::ExperimentCatalog.find_or_create({ "link_color" => ["goal_1", "goal_2"] }, "blue", "red") } let(:metric) { @@ -188,7 +187,7 @@ def link(color) it "calls disable of cohorting when action is disable" do post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "disable" } - + expect(experiment.cohorting_disabled?).to eq true end @@ -226,7 +225,7 @@ def link(color) it "should mark an alternative as the winner" do expect(experiment.winner).to be_nil - post "/experiment?experiment=#{experiment.name}", :alternative => 'red' + post "/experiment?experiment=#{experiment.name}", alternative: 'red' expect(last_response).to be_redirect expect(experiment.winner.name).to eq('red') diff --git a/split.gemspec b/split.gemspec index 626d5586..729fd390 100644 --- a/split.gemspec +++ b/split.gemspec @@ -31,7 +31,6 @@ Gem::Specification.new do |s| s.require_paths = ["lib"] s.add_dependency 'redis', '>= 4.2' - s.add_dependency 'sinatra', '>= 1.2.6' s.add_dependency 'rubystats', '>= 0.3.0' s.add_development_dependency 'bundler', '>= 1.17' From 20ec72dc4be361850d5faa196e067c3e39d4f3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sat, 8 Aug 2020 16:19:22 -0300 Subject: [PATCH 031/136] Implement usage of middlewares on new rack app --- lib/split/dashboard.rb | 81 ++++++++------------------------- lib/split/dashboard/routing.rb | 5 +- lib/split/dashboard/web.rb | 83 ++++++++++++++++++++++++++++++++++ spec/dashboard_spec.rb | 4 +- 4 files changed, 106 insertions(+), 67 deletions(-) create mode 100644 lib/split/dashboard/web.rb diff --git a/lib/split/dashboard.rb b/lib/split/dashboard.rb index 08ce7771..d6af3edc 100755 --- a/lib/split/dashboard.rb +++ b/lib/split/dashboard.rb @@ -1,81 +1,40 @@ # frozen_string_literal: true require 'split' -require 'bigdecimal' -require 'split/dashboard/routing' -require 'split/dashboard/rendering' -require 'split/dashboard/action' +require 'split/dashboard/web' module Split class Dashboard - include Routing - include Rendering - - get '/' do - # Display experiments without a winner at the top of the dashboard - @experiments = Split::ExperimentCatalog.all_active_first - - @metrics = Split::Metric.all - - # Display Rails Environment mode (or Rack version if not using Rails) - if Object.const_defined?('Rails') - @current_env = Rails.env.titlecase - else - @current_env = "Rack: #{Rack.version}" + class << self + def middlewares + @middlewares ||= [] end - render_erb :index - end - - post '/force_alternative' do - experiment = Split::ExperimentCatalog.find(params['experiment']) - alternative = Split::Alternative.new(params['alternative'], experiment.name) - cookies = JSON.parse(request.cookies['split_override']) rescue {} - cookies[experiment.name] = alternative.name - response.set_cookie('split_override', { value: cookies.to_json, path: '/' }) - redirect url('/') + def use(*middleware_args, &block) + middlewares << [middleware_args, block] + end end - post '/experiment' do - @experiment = Split::ExperimentCatalog.find(params['experiment']) - @alternative = Split::Alternative.new(params['alternative'], params['experiment']) - @experiment.winner = @alternative.name - redirect url('/') + def self.call(env) + @dashboard ||= new + @dashboard.call(env) end - post '/start' do - @experiment = Split::ExperimentCatalog.find(params['experiment']) - @experiment.start - redirect url('/') + def call(env) + app.call(env) end - post '/reset' do - @experiment = Split::ExperimentCatalog.find(params['experiment']) - @experiment.reset - redirect url('/') - end + def app + @app ||= begin + middlewares = self.class.middlewares - post '/reopen' do - @experiment = Split::ExperimentCatalog.find(params['experiment']) - @experiment.reset_winner - redirect url('/') - end + Rack::Builder.new do + use Rack::MethodOverride + run Split::Dashboard::Web - post '/update_cohorting' do - @experiment = Split::ExperimentCatalog.find(params['experiment']) - case params['cohorting_action'].downcase - when "enable" - @experiment.enable_cohorting - when "disable" - @experiment.disable_cohorting + middlewares.each { |middleware, block| use(*middleware, &block) } + end end - redirect url('/') - end - - delete '/experiment' do - @experiment = Split::ExperimentCatalog.find(params['experiment']) - @experiment.delete - redirect url('/') end end end diff --git a/lib/split/dashboard/routing.rb b/lib/split/dashboard/routing.rb index 86ee659a..1311d291 100644 --- a/lib/split/dashboard/routing.rb +++ b/lib/split/dashboard/routing.rb @@ -14,10 +14,6 @@ def self.included(mod) mod.extend(ClassMethods) end - def call(env) - self.class.call(env) - end - module ClassMethods def call(env) route = route_for(env["REQUEST_METHOD"], env["PATH_INFO"]) @@ -49,6 +45,7 @@ def routes end def route_for(method, path) + path = '/' if path.empty? key = [method, path] routes[key] end diff --git a/lib/split/dashboard/web.rb b/lib/split/dashboard/web.rb new file mode 100644 index 00000000..07b9b604 --- /dev/null +++ b/lib/split/dashboard/web.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'split' +require 'bigdecimal' +require 'split/dashboard/routing' +require 'split/dashboard/rendering' +require 'split/dashboard/action' + +module Split + class Dashboard + class Web + include Routing + include Rendering + + get '/' do + # Display experiments without a winner at the top of the dashboard + @experiments = Split::ExperimentCatalog.all_active_first + + @metrics = Split::Metric.all + + # Display Rails Environment mode (or Rack version if not using Rails) + if Object.const_defined?('Rails') + @current_env = Rails.env.titlecase + else + @current_env = "Rack: #{Rack.version}" + end + render_erb :index + end + + post '/force_alternative' do + experiment = Split::ExperimentCatalog.find(params['experiment']) + alternative = Split::Alternative.new(params['alternative'], experiment.name) + + cookies = JSON.parse(request.cookies['split_override']) rescue {} + cookies[experiment.name] = alternative.name + response.set_cookie('split_override', { value: cookies.to_json, path: '/' }) + redirect url('/') + end + + post '/experiment' do + @experiment = Split::ExperimentCatalog.find(params['experiment']) + @alternative = Split::Alternative.new(params['alternative'], params['experiment']) + @experiment.winner = @alternative.name + redirect url('/') + end + + post '/start' do + @experiment = Split::ExperimentCatalog.find(params['experiment']) + @experiment.start + redirect url('/') + end + + post '/reset' do + @experiment = Split::ExperimentCatalog.find(params['experiment']) + @experiment.reset + redirect url('/') + end + + post '/reopen' do + @experiment = Split::ExperimentCatalog.find(params['experiment']) + @experiment.reset_winner + redirect url('/') + end + + post '/update_cohorting' do + @experiment = Split::ExperimentCatalog.find(params['experiment']) + case params['cohorting_action'].downcase + when "enable" + @experiment.enable_cohorting + when "disable" + @experiment.disable_cohorting + end + redirect url('/') + end + + delete '/experiment' do + @experiment = Split::ExperimentCatalog.find(params['experiment']) + @experiment.delete + redirect url('/') + end + end + end +end diff --git a/spec/dashboard_spec.rb b/spec/dashboard_spec.rb index 9cc44d63..7bfb7ea3 100644 --- a/spec/dashboard_spec.rb +++ b/spec/dashboard_spec.rb @@ -7,14 +7,14 @@ describe Split::Dashboard do include Rack::Test::Methods - class Split::Dashboard + class Split::Dashboard::Web get '/my_experiment' do [200, {}, [ ab_test(params['experiment'], 'blue', 'red') ]] end end def app - @app ||= Split::Dashboard + @app ||= Split::Dashboard.new end def link(color) From cb6dfde57a290b8372d929c9f35c808fe5ae3868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 9 Aug 2020 23:08:51 -0300 Subject: [PATCH 032/136] Fix Layout/SpaceAfterComma offenses --- .rubocop_todo.yml | 11 ----------- lib/split/algorithms/weighted_sample.rb | 2 +- lib/split/configuration.rb | 2 +- lib/split/encapsulated_helper.rb | 4 ++-- lib/split/experiment.rb | 6 +++--- lib/split/metric.rb | 2 +- lib/split/user.rb | 2 +- 7 files changed, 9 insertions(+), 20 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 97c4fd43..81cda7db 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -96,17 +96,6 @@ Layout/IndentationWidth: - 'lib/split/trial.rb' - 'lib/split/user.rb' -# Offense count: 10 -# Cop supports --auto-correct. -Layout/SpaceAfterComma: - Exclude: - - 'lib/split/algorithms/weighted_sample.rb' - - 'lib/split/configuration.rb' - - 'lib/split/encapsulated_helper.rb' - - 'lib/split/experiment.rb' - - 'lib/split/metric.rb' - - 'lib/split/user.rb' - # Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/lib/split/algorithms/weighted_sample.rb b/lib/split/algorithms/weighted_sample.rb index 7b8daca1..9eab9305 100644 --- a/lib/split/algorithms/weighted_sample.rb +++ b/lib/split/algorithms/weighted_sample.rb @@ -9,7 +9,7 @@ def self.choose_alternative(experiment) total = weights.inject(:+) point = rand * total - experiment.alternatives.zip(weights).each do |n,w| + experiment.alternatives.zip(weights).each do |n, w| return n if w >= point point -= w end diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index ffbe3352..104f09b9 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -175,7 +175,7 @@ def normalized_experiments end def normalize_alternatives(alternatives) - given_probability, num_with_probability = alternatives.inject([0,0]) do |a,v| + given_probability, num_with_probability = alternatives.inject([0, 0]) do |a, v| p, n = a if percent = value_for(v, :percent) [p + percent, n + 1] diff --git a/lib/split/encapsulated_helper.rb b/lib/split/encapsulated_helper.rb index a2581b95..bcdad08c 100644 --- a/lib/split/encapsulated_helper.rb +++ b/lib/split/encapsulated_helper.rb @@ -29,8 +29,8 @@ def ab_user end end - def ab_test(*arguments,&block) - split_context_shim.ab_test(*arguments,&block) + def ab_test(*arguments, &block) + split_context_shim.ab_test(*arguments, &block) end private diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 758cedbe..97161e76 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -45,7 +45,7 @@ def extract_alternatives_from_options(options) if alts.length == 1 if alts[0].is_a? Hash - alts = alts[0].map{|k,v| {k => v} } + alts = alts[0].map{|k, v| {k => v} } end end @@ -148,7 +148,7 @@ def winner=(winner_name) end def participant_count - alternatives.inject(0){|sum,a| sum + a.participant_count} + alternatives.inject(0){|sum, a| sum + a.participant_count} end def control @@ -333,7 +333,7 @@ def count_simulated_wins(winning_alternatives) def find_simulated_winner(simulated_cr_hash) # figure out which alternative had the highest simulated conversion rate - winning_pair = ["",0.0] + winning_pair = ["", 0.0] simulated_cr_hash.each do |alternative, rate| if rate > winning_pair[1] winning_pair = [alternative, rate] diff --git a/lib/split/metric.rb b/lib/split/metric.rb index 266d2e8f..b367da6c 100644 --- a/lib/split/metric.rb +++ b/lib/split/metric.rb @@ -6,7 +6,7 @@ class Metric attr_accessor :experiments def initialize(attrs = {}) - attrs.each do |key,value| + attrs.each do |key, value| if self.respond_to?("#{key}=") self.send("#{key}=", value) end diff --git a/lib/split/user.rb b/lib/split/user.rb index 181b224b..efa7db3d 100644 --- a/lib/split/user.rb +++ b/lib/split/user.rb @@ -29,7 +29,7 @@ def max_experiments_reached?(experiment_key) if Split.configuration.allow_multiple_experiments == 'control' experiments = active_experiments experiment_key_without_version = key_without_version(experiment_key) - count_control = experiments.count {|k,v| k == experiment_key_without_version || v == 'control'} + count_control = experiments.count {|k, v| k == experiment_key_without_version || v == 'control'} experiments.size > count_control else !Split.configuration.allow_multiple_experiments && From 278e89eaa2ba4ef932869078aaedd9267a9f408c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sat, 22 Aug 2020 19:51:21 -0300 Subject: [PATCH 033/136] Alternative as Sets were deprecated on 0.x --- lib/split/experiment.rb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 97161e76..3b6e69d8 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -431,15 +431,7 @@ def load_alternatives_from_configuration end def load_alternatives_from_redis - alternatives = case redis.type(@name) - when 'set' # convert legacy sets to lists - alts = redis.smembers(@name) - redis.del(@name) - alts.reverse.each {|a| redis.lpush(@name, a) } - redis.lrange(@name, 0, -1) - else - redis.lrange(@name, 0, -1) - end + alternatives = redis.lrange(@name, 0, -1) alternatives.map do |alt| alt = begin JSON.parse(alt) From 930873faec2e127db27455c776dba7c07dc5a390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sat, 22 Aug 2020 20:43:46 -0300 Subject: [PATCH 034/136] shorten multiples hsets into a single hmset --- lib/split/experiment.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 3b6e69d8..24a32a48 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -80,8 +80,8 @@ def save persist_experiment_configuration end - redis.hset(experiment_config_key, :resettable, resettable) - redis.hset(experiment_config_key, :algorithm, algorithm.to_s) + redis.hset(experiment_config_key, :resettable, resettable, + :algorithm, algorithm.to_s) self end From 3cd4ec35f79bc3ee2b2635492f8065d6b7bcfcbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Tue, 1 Sep 2020 20:23:56 -0300 Subject: [PATCH 035/136] Revert "Remove Sinatra Dependency for Split Dashboard" --- lib/split/dashboard.rb | 91 ++++++++++++++----- lib/split/dashboard/action.rb | 31 ------- lib/split/dashboard/helpers.rb | 2 +- lib/split/dashboard/pagination_helpers.rb | 4 +- lib/split/dashboard/rendering.rb | 48 ---------- lib/split/dashboard/routing.rb | 55 ----------- lib/split/dashboard/views/_experiment.erb | 2 +- .../views/_experiment_with_goal_header.erb | 2 +- lib/split/dashboard/views/index.erb | 6 +- lib/split/dashboard/web.rb | 83 ----------------- spec/dashboard/pagination_helpers_spec.rb | 38 ++++---- spec/dashboard_helpers_spec.rb | 1 - spec/dashboard_spec.rb | 15 +-- split.gemspec | 1 + 14 files changed, 106 insertions(+), 273 deletions(-) delete mode 100644 lib/split/dashboard/action.rb delete mode 100644 lib/split/dashboard/rendering.rb delete mode 100644 lib/split/dashboard/routing.rb delete mode 100644 lib/split/dashboard/web.rb diff --git a/lib/split/dashboard.rb b/lib/split/dashboard.rb index d6af3edc..ba7e1ab0 100755 --- a/lib/split/dashboard.rb +++ b/lib/split/dashboard.rb @@ -1,40 +1,89 @@ # frozen_string_literal: true +require 'sinatra/base' require 'split' -require 'split/dashboard/web' +require 'bigdecimal' +require 'split/dashboard/helpers' +require 'split/dashboard/pagination_helpers' module Split - class Dashboard - class << self - def middlewares - @middlewares ||= [] - end + class Dashboard < Sinatra::Base + dir = File.dirname(File.expand_path(__FILE__)) + + set :views, "#{dir}/dashboard/views" + set :public_folder, "#{dir}/dashboard/public" + set :static, true + set :method_override, true + + helpers Split::DashboardHelpers + helpers Split::DashboardPaginationHelpers + + get '/' do + # Display experiments without a winner at the top of the dashboard + @experiments = Split::ExperimentCatalog.all_active_first - def use(*middleware_args, &block) - middlewares << [middleware_args, block] + @metrics = Split::Metric.all + + # Display Rails Environment mode (or Rack version if not using Rails) + if Object.const_defined?('Rails') + @current_env = Rails.env.titlecase + else + @current_env = "Rack: #{Rack.version}" end + erb :index end - def self.call(env) - @dashboard ||= new - @dashboard.call(env) + post '/force_alternative' do + experiment = Split::ExperimentCatalog.find(params[:experiment]) + alternative = Split::Alternative.new(params[:alternative], experiment.name) + + cookies = JSON.parse(request.cookies['split_override']) rescue {} + cookies[experiment.name] = alternative.name + response.set_cookie('split_override', { value: cookies.to_json, path: '/' }) + + redirect url('/') end - def call(env) - app.call(env) + post '/experiment' do + @experiment = Split::ExperimentCatalog.find(params[:experiment]) + @alternative = Split::Alternative.new(params[:alternative], params[:experiment]) + @experiment.winner = @alternative.name + redirect url('/') end - def app - @app ||= begin - middlewares = self.class.middlewares + post '/start' do + @experiment = Split::ExperimentCatalog.find(params[:experiment]) + @experiment.start + redirect url('/') + end + + post '/reset' do + @experiment = Split::ExperimentCatalog.find(params[:experiment]) + @experiment.reset + redirect url('/') + end - Rack::Builder.new do - use Rack::MethodOverride - run Split::Dashboard::Web + post '/reopen' do + @experiment = Split::ExperimentCatalog.find(params[:experiment]) + @experiment.reset_winner + redirect url('/') + end - middlewares.each { |middleware, block| use(*middleware, &block) } - end + post '/update_cohorting' do + @experiment = Split::ExperimentCatalog.find(params[:experiment]) + case params[:cohorting_action].downcase + when "enable" + @experiment.enable_cohorting + when "disable" + @experiment.disable_cohorting end + redirect url('/') + end + + delete '/experiment' do + @experiment = Split::ExperimentCatalog.find(params[:experiment]) + @experiment.delete + redirect url('/') end end end diff --git a/lib/split/dashboard/action.rb b/lib/split/dashboard/action.rb deleted file mode 100644 index 66d6b899..00000000 --- a/lib/split/dashboard/action.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -module Split - class Dashboard - class Action - include Rendering - include Split::Helper - - def initialize(env, block) - @action = block - @env = env - end - - def call - instance_eval(&@action) - end - - def request - @request ||= Rack::Request.new(@env) - end - - def response - @response ||= Rack::Response.new - end - - def params - request.params - end - end - end -end diff --git a/lib/split/dashboard/helpers.rb b/lib/split/dashboard/helpers.rb index c11418fb..c3b1b8ee 100644 --- a/lib/split/dashboard/helpers.rb +++ b/lib/split/dashboard/helpers.rb @@ -11,7 +11,7 @@ def url(*path_parts) end def path_prefix - request.script_name + request.env['SCRIPT_NAME'] end def number_to_percentage(number, precision = 2) diff --git a/lib/split/dashboard/pagination_helpers.rb b/lib/split/dashboard/pagination_helpers.rb index 1aa99450..a003543d 100644 --- a/lib/split/dashboard/pagination_helpers.rb +++ b/lib/split/dashboard/pagination_helpers.rb @@ -6,11 +6,11 @@ module Split module DashboardPaginationHelpers def pagination_per default_per_page = Split.configuration.dashboard_pagination_default_per_page - @pagination_per ||= (params['per'] || default_per_page).to_i + @pagination_per ||= (params[:per] || default_per_page).to_i end def page_number - @page_number ||= (params['page'] || 1).to_i + @page_number ||= (params[:page] || 1).to_i end def paginated(collection) diff --git a/lib/split/dashboard/rendering.rb b/lib/split/dashboard/rendering.rb deleted file mode 100644 index 063602d8..00000000 --- a/lib/split/dashboard/rendering.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -require 'split/dashboard/helpers' -require 'split/dashboard/pagination_helpers' - -module Split - class Dashboard - module Rendering - VIEWS_PATH = File.join( - File.dirname(File.expand_path(__FILE__)), - 'views' - ) - - def render_erb(view) - [200, {}, [erb(view)]] - end - - def erb(view, opts = {}) - render_layout do - partial(view, opts) - end - end - - def partial(view, opts = {}) - filename = view.to_s + '.erb' - file_path = File.join(VIEWS_PATH, filename) - raw = File.read(file_path) - b = binding - (opts[:locals] || {}).each do |k, v| - b.local_variable_set(k, v) - end - ERB.new(raw).result(b) - end - - def render_layout(&block) - partial(:layout, &block) - end - - def redirect(url) - response.redirect(url) - response.finish - end - - include Split::DashboardHelpers - include Split::DashboardPaginationHelpers - end - end -end diff --git a/lib/split/dashboard/routing.rb b/lib/split/dashboard/routing.rb deleted file mode 100644 index 1311d291..00000000 --- a/lib/split/dashboard/routing.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -module Split - class Dashboard - module Routing - STATIC_RACK = Rack::File.new( - File.join( - File.dirname(File.expand_path(__FILE__)), - 'public' - ) - ) - - def self.included(mod) - mod.extend(ClassMethods) - end - - module ClassMethods - def call(env) - route = route_for(env["REQUEST_METHOD"], env["PATH_INFO"]) - - if route - Action.new(env, route).call - else - STATIC_RACK.call(env) - end - end - - def get(path, &block) - key = ['GET', path] - routes[key] = block - end - - def post(path, &block) - key = ['POST', path] - routes[key] = block - end - - def delete(path, &block) - key = ['DELETE', path] - routes[key] = block - end - - def routes - @routes ||= Hash.new - end - - def route_for(method, path) - path = '/' if path.empty? - key = [method, path] - routes[key] - end - end - end - end -end diff --git a/lib/split/dashboard/views/_experiment.erb b/lib/split/dashboard/views/_experiment.erb index 211fd7c8..2f2459ad 100644 --- a/lib/split/dashboard/views/_experiment.erb +++ b/lib/split/dashboard/views/_experiment.erb @@ -40,7 +40,7 @@ <% if goal.nil? %>
<%= experiment.start_time ? experiment.start_time.strftime('%Y-%m-%d') : 'Unknown' %> - <%= partial :_controls, :locals => {:experiment => experiment} %> + <%= erb :_controls, :locals => {:experiment => experiment} %>
<% end %> diff --git a/lib/split/dashboard/views/_experiment_with_goal_header.erb b/lib/split/dashboard/views/_experiment_with_goal_header.erb index b50d2af8..3149966d 100644 --- a/lib/split/dashboard/views/_experiment_with_goal_header.erb +++ b/lib/split/dashboard/views/_experiment_with_goal_header.erb @@ -2,7 +2,7 @@
<%= experiment.start_time ? experiment.start_time.strftime('%Y-%m-%d') : 'Unknown' %> - <%= partial :_controls, locals: {:experiment => experiment} %> + <%= erb :_controls, :locals => {:experiment => experiment} %>
diff --git a/lib/split/dashboard/views/index.erb b/lib/split/dashboard/views/index.erb index eeb82339..2bdbb708 100644 --- a/lib/split/dashboard/views/index.erb +++ b/lib/split/dashboard/views/index.erb @@ -8,11 +8,11 @@ <% paginated(@experiments).each do |experiment| %> <% if experiment.goals.empty? %> - <%= partial :_experiment, locals: {:goal => nil, :experiment => experiment} %> + <%= erb :_experiment, :locals => {:goal => nil, :experiment => experiment} %> <% else %> - <%= partial :_experiment_with_goal_header, locals: {:experiment => experiment} %> + <%= erb :_experiment_with_goal_header, :locals => {:experiment => experiment} %> <% experiment.goals.each do |g| %> - <%= partial :_experiment, locals: {:goal => g, :experiment => experiment} %> + <%= erb :_experiment, :locals => {:goal => g, :experiment => experiment} %> <% end %> <% end %> <% end %> diff --git a/lib/split/dashboard/web.rb b/lib/split/dashboard/web.rb deleted file mode 100644 index 07b9b604..00000000 --- a/lib/split/dashboard/web.rb +++ /dev/null @@ -1,83 +0,0 @@ -# frozen_string_literal: true - -require 'split' -require 'bigdecimal' -require 'split/dashboard/routing' -require 'split/dashboard/rendering' -require 'split/dashboard/action' - -module Split - class Dashboard - class Web - include Routing - include Rendering - - get '/' do - # Display experiments without a winner at the top of the dashboard - @experiments = Split::ExperimentCatalog.all_active_first - - @metrics = Split::Metric.all - - # Display Rails Environment mode (or Rack version if not using Rails) - if Object.const_defined?('Rails') - @current_env = Rails.env.titlecase - else - @current_env = "Rack: #{Rack.version}" - end - render_erb :index - end - - post '/force_alternative' do - experiment = Split::ExperimentCatalog.find(params['experiment']) - alternative = Split::Alternative.new(params['alternative'], experiment.name) - - cookies = JSON.parse(request.cookies['split_override']) rescue {} - cookies[experiment.name] = alternative.name - response.set_cookie('split_override', { value: cookies.to_json, path: '/' }) - redirect url('/') - end - - post '/experiment' do - @experiment = Split::ExperimentCatalog.find(params['experiment']) - @alternative = Split::Alternative.new(params['alternative'], params['experiment']) - @experiment.winner = @alternative.name - redirect url('/') - end - - post '/start' do - @experiment = Split::ExperimentCatalog.find(params['experiment']) - @experiment.start - redirect url('/') - end - - post '/reset' do - @experiment = Split::ExperimentCatalog.find(params['experiment']) - @experiment.reset - redirect url('/') - end - - post '/reopen' do - @experiment = Split::ExperimentCatalog.find(params['experiment']) - @experiment.reset_winner - redirect url('/') - end - - post '/update_cohorting' do - @experiment = Split::ExperimentCatalog.find(params['experiment']) - case params['cohorting_action'].downcase - when "enable" - @experiment.enable_cohorting - when "disable" - @experiment.disable_cohorting - end - redirect url('/') - end - - delete '/experiment' do - @experiment = Split::ExperimentCatalog.find(params['experiment']) - @experiment.delete - redirect url('/') - end - end - end -end diff --git a/spec/dashboard/pagination_helpers_spec.rb b/spec/dashboard/pagination_helpers_spec.rb index de42c337..315595e0 100644 --- a/spec/dashboard/pagination_helpers_spec.rb +++ b/spec/dashboard/pagination_helpers_spec.rb @@ -8,7 +8,7 @@ describe '#pagination_per' do context 'when params empty' do - let(:params) { { } } + let(:params) { Hash[] } it 'returns the default (10)' do default_per_page = Split.configuration.dashboard_pagination_default_per_page @@ -18,7 +18,7 @@ end context 'when params[:per] is 5' do - let(:params) { { 'per' => '5' } } + let(:params) { Hash[per: 5] } it 'returns 5' do expect(pagination_per).to eql 5 @@ -28,7 +28,7 @@ describe '#page_number' do context 'when params empty' do - let(:params) { { } } + let(:params) { Hash[] } it 'returns 1' do expect(page_number).to eql 1 @@ -36,7 +36,7 @@ end context 'when params[:page] is "2"' do - let(:params) { { 'page' => '2' } } + let(:params) { Hash[page: '2'] } it 'returns 2' do expect(page_number).to eql 2 @@ -46,7 +46,7 @@ describe '#paginated' do let(:collection) { (1..20).to_a } - let(:params) { { 'per' => '5', 'page' => '3' } } + let(:params) { Hash[per: '5', page: '3'] } it { expect(paginated(collection)).to eql [11, 12, 13, 14, 15] } end @@ -57,7 +57,7 @@ end context 'when page is 3' do - let(:params) { { 'page' => '3' } } + let(:params) { Hash[page: '3'] } it { expect(show_first_page_tag?).to be true } end end @@ -72,7 +72,7 @@ end context 'when page is 4' do - let(:params) { { 'page' => '4' } } + let(:params) { Hash[page: '4'] } it { expect(show_first_ellipsis_tag?).to be true } end end @@ -87,14 +87,14 @@ end context 'when page is 2' do - let(:params) { { 'page' => '2' } } + let(:params) { Hash[page: '2'] } it { expect(show_prev_page_tag?).to be true } end end describe '#prev_page_tag' do context 'when page is 2' do - let(:params) { { 'page' => '2' } } + let(:params) { Hash[page: '2'] } it do expect(prev_page_tag).to eql '1' @@ -102,7 +102,7 @@ end context 'when page is 3' do - let(:params) { { 'page' => '3' } } + let(:params) { Hash[page: '3'] } it do expect(prev_page_tag).to eql '2' @@ -116,26 +116,26 @@ end context 'when page is 2' do - let(:params) { { 'page' => '2' } } + let(:params) { Hash[page: '2'] } it { expect(show_prev_page_tag?).to be true } end end describe '#current_page_tag' do context 'when page is 1' do - let(:params) { { 'page' => '1' } } + let(:params) { Hash[page: '1'] } it { expect(current_page_tag).to eql '1' } end context 'when page is 2' do - let(:params) { { 'page' => '2' } } + let(:params) { Hash[page: '2'] } it { expect(current_page_tag).to eql '2' } end end describe '#show_next_page_tag?' do context 'when page is 2' do - let(:params) { { 'page' => '2' } } + let(:params) { Hash[page: '2'] } context 'when collection length is 20' do let(:collection) { (1..20).to_a } @@ -151,12 +151,12 @@ describe '#next_page_tag' do context 'when page is 1' do - let(:params) { { 'page' => '1' } } + let(:params) { Hash[page: '1'] } it { expect(next_page_tag).to eql '2' } end context 'when page is 2' do - let(:params) { { 'page' => '2' } } + let(:params) { Hash[page: '2'] } it { expect(next_page_tag).to eql '3' } end end @@ -175,7 +175,7 @@ describe '#show_last_ellipsis_tag?' do let(:collection) { (1..30).to_a } - let(:params) { { 'per' => '5', 'page' => '2' } } + let(:params) { Hash[per: '5', page: '2'] } it { expect(show_last_ellipsis_tag?(collection)).to be true } end @@ -183,12 +183,12 @@ let(:collection) { (1..30).to_a } context 'when page is 5/6' do - let(:params) { { 'per' => '5', 'page' => '5' } } + let(:params) { Hash[per: '5', page: '5'] } it { expect(show_last_page_tag?(collection)).to be false } end context 'when page is 4/6' do - let(:params) { { 'per' => '5', 'page' => '4' } } + let(:params) { Hash[per: '5', page: '4'] } it { expect(show_last_page_tag?(collection)).to be true } end end diff --git a/spec/dashboard_helpers_spec.rb b/spec/dashboard_helpers_spec.rb index 7b608ead..3643c232 100644 --- a/spec/dashboard_helpers_spec.rb +++ b/spec/dashboard_helpers_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true - require 'spec_helper' require 'split/dashboard/helpers' diff --git a/spec/dashboard_spec.rb b/spec/dashboard_spec.rb index 7bfb7ea3..dcc7732d 100644 --- a/spec/dashboard_spec.rb +++ b/spec/dashboard_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true - require 'spec_helper' require 'rack/test' require 'split/dashboard' @@ -7,14 +6,16 @@ describe Split::Dashboard do include Rack::Test::Methods - class Split::Dashboard::Web + class TestDashboard < Split::Dashboard + include Split::Helper + get '/my_experiment' do - [200, {}, [ ab_test(params['experiment'], 'blue', 'red') ]] + ab_test(params[:experiment], 'blue', 'red') end end def app - @app ||= Split::Dashboard.new + @app ||= TestDashboard end def link(color) @@ -26,7 +27,7 @@ def link(color) } let(:experiment_with_goals) { - Split::ExperimentCatalog.find_or_create({ "link_color" => ["goal_1", "goal_2"] }, "blue", "red") + Split::ExperimentCatalog.find_or_create({"link_color" => ["goal_1", "goal_2"]}, "blue", "red") } let(:metric) { @@ -187,7 +188,7 @@ def link(color) it "calls disable of cohorting when action is disable" do post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "disable" } - + expect(experiment.cohorting_disabled?).to eq true end @@ -225,7 +226,7 @@ def link(color) it "should mark an alternative as the winner" do expect(experiment.winner).to be_nil - post "/experiment?experiment=#{experiment.name}", alternative: 'red' + post "/experiment?experiment=#{experiment.name}", :alternative => 'red' expect(last_response).to be_redirect expect(experiment.winner.name).to eq('red') diff --git a/split.gemspec b/split.gemspec index 729fd390..626d5586 100644 --- a/split.gemspec +++ b/split.gemspec @@ -31,6 +31,7 @@ Gem::Specification.new do |s| s.require_paths = ["lib"] s.add_dependency 'redis', '>= 4.2' + s.add_dependency 'sinatra', '>= 1.2.6' s.add_dependency 'rubystats', '>= 0.3.0' s.add_development_dependency 'bundler', '>= 1.17' From e5c15349cc779d5cf4ed05e139170bfd018da74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Tue, 1 Sep 2020 22:03:10 -0300 Subject: [PATCH 036/136] add a simple way to load users outside web session --- lib/split/persistence.rb | 6 ++++-- lib/split/persistence/redis_adapter.rb | 4 ++++ lib/split/user.rb | 12 +++++++++++- spec/persistence/redis_adapter_spec.rb | 9 +++++++++ spec/user_spec.rb | 17 +++++++++++++++++ 5 files changed, 45 insertions(+), 3 deletions(-) diff --git a/lib/split/persistence.rb b/lib/split/persistence.rb index a464c463..cb14e681 100644 --- a/lib/split/persistence.rb +++ b/lib/split/persistence.rb @@ -8,8 +8,10 @@ module Persistence require 'split/persistence/session_adapter' ADAPTERS = { - :cookie => Split::Persistence::CookieAdapter, - :session => Split::Persistence::SessionAdapter + cookie: Split::Persistence::CookieAdapter, + session: Split::Persistence::SessionAdapter, + redis: Split::Persistence::RedisAdapter, + dual_adapter: Split::Persistence::DualAdapter }.freeze def self.adapter diff --git a/lib/split/persistence/redis_adapter.rb b/lib/split/persistence/redis_adapter.rb index 26e03eab..40ed3fd2 100644 --- a/lib/split/persistence/redis_adapter.rb +++ b/lib/split/persistence/redis_adapter.rb @@ -40,6 +40,10 @@ def keys Split.redis.hkeys(redis_key) end + def self.find(user_id) + new(nil, user_id) + end + def self.with_config(options={}) self.config.merge!(options) self diff --git a/lib/split/user.rb b/lib/split/user.rb index efa7db3d..6c5911ab 100644 --- a/lib/split/user.rb +++ b/lib/split/user.rb @@ -8,7 +8,7 @@ class User def_delegators :@user, :keys, :[], :[]=, :delete attr_reader :user - def initialize(context, adapter=nil) + def initialize(context, adapter = nil) @user = adapter || Split::Persistence.adapter.new(context) @cleaned_up = false end @@ -54,6 +54,16 @@ def active_experiments experiment_pairs end + def self.find(user_id, adapter) + adapter = adapter.is_a?(Symbol) ? Split::Persistence::ADAPTERS[adapter] : adapter + + if adapter.respond_to?(:find) + User.new(nil, adapter.find(user_id)) + else + nil + end + end + private def keys_without_experiment(keys, experiment_key) diff --git a/spec/persistence/redis_adapter_spec.rb b/spec/persistence/redis_adapter_spec.rb index 1766c075..c7321c86 100644 --- a/spec/persistence/redis_adapter_spec.rb +++ b/spec/persistence/redis_adapter_spec.rb @@ -60,6 +60,15 @@ end end + describe '#find' do + before { Split::Persistence::RedisAdapter.with_config(:lookup_by => proc{'frag'}, :namespace => 'a_namespace') } + + it "should create and user from a given key" do + adapter = Split::Persistence::RedisAdapter.find(2) + expect(adapter.redis_key).to eq("a_namespace:2") + end + end + context 'functional tests' do before { Split::Persistence::RedisAdapter.with_config(:lookup_by => 'lookup') } diff --git a/spec/user_spec.rb b/spec/user_spec.rb index f5af62e4..546c92a9 100644 --- a/spec/user_spec.rb +++ b/spec/user_spec.rb @@ -72,6 +72,23 @@ end end + context 'allows user to be loaded from adapter' do + it 'loads user from adapter (RedisAdapter)' do + user = Split::Persistence::RedisAdapter.new(nil, 112233) + user['foo'] = 'bar' + + ab_user = Split::User.find(112233, :redis) + + expect(ab_user['foo']).to eql('bar') + end + + it 'returns nil if adapter does not implement a finder method' do + ab_user = Split::User.find(112233, :dual_adapter) + expect(ab_user).to be_nil + end + + end + context "instantiated with custom adapter" do let(:custom_adapter) { double(:persistence_adapter) } From 5ee828c3b54c74b4ea82d0b5656534df20a3422e Mon Sep 17 00:00:00 2001 From: Tomas Barry Date: Wed, 21 Oct 2020 13:21:36 +0100 Subject: [PATCH 037/136] Fix typo of in `Split::Trial` class variable There is a class variable, `alternative_choosen` in the `Split::Trial` class. This class variable has a typo. The correct spelling should be [`alternative_chosen`](http://www.english-for-students.com/Chosen-vs-Choosen.html) This commit fixes the class variable typo and updates all usage of the class variable. The class variable is only used within the class itself. --- lib/split/trial.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/split/trial.rb b/lib/split/trial.rb index d7018e46..ee460f91 100644 --- a/lib/split/trial.rb +++ b/lib/split/trial.rb @@ -15,7 +15,7 @@ def initialize(attrs = {}) @user = attrs.delete(:user) @options = attrs - @alternative_choosen = false + @alternative_chosen = false end def metadata @@ -54,7 +54,7 @@ def complete!(context = nil) def choose!(context = nil) @user.cleanup_old_experiments! # Only run the process once - return alternative if @alternative_choosen + return alternative if @alternative_chosen new_participant = @user[@experiment.key].nil? if override_is_alternative? @@ -91,7 +91,7 @@ def choose!(context = nil) new_participant_and_cohorting_disabled = new_participant && @experiment.cohorting_disabled? @user[@experiment.key] = alternative.name unless @experiment.has_winner? || !should_store_alternative? || new_participant_and_cohorting_disabled - @alternative_choosen = true + @alternative_chosen = true run_callback context, Split.configuration.on_trial unless @options[:disabled] || Split.configuration.disabled? || new_participant_and_cohorting_disabled alternative end From da021ef5fdfad7ae615c4d4dbc72d352ce5849db Mon Sep 17 00:00:00 2001 From: Rich Humphrey Date: Mon, 23 Nov 2020 18:33:36 -0800 Subject: [PATCH 038/136] Added in-memory cache for `ExperimentCatalog#find` --- lib/split/experiment_catalog.rb | 8 +++++++- spec/dashboard_spec.rb | 3 ++- spec/experiment_spec.rb | 1 + spec/spec_helper.rb | 1 + 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/split/experiment_catalog.rb b/lib/split/experiment_catalog.rb index 2b18cf61..88de2dfe 100644 --- a/lib/split/experiment_catalog.rb +++ b/lib/split/experiment_catalog.rb @@ -14,8 +14,11 @@ def self.all_active_first end def self.find(name) + @cache ||= {} + return @cache[name] if @cache[name] + return unless Split.redis.exists?(name) - Experiment.new(name).tap { |exp| exp.load_from_redis } + @cache[name] = Experiment.new(name).tap { |exp| exp.load_from_redis } end def self.find_or_initialize(metric_descriptor, control = nil, *alternatives) @@ -48,5 +51,8 @@ def self.normalize_experiment(metric_descriptor) end private_class_method :normalize_experiment + def self.clear_cache + @cache = {} + end end end diff --git a/spec/dashboard_spec.rb b/spec/dashboard_spec.rb index dcc7732d..c52cdee8 100644 --- a/spec/dashboard_spec.rb +++ b/spec/dashboard_spec.rb @@ -188,7 +188,7 @@ def link(color) it "calls disable of cohorting when action is disable" do post "/update_cohorting?experiment=#{experiment.name}", { "cohorting_action": "disable" } - + expect(experiment.cohorting_disabled?).to eq true end @@ -221,6 +221,7 @@ def link(color) it "should delete an experiment" do delete "/experiment?experiment=#{experiment.name}" expect(last_response).to be_redirect + Split::ExperimentCatalog.clear_cache expect(Split::ExperimentCatalog.find(experiment.name)).to be_nil end diff --git a/spec/experiment_spec.rb b/spec/experiment_spec.rb index 98699e4c..9142b0c8 100644 --- a/spec/experiment_spec.rb +++ b/spec/experiment_spec.rb @@ -530,6 +530,7 @@ def same_but_different_alternative it "should reset an experiment if it is loaded with different goals" do same_but_different_goals + Split::ExperimentCatalog.clear_cache expect(Split::ExperimentCatalog.find("link_color").goals).to eq(["purchase", "refund"]) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 178b420f..3ee0438c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -22,6 +22,7 @@ module GlobalSharedContext Split.redis = Redis.new Split.redis.select(10) Split.redis.flushdb + Split::ExperimentCatalog.clear_cache @ab_user = mock_user params = nil end From e050109b54e5a3dc0ef8540c4dce695760e4640c Mon Sep 17 00:00:00 2001 From: Rich Humphrey Date: Mon, 23 Nov 2020 19:13:58 -0800 Subject: [PATCH 039/136] Added `cache_catalog` config --- lib/split/configuration.rb | 1 + lib/split/experiment_catalog.rb | 2 +- spec/dashboard_spec.rb | 1 - spec/experiment_spec.rb | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index 104f09b9..db336a65 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -28,6 +28,7 @@ class Configuration attr_accessor :winning_alternative_recalculation_interval attr_accessor :redis attr_accessor :dashboard_pagination_default_per_page + attr_accessor :cache_catalog attr_reader :experiments diff --git a/lib/split/experiment_catalog.rb b/lib/split/experiment_catalog.rb index 88de2dfe..b89f0eb9 100644 --- a/lib/split/experiment_catalog.rb +++ b/lib/split/experiment_catalog.rb @@ -15,7 +15,7 @@ def self.all_active_first def self.find(name) @cache ||= {} - return @cache[name] if @cache[name] + return @cache[name] if @cache[name] && Split.configuration.cache_catalog return unless Split.redis.exists?(name) @cache[name] = Experiment.new(name).tap { |exp| exp.load_from_redis } diff --git a/spec/dashboard_spec.rb b/spec/dashboard_spec.rb index c52cdee8..314bbf5b 100644 --- a/spec/dashboard_spec.rb +++ b/spec/dashboard_spec.rb @@ -221,7 +221,6 @@ def link(color) it "should delete an experiment" do delete "/experiment?experiment=#{experiment.name}" expect(last_response).to be_redirect - Split::ExperimentCatalog.clear_cache expect(Split::ExperimentCatalog.find(experiment.name)).to be_nil end diff --git a/spec/experiment_spec.rb b/spec/experiment_spec.rb index 9142b0c8..98699e4c 100644 --- a/spec/experiment_spec.rb +++ b/spec/experiment_spec.rb @@ -530,7 +530,6 @@ def same_but_different_alternative it "should reset an experiment if it is loaded with different goals" do same_but_different_goals - Split::ExperimentCatalog.clear_cache expect(Split::ExperimentCatalog.find("link_color").goals).to eq(["purchase", "refund"]) end From 80704b1ff4d5c889a893be3adb6e5465ea1ce6a4 Mon Sep 17 00:00:00 2001 From: Rich Humphrey Date: Mon, 23 Nov 2020 19:48:53 -0800 Subject: [PATCH 040/136] Added Split::Cache with config and spec, and wired into a few places --- lib/split.rb | 5 +++ lib/split/cache.rb | 22 ++++++++++ lib/split/configuration.rb | 2 +- lib/split/experiment.rb | 28 +++++++------ lib/split/experiment_catalog.rb | 9 ++-- spec/cache_spec.rb | 74 +++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 2 +- 7 files changed, 123 insertions(+), 19 deletions(-) create mode 100644 lib/split/cache.rb create mode 100644 spec/cache_spec.rb diff --git a/lib/split.rb b/lib/split.rb index 94160d87..b7f364a6 100755 --- a/lib/split.rb +++ b/lib/split.rb @@ -6,6 +6,7 @@ require 'split/algorithms/weighted_sample' require 'split/algorithms/whiplash' require 'split/alternative' +require 'split/cache' require 'split/configuration' require 'split/encapsulated_helper' require 'split/exceptions' @@ -65,6 +66,10 @@ def configure self.configuration ||= Configuration.new yield(configuration) end + + def cache(namespace, key, &block) + Split::Cache.fetch(namespace, key, &block) + end end # 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. diff --git a/lib/split/cache.rb b/lib/split/cache.rb new file mode 100644 index 00000000..31ce0aef --- /dev/null +++ b/lib/split/cache.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Split + class Cache + + def self.clear + @cache = nil + end + + def self.fetch(namespace, key) + return yield unless Split.configuration.cache + + @cache ||= {} + @cache[namespace] ||= {} + + value = @cache[namespace][key] + return value if value + + @cache[namespace][key] = yield + end + end +end diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index db336a65..decf6f93 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -28,7 +28,7 @@ class Configuration attr_accessor :winning_alternative_recalculation_interval attr_accessor :redis attr_accessor :dashboard_pagination_default_per_page - attr_accessor :cache_catalog + attr_accessor :cache attr_reader :experiments diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 24a32a48..e357e7d4 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -128,11 +128,13 @@ def alternatives=(alts) end def winner - experiment_winner = redis.hget(:experiment_winner, name) - if experiment_winner - Split::Alternative.new(experiment_winner, name) - else - nil + Split.cache(:experiment_winner, name) do + experiment_winner = redis.hget(:experiment_winner, name) + if experiment_winner + Split::Alternative.new(experiment_winner, name) + else + nil + end end end @@ -165,13 +167,15 @@ def start end def start_time - t = redis.hget(:experiment_start_times, @name) - if t - # Check if stored time is an integer - if t =~ /^[-+]?[0-9]+$/ - Time.at(t.to_i) - else - Time.parse(t) + Split.cache(:experiment_start_times, @name) do + t = redis.hget(:experiment_start_times, @name) + if t + # Check if stored time is an integer + if t =~ /^[-+]?[0-9]+$/ + Time.at(t.to_i) + else + Time.parse(t) + end end end end diff --git a/lib/split/experiment_catalog.rb b/lib/split/experiment_catalog.rb index b89f0eb9..117b3f7e 100644 --- a/lib/split/experiment_catalog.rb +++ b/lib/split/experiment_catalog.rb @@ -14,11 +14,10 @@ def self.all_active_first end def self.find(name) - @cache ||= {} - return @cache[name] if @cache[name] && Split.configuration.cache_catalog - - return unless Split.redis.exists?(name) - @cache[name] = Experiment.new(name).tap { |exp| exp.load_from_redis } + Split.cache(:experiment_catalog, name) do + return unless Split.redis.exists?(name) + Experiment.new(name).tap { |exp| exp.load_from_redis } + end end def self.find_or_initialize(metric_descriptor, control = nil, *alternatives) diff --git a/spec/cache_spec.rb b/spec/cache_spec.rb new file mode 100644 index 00000000..5f9d83f9 --- /dev/null +++ b/spec/cache_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Split::Cache do + + let(:namespace) { :test_namespace } + let(:key) { :test_key } + let(:now) { 1606189017 } + + before { allow(Time).to receive(:now).and_return(now) } + + describe 'clear' do + + before { Split.configuration.cache = true } + + it 'clears the cache' do + expect(Time).to receive(:now).and_return(now).exactly(2).times + Split::Cache.fetch(namespace, key) { Time.now } + Split::Cache.clear + Split::Cache.fetch(namespace, key) { Time.now } + end + end + + describe 'fetch' do + + subject { Split::Cache.fetch(namespace, key) { Time.now } } + + context 'when cache disabled' do + + before { Split.configuration.cache = false } + + it 'returns the yield' do + expect(subject).to eql(now) + end + + it 'yields every time' do + expect(Time).to receive(:now).and_return(now).exactly(2).times + Split::Cache.fetch(namespace, key) { Time.now } + Split::Cache.fetch(namespace, key) { Time.now } + end + end + + context 'when cache enabled' do + + before { Split.configuration.cache = true } + + it 'returns the yield' do + expect(subject).to eql(now) + end + + it 'yields once' do + expect(Time).to receive(:now).and_return(now).once + Split::Cache.fetch(namespace, key) { Time.now } + Split::Cache.fetch(namespace, key) { Time.now } + end + + it 'honors namespace' do + expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a) + expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b) + + expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a) + expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b) + end + + it 'honors key' do + expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a) + expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b) + + expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a) + expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b) + end + end + end +end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3ee0438c..52c39f75 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -22,7 +22,7 @@ module GlobalSharedContext Split.redis = Redis.new Split.redis.select(10) Split.redis.flushdb - Split::ExperimentCatalog.clear_cache + Split::Cache.clear @ab_user = mock_user params = nil end From 48280948e0c760c17c4c8663f56eb937a102f23a Mon Sep 17 00:00:00 2001 From: Rich Humphrey Date: Mon, 23 Nov 2020 20:22:47 -0800 Subject: [PATCH 041/136] Add `disable_metrics` config to short circuit metrics lookup in Redis --- lib/split/configuration.rb | 1 + lib/split/metric.rb | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index decf6f93..c9283912 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -29,6 +29,7 @@ class Configuration attr_accessor :redis attr_accessor :dashboard_pagination_default_per_page attr_accessor :cache + attr_accessor :disable_metrics attr_reader :experiments diff --git a/lib/split/metric.rb b/lib/split/metric.rb index b367da6c..e11f0e70 100644 --- a/lib/split/metric.rb +++ b/lib/split/metric.rb @@ -38,6 +38,8 @@ def self.load_from_configuration(name) end def self.find(name) + return if Split.configuration.disable_metrics + name = name.intern if name.is_a?(String) metric = load_from_configuration(name) metric = load_from_redis(name) if metric.nil? From e2c265c3c893298c522929053f5df31778fad83a Mon Sep 17 00:00:00 2001 From: Rich Humphrey Date: Mon, 23 Nov 2020 23:52:31 -0800 Subject: [PATCH 042/136] Back out `Split.configuration.disable_metrics` --- lib/split/configuration.rb | 1 - lib/split/metric.rb | 2 -- 2 files changed, 3 deletions(-) diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index c9283912..decf6f93 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -29,7 +29,6 @@ class Configuration attr_accessor :redis attr_accessor :dashboard_pagination_default_per_page attr_accessor :cache - attr_accessor :disable_metrics attr_reader :experiments diff --git a/lib/split/metric.rb b/lib/split/metric.rb index e11f0e70..b367da6c 100644 --- a/lib/split/metric.rb +++ b/lib/split/metric.rb @@ -38,8 +38,6 @@ def self.load_from_configuration(name) end def self.find(name) - return if Split.configuration.disable_metrics - name = name.intern if name.is_a?(String) metric = load_from_configuration(name) metric = load_from_redis(name) if metric.nil? From 623e556f07fd059d40681f537cc426a8714d3121 Mon Sep 17 00:00:00 2001 From: Rich Humphrey Date: Thu, 3 Dec 2020 13:46:10 -0800 Subject: [PATCH 043/136] Remove obsolete cache clear left over from before refactoring --- lib/split/experiment_catalog.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/split/experiment_catalog.rb b/lib/split/experiment_catalog.rb index 117b3f7e..1bc57f6c 100644 --- a/lib/split/experiment_catalog.rb +++ b/lib/split/experiment_catalog.rb @@ -49,9 +49,5 @@ def self.normalize_experiment(metric_descriptor) return experiment_name, goals end private_class_method :normalize_experiment - - def self.clear_cache - @cache = {} - end end end From a26b595534e7227326b116f5385a4a6f400781e3 Mon Sep 17 00:00:00 2001 From: Rich Humphrey Date: Thu, 3 Dec 2020 16:51:51 -0800 Subject: [PATCH 044/136] Added docs for `Split.configuration.cache` --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index a2e3552b..e8e15bfe 100644 --- a/README.md +++ b/README.md @@ -756,6 +756,21 @@ split_config = YAML.load_file(Rails.root.join('config', 'split.yml')) Split.redis = split_config[Rails.env] ``` +### Redis Caching + +In some high-volume usage scenarios, Redis load can be incurred by repeated +fetches for fairly static data. Enabling caching will reduce this load, but +require a restart for changes to experiment definitions to take effect. + + ```ruby +Split.configuration.cache = true +```` + +This currently caches: + - `Split::ExperimentCatalog.find` + - `Split::Experiment.start_time` + - `Split::Experiment.winner` + ## Namespaces If you're running multiple, separate instances of Split you may want From 3e9c1aeda5218d048f29905af2125f749f1a19a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 6 Dec 2020 22:20:56 -0300 Subject: [PATCH 045/136] Centralize checks for experiment existance in experiment catalog --- lib/split/experiment.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index e357e7d4..a03b0d7f 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -94,7 +94,7 @@ def validate! end def new_record? - !redis.exists?(name) + ExperimentCatalog.find(name).nil? end def ==(obj) From 5ea585cb9bdc268b7f0d02c9f8b92d4fe54f0a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 6 Dec 2020 22:23:25 -0300 Subject: [PATCH 046/136] Add note for caching only being available on v4.0+ --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e8e15bfe..de797e62 100644 --- a/README.md +++ b/README.md @@ -756,7 +756,7 @@ split_config = YAML.load_file(Rails.root.join('config', 'split.yml')) Split.redis = split_config[Rails.env] ``` -### Redis Caching +### Redis Caching (v4.0+) In some high-volume usage scenarios, Redis load can be incurred by repeated fetches for fairly static data. Enabling caching will reduce this load, but From deaa469fcec628f7c233f21ffdbccef1c07c0657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 6 Dec 2020 22:27:07 -0300 Subject: [PATCH 047/136] Set main branch as 4.0.0.pre --- lib/split/version.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/split/version.rb b/lib/split/version.rb index f281a7e6..e8f4df20 100644 --- a/lib/split/version.rb +++ b/lib/split/version.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true module Split - MAJOR = 3 - MINOR = 4 - PATCH = 1 - VERSION = [MAJOR, MINOR, PATCH].join('.') + VERSION = "4.0.0.pre" end From 310cfc68332519aa80bd06fefb1403612dfd1474 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Wed, 9 Dec 2020 09:33:35 -0800 Subject: [PATCH 048/136] Cherry pick the second split cache optimization --- lib/split/cache.rb | 6 ++++++ lib/split/experiment.rb | 20 ++++++++++++++------ lib/split/experiment_catalog.rb | 7 ++----- spec/cache_spec.rb | 16 +++++++++++++++- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/lib/split/cache.rb b/lib/split/cache.rb index 31ce0aef..6611bfdb 100644 --- a/lib/split/cache.rb +++ b/lib/split/cache.rb @@ -18,5 +18,11 @@ def self.fetch(namespace, key) @cache[namespace][key] = yield end + + def self.clear_key(key) + @cache&.keys&.each do |namespace| + @cache[namespace]&.delete(key) + end + end end end diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index e357e7d4..24f48b46 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -16,6 +16,13 @@ class Experiment :resettable => true } + def self.find(name) + Split.cache(:experiments, name) do + return unless Split.redis.exists?(name) + Experiment.new(name).tap { |exp| exp.load_from_redis } + end + end + def initialize(name, options = {}) options = DEFAULT_OPTIONS.merge(options) @@ -160,6 +167,7 @@ def control def reset_winner redis.hdel(:experiment_winner, name) @has_winner = false + Split::Cache.clear_key(@name) end def start @@ -226,6 +234,7 @@ def resettable? def reset Split.configuration.on_before_experiment_reset.call(self) + Split::Cache.clear_key(@name) alternatives.each(&:reset) reset_winner Split.configuration.on_experiment_reset.call(self) @@ -476,12 +485,11 @@ def remove_experiment_configuration end def experiment_configuration_has_changed? - existing_alternatives = load_alternatives_from_redis - existing_goals = Split::GoalsCollection.new(@name).load_from_redis - existing_metadata = load_metadata_from_redis - existing_alternatives.map(&:to_s) != @alternatives.map(&:to_s) || - existing_goals != @goals || - existing_metadata != @metadata + existing_experiment = Experiment.find(@name) + + existing_experiment.alternatives.map(&:to_s) != @alternatives.map(&:to_s) || + existing_experiment.goals != @goals || + existing_experiment.metadata != @metadata end def goals_collection diff --git a/lib/split/experiment_catalog.rb b/lib/split/experiment_catalog.rb index 1bc57f6c..2df13aa9 100644 --- a/lib/split/experiment_catalog.rb +++ b/lib/split/experiment_catalog.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true - module Split class ExperimentCatalog # Return all experiments @@ -14,10 +13,7 @@ def self.all_active_first end def self.find(name) - Split.cache(:experiment_catalog, name) do - return unless Split.redis.exists?(name) - Experiment.new(name).tap { |exp| exp.load_from_redis } - end + Experiment.find(name) end def self.find_or_initialize(metric_descriptor, control = nil, *alternatives) @@ -49,5 +45,6 @@ def self.normalize_experiment(metric_descriptor) return experiment_name, goals end private_class_method :normalize_experiment + end end diff --git a/spec/cache_spec.rb b/spec/cache_spec.rb index 5f9d83f9..e411800b 100644 --- a/spec/cache_spec.rb +++ b/spec/cache_spec.rb @@ -21,6 +21,20 @@ end end + describe 'clear_key' do + before { Split.configuration.cache = true } + + it 'clears the cache' do + expect(Time).to receive(:now).and_return(now).exactly(3).times + Split::Cache.fetch(namespace, :key1) { Time.now } + Split::Cache.fetch(namespace, :key2) { Time.now } + Split::Cache.clear_key(:key1) + + Split::Cache.fetch(namespace, :key1) { Time.now } + Split::Cache.fetch(namespace, :key2) { Time.now } + end + end + describe 'fetch' do subject { Split::Cache.fetch(namespace, key) { Time.now } } @@ -71,4 +85,4 @@ end end end -end \ No newline at end of file +end From b611d6c3d889e41b8b9bfb0cb1b9f4f054df0b4d Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Wed, 9 Dec 2020 10:00:47 -0800 Subject: [PATCH 049/136] Deleting an extra line --- lib/split/experiment_catalog.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/split/experiment_catalog.rb b/lib/split/experiment_catalog.rb index 2df13aa9..649b448a 100644 --- a/lib/split/experiment_catalog.rb +++ b/lib/split/experiment_catalog.rb @@ -45,6 +45,5 @@ def self.normalize_experiment(metric_descriptor) return experiment_name, goals end private_class_method :normalize_experiment - end end From 73ef502ebb3ef0899e64f17cc3cfe91a158db324 Mon Sep 17 00:00:00 2001 From: Aman Gupta Date: Wed, 9 Dec 2020 10:07:22 -0800 Subject: [PATCH 050/136] Removing the comment which says reboot required for new experiment config to take effect --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index e8e15bfe..94299b08 100644 --- a/README.md +++ b/README.md @@ -759,8 +759,7 @@ Split.redis = split_config[Rails.env] ### Redis Caching In some high-volume usage scenarios, Redis load can be incurred by repeated -fetches for fairly static data. Enabling caching will reduce this load, but -require a restart for changes to experiment definitions to take effect. +fetches for fairly static data. Enabling caching will reduce this load. ```ruby Split.configuration.cache = true From 94f297f96b30bce243c3da0cd3352a69509763a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Thu, 17 Dec 2020 10:27:50 -0300 Subject: [PATCH 051/136] Update changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1d5c739..74d3ff76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,15 @@ Bugfixes: - Fix versioned experiments when used with allow_multiple_experiments=control (@andrehjr, #613) - Only block Pinterest bot (@huoxito, #606) - Respect experiment defaults when loading experiments in initializer. (@mattwd7, #599) +- Removes metadata key when it updated to nil (@andrehjr, #633) Features: - Make goals accessible via on_trial_complete callbacks (@robin-phung, #625) - Replace usage of SimpleRandom with RubyStats(Used for Beta Distribution RNG) (@andrehjr, #616) - Introduce enable/disable experiment cohorting (@robin-phung, #615) - Add on_experiment_winner_choose callback (@GenaMinenkov, #574) +- Add Split::Cache to reduce load on Redis (@rdh, #648) +- Caching based optimization in the experiment#save path (@amangup, #652) Misc: - Drop support for Ruby < 2.5 (@andrehjr, #627) @@ -20,6 +23,10 @@ Misc: - Simplify RedisInterface usage when persisting Experiment alternatives (@andrehjr, #632) - Remove redis_url impl. Deprecated on version 2.2 (@andrehjr, #631) - Remove thread_safe config as redis-rb is thread_safe by default (@andrehjr, #630) +- Fix typo of in `Split::Trial` class variable (TomasBarry, #644) +- Single HSET to update values, instead of multiple ones (@andrehjr, #640) +- Remove 'set' parsing for alternatives. Sets were used as storage and deprecated on 0.x (@andrehjr, #639) +- Adding documentation related to what is stored on cookies. (@andrehjr, #634) ## 3.4.1 (November 12th, 2019) From c4ed88cbde0b319f52ba03746792fb9cc6c69190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Thu, 17 Dec 2020 10:29:39 -0300 Subject: [PATCH 052/136] Reference 4.0.0.pre on CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74d3ff76..cf8b49bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased 4.0.0 +## 4.0.0.pre Bugfixes: - ab_test must return metadata on error or if split is disabled/excluded user (@andrehjr, #622) From 74266e82217db048d2fb79857105ee11589ddf33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sat, 27 Mar 2021 12:56:32 -0300 Subject: [PATCH 053/136] Add missing entry to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf8b49bc..7b535980 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Bugfixes: - Only block Pinterest bot (@huoxito, #606) - Respect experiment defaults when loading experiments in initializer. (@mattwd7, #599) - Removes metadata key when it updated to nil (@andrehjr, #633) +- Force experiment does not count for metrics (@andrehjr, #637) Features: - Make goals accessible via on_trial_complete callbacks (@robin-phung, #625) From b8e7838e41c1fae4ed8b8d73775b8e8a2d7e6ee7 Mon Sep 17 00:00:00 2001 From: Lazarus Lazaridis <9477868+iridakos@users.noreply.github.com> Date: Tue, 27 Apr 2021 08:01:28 +0300 Subject: [PATCH 054/136] Fix minor typos in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 32653d3b..81dad526 100644 --- a/README.md +++ b/README.md @@ -648,7 +648,7 @@ The API to define goals for an experiment is this: ab_test({link_color: ["purchase", "refund"]}, "red", "blue") ``` -or you can you can define them in a configuration file: +or you can define them in a configuration file: ```ruby Split.configure do |config| @@ -786,7 +786,7 @@ library. To configure Split to use `Redis::Namespace`, do the following: ``` 2. Configure `Split.redis` to use a `Redis::Namespace` instance (possible in an - intializer): + initializer): ```ruby redis = Redis.new(url: ENV['REDIS_URL']) # or whatever config you want From 4dfb5da87f2b99e4de1107eaa3d790faf9800895 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 19:19:28 +0000 Subject: [PATCH 055/136] Upgrade to GitHub-native Dependabot --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..452ebb34 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: bundler + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 From cc7c41bfc72774c59068fe86a1684fc3db0cee23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Wed, 19 May 2021 17:27:48 -0300 Subject: [PATCH 056/136] Uset Redis#hmset to keep compatibility with Redis < 4.0 --- lib/split/experiment.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 6b627793..5010a4ef 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -87,8 +87,8 @@ def save persist_experiment_configuration end - redis.hset(experiment_config_key, :resettable, resettable, - :algorithm, algorithm.to_s) + redis.hmset(experiment_config_key, :resettable, resettable, + :algorithm, algorithm.to_s) self end From 4b6c5c0ac84b1d17139139f151fbba9e70bc87cf Mon Sep 17 00:00:00 2001 From: Sergey Glukhov Date: Wed, 14 Jul 2021 16:38:26 +0300 Subject: [PATCH 057/136] Fix cleanup_old_versions! misbehaviour --- lib/split/user.rb | 2 +- spec/user_spec.rb | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/split/user.rb b/lib/split/user.rb index 6c5911ab..c5f6108c 100644 --- a/lib/split/user.rb +++ b/lib/split/user.rb @@ -38,7 +38,7 @@ def max_experiments_reached?(experiment_key) end def cleanup_old_versions!(experiment) - keys = user.keys.select { |k| k.match(Regexp.new(experiment.name)) } + keys = user.keys.select { |k| k.start_with?(experiment.name + ':') } keys_without_experiment(keys, experiment.key).each { |key| user.delete(key) } end diff --git a/spec/user_spec.rb b/spec/user_spec.rb index 546c92a9..0291e593 100644 --- a/spec/user_spec.rb +++ b/spec/user_spec.rb @@ -17,11 +17,25 @@ end context '#cleanup_old_versions!' do - let(:user_keys) { { 'link_color:1' => 'blue' } } + let(:experiment_version) { "#{experiment.name}:1" } + let(:second_experiment_version) { "#{experiment.name}_another:1" } + let(:third_experiment_version) { "variation_of_#{experiment.name}:1" } + let(:user_keys) do + { + experiment_version => 'blue', + second_experiment_version => 'red', + third_experiment_version => 'yellow' + } + end + + before(:each) { @subject.cleanup_old_versions!(experiment) } it 'removes key if old experiment is found' do - @subject.cleanup_old_versions!(experiment) - expect(@subject.keys).to be_empty + expect(@subject.keys).not_to include(experiment_version) + end + + it 'does not remove other keys' do + expect(@subject.keys).to include(second_experiment_version, third_experiment_version) end end From 92c60b4e550687bc41885ad04a812e03d3e97c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 18 Jul 2021 19:19:36 -0300 Subject: [PATCH 058/136] Add initial workflow --- .github/workflows/ci.yml | 61 ++++++++++++++++++++++++++++++++++++++ spec/configuration_spec.rb | 8 +++-- 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..444bc5e9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: split + +on: [push] + +jobs: + test: + strategy: + matrix: + include: + - gemfile: 5.2.gemfile + ruby: 2.5 + + - gemfile: 5.2.gemfile + ruby: 2.6 + + - gemfile: 5.2.gemfile + ruby: 2.7 + + - gemfile: 6.0.gemfile + ruby: 2.5 + + - gemfile: 6.0.gemfile + ruby: 2.6 + + - gemfile: 6.0.gemfile + ruby: 2.7 + + - gemfile: 6.0.gemfile + ruby: '3.0' + + runs-on: ubuntu-latest + + services: + redis: + image: redis + ports: ['6379:6379'] + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v2 + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + + - name: Install dependencies + run: | + bundle config set gemfile "${GITHUB_WORKSPACE}/gemfiles/${{ matrix.gemfile }}" + bundle install --jobs 4 --retry 3 + + - name: Display Ruby version + run: ruby -v + + - name: Test + run: bundle exec rspec + env: + REDIS_URL: redis:6379 diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb index 4001f6e6..06db7a5f 100644 --- a/spec/configuration_spec.rb +++ b/spec/configuration_spec.rb @@ -214,7 +214,10 @@ context "redis configuration" do it "should default to local redis server" do - expect(@config.redis).to eq("redis://localhost:6379") + old_redis_url = ENV['REDIS_URL'] + ENV.delete('REDIS_URL') + expect(Split::Configuration.new.redis).to eq("redis://localhost:6379") + ENV['REDIS_URL'] = old_redis_url end it "should allow for redis url to be configured" do @@ -224,9 +227,10 @@ context "provided REDIS_URL environment variable" do it "should use the ENV variable" do + old_redis_url = ENV['REDIS_URL'] ENV['REDIS_URL'] = "env_redis_url" expect(Split::Configuration.new.redis).to eq("env_redis_url") - ENV.delete('REDIS_URL') + ENV['REDIS_URL'] = old_redis_url end end end From c9a51fb4adafd08426785b6fd50571e3604e6445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 18 Jul 2021 19:46:48 -0300 Subject: [PATCH 059/136] Remove travis.yml --- .travis.yml | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 300bb929..00000000 --- a/.travis.yml +++ /dev/null @@ -1,32 +0,0 @@ -language: ruby -rvm: - - 2.5.7 - - 2.6.6 - - 2.7.1 - -services: - - redis-server - -gemfile: - - gemfiles/5.0.gemfile - - gemfiles/5.1.gemfile - - gemfiles/5.2.gemfile - - gemfiles/6.0.gemfile - -before_install: - - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true - - gem install bundler --version=1.17.3 - -before_script: - - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - - chmod +x ./cc-test-reporter - - ./cc-test-reporter before-build - -script: - - RAILS_ENV=test bundle exec rake spec - -after_script: - - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT - -cache: bundler -sudo: false From d549a5e68fffa516f50fdbe2ccfe6aa5a08d3e44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 18 Jul 2021 19:55:12 -0300 Subject: [PATCH 060/136] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 81dad526..940230cb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # [Split](https://libraries.io/rubygems/split) [![Gem Version](https://badge.fury.io/rb/split.svg)](http://badge.fury.io/rb/split) -[![Build Status](https://secure.travis-ci.org/splitrb/split.svg?branch=master)](https://travis-ci.org/splitrb/split) +![Build status](https://github.com/splitrb/split/actions/workflows/ci.yml/badge.svg?branch=main) [![Code Climate](https://codeclimate.com/github/splitrb/split/badges/gpa.svg)](https://codeclimate.com/github/splitrb/split) [![Test Coverage](https://codeclimate.com/github/splitrb/split/badges/coverage.svg)](https://codeclimate.com/github/splitrb/split/coverage) [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) From 2037c4a19232691379dce4d66aaab482abdf64e0 Mon Sep 17 00:00:00 2001 From: Sergey Glukhov Date: Mon, 19 Jul 2021 11:16:31 +0300 Subject: [PATCH 061/136] fix spec failures --- lib/split/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/split/user.rb b/lib/split/user.rb index c5f6108c..0092f864 100644 --- a/lib/split/user.rb +++ b/lib/split/user.rb @@ -38,7 +38,7 @@ def max_experiments_reached?(experiment_key) end def cleanup_old_versions!(experiment) - keys = user.keys.select { |k| k.start_with?(experiment.name + ':') } + keys = user.keys.select { |k| k == experiment.name || k.start_with?(experiment.name + ':') } keys_without_experiment(keys, experiment.key).each { |key| user.delete(key) } end From dfaa05b782926d23d689d2a06cc3f27a2dee97e9 Mon Sep 17 00:00:00 2001 From: Joe d'Elia Date: Fri, 13 Aug 2021 11:42:16 +0100 Subject: [PATCH 062/136] Adds config option for cookie domain --- lib/split/configuration.rb | 2 ++ lib/split/persistence/cookie_adapter.rb | 6 +++++- spec/configuration_spec.rb | 11 +++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index decf6f93..08481319 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -11,6 +11,7 @@ class Configuration attr_accessor :enabled attr_accessor :persistence attr_accessor :persistence_cookie_length + attr_accessor :persistence_cookie_domain attr_accessor :algorithm attr_accessor :store_override attr_accessor :start_manually @@ -226,6 +227,7 @@ def initialize @experiments = {} @persistence = Split::Persistence::SessionAdapter @persistence_cookie_length = 31536000 # One year from now + @persistence_cookie_domain = nil @algorithm = Split::Algorithms::WeightedSample @include_rails_helper = true @beta_probability_simulations = 10000 diff --git a/lib/split/persistence/cookie_adapter.rb b/lib/split/persistence/cookie_adapter.rb index b3e693db..d30cf149 100644 --- a/lib/split/persistence/cookie_adapter.rb +++ b/lib/split/persistence/cookie_adapter.rb @@ -45,7 +45,7 @@ def set_cookie(value = {}) end def default_options - { expires: @expires, path: '/' } + { expires: @expires, path: '/', domain: cookie_domain_config } end def set_cookie_via_rack(key, value) @@ -87,6 +87,10 @@ def cookie_length_config Split.configuration.persistence_cookie_length end + def cookie_domain_config + Split.configuration.persistence_cookie_domain + end + def action_dispatch? defined?(Rails) && @response.is_a?(ActionDispatch::Response) end diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb index 4001f6e6..f8632083 100644 --- a/spec/configuration_spec.rb +++ b/spec/configuration_spec.rb @@ -242,4 +242,15 @@ end end + context "persistence cookie domain" do + it "should default to nil" do + expect(@config.persistence_cookie_domain).to eq(nil) + end + + it "should allow the persistence cookie domain to be configured" do + @config.persistence_cookie_domain = '.acme.com' + expect(@config.persistence_cookie_domain).to eq('.acme.com') + end + end + end From dca0d242ef6474e528e23db0f9e09f34a734bcda Mon Sep 17 00:00:00 2001 From: Joe d'Elia Date: Fri, 13 Aug 2021 11:46:43 +0100 Subject: [PATCH 063/136] Removes domain option if set to nil --- lib/split/persistence/cookie_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/split/persistence/cookie_adapter.rb b/lib/split/persistence/cookie_adapter.rb index d30cf149..b70e112c 100644 --- a/lib/split/persistence/cookie_adapter.rb +++ b/lib/split/persistence/cookie_adapter.rb @@ -45,7 +45,7 @@ def set_cookie(value = {}) end def default_options - { expires: @expires, path: '/', domain: cookie_domain_config } + { expires: @expires, path: '/', domain: cookie_domain_config }.compact end def set_cookie_via_rack(key, value) From b65e5b2244023cd46fa9f4d38ba2286a6e406350 Mon Sep 17 00:00:00 2001 From: Chris Lowder Date: Wed, 25 Aug 2021 10:48:25 +0100 Subject: [PATCH 064/136] Update RSpec helper to support block syntax Adds support for the helpers block form `ab_test(:experiment) { |variant| ... }`, based on the example from RSpecs docs[1]. [1]: https://relishapp.com/rspec/rspec-mocks/v/3-2/docs/configuring-responses/block-implementation#yield-to-the-caller's-block --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 940230cb..d30112ef 100644 --- a/README.md +++ b/README.md @@ -175,8 +175,10 @@ module SplitHelper # use_ab_test(signup_form: "single_page", pricing: "show_enterprise_prices") # def use_ab_test(alternatives_by_experiment) - allow_any_instance_of(Split::Helper).to receive(:ab_test) do |_receiver, experiment| - alternatives_by_experiment.fetch(experiment) { |key| raise "Unknown experiment '#{key}'" } + allow_any_instance_of(Split::Helper).to receive(:ab_test) do |_receiver, experiment, &block| + variant = alternatives_by_experiment.fetch(experiment) { |key| raise "Unknown experiment '#{key}'" } + block.call(variant) unless block.nil? + variant end end end From 047b62161e02c6f0ea3e2b5b7f1e0445239f3212 Mon Sep 17 00:00:00 2001 From: Andrew Vit Date: Wed, 22 Sep 2021 22:47:08 -0700 Subject: [PATCH 065/136] Keep railtie defined under the Split gem namespace --- lib/split.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/split.rb b/lib/split.rb index b7f364a6..9c87343d 100755 --- a/lib/split.rb +++ b/lib/split.rb @@ -74,7 +74,7 @@ def cache(namespace, key, &block) # 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. if defined?(::Rails) - class Railtie < Rails::Railtie + class Split::Railtie < Rails::Railtie config.before_initialize { Split.configure {} } end else From d6e96108e7d5d025bc8ad2bf86f8783165085f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 7 Nov 2021 13:53:09 -0300 Subject: [PATCH 066/136] Simply check for the experiment name without the version --- lib/split/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/split/user.rb b/lib/split/user.rb index 0092f864..61772405 100644 --- a/lib/split/user.rb +++ b/lib/split/user.rb @@ -38,7 +38,7 @@ def max_experiments_reached?(experiment_key) end def cleanup_old_versions!(experiment) - keys = user.keys.select { |k| k == experiment.name || k.start_with?(experiment.name + ':') } + keys = user.keys.select { |k| key_without_version(k) == experiment.name } keys_without_experiment(keys, experiment.key).each { |key| user.delete(key) } end From f88bb075c3976bbbbd7de4786521fdd3f89f390a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 7 Nov 2021 14:29:20 -0300 Subject: [PATCH 067/136] 4.0.0.pre2 --- CHANGELOG.md | 8 ++++++-- lib/split/version.rb | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b535980..c7b7ce52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.0.0.pre +## 4.0.0.pre2 Bugfixes: - ab_test must return metadata on error or if split is disabled/excluded user (@andrehjr, #622) @@ -7,6 +7,7 @@ Bugfixes: - Respect experiment defaults when loading experiments in initializer. (@mattwd7, #599) - Removes metadata key when it updated to nil (@andrehjr, #633) - Force experiment does not count for metrics (@andrehjr, #637) +- Fix cleanup_old_versions! misbehaviour (@serggi, #661) Features: - Make goals accessible via on_trial_complete callbacks (@robin-phung, #625) @@ -15,10 +16,11 @@ Features: - Add on_experiment_winner_choose callback (@GenaMinenkov, #574) - Add Split::Cache to reduce load on Redis (@rdh, #648) - Caching based optimization in the experiment#save path (@amangup, #652) +- Adds config option for cookie domain (@joedelia, #664) Misc: - Drop support for Ruby < 2.5 (@andrehjr, #627) -- Drop support for Rails < 5 (@andrehkr, #607) +- Drop support for Rails < 5 (@andrehjr, #607) - Bump minimum required redis to 4.2 (@andrehjr, #628) - Removed repeated loading from config (@robin-phung, #619) - Simplify RedisInterface usage when persisting Experiment alternatives (@andrehjr, #632) @@ -26,8 +28,10 @@ Misc: - Remove thread_safe config as redis-rb is thread_safe by default (@andrehjr, #630) - Fix typo of in `Split::Trial` class variable (TomasBarry, #644) - Single HSET to update values, instead of multiple ones (@andrehjr, #640) +- Use Redis#hmset to keep compatibility with Redis < 4.0 (@andrehjr, #659) - Remove 'set' parsing for alternatives. Sets were used as storage and deprecated on 0.x (@andrehjr, #639) - Adding documentation related to what is stored on cookies. (@andrehjr, #634) +- Keep railtie defined under the Split gem namespace (@avit, #666) ## 3.4.1 (November 12th, 2019) diff --git a/lib/split/version.rb b/lib/split/version.rb index e8f4df20..57d86dda 100644 --- a/lib/split/version.rb +++ b/lib/split/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Split - VERSION = "4.0.0.pre" + VERSION = "4.0.0.pre2" end From 09eed001b3ef3c0532f7bdf42a56d045053adc33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sat, 25 Dec 2021 22:01:26 -0300 Subject: [PATCH 068/136] Add newer versions of Rails and Ruby to be tested on CI --- .github/workflows/ci.yml | 10 ++++++++++ gemfiles/6.1.gemfile | 9 +++++++++ gemfiles/7.0.gemfile | 9 +++++++++ 3 files changed, 28 insertions(+) create mode 100644 gemfiles/6.1.gemfile create mode 100644 gemfiles/7.0.gemfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 444bc5e9..9d58a8ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,16 @@ jobs: - gemfile: 6.0.gemfile ruby: '3.0' + - gemfile: 6.1.gemfile + ruby: '3.0' + + - gemfile: 7.0.gemfile + ruby: '3.0' + + - gemfile: 7.0.gemfile + ruby: '3.1' + + runs-on: ubuntu-latest services: diff --git a/gemfiles/6.1.gemfile b/gemfiles/6.1.gemfile new file mode 100644 index 00000000..0342c40b --- /dev/null +++ b/gemfiles/6.1.gemfile @@ -0,0 +1,9 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "appraisal" +gem "codeclimate-test-reporter" +gem "rails", "~> 6.1" + +gemspec path: "../" diff --git a/gemfiles/7.0.gemfile b/gemfiles/7.0.gemfile new file mode 100644 index 00000000..220c35ab --- /dev/null +++ b/gemfiles/7.0.gemfile @@ -0,0 +1,9 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "appraisal" +gem "codeclimate-test-reporter" +gem "rails", "~> 7.0" + +gemspec path: "../" From fbb9b737ceb6bd1e679181292dccb7f708ebbcd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sat, 25 Dec 2021 22:20:49 -0300 Subject: [PATCH 069/136] Need to fix a few things before pushing ruby 3.1 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d58a8ca..5e8fe56d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,8 +34,8 @@ jobs: - gemfile: 7.0.gemfile ruby: '3.0' - - gemfile: 7.0.gemfile - ruby: '3.1' + # - gemfile: 7.0.gemfile + # ruby: '3.1' runs-on: ubuntu-latest From 6f95d35f8ae531207f8adec7de2dd4c184f3e5b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Thu, 30 Dec 2021 18:16:08 -0300 Subject: [PATCH 070/136] v4.0.0 --- CHANGELOG.md | 5 +++-- lib/split/version.rb | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7b7ce52..5230c738 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.0.0.pre2 +## 4.0.0 (December 30th, 2021) Bugfixes: - ab_test must return metadata on error or if split is disabled/excluded user (@andrehjr, #622) @@ -7,7 +7,7 @@ Bugfixes: - Respect experiment defaults when loading experiments in initializer. (@mattwd7, #599) - Removes metadata key when it updated to nil (@andrehjr, #633) - Force experiment does not count for metrics (@andrehjr, #637) -- Fix cleanup_old_versions! misbehaviour (@serggi, #661) +- Fix cleanup_old_versions! misbehaviour (@serggl, #661) Features: - Make goals accessible via on_trial_complete callbacks (@robin-phung, #625) @@ -32,6 +32,7 @@ Misc: - Remove 'set' parsing for alternatives. Sets were used as storage and deprecated on 0.x (@andrehjr, #639) - Adding documentation related to what is stored on cookies. (@andrehjr, #634) - Keep railtie defined under the Split gem namespace (@avit, #666) +- Update RSpec helper to support block syntax (@clowder, #665) ## 3.4.1 (November 12th, 2019) diff --git a/lib/split/version.rb b/lib/split/version.rb index 57d86dda..dbb9b4c0 100644 --- a/lib/split/version.rb +++ b/lib/split/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Split - VERSION = "4.0.0.pre2" + VERSION = "4.0.0" end From c1b198f2f964a36db2b735321a86ba1585d069ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Thu, 30 Dec 2021 18:36:11 -0300 Subject: [PATCH 071/136] v4.0.1 --- CHANGELOG.md | 2 +- lib/split/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5230c738..b4567e44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.0.0 (December 30th, 2021) +## 4.0.1 (December 30th, 2021) Bugfixes: - ab_test must return metadata on error or if split is disabled/excluded user (@andrehjr, #622) diff --git a/lib/split/version.rb b/lib/split/version.rb index dbb9b4c0..037a710e 100644 --- a/lib/split/version.rb +++ b/lib/split/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Split - VERSION = "4.0.0" + VERSION = "4.0.1" end From 080c0eacfbd25b99a59894b9698ea689d037b9cc Mon Sep 17 00:00:00 2001 From: Robin Phung Date: Thu, 10 Feb 2022 16:07:53 -0800 Subject: [PATCH 072/136] Add ability to initialize experiments --- lib/split/dashboard.rb | 6 ++++++ lib/split/dashboard/public/style.css | 7 +++++-- lib/split/dashboard/views/index.erb | 23 +++++++++++++++++---- spec/dashboard_spec.rb | 31 ++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 6 deletions(-) diff --git a/lib/split/dashboard.rb b/lib/split/dashboard.rb index ba7e1ab0..9d57720f 100755 --- a/lib/split/dashboard.rb +++ b/lib/split/dashboard.rb @@ -21,6 +21,7 @@ class Dashboard < Sinatra::Base get '/' do # Display experiments without a winner at the top of the dashboard @experiments = Split::ExperimentCatalog.all_active_first + @unintialized_experiments = Split.configuration.experiments.keys - @experiments.map(&:name) @metrics = Split::Metric.all @@ -33,6 +34,11 @@ class Dashboard < Sinatra::Base erb :index end + post '/initialize_experiment' do + Split::ExperimentCatalog.find_or_create(params[:experiment]) unless params[:experiment].nil? || params[:experiment].empty? + redirect url('/') + end + post '/force_alternative' do experiment = Split::ExperimentCatalog.find(params[:experiment]) alternative = Split::Alternative.new(params[:alternative], experiment.name) diff --git a/lib/split/dashboard/public/style.css b/lib/split/dashboard/public/style.css index 3c2c640e..71273f53 100644 --- a/lib/split/dashboard/public/style.css +++ b/lib/split/dashboard/public/style.css @@ -258,7 +258,7 @@ body { color: #408C48; } -a.button, button, input[type="submit"] { +.experiment a.button, .experiment button, .experiment input[type="submit"] { padding: 4px 10px; overflow: hidden; background: #d8dae0; @@ -312,10 +312,13 @@ a.button.green:focus, button.green:focus, input[type="submit"].green:focus { background:#768E7A; } -#filter, #clear-filter { +.dashboard-controls input, .dashboard-controls select { padding: 10px; } +.dashboard-controls-bottom { + margin-top: 10px; +} .pagination { text-align: center; diff --git a/lib/split/dashboard/views/index.erb b/lib/split/dashboard/views/index.erb index 2bdbb708..b34f5d60 100644 --- a/lib/split/dashboard/views/index.erb +++ b/lib/split/dashboard/views/index.erb @@ -1,10 +1,12 @@ <% if @experiments.any? %>

The list below contains all the registered experiments along with the number of test participants, completed and conversion rate currently in the system.

- - - - +
+ + + + +
<% paginated(@experiments).each do |experiment| %> <% if experiment.goals.empty? %> @@ -24,3 +26,16 @@

No experiments have started yet, you need to define them in your code and introduce them to your users.

Check out the Readme for more help getting started.

<% end %> + +
+ " method='post'> + + + + +
diff --git a/spec/dashboard_spec.rb b/spec/dashboard_spec.rb index 314bbf5b..ef2f0f06 100644 --- a/spec/dashboard_spec.rb +++ b/spec/dashboard_spec.rb @@ -201,6 +201,37 @@ def link(color) end end + describe "initialize experiment" do + before do + Split.configuration.experiments = { + :my_experiment => { + :alternatives => [ "control", "alternative" ], + } + } + end + + it "initializes the experiment when the experiment is given" do + expect(Split::ExperimentCatalog.find("my_experiment")).to be nil + + post "/initialize_experiment", { experiment: "my_experiment"} + + experiment = Split::ExperimentCatalog.find("my_experiment") + expect(experiment).to be_a(Split::Experiment) + end + + it "does not attempt to intialize the experiment when empty experiment is given" do + post "/initialize_experiment", { experiment: ""} + + expect(Split::ExperimentCatalog).to_not receive(:find_or_create) + end + + it "does not attempt to intialize the experiment when no experiment is given" do + post "/initialize_experiment" + + expect(Split::ExperimentCatalog).to_not receive(:find_or_create) + end + end + it "should reset an experiment" do red_link.participant_count = 5 blue_link.participant_count = 7 From da3d77c041d577dc96556ddf685abd0175c2cc9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 16:16:19 -0300 Subject: [PATCH 073/136] Update Ruby and Rails requirements on README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d30112ef..7542aafa 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,13 @@ Split is designed to be hacker friendly, allowing for maximum customisation and ### Requirements -Split currently requires Ruby 1.9.3 or higher. If your project requires compatibility with Ruby 1.8.x and Rails 2.3, please use v0.8.0. +Split v4.0+ is currently tested with Ruby >= 2.5 and Rails >= 5.0. + +If your project requires compatibility with Ruby 2.4.x or older Rails versions. You can try v3.0 or v0.8.0(for Ruby 1.9.3) Split uses Redis as a datastore. -Split only supports Redis 2.0 or greater. +Split only supports Redis 4.0 or greater. If you're on OS X, Homebrew is the simplest way to install Redis: From c686794aafa120fafe6178e1788d0a8a89f1313f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 16:28:29 -0300 Subject: [PATCH 074/136] Remove unused Gemfiles from tests --- README.md | 2 +- gemfiles/5.0.gemfile | 9 --------- gemfiles/5.1.gemfile | 9 --------- 3 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 gemfiles/5.0.gemfile delete mode 100644 gemfiles/5.1.gemfile diff --git a/README.md b/README.md index 7542aafa..109b61ec 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Split is designed to be hacker friendly, allowing for maximum customisation and ### Requirements -Split v4.0+ is currently tested with Ruby >= 2.5 and Rails >= 5.0. +Split v4.0+ is currently tested with Ruby >= 2.5 and Rails >= 5.2. If your project requires compatibility with Ruby 2.4.x or older Rails versions. You can try v3.0 or v0.8.0(for Ruby 1.9.3) diff --git a/gemfiles/5.0.gemfile b/gemfiles/5.0.gemfile deleted file mode 100644 index a289e546..00000000 --- a/gemfiles/5.0.gemfile +++ /dev/null @@ -1,9 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "appraisal" -gem "codeclimate-test-reporter" -gem "rails", "~> 5.0" - -gemspec path: "../" diff --git a/gemfiles/5.1.gemfile b/gemfiles/5.1.gemfile deleted file mode 100644 index fc8263b9..00000000 --- a/gemfiles/5.1.gemfile +++ /dev/null @@ -1,9 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "appraisal" -gem "codeclimate-test-reporter" -gem "rails", "~> 5.1" - -gemspec path: "../" From d10991543908f3ff84278d173e6337ca46650d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 16:58:07 -0300 Subject: [PATCH 075/136] Encapsulate Split::Algorithms at our own module to avoid explicit calling rubystats everywhere --- lib/split.rb | 1 + lib/split/algorithms.rb | 11 +++++++++++ lib/split/algorithms/whiplash.rb | 3 +-- lib/split/experiment.rb | 4 +--- 4 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 lib/split/algorithms.rb diff --git a/lib/split.rb b/lib/split.rb index 9c87343d..5ffd5214 100755 --- a/lib/split.rb +++ b/lib/split.rb @@ -2,6 +2,7 @@ require 'redis' +require 'split/algorithms' require 'split/algorithms/block_randomization' require 'split/algorithms/weighted_sample' require 'split/algorithms/whiplash' diff --git a/lib/split/algorithms.rb b/lib/split/algorithms.rb new file mode 100644 index 00000000..75b832de --- /dev/null +++ b/lib/split/algorithms.rb @@ -0,0 +1,11 @@ +require 'rubystats' + +module Split + module Algorithms + class << self + def beta_distribution_rng(a, b) + Rubystats::BetaDistribution.new(a, b).rng + end + end + end +end \ No newline at end of file diff --git a/lib/split/algorithms/whiplash.rb b/lib/split/algorithms/whiplash.rb index 6f9d6540..1a354c49 100644 --- a/lib/split/algorithms/whiplash.rb +++ b/lib/split/algorithms/whiplash.rb @@ -2,7 +2,6 @@ # A multi-armed bandit implementation inspired by # @aaronsw and victorykit/whiplash -require 'rubystats' module Split module Algorithms @@ -17,7 +16,7 @@ def choose_alternative(experiment) def arm_guess(participants, completions) a = [participants, 0].max b = [participants-completions, 0].max - Rubystats::BetaDistribution.new(a+fairness_constant, b+fairness_constant).rng + Split::Algorithms.beta_distribution_rng(a + fairness_constant, b + fairness_constant) end def best_guess(alternatives) diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 5010a4ef..2b892e85 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'rubystats' - module Split class Experiment attr_accessor :name @@ -363,7 +361,7 @@ def calc_simulated_conversion_rates(beta_params) beta_params.each do |alternative, params| alpha = params[0] beta = params[1] - simulated_conversion_rate = Rubystats::BetaDistribution.new(alpha, beta).rng + simulated_conversion_rate = Split::Algorithms.beta_distribution_rng(alpha, beta) simulated_cr_hash[alternative] = simulated_conversion_rate end From 1faca22fdcc0296788fcd1a818bd8d0b961eda4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 17:03:15 -0300 Subject: [PATCH 076/136] Require matrix to be installed in order to make it compatible with Ruby 3.1+ --- Gemfile | 1 + lib/split/algorithms.rb | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/Gemfile b/Gemfile index 479b7d8a..05826da4 100644 --- a/Gemfile +++ b/Gemfile @@ -4,5 +4,6 @@ source "https://rubygems.org" gemspec +gem "matrix" gem "appraisal" gem "codeclimate-test-reporter" diff --git a/lib/split/algorithms.rb b/lib/split/algorithms.rb index 75b832de..eddc0d01 100644 --- a/lib/split/algorithms.rb +++ b/lib/split/algorithms.rb @@ -1,3 +1,12 @@ +begin + require "matrix" +rescue LoadError => error + if error.message.match?(/matrix/) + $stderr.puts "You don't have matrix installed in your application. Please add it to your Gemfile and run bundle install" + raise + end +end + require 'rubystats' module Split From c54ed802acc1590f7ef241fb037e929e142c7af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 17:16:13 -0300 Subject: [PATCH 077/136] Add Ruby 3.1 to the matrix --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e8fe56d..9d58a8ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,8 +34,8 @@ jobs: - gemfile: 7.0.gemfile ruby: '3.0' - # - gemfile: 7.0.gemfile - # ruby: '3.1' + - gemfile: 7.0.gemfile + ruby: '3.1' runs-on: ubuntu-latest From 6280af138798082386f495065231ff41498b5d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 17:33:51 -0300 Subject: [PATCH 078/136] Add matrix to Rails 7.0 gemfile --- gemfiles/7.0.gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/gemfiles/7.0.gemfile b/gemfiles/7.0.gemfile index 220c35ab..dd721c01 100644 --- a/gemfiles/7.0.gemfile +++ b/gemfiles/7.0.gemfile @@ -5,5 +5,6 @@ source "https://rubygems.org" gem "appraisal" gem "codeclimate-test-reporter" gem "rails", "~> 7.0" +gem "matrix" gemspec path: "../" From a47651c27f7a32f069592a3c3fa7a3dbc60289d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 17:38:16 -0300 Subject: [PATCH 079/136] Remove appraisals --- .rubocop.yml | 1 - Appraisals | 19 ------------------- Gemfile | 1 - Rakefile | 1 - gemfiles/5.2.gemfile | 3 --- gemfiles/6.0.gemfile | 3 --- gemfiles/6.1.gemfile | 3 --- gemfiles/7.0.gemfile | 3 --- 8 files changed, 34 deletions(-) delete mode 100644 Appraisals diff --git a/.rubocop.yml b/.rubocop.yml index 08f75e8f..ad011bd1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -4,7 +4,6 @@ AllCops: TargetRubyVersion: 2.5 DisabledByDefault: true Exclude: - - 'Appraisals' - 'gemfiles/**/*' - 'spec/**/*.rb' diff --git a/Appraisals b/Appraisals deleted file mode 100644 index 7c855c23..00000000 --- a/Appraisals +++ /dev/null @@ -1,19 +0,0 @@ -appraise "4.2" do - gem "rails", "~> 4.2" -end - -appraise "5.0" do - gem "rails", "~> 5.0" -end - -appraise "5.1" do - gem "rails", "~> 5.1" -end - -appraise "5.2" do - gem "rails", "~> 5.2" -end - -appraise "6.0" do - gem 'rails', '~> 6.0' -end diff --git a/Gemfile b/Gemfile index 05826da4..42dcaf57 100644 --- a/Gemfile +++ b/Gemfile @@ -5,5 +5,4 @@ source "https://rubygems.org" gemspec gem "matrix" -gem "appraisal" gem "codeclimate-test-reporter" diff --git a/Rakefile b/Rakefile index 4444c77f..76d8776e 100755 --- a/Rakefile +++ b/Rakefile @@ -3,7 +3,6 @@ require 'bundler/gem_tasks' require 'rspec/core/rake_task' -require 'appraisal' RSpec::Core::RakeTask.new('spec') diff --git a/gemfiles/5.2.gemfile b/gemfiles/5.2.gemfile index 1c5d67bc..3cf9b401 100644 --- a/gemfiles/5.2.gemfile +++ b/gemfiles/5.2.gemfile @@ -1,8 +1,5 @@ -# This file was generated by Appraisal - source "https://rubygems.org" -gem "appraisal" gem "codeclimate-test-reporter" gem "rails", "~> 5.2" diff --git a/gemfiles/6.0.gemfile b/gemfiles/6.0.gemfile index 4960099f..c4f80588 100644 --- a/gemfiles/6.0.gemfile +++ b/gemfiles/6.0.gemfile @@ -1,8 +1,5 @@ -# This file was generated by Appraisal - source "https://rubygems.org" -gem "appraisal" gem "codeclimate-test-reporter" gem "rails", "~> 6.0" diff --git a/gemfiles/6.1.gemfile b/gemfiles/6.1.gemfile index 0342c40b..1aea2d76 100644 --- a/gemfiles/6.1.gemfile +++ b/gemfiles/6.1.gemfile @@ -1,8 +1,5 @@ -# This file was generated by Appraisal - source "https://rubygems.org" -gem "appraisal" gem "codeclimate-test-reporter" gem "rails", "~> 6.1" diff --git a/gemfiles/7.0.gemfile b/gemfiles/7.0.gemfile index dd721c01..29da9d65 100644 --- a/gemfiles/7.0.gemfile +++ b/gemfiles/7.0.gemfile @@ -1,8 +1,5 @@ -# This file was generated by Appraisal - source "https://rubygems.org" -gem "appraisal" gem "codeclimate-test-reporter" gem "rails", "~> 7.0" gem "matrix" From 49bf6c237357ac3739165fd24483b8f336e14dff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:05:49 -0300 Subject: [PATCH 080/136] Remove usage of deprecated implicit block expectation from specs --- spec/combined_experiments_helper_spec.rb | 6 +-- spec/encapsulated_helper_spec.rb | 2 +- spec/experiment_spec.rb | 4 +- spec/helper_spec.rb | 52 ++++++++++++------------ spec/spec_helper.rb | 1 + 5 files changed, 33 insertions(+), 32 deletions(-) diff --git a/spec/combined_experiments_helper_spec.rb b/spec/combined_experiments_helper_spec.rb index c9d0d3b3..129eb258 100644 --- a/spec/combined_experiments_helper_spec.rb +++ b/spec/combined_experiments_helper_spec.rb @@ -26,7 +26,7 @@ let!(:config_enabled) { false } it "raises an error" do - expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError ) + expect { ab_combined_test :combined_exp_1 }.to raise_error(Split::InvalidExperimentsFormatError ) end end @@ -34,7 +34,7 @@ let!(:allow_multiple_experiments) { false } it "raises an error if multiple experiments is disabled" do - expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError) + expect { ab_combined_test :combined_exp_1 }.to raise_error(Split::InvalidExperimentsFormatError) end end @@ -42,7 +42,7 @@ let!(:combined_experiments) { nil } it "raises an error" do - expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError ) + expect { ab_combined_test :combined_exp_1 }.to raise_error(Split::InvalidExperimentsFormatError ) end end diff --git a/spec/encapsulated_helper_spec.rb b/spec/encapsulated_helper_spec.rb index a39d98c9..e6e7aa32 100644 --- a/spec/encapsulated_helper_spec.rb +++ b/spec/encapsulated_helper_spec.rb @@ -17,7 +17,7 @@ def params it "should not raise an error when params raises an error" do expect{ params }.to raise_error(NoMethodError) - expect(lambda { ab_test('link_color', 'blue', 'red') }).not_to raise_error + expect { ab_test('link_color', 'blue', 'red') }.not_to raise_error end it "calls the block with selected alternative" do diff --git a/spec/experiment_spec.rb b/spec/experiment_spec.rb index 98699e4c..ecaeeab0 100644 --- a/spec/experiment_spec.rb +++ b/spec/experiment_spec.rb @@ -494,8 +494,8 @@ def same_but_different_alternative describe 'alternatives passed as non-strings' do it "should throw an exception if an alternative is passed that is not a string" do - expect(lambda { Split::ExperimentCatalog.find_or_create('link_color', :blue, :red) }).to raise_error(ArgumentError) - expect(lambda { Split::ExperimentCatalog.find_or_create('link_enabled', true, false) }).to raise_error(ArgumentError) + expect { Split::ExperimentCatalog.find_or_create('link_color', :blue, :red) }.to raise_error(ArgumentError) + expect { Split::ExperimentCatalog.find_or_create('link_enabled', true, false) }.to raise_error(ArgumentError) end end diff --git a/spec/helper_spec.rb b/spec/helper_spec.rb index 0e0b4fc7..6decfe71 100755 --- a/spec/helper_spec.rb +++ b/spec/helper_spec.rb @@ -12,27 +12,27 @@ describe "ab_test" do it "should not raise an error when passed strings for alternatives" do - expect(lambda { ab_test('xyz', '1', '2', '3') }).not_to raise_error + expect { ab_test('xyz', '1', '2', '3') }.not_to raise_error end it "should not raise an error when passed an array for alternatives" do - expect(lambda { ab_test('xyz', ['1', '2', '3']) }).not_to raise_error + expect { ab_test('xyz', ['1', '2', '3']) }.not_to raise_error end it "should raise the appropriate error when passed integers for alternatives" do - expect(lambda { ab_test('xyz', 1, 2, 3) }).to raise_error(ArgumentError) + expect { ab_test('xyz', 1, 2, 3) }.to raise_error(ArgumentError) end it "should raise the appropriate error when passed symbols for alternatives" do - expect(lambda { ab_test('xyz', :a, :b, :c) }).to raise_error(ArgumentError) + expect { ab_test('xyz', :a, :b, :c) }.to raise_error(ArgumentError) end it "should not raise error when passed an array for goals" do - expect(lambda { ab_test({'link_color' => ["purchase", "refund"]}, 'blue', 'red') }).not_to raise_error + expect { ab_test({'link_color' => ["purchase", "refund"]}, 'blue', 'red') }.not_to raise_error end it "should not raise error when passed just one goal" do - expect(lambda { ab_test({'link_color' => "purchase"}, 'blue', 'red') }).not_to raise_error + expect { ab_test({'link_color' => "purchase"}, 'blue', 'red') }.not_to raise_error end it "raises an appropriate error when processing combined expirements" do @@ -44,7 +44,7 @@ } } Split::ExperimentCatalog.find_or_create('combined_exp_1') - expect(lambda { ab_test('combined_exp_1')}).to raise_error(Split::InvalidExperimentsFormatError ) + expect { ab_test('combined_exp_1') }.to raise_error(Split::InvalidExperimentsFormatError ) end it "should assign a random alternative to a new user when there are an equal number of alternatives assigned" do @@ -67,28 +67,28 @@ it 'should not increment the counter for an experiment that the user is not participating in' do ab_test('link_color', 'blue', 'red') e = Split::ExperimentCatalog.find_or_create('button_size', 'small', 'big') - expect(lambda { + expect { # User shouldn't participate in this second experiment ab_test('button_size', 'small', 'big') - }).not_to change { e.participant_count } + }.not_to change { e.participant_count } end it 'should not increment the counter for an ended experiment' do e = Split::ExperimentCatalog.find_or_create('button_size', 'small', 'big') e.winner = 'small' - expect(lambda { + expect { a = ab_test('button_size', 'small', 'big') expect(a).to eq('small') - }).not_to change { e.participant_count } + }.not_to change { e.participant_count } end it 'should not increment the counter for an not started experiment' do expect(Split.configuration).to receive(:start_manually).and_return(true) e = Split::ExperimentCatalog.find_or_create('button_size', 'small', 'big') - expect(lambda { + expect { a = ab_test('button_size', 'small', 'big') expect(a).to eq('small') - }).not_to change { e.participant_count } + }.not_to change { e.participant_count } end it "should return the given alternative for an existing user" do @@ -370,9 +370,9 @@ e.winner = 'small' a = ab_test('button_size', 'small', 'big') expect(a).to eq('small') - expect(lambda { + expect { ab_finished('button_size') - }).not_to change { Split::Alternative.new(a, 'button_size').completed_count } + }.not_to change { Split::Alternative.new(a, 'button_size').completed_count } end it "should clear out the user's participation from their session" do @@ -431,9 +431,9 @@ # receive the control for button_size. As the user is not participating in # the button size experiment, finishing it should not increase the # completion count for that alternative. - expect(lambda { + expect { ab_finished('button_size') - }).not_to change { Split::Alternative.new('small', 'button_size').completed_count } + }.not_to change { Split::Alternative.new('small', 'button_size').completed_count } end end @@ -875,13 +875,13 @@ def should_finish_experiment(experiment_name, should_finish=true) describe 'ab_test' do it 'should raise an exception' do - expect(lambda { ab_test('link_color', 'blue', 'red') }).to raise_error(Errno::ECONNREFUSED) + expect { ab_test('link_color', 'blue', 'red') }.to raise_error(Errno::ECONNREFUSED) end end describe 'finished' do it 'should raise an exception' do - expect(lambda { ab_finished('link_color') }).to raise_error(Errno::ECONNREFUSED) + expect { ab_finished('link_color') }.to raise_error(Errno::ECONNREFUSED) end end @@ -893,12 +893,12 @@ def should_finish_experiment(experiment_name, should_finish=true) end it "should not attempt to connect to redis" do - expect(lambda { ab_test('link_color', 'blue', 'red') }).not_to raise_error + expect { ab_test('link_color', 'blue', 'red') }.not_to raise_error end it "should return control variable" do expect(ab_test('link_color', 'blue', 'red')).to eq('blue') - expect(lambda { ab_finished('link_color') }).not_to raise_error + expect { ab_finished('link_color') }.not_to raise_error end end end @@ -912,7 +912,7 @@ def should_finish_experiment(experiment_name, should_finish=true) describe 'ab_test' do it 'should not raise an exception' do - expect(lambda { ab_test('link_color', 'blue', 'red') }).not_to raise_error + expect { ab_test('link_color', 'blue', 'red') }.not_to raise_error end it 'should call db_failover_on_db_error proc with error as parameter' do @@ -971,7 +971,7 @@ def should_finish_experiment(experiment_name, should_finish=true) describe 'finished' do it 'should not raise an exception' do - expect(lambda { ab_finished('link_color') }).not_to raise_error + expect { ab_finished('link_color') }.not_to raise_error end it 'should call db_failover_on_db_error proc with error as parameter' do @@ -1086,16 +1086,16 @@ def should_finish_experiment(experiment_name, should_finish=true) it "fails gracefully if config is missing experiment" do Split.configuration.experiments = { :other_experiment => { :foo => "Bar" } } - expect(lambda { ab_test :my_experiment }).to raise_error(Split::ExperimentNotFound) + expect { ab_test :my_experiment }.to raise_error(Split::ExperimentNotFound) end it "fails gracefully if config is missing" do - expect(lambda { Split.configuration.experiments = nil }).to raise_error(Split::InvalidExperimentsFormatError) + expect { Split.configuration.experiments = nil }.to raise_error(Split::InvalidExperimentsFormatError) end it "fails gracefully if config is missing alternatives" do Split.configuration.experiments[:my_experiment] = { :foo => "Bar" } - expect(lambda { ab_test :my_experiment }).to raise_error(NoMethodError) + expect { ab_test :my_experiment }.to raise_error(NoMethodError) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 52c39f75..beb529b0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -31,6 +31,7 @@ module GlobalSharedContext RSpec.configure do |config| config.order = 'random' config.include GlobalSharedContext + config.raise_errors_for_deprecations! end def session From c02d1c185aa1b4c971a0600ced00758a1e3c79e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:12:21 -0300 Subject: [PATCH 081/136] Fix rubocop Layout issues --- lib/split/algorithms.rb | 2 +- lib/split/cache.rb | 1 - lib/split/experiment_catalog.rb | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/split/algorithms.rb b/lib/split/algorithms.rb index eddc0d01..defd8325 100644 --- a/lib/split/algorithms.rb +++ b/lib/split/algorithms.rb @@ -17,4 +17,4 @@ def beta_distribution_rng(a, b) end end end -end \ No newline at end of file +end diff --git a/lib/split/cache.rb b/lib/split/cache.rb index 6611bfdb..f570af7f 100644 --- a/lib/split/cache.rb +++ b/lib/split/cache.rb @@ -2,7 +2,6 @@ module Split class Cache - def self.clear @cache = nil end diff --git a/lib/split/experiment_catalog.rb b/lib/split/experiment_catalog.rb index 649b448a..74d481be 100644 --- a/lib/split/experiment_catalog.rb +++ b/lib/split/experiment_catalog.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module Split class ExperimentCatalog # Return all experiments From accf1576b5a8fe4f7f67a6815dcb74a5c2c6f17d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:17:47 -0300 Subject: [PATCH 082/136] Fix Style/HashSyntax offenses --- .rubocop_todo.yml | 14 --- Rakefile | 2 +- lib/split/experiment.rb | 2 +- lib/split/experiment_catalog.rb | 2 +- lib/split/helper.rb | 20 ++-- lib/split/metric.rb | 4 +- lib/split/persistence/redis_adapter.rb | 2 +- spec/alternative_spec.rb | 28 ++--- spec/combined_experiments_helper_spec.rb | 8 +- spec/configuration_spec.rb | 28 +++-- spec/dashboard_spec.rb | 2 +- spec/experiment_spec.rb | 22 ++-- spec/helper_spec.rb | 128 +++++++++++------------ spec/metric_spec.rb | 4 +- spec/persistence/cookie_adapter_spec.rb | 4 +- spec/persistence/redis_adapter_spec.rb | 18 ++-- spec/persistence/session_adapter_spec.rb | 2 +- spec/trial_spec.rb | 52 ++++----- spec/user_spec.rb | 2 +- 19 files changed, 164 insertions(+), 180 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 81cda7db..8d6cad1d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -186,20 +186,6 @@ Style/DefWithParentheses: Exclude: - 'lib/split/helper.rb' -# Offense count: 23 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. -# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys -Style/HashSyntax: - Exclude: - - 'Rakefile' - - 'lib/split/experiment.rb' - - 'lib/split/experiment_catalog.rb' - - 'lib/split/helper.rb' - - 'lib/split/metric.rb' - - 'lib/split/persistence.rb' - - 'lib/split/persistence/redis_adapter.rb' - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/Rakefile b/Rakefile index 76d8776e..f47f8b65 100755 --- a/Rakefile +++ b/Rakefile @@ -6,4 +6,4 @@ require 'rspec/core/rake_task' RSpec::Core::RakeTask.new('spec') -task :default => :spec \ No newline at end of file +task default: :spec \ No newline at end of file diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 2b892e85..b31e09a4 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -11,7 +11,7 @@ class Experiment attr_reader :resettable DEFAULT_OPTIONS = { - :resettable => true + resettable: true } def self.find(name) diff --git a/lib/split/experiment_catalog.rb b/lib/split/experiment_catalog.rb index 74d481be..076fd3e1 100644 --- a/lib/split/experiment_catalog.rb +++ b/lib/split/experiment_catalog.rb @@ -27,7 +27,7 @@ def self.find_or_initialize(metric_descriptor, control = nil, *alternatives) experiment_name_with_version, goals = normalize_experiment(metric_descriptor) experiment_name = experiment_name_with_version.to_s.split(':')[0] Split::Experiment.new(experiment_name, - :alternatives => [control].compact + alternatives, :goals => goals) + alternatives: [control].compact + alternatives, goals: goals) end def self.find_or_create(metric_descriptor, control = nil, *alternatives) diff --git a/lib/split/helper.rb b/lib/split/helper.rb index 5494ca42..983cdded 100644 --- a/lib/split/helper.rb +++ b/lib/split/helper.rb @@ -12,9 +12,9 @@ def ab_test(metric_descriptor, control = nil, *alternatives) alternative = if Split.configuration.enabled && !exclude_visitor? experiment.save raise(Split::InvalidExperimentsFormatError) unless (Split.configuration.experiments || {}).fetch(experiment.name.to_sym, {})[:combined_experiments].nil? - trial = Trial.new(:user => ab_user, :experiment => experiment, - :override => override_alternative(experiment.name), :exclude => exclude_visitor?, - :disabled => split_generically_disabled?) + trial = Trial.new(user: ab_user, experiment: experiment, + override: override_alternative(experiment.name), exclude: exclude_visitor?, + disabled: split_generically_disabled?) alt = trial.choose!(self) alt ? alt.name : nil else @@ -44,7 +44,7 @@ def reset!(experiment) ab_user.delete(experiment.key) end - def finish_experiment(experiment, options = {:reset => true}) + def finish_experiment(experiment, options = {reset: true}) return false if active_experiments[experiment.name].nil? return true if experiment.has_winner? should_reset = experiment.resettable? && options[:reset] @@ -53,10 +53,10 @@ def finish_experiment(experiment, options = {:reset => true}) else alternative_name = ab_user[experiment.key] trial = Trial.new( - :user => ab_user, - :experiment => experiment, - :alternative => alternative_name, - :goals => options[:goals], + user: ab_user, + experiment: experiment, + alternative: alternative_name, + goals: options[:goals], ) trial.complete!(self) @@ -69,7 +69,7 @@ def finish_experiment(experiment, options = {:reset => true}) end end - def ab_finished(metric_descriptor, options = {:reset => true}) + def ab_finished(metric_descriptor, options = {reset: true}) return if exclude_visitor? || Split.configuration.disabled? metric_descriptor, goals = normalize_metric(metric_descriptor) experiments = Metric.possible_experiments(metric_descriptor) @@ -77,7 +77,7 @@ def ab_finished(metric_descriptor, options = {:reset => true}) if experiments.any? experiments.each do |experiment| next if override_present?(experiment.key) - finish_experiment(experiment, options.merge(:goals => goals)) + finish_experiment(experiment, options.merge(goals: goals)) end end rescue => e diff --git a/lib/split/metric.rb b/lib/split/metric.rb index b367da6c..6b4f1a9a 100644 --- a/lib/split/metric.rb +++ b/lib/split/metric.rb @@ -22,7 +22,7 @@ def self.load_from_redis(name) Split::ExperimentCatalog.find(experiment_name) end - Split::Metric.new(:name => name, :experiments => experiments) + Split::Metric.new(name: name, experiments: experiments) else nil end @@ -31,7 +31,7 @@ def self.load_from_redis(name) def self.load_from_configuration(name) metrics = Split.configuration.metrics if metrics && metrics[name] - Split::Metric.new(:experiments => metrics[name], :name => name) + Split::Metric.new(experiments: metrics[name], name: name) else nil end diff --git a/lib/split/persistence/redis_adapter.rb b/lib/split/persistence/redis_adapter.rb index 40ed3fd2..480f38ae 100644 --- a/lib/split/persistence/redis_adapter.rb +++ b/lib/split/persistence/redis_adapter.rb @@ -3,7 +3,7 @@ module Split module Persistence class RedisAdapter - DEFAULT_CONFIG = {:namespace => 'persistence'}.freeze + DEFAULT_CONFIG = {namespace: 'persistence'}.freeze attr_reader :redis_key diff --git a/spec/alternative_spec.rb b/spec/alternative_spec.rb index 5e6d3704..8ed16fa2 100644 --- a/spec/alternative_spec.rb +++ b/spec/alternative_spec.rb @@ -29,7 +29,7 @@ describe 'weights' do it "should set the weights" do - experiment = Split::Experiment.new('basket_text', :alternatives => [{'Basket' => 0.6}, {"Cart" => 0.4}]) + experiment = Split::Experiment.new('basket_text', alternatives: [{'Basket' => 0.6}, {"Cart" => 0.4}]) first = experiment.alternatives[0] expect(first.name).to eq('Basket') expect(first.weight).to eq(0.6) @@ -41,11 +41,11 @@ it "accepts probability on alternatives" do Split.configuration.experiments = { - :my_experiment => { - :alternatives => [ - { :name => "control_opt", :percent => 67 }, - { :name => "second_opt", :percent => 10 }, - { :name => "third_opt", :percent => 23 }, + my_experiment: { + alternatives: [ + { name: "control_opt", percent: 67 }, + { name: "second_opt", percent: 10 }, + { name: "third_opt", percent: 23 }, ] } } @@ -61,11 +61,11 @@ it "accepts probability on some alternatives" do Split.configuration.experiments = { - :my_experiment => { - :alternatives => [ - { :name => "control_opt", :percent => 34 }, + my_experiment: { + alternatives: [ + { name: "control_opt", percent: 34 }, "second_opt", - { :name => "third_opt", :percent => 23 }, + { name: "third_opt", percent: 23 }, "fourth_opt", ], } @@ -87,11 +87,11 @@ # it "allows name param without probability" do Split.configuration.experiments = { - :my_experiment => { - :alternatives => [ - { :name => "control_opt" }, + my_experiment: { + alternatives: [ + { name: "control_opt" }, "second_opt", - { :name => "third_opt", :percent => 64 }, + { name: "third_opt", percent: 64 }, ], } } diff --git a/spec/combined_experiments_helper_spec.rb b/spec/combined_experiments_helper_spec.rb index 129eb258..a5561fb5 100644 --- a/spec/combined_experiments_helper_spec.rb +++ b/spec/combined_experiments_helper_spec.rb @@ -12,10 +12,10 @@ before do Split.configuration.experiments = { - :combined_exp_1 => { - :alternatives => [ {"control"=> 0.5}, {"test-alt"=> 0.5} ], - :metric => :my_metric, - :combined_experiments => combined_experiments + combined_exp_1: { + alternatives: [ {"control"=> 0.5}, {"test-alt"=> 0.5} ], + metric: :my_metric, + combined_experiments: combined_experiments } } Split.configuration.enabled = config_enabled diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb index c065229c..6299e949 100644 --- a/spec/configuration_spec.rb +++ b/spec/configuration_spec.rb @@ -58,17 +58,15 @@ end it "should load a metric" do - @config.experiments = {:my_experiment=> - {:alternatives=>["control_opt", "other_opt"], :metric=>:my_metric}} + @config.experiments = { my_experiment: {alternatives: ["control_opt", "other_opt"], metric: :my_metric } } expect(@config.metrics).not_to be_nil expect(@config.metrics.keys).to eq([:my_metric]) end it "should allow loading of experiment using experment_for" do - @config.experiments = {:my_experiment=> - {:alternatives=>["control_opt", "other_opt"], :metric=>:my_metric}} - expect(@config.experiment_for(:my_experiment)).to eq({:alternatives=>["control_opt", ["other_opt"]]}) + @config.experiments = { my_experiment: { alternatives: ["control_opt", "other_opt"], metric: :my_metric } } + expect(@config.experiment_for(:my_experiment)).to eq({ alternatives: ["control_opt", ["other_opt"]] }) end context "when experiments are defined via YAML" do @@ -87,7 +85,7 @@ end it 'should normalize experiments' do - expect(@config.normalized_experiments).to eq({:my_experiment=>{:resettable=>false,:alternatives=>["Control Opt", ["Alt One", "Alt Two"]]}}) + expect(@config.normalized_experiments).to eq({ my_experiment: { resettable: false, alternatives: ["Control Opt", ["Alt One", "Alt Two"]] } }) end end @@ -143,8 +141,8 @@ end it "should normalize experiments" do - expect(@config.normalized_experiments).to eq({:my_experiment=>{:resettable=>false,:alternatives=>[{"Control Opt"=>0.67}, - [{"Alt One"=>0.1}, {"Alt Two"=>0.23}]]}, :another_experiment=>{:alternatives=>["a", ["b"]]}}) + expect(@config.normalized_experiments).to eq({ my_experiment: { resettable: false, alternatives: [{"Control Opt"=>0.67}, + [{"Alt One"=>0.1}, {"Alt Two"=>0.23}]]}, another_experiment: { alternatives: ["a", ["b"]]}}) end it "should recognize metrics" do @@ -171,7 +169,7 @@ end it "should normalize experiments" do - expect(@config.normalized_experiments).to eq({:my_experiment=>{:resettable=>false,:alternatives=>["Control Opt", ["Alt One", "Alt Two"]]}}) + expect(@config.normalized_experiments).to eq({my_experiment: { resettable: false, alternatives: ["Control Opt", ["Alt One", "Alt Two"]]}}) end end @@ -200,16 +198,16 @@ it "should normalize experiments" do @config.experiments = { - :my_experiment => { - :alternatives => [ - { :name => "control_opt", :percent => 67 }, - { :name => "second_opt", :percent => 10 }, - { :name => "third_opt", :percent => 23 }, + my_experiment: { + alternatives: [ + { name: "control_opt", percent: 67 }, + { name: "second_opt", percent: 10 }, + { name: "third_opt", percent: 23 }, ], } } - expect(@config.normalized_experiments).to eq({:my_experiment=>{:alternatives=>[{"control_opt"=>0.67}, [{"second_opt"=>0.1}, {"third_opt"=>0.23}]]}}) + expect(@config.normalized_experiments).to eq({ my_experiment: { alternatives: [{"control_opt"=>0.67}, [{"second_opt"=>0.1}, {"third_opt"=>0.23}]]}}) end context "redis configuration" do diff --git a/spec/dashboard_spec.rb b/spec/dashboard_spec.rb index 314bbf5b..3bf0cfa0 100644 --- a/spec/dashboard_spec.rb +++ b/spec/dashboard_spec.rb @@ -226,7 +226,7 @@ def link(color) it "should mark an alternative as the winner" do expect(experiment.winner).to be_nil - post "/experiment?experiment=#{experiment.name}", :alternative => 'red' + post "/experiment?experiment=#{experiment.name}", alternative: 'red' expect(last_response).to be_redirect expect(experiment.winner.name).to eq('red') diff --git a/spec/experiment_spec.rb b/spec/experiment_spec.rb index ecaeeab0..7d7f27f9 100644 --- a/spec/experiment_spec.rb +++ b/spec/experiment_spec.rb @@ -4,7 +4,7 @@ describe Split::Experiment do def new_experiment(goals=[]) - Split::Experiment.new('link_color', :alternatives => ['blue', 'red', 'green'], :goals => goals) + Split::Experiment.new('link_color', alternatives: ['blue', 'red', 'green'], goals: goals) end def alternative(color) @@ -17,7 +17,7 @@ def alternative(color) let(:green) { alternative("green") } context "with an experiment" do - let(:experiment) { Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"]) } + let(:experiment) { Split::Experiment.new('basket_text', alternatives: ['Basket', "Cart"]) } it "should have a name" do expect(experiment.name).to eq('basket_text') @@ -110,12 +110,12 @@ def alternative(color) describe 'initialization' do it "should set the algorithm when passed as an option to the initializer" do - experiment = Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :algorithm => Split::Algorithms::Whiplash) + experiment = Split::Experiment.new('basket_text', alternatives: ['Basket', "Cart"], algorithm: Split::Algorithms::Whiplash) expect(experiment.algorithm).to eq(Split::Algorithms::Whiplash) end it "should be possible to make an experiment not resettable" do - experiment = Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :resettable => false) + experiment = Split::Experiment.new('basket_text', alternatives: ['Basket', "Cart"], resettable: false) expect(experiment.resettable).to be_falsey end @@ -124,7 +124,7 @@ def alternative(color) let(:experiments) do { experiment_name => { - :alternatives => ['Control Opt', 'Alt one'] + alternatives: ['Control Opt', 'Alt one'] } } end @@ -140,7 +140,7 @@ def alternative(color) describe 'persistent configuration' do it "should persist resettable in redis" do - experiment = Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :resettable => false) + experiment = Split::Experiment.new('basket_text', alternatives: ['Basket', "Cart"], resettable: false) experiment.save e = Split::ExperimentCatalog.find('basket_text') @@ -150,7 +150,7 @@ def alternative(color) end describe '#metadata' do - let(:experiment) { Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :algorithm => Split::Algorithms::Whiplash, :metadata => meta) } + let(:experiment) { Split::Experiment.new('basket_text', alternatives: ['Basket', "Cart"], algorithm: Split::Algorithms::Whiplash, metadata: meta) } let(:meta) { { a: 'b' }} before do @@ -185,7 +185,7 @@ def alternative(color) end it "should persist algorithm in redis" do - experiment = Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :algorithm => Split::Algorithms::Whiplash) + experiment = Split::Experiment.new('basket_text', alternatives: ['Basket', "Cart"], algorithm: Split::Algorithms::Whiplash) experiment.save e = Split::ExperimentCatalog.find('basket_text') @@ -194,7 +194,7 @@ def alternative(color) end it "should persist a new experiment in redis, that does not exist in the configuration file" do - experiment = Split::Experiment.new('foobar', :alternatives => ['tra', 'la'], :algorithm => Split::Algorithms::Whiplash) + experiment = Split::Experiment.new('foobar', alternatives: ['tra', 'la'], algorithm: Split::Algorithms::Whiplash) experiment.save e = Split::ExperimentCatalog.find('foobar') @@ -205,7 +205,7 @@ def alternative(color) describe 'deleting' do it 'should delete itself' do - experiment = Split::Experiment.new('basket_text', :alternatives => [ 'Basket', "Cart"]) + experiment = Split::Experiment.new('basket_text', alternatives: [ 'Basket', "Cart"]) experiment.save experiment.delete @@ -562,7 +562,7 @@ def same_but_different_alternative expect(experiment.alternatives[0].p_winner).to be_within(0.04).of(0.50) end - it "should calculate the probability of being the winning alternative separately for each goal", :skip => true do + it "should calculate the probability of being the winning alternative separately for each goal", skip: true do experiment = Split::ExperimentCatalog.find_or_create({'link_color3' => ["purchase", "refund"]}, 'blue', 'red', 'green') goal1 = experiment.goals[0] goal2 = experiment.goals[1] diff --git a/spec/helper_spec.rb b/spec/helper_spec.rb index 6decfe71..ad9c87ba 100755 --- a/spec/helper_spec.rb +++ b/spec/helper_spec.rb @@ -37,10 +37,10 @@ it "raises an appropriate error when processing combined expirements" do Split.configuration.experiments = { - :combined_exp_1 => { - :alternatives => [ { name: "control", percent: 50 }, { name: "test-alt", percent: 50 } ], - :metric => :my_metric, - :combined_experiments => [:combined_exp_1_sub_1] + combined_exp_1: { + alternatives: [ { name: "control", percent: 50 }, { name: "test-alt", percent: 50 } ], + metric: :my_metric, + combined_experiments: [:combined_exp_1_sub_1] } } Split::ExperimentCatalog.find_or_create('combined_exp_1') @@ -280,10 +280,10 @@ context 'is defined' do before do Split.configuration.experiments = { - :my_experiment => { - :alternatives => ["one", "two"], - :resettable => false, - :metadata => { 'one' => 'Meta1', 'two' => 'Meta2' } + my_experiment: { + alternatives: ["one", "two"], + resettable: false, + metadata: { 'one' => 'Meta1', 'two' => 'Meta2' } } } end @@ -311,10 +311,10 @@ context 'is not defined' do before do Split.configuration.experiments = { - :my_experiment => { - :alternatives => ["one", "two"], - :resettable => false, - :metadata => nil + my_experiment: { + alternatives: ["one", "two"], + resettable: false, + metadata: nil } } end @@ -354,13 +354,13 @@ end it "should set experiment's finished key if reset is false" do - ab_finished(@experiment_name, {:reset => false}) + ab_finished(@experiment_name, {reset: false}) expect(ab_user[@experiment.key]).to eq(@alternative_name) expect(ab_user[@experiment.finished_key]).to eq(true) end it 'should not increment the counter if reset is false and the experiment has been already finished' do - 2.times { ab_finished(@experiment_name, {:reset => false}) } + 2.times { ab_finished(@experiment_name, {reset: false}) } new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count expect(new_completion_count).to eq(@previous_completion_count + 1) end @@ -383,7 +383,7 @@ it "should not clear out the users session if reset is false" do expect(ab_user[@experiment.key]).to eq(@alternative_name) - ab_finished(@experiment_name, {:reset => false}) + ab_finished(@experiment_name, {reset: false}) expect(ab_user[@experiment.key]).to eq(@alternative_name) expect(ab_user[@experiment.finished_key]).to eq(true) end @@ -465,9 +465,9 @@ context "finished with config" do it "passes reset option" do Split.configuration.experiments = { - :my_experiment => { - :alternatives => ["one", "two"], - :resettable => false, + my_experiment: { + alternatives: ["one", "two"], + resettable: false, } } alternative = ab_test(:my_experiment) @@ -499,8 +499,8 @@ def should_finish_experiment(experiment_name, should_finish=true) it "completes the test" do Split.configuration.experiments[:my_experiment] = { - :alternatives => [ "control_opt", "other_opt" ], - :metric => :my_metric + alternatives: [ "control_opt", "other_opt" ], + metric: :my_metric } should_finish_experiment :my_experiment ab_finished :my_metric @@ -508,17 +508,17 @@ def should_finish_experiment(experiment_name, should_finish=true) it "completes all relevant tests" do Split.configuration.experiments = { - :exp_1 => { - :alternatives => [ "1-1", "1-2" ], - :metric => :my_metric + exp_1: { + alternatives: [ "1-1", "1-2" ], + metric: :my_metric }, - :exp_2 => { - :alternatives => [ "2-1", "2-2" ], - :metric => :another_metric + exp_2: { + alternatives: [ "2-1", "2-2" ], + metric: :another_metric }, - :exp_3 => { - :alternatives => [ "3-1", "3-2" ], - :metric => :my_metric + exp_3: { + alternatives: [ "3-1", "3-2" ], + metric: :my_metric }, } should_finish_experiment :exp_1 @@ -529,10 +529,10 @@ def should_finish_experiment(experiment_name, should_finish=true) it "passes reset option" do Split.configuration.experiments = { - :my_exp => { - :alternatives => ["one", "two"], - :metric => :my_metric, - :resettable => false, + my_exp: { + alternatives: ["one", "two"], + metric: :my_metric, + resettable: false, } } alternative_name = ab_test(:my_exp) @@ -545,15 +545,15 @@ def should_finish_experiment(experiment_name, should_finish=true) it "passes through options" do Split.configuration.experiments = { - :my_exp => { - :alternatives => ["one", "two"], - :metric => :my_metric, + my_exp: { + alternatives: ["one", "two"], + metric: :my_metric, } } alternative_name = ab_test(:my_exp) exp = Split::ExperimentCatalog.find :my_exp - ab_finished :my_metric, :reset => false + ab_finished :my_metric, reset: false expect(ab_user[exp.key]).to eq(alternative_name) expect(ab_user[exp.finished_key]).to be_truthy end @@ -583,7 +583,7 @@ def should_finish_experiment(experiment_name, should_finish=true) it 'should show a finished test' do alternative = ab_test('def', '4', '5', '6') - ab_finished('def', {:reset => false}) + ab_finished('def', {reset: false}) expect(active_experiments.count).to eq 1 expect(active_experiments.first[0]).to eq "def" expect(active_experiments.first[1]).to eq alternative @@ -635,7 +635,7 @@ def should_finish_experiment(experiment_name, should_finish=true) describe 'when user is a robot' do before(:each) do - @request = OpenStruct.new(:user_agent => 'Googlebot/2.1 (+http://www.google.com/bot.html)') + @request = OpenStruct.new(user_agent: 'Googlebot/2.1 (+http://www.google.com/bot.html)') end describe 'ab_test' do @@ -735,7 +735,7 @@ def should_finish_experiment(experiment_name, should_finish=true) describe 'when ip address is ignored' do context "individually" do before(:each) do - @request = OpenStruct.new(:ip => '81.19.48.130') + @request = OpenStruct.new(ip: '81.19.48.130') Split.configure do |c| c.ignore_ip_addresses << '81.19.48.130' end @@ -746,7 +746,7 @@ def should_finish_experiment(experiment_name, should_finish=true) context "for a range" do before(:each) do - @request = OpenStruct.new(:ip => '81.19.48.129') + @request = OpenStruct.new(ip: '81.19.48.129') Split.configure do |c| c.ignore_ip_addresses << /81\.19\.48\.[0-9]+/ end @@ -757,7 +757,7 @@ def should_finish_experiment(experiment_name, should_finish=true) context "using both a range and a specific value" do before(:each) do - @request = OpenStruct.new(:ip => '81.19.48.128') + @request = OpenStruct.new(ip: '81.19.48.128') Split.configure do |c| c.ignore_ip_addresses << '81.19.48.130' c.ignore_ip_addresses << /81\.19\.48\.[0-9]+/ @@ -769,7 +769,7 @@ def should_finish_experiment(experiment_name, should_finish=true) context "when ignored other address" do before do - @request = OpenStruct.new(:ip => '1.1.1.1') + @request = OpenStruct.new(ip: '1.1.1.1') Split.configure do |c| c.ignore_ip_addresses << '81.19.48.130' end @@ -959,7 +959,7 @@ def should_finish_experiment(experiment_name, should_finish=true) context 'and preloaded config given' do before do Split.configuration.experiments[:link_color] = { - :alternatives => [ "blue", "red" ], + alternatives: [ "blue", "red" ], } end @@ -993,8 +993,8 @@ def should_finish_experiment(experiment_name, should_finish=true) it "pulls options from config file" do Split.configuration.experiments[:my_experiment] = { - :alternatives => [ "control_opt", "other_opt" ], - :goals => ["goal1", "goal2"] + alternatives: [ "control_opt", "other_opt" ], + goals: ["goal1", "goal2"] } ab_test :my_experiment expect(Split::Experiment.new(:my_experiment).alternatives.map(&:name)).to eq([ "control_opt", "other_opt" ]) @@ -1003,8 +1003,8 @@ def should_finish_experiment(experiment_name, should_finish=true) it "can be called multiple times" do Split.configuration.experiments[:my_experiment] = { - :alternatives => [ "control_opt", "other_opt" ], - :goals => ["goal1", "goal2"] + alternatives: [ "control_opt", "other_opt" ], + goals: ["goal1", "goal2"] } 5.times { ab_test :my_experiment } experiment = Split::Experiment.new(:my_experiment) @@ -1015,8 +1015,8 @@ def should_finish_experiment(experiment_name, should_finish=true) it "accepts multiple goals" do Split.configuration.experiments[:my_experiment] = { - :alternatives => [ "control_opt", "other_opt" ], - :goals => [ "goal1", "goal2", "goal3" ] + alternatives: [ "control_opt", "other_opt" ], + goals: [ "goal1", "goal2", "goal3" ] } ab_test :my_experiment experiment = Split::Experiment.new(:my_experiment) @@ -1025,7 +1025,7 @@ def should_finish_experiment(experiment_name, should_finish=true) it "allow specifying goals to be optional" do Split.configuration.experiments[:my_experiment] = { - :alternatives => [ "control_opt", "other_opt" ] + alternatives: [ "control_opt", "other_opt" ] } experiment = Split::Experiment.new(:my_experiment) expect(experiment.goals).to eq([]) @@ -1033,7 +1033,7 @@ def should_finish_experiment(experiment_name, should_finish=true) it "accepts multiple alternatives" do Split.configuration.experiments[:my_experiment] = { - :alternatives => [ "control_opt", "second_opt", "third_opt" ], + alternatives: [ "control_opt", "second_opt", "third_opt" ], } ab_test :my_experiment experiment = Split::Experiment.new(:my_experiment) @@ -1042,10 +1042,10 @@ def should_finish_experiment(experiment_name, should_finish=true) it "accepts probability on alternatives" do Split.configuration.experiments[:my_experiment] = { - :alternatives => [ - { :name => "control_opt", :percent => 67 }, - { :name => "second_opt", :percent => 10 }, - { :name => "third_opt", :percent => 23 }, + alternatives: [ + { name: "control_opt", percent: 67 }, + { name: "second_opt", percent: 10 }, + { name: "third_opt", percent: 23 }, ], } ab_test :my_experiment @@ -1055,10 +1055,10 @@ def should_finish_experiment(experiment_name, should_finish=true) it "accepts probability on some alternatives" do Split.configuration.experiments[:my_experiment] = { - :alternatives => [ - { :name => "control_opt", :percent => 34 }, + alternatives: [ + { name: "control_opt", percent: 34 }, "second_opt", - { :name => "third_opt", :percent => 23 }, + { name: "third_opt", percent: 23 }, "fourth_opt", ], } @@ -1071,10 +1071,10 @@ def should_finish_experiment(experiment_name, should_finish=true) it "allows name param without probability" do Split.configuration.experiments[:my_experiment] = { - :alternatives => [ - { :name => "control_opt" }, + alternatives: [ + { name: "control_opt" }, "second_opt", - { :name => "third_opt", :percent => 64 }, + { name: "third_opt", percent: 64 }, ], } ab_test :my_experiment @@ -1085,7 +1085,7 @@ def should_finish_experiment(experiment_name, should_finish=true) end it "fails gracefully if config is missing experiment" do - Split.configuration.experiments = { :other_experiment => { :foo => "Bar" } } + Split.configuration.experiments = { other_experiment: { foo: "Bar" } } expect { ab_test :my_experiment }.to raise_error(Split::ExperimentNotFound) end @@ -1094,7 +1094,7 @@ def should_finish_experiment(experiment_name, should_finish=true) end it "fails gracefully if config is missing alternatives" do - Split.configuration.experiments[:my_experiment] = { :foo => "Bar" } + Split.configuration.experiments[:my_experiment] = { foo: "Bar" } expect { ab_test :my_experiment }.to raise_error(NoMethodError) end end diff --git a/spec/metric_spec.rb b/spec/metric_spec.rb index 21bbc758..3dbc9b5f 100644 --- a/spec/metric_spec.rb +++ b/spec/metric_spec.rb @@ -13,7 +13,7 @@ experiment1 = Split::ExperimentCatalog.find_or_create('color', 'red', 'blue') experiment2 = Split::ExperimentCatalog.find_or_create('size', 'big', 'small') - metric = Split::Metric.new(:name => 'purchase', :experiments => [experiment1, experiment2]) + metric = Split::Metric.new(name: 'purchase', experiments: [experiment1, experiment2]) metric.save expect(Split::Metric.possible_experiments('purchase')).to include(experiment1, experiment2) end @@ -22,7 +22,7 @@ experiment1 = Split::ExperimentCatalog.find_or_create('purchase', 'red', 'blue') experiment2 = Split::ExperimentCatalog.find_or_create('size', 'big', 'small') - metric = Split::Metric.new(:name => 'purchase', :experiments => [experiment2]) + metric = Split::Metric.new(name: 'purchase', experiments: [experiment2]) metric.save expect(Split::Metric.possible_experiments('purchase')).to include(experiment1, experiment2) end diff --git a/spec/persistence/cookie_adapter_spec.rb b/spec/persistence/cookie_adapter_spec.rb index e9758a47..0e8719b1 100644 --- a/spec/persistence/cookie_adapter_spec.rb +++ b/spec/persistence/cookie_adapter_spec.rb @@ -14,8 +14,8 @@ it "handles invalid JSON" do context.request.cookies[:split] = { - :value => '{"foo":2,', - :expires => Time.now + value: '{"foo":2,', + expires: Time.now } expect(subject["my_key"]).to be_nil subject["my_key"] = "my_value" diff --git a/spec/persistence/redis_adapter_spec.rb b/spec/persistence/redis_adapter_spec.rb index c7321c86..d7e20213 100644 --- a/spec/persistence/redis_adapter_spec.rb +++ b/spec/persistence/redis_adapter_spec.rb @@ -3,7 +3,7 @@ describe Split::Persistence::RedisAdapter do - let(:context) { double(:lookup => 'blah') } + let(:context) { double(lookup: 'blah') } subject { Split::Persistence::RedisAdapter.new(context) } @@ -26,7 +26,7 @@ end context 'config with lookup_by = proc { "block" }' do - before { Split::Persistence::RedisAdapter.with_config(:lookup_by => proc{'block'}) } + before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc{'block'}) } it 'should be "persistence:block"' do expect(subject.redis_key).to eq('persistence:block') @@ -34,8 +34,8 @@ end context 'config with lookup_by = proc { |context| context.test }' do - before { Split::Persistence::RedisAdapter.with_config(:lookup_by => proc{'block'}) } - let(:context) { double(:test => 'block') } + before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc{'block'}) } + let(:context) { double(test: 'block') } it 'should be "persistence:block"' do expect(subject.redis_key).to eq('persistence:block') @@ -43,8 +43,8 @@ end context 'config with lookup_by = "method_name"' do - before { Split::Persistence::RedisAdapter.with_config(:lookup_by => 'method_name') } - let(:context) { double(:method_name => 'val') } + before { Split::Persistence::RedisAdapter.with_config(lookup_by: 'method_name') } + let(:context) { double(method_name: 'val') } it 'should be "persistence:bar"' do expect(subject.redis_key).to eq('persistence:val') @@ -52,7 +52,7 @@ end context 'config with namespace and lookup_by' do - before { Split::Persistence::RedisAdapter.with_config(:lookup_by => proc{'frag'}, :namespace => 'namer') } + before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc{'frag'}, namespace: 'namer') } it 'should be "namer"' do expect(subject.redis_key).to eq('namer:frag') @@ -61,7 +61,7 @@ end describe '#find' do - before { Split::Persistence::RedisAdapter.with_config(:lookup_by => proc{'frag'}, :namespace => 'a_namespace') } + before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc{'frag'}, namespace: 'a_namespace') } it "should create and user from a given key" do adapter = Split::Persistence::RedisAdapter.find(2) @@ -70,7 +70,7 @@ end context 'functional tests' do - before { Split::Persistence::RedisAdapter.with_config(:lookup_by => 'lookup') } + before { Split::Persistence::RedisAdapter.with_config(lookup_by: 'lookup') } describe "#[] and #[]=" do it "should set and return the value for given key" do diff --git a/spec/persistence/session_adapter_spec.rb b/spec/persistence/session_adapter_spec.rb index 2d57d1ed..bda18969 100644 --- a/spec/persistence/session_adapter_spec.rb +++ b/spec/persistence/session_adapter_spec.rb @@ -3,7 +3,7 @@ describe Split::Persistence::SessionAdapter do - let(:context) { double(:session => {}) } + let(:context) { double(session: {}) } subject { Split::Persistence::SessionAdapter.new(context) } describe "#[] and #[]=" do diff --git a/spec/trial_spec.rb b/spec/trial_spec.rb index c171944b..80bafe04 100644 --- a/spec/trial_spec.rb +++ b/spec/trial_spec.rb @@ -6,31 +6,31 @@ let(:user) { mock_user } let(:alternatives) { ['basket', 'cart'] } let(:experiment) do - Split::Experiment.new('basket_text', :alternatives => alternatives).save + Split::Experiment.new('basket_text', alternatives: alternatives).save end it "should be initializeable" do experiment = double('experiment') - alternative = double('alternative', :kind_of? => Split::Alternative) - trial = Split::Trial.new(:experiment => experiment, :alternative => alternative) + alternative = double('alternative', kind_of?: Split::Alternative) + trial = Split::Trial.new(experiment: experiment, alternative: alternative) expect(trial.experiment).to eq(experiment) expect(trial.alternative).to eq(alternative) end describe "alternative" do it "should use the alternative if specified" do - alternative = double('alternative', :kind_of? => Split::Alternative) - trial = Split::Trial.new(:experiment => double('experiment'), - :alternative => alternative, :user => user) + alternative = double('alternative', kind_of?: Split::Alternative) + trial = Split::Trial.new(experiment: double('experiment'), + alternative: alternative, user: user) expect(trial).not_to receive(:choose) expect(trial.alternative).to eq(alternative) end it "should load the alternative when the alternative name is set" do - experiment = Split::Experiment.new('basket_text', :alternatives => ['basket', 'cart']) + experiment = Split::Experiment.new('basket_text', alternatives: ['basket', 'cart']) experiment.save - trial = Split::Trial.new(:experiment => experiment, :alternative => 'basket') + trial = Split::Trial.new(experiment: experiment, alternative: 'basket') expect(trial.alternative.name).to eq('basket') end end @@ -38,17 +38,17 @@ describe "metadata" do let(:metadata) { Hash[alternatives.map { |k| [k, "Metadata for #{k}"] }] } let(:experiment) do - Split::Experiment.new('basket_text', :alternatives => alternatives, :metadata => metadata).save + Split::Experiment.new('basket_text', alternatives: alternatives, metadata: metadata).save end it 'has metadata on each trial' do - trial = Split::Trial.new(:experiment => experiment, :user => user, :metadata => metadata['cart'], - :override => 'cart') + trial = Split::Trial.new(experiment: experiment, user: user, metadata: metadata['cart'], + override: 'cart') expect(trial.metadata).to eq(metadata['cart']) end it 'has metadata on each trial from the experiment' do - trial = Split::Trial.new(:experiment => experiment, :user => user) + trial = Split::Trial.new(experiment: experiment, user: user) trial.choose! expect(trial.metadata).to eq(metadata[trial.alternative.name]) expect(trial.metadata).to match(/#{trial.alternative.name}/) @@ -58,7 +58,7 @@ describe "#choose!" do let(:context) { double(on_trial_callback: 'test callback') } let(:trial) do - Split::Trial.new(:user => user, :experiment => experiment) + Split::Trial.new(user: user, experiment: experiment) end shared_examples_for 'a trial with callbacks' do @@ -90,7 +90,7 @@ def expect_alternative(trial, alternative_name) context "when override is present" do let(:override) { 'cart' } let(:trial) do - Split::Trial.new(:user => user, :experiment => experiment, :override => override) + Split::Trial.new(user: user, experiment: experiment, override: override) end it_behaves_like 'a trial with callbacks' @@ -111,7 +111,7 @@ def expect_alternative(trial, alternative_name) context "when disabled option is true" do let(:trial) do - Split::Trial.new(:user => user, :experiment => experiment, :disabled => true) + Split::Trial.new(user: user, experiment: experiment, disabled: true) end it "picks the control", :aggregate_failures do @@ -141,7 +141,7 @@ def expect_alternative(trial, alternative_name) context "when experiment has winner" do let(:trial) do - Split::Trial.new(:user => user, :experiment => experiment) + Split::Trial.new(user: user, experiment: experiment) end it_behaves_like 'a trial with callbacks' @@ -156,7 +156,7 @@ def expect_alternative(trial, alternative_name) context "when exclude is true" do let(:trial) do - Split::Trial.new(:user => user, :experiment => experiment, :exclude => true) + Split::Trial.new(user: user, experiment: experiment, exclude: true) end it_behaves_like 'a trial with callbacks' @@ -230,7 +230,7 @@ def expect_alternative(trial, alternative_name) describe "#complete!" do context 'when there are no goals' do - let(:trial) { Split::Trial.new(:user => user, :experiment => experiment) } + let(:trial) { Split::Trial.new(user: user, experiment: experiment) } it 'should complete the trial' do trial.choose! old_completed_count = trial.alternative.completed_count @@ -241,7 +241,7 @@ def expect_alternative(trial, alternative_name) context "when there are many goals" do let(:goals) { [ "goal1", "goal2" ] } - let(:trial) { Split::Trial.new(:user => user, :experiment => experiment, :goals => goals) } + let(:trial) { Split::Trial.new(user: user, experiment: experiment, goals: goals) } it "increments the completed count corresponding to the goals" do trial.choose! @@ -253,7 +253,7 @@ def expect_alternative(trial, alternative_name) context "when there is 1 goal of type string" do let(:goal) { "goal" } - let(:trial) { Split::Trial.new(:user => user, :experiment => experiment, :goals => goal) } + let(:trial) { Split::Trial.new(user: user, experiment: experiment, goals: goal) } it "increments the completed count corresponding to the goal" do trial.choose! old_completed_count = trial.alternative.completed_count(goal) @@ -268,7 +268,7 @@ def expect_alternative(trial, alternative_name) context "when override is present" do it "stores when store_override is true" do - trial = Split::Trial.new(:user => user, :experiment => experiment, :override => 'basket') + trial = Split::Trial.new(user: user, experiment: experiment, override: 'basket') Split.configuration.store_override = true expect(user).to receive("[]=") @@ -277,7 +277,7 @@ def expect_alternative(trial, alternative_name) end it "does not store when store_override is false" do - trial = Split::Trial.new(:user => user, :experiment => experiment, :override => 'basket') + trial = Split::Trial.new(user: user, experiment: experiment, override: 'basket') expect(user).to_not receive("[]=") trial.choose! @@ -286,7 +286,7 @@ def expect_alternative(trial, alternative_name) context "when disabled is present" do it "stores when store_override is true" do - trial = Split::Trial.new(:user => user, :experiment => experiment, :disabled => true) + trial = Split::Trial.new(user: user, experiment: experiment, disabled: true) Split.configuration.store_override = true expect(user).to receive("[]=") @@ -294,7 +294,7 @@ def expect_alternative(trial, alternative_name) end it "does not store when store_override is false" do - trial = Split::Trial.new(:user => user, :experiment => experiment, :disabled => true) + trial = Split::Trial.new(user: user, experiment: experiment, disabled: true) expect(user).to_not receive("[]=") trial.choose! @@ -303,7 +303,7 @@ def expect_alternative(trial, alternative_name) context "when exclude is present" do it "does not store" do - trial = Split::Trial.new(:user => user, :experiment => experiment, :exclude => true) + trial = Split::Trial.new(user: user, experiment: experiment, exclude: true) expect(user).to_not receive("[]=") trial.choose! @@ -313,7 +313,7 @@ def expect_alternative(trial, alternative_name) context 'when experiment has winner' do let(:trial) do experiment.winner = 'cart' - Split::Trial.new(:user => user, :experiment => experiment) + Split::Trial.new(user: user, experiment: experiment) end it 'does not store' do diff --git a/spec/user_spec.rb b/spec/user_spec.rb index 0291e593..62e6ed0a 100644 --- a/spec/user_spec.rb +++ b/spec/user_spec.rb @@ -5,7 +5,7 @@ describe Split::User do let(:user_keys) { { 'link_color' => 'blue' } } - let(:context) { double(:session => { split: user_keys }) } + let(:context) { double(session: { split: user_keys }) } let(:experiment) { Split::Experiment.new('link_color') } before(:each) do From 88d600edf7b551adba401d6481588ee737b27de1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:30:41 -0300 Subject: [PATCH 083/136] Fix Layout/EmptyLinesAroundAccessModifier --- .rubocop_todo.yml | 20 ----- lib/split/algorithms/block_randomization.rb | 1 - lib/split/algorithms/whiplash.rb | 1 - lib/split/alternative.rb | 1 - lib/split/configuration.rb | 1 - lib/split/dashboard/pagination_helpers.rb | 1 - lib/split/encapsulated_helper.rb | 1 - lib/split/experiment.rb | 2 - lib/split/goals_collection.rb | 1 - lib/split/persistence/cookie_adapter.rb | 1 - lib/split/persistence/dual_adapter.rb | 1 - lib/split/redis_interface.rb | 1 - lib/split/trial.rb | 1 - lib/split/user.rb | 1 - spec/algorithms/block_randomization_spec.rb | 1 - spec/algorithms/weighted_sample_spec.rb | 7 +- spec/algorithms/whiplash_spec.rb | 7 +- spec/alternative_spec.rb | 9 +- spec/cache_spec.rb | 6 +- spec/combined_experiments_helper_spec.rb | 13 +-- spec/configuration_spec.rb | 24 +++--- spec/dashboard/paginator_spec.rb | 1 + spec/dashboard_helpers_spec.rb | 1 + spec/dashboard_spec.rb | 3 +- spec/encapsulated_helper_spec.rb | 13 ++- spec/experiment_catalog_spec.rb | 5 +- spec/experiment_spec.rb | 26 +++--- spec/helper_spec.rb | 91 ++++++++++----------- spec/metric_spec.rb | 2 +- spec/persistence/cookie_adapter_spec.rb | 1 + spec/persistence/dual_adapter_spec.rb | 8 +- spec/persistence/redis_adapter_spec.rb | 13 ++- spec/persistence/session_adapter_spec.rb | 3 +- spec/persistence_spec.rb | 3 +- spec/spec_helper.rb | 3 +- spec/split_spec.rb | 4 +- spec/support/cookies_mock.rb | 3 +- spec/trial_spec.rb | 3 +- spec/user_spec.rb | 4 +- 39 files changed, 120 insertions(+), 168 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8d6cad1d..58156a38 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -18,26 +18,6 @@ Layout/ElseAlignment: Exclude: - 'lib/split/experiment.rb' -# Offense count: 14 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: around, only_before -Layout/EmptyLinesAroundAccessModifier: - Exclude: - - 'lib/split/algorithms/block_randomization.rb' - - 'lib/split/algorithms/whiplash.rb' - - 'lib/split/alternative.rb' - - 'lib/split/configuration.rb' - - 'lib/split/dashboard/pagination_helpers.rb' - - 'lib/split/encapsulated_helper.rb' - - 'lib/split/experiment.rb' - - 'lib/split/goals_collection.rb' - - 'lib/split/persistence/cookie_adapter.rb' - - 'lib/split/persistence/dual_adapter.rb' - - 'lib/split/redis_interface.rb' - - 'lib/split/trial.rb' - - 'lib/split/user.rb' - # Offense count: 8 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/lib/split/algorithms/block_randomization.rb b/lib/split/algorithms/block_randomization.rb index a69cc25a..145e5e5c 100644 --- a/lib/split/algorithms/block_randomization.rb +++ b/lib/split/algorithms/block_randomization.rb @@ -12,7 +12,6 @@ def choose_alternative(experiment) end private - def minimum_participant_alternatives(alternatives) alternatives_by_count = alternatives.group_by(&:participant_count) min_group = alternatives_by_count.min_by { |k, v| k } diff --git a/lib/split/algorithms/whiplash.rb b/lib/split/algorithms/whiplash.rb index 1a354c49..997e7a0e 100644 --- a/lib/split/algorithms/whiplash.rb +++ b/lib/split/algorithms/whiplash.rb @@ -12,7 +12,6 @@ def choose_alternative(experiment) end private - def arm_guess(participants, completions) a = [participants, 0].max b = [participants-completions, 0].max diff --git a/lib/split/alternative.rb b/lib/split/alternative.rb index 43c38773..6cef3766 100644 --- a/lib/split/alternative.rb +++ b/lib/split/alternative.rb @@ -180,7 +180,6 @@ def delete end private - def hash_with_correct_values?(name) Hash === name && String === name.keys.first && Float(name.values.first) rescue false end diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index 08481319..2bcb4c34 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -237,7 +237,6 @@ def initialize end private - def value_for(hash, key) if hash.kind_of?(Hash) hash.has_key?(key.to_s) ? hash[key.to_s] : hash[key.to_sym] diff --git a/lib/split/dashboard/pagination_helpers.rb b/lib/split/dashboard/pagination_helpers.rb index a003543d..b61b57c3 100644 --- a/lib/split/dashboard/pagination_helpers.rb +++ b/lib/split/dashboard/pagination_helpers.rb @@ -30,7 +30,6 @@ def pagination(collection) end private - def show_first_page_tag? page_number > 2 end diff --git a/lib/split/encapsulated_helper.rb b/lib/split/encapsulated_helper.rb index bcdad08c..0a00cacd 100644 --- a/lib/split/encapsulated_helper.rb +++ b/lib/split/encapsulated_helper.rb @@ -34,7 +34,6 @@ def ab_test(*arguments, &block) end private - # instantiate and memoize a context shim in case of multiple ab_test* calls def split_context_shim @split_context_shim ||= ContextShim.new(self) diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index b31e09a4..22a3c688 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -417,7 +417,6 @@ def enable_cohorting end protected - def experiment_config_key "experiment_configurations/#{@name}" end @@ -454,7 +453,6 @@ def load_alternatives_from_redis end private - def redis Split.redis end diff --git a/lib/split/goals_collection.rb b/lib/split/goals_collection.rb index 85634383..34cd42b9 100644 --- a/lib/split/goals_collection.rb +++ b/lib/split/goals_collection.rb @@ -38,7 +38,6 @@ def delete end private - def goals_key "#{@experiment_name}:goals" end diff --git a/lib/split/persistence/cookie_adapter.rb b/lib/split/persistence/cookie_adapter.rb index b70e112c..80c65a58 100644 --- a/lib/split/persistence/cookie_adapter.rb +++ b/lib/split/persistence/cookie_adapter.rb @@ -30,7 +30,6 @@ def keys end private - def set_cookie(value = {}) cookie_key = :split.to_s cookie_value = default_options.merge(value: JSON.generate(value)) diff --git a/lib/split/persistence/dual_adapter.rb b/lib/split/persistence/dual_adapter.rb index d5449c14..b6c7b066 100644 --- a/lib/split/persistence/dual_adapter.rb +++ b/lib/split/persistence/dual_adapter.rb @@ -72,7 +72,6 @@ def delete(key) end private - def decrement_participation?(old_value, value) !old_value.nil? && !value.nil? && old_value != value end diff --git a/lib/split/redis_interface.rb b/lib/split/redis_interface.rb index 4668a1c0..5cc0a828 100644 --- a/lib/split/redis_interface.rb +++ b/lib/split/redis_interface.rb @@ -24,7 +24,6 @@ def add_to_set(set_name, value) end private - attr_accessor :redis end end diff --git a/lib/split/trial.rb b/lib/split/trial.rb index ee460f91..f9ea1601 100644 --- a/lib/split/trial.rb +++ b/lib/split/trial.rb @@ -97,7 +97,6 @@ def choose!(context = nil) end private - def run_callback(context, callback_name) context.send(callback_name, self) if callback_name && context.respond_to?(callback_name, true) end diff --git a/lib/split/user.rb b/lib/split/user.rb index 61772405..ff923829 100644 --- a/lib/split/user.rb +++ b/lib/split/user.rb @@ -65,7 +65,6 @@ def self.find(user_id, adapter) end private - def keys_without_experiment(keys, experiment_key) keys.reject { |k| k.match(Regexp.new("^#{experiment_key}(:finished)?$")) } end diff --git a/spec/algorithms/block_randomization_spec.rb b/spec/algorithms/block_randomization_spec.rb index c19935c7..e257f96d 100644 --- a/spec/algorithms/block_randomization_spec.rb +++ b/spec/algorithms/block_randomization_spec.rb @@ -1,7 +1,6 @@ require "spec_helper" describe Split::Algorithms::BlockRandomization do - let(:experiment) { Split::Experiment.new 'experiment' } let(:alternative_A) { Split::Alternative.new 'A', 'experiment' } let(:alternative_B) { Split::Alternative.new 'B', 'experiment' } diff --git a/spec/algorithms/weighted_sample_spec.rb b/spec/algorithms/weighted_sample_spec.rb index d350482b..cf62d40a 100644 --- a/spec/algorithms/weighted_sample_spec.rb +++ b/spec/algorithms/weighted_sample_spec.rb @@ -1,19 +1,20 @@ # frozen_string_literal: true + require "spec_helper" describe Split::Algorithms::WeightedSample do it "should return an alternative" do - experiment = Split::ExperimentCatalog.find_or_create('link_color', {'blue' => 100}, {'red' => 0 }) + experiment = Split::ExperimentCatalog.find_or_create('link_color', { 'blue' => 100 }, { 'red' => 0 }) expect(Split::Algorithms::WeightedSample.choose_alternative(experiment).class).to eq(Split::Alternative) end it "should always return a heavily weighted option" do - experiment = Split::ExperimentCatalog.find_or_create('link_color', {'blue' => 100}, {'red' => 0 }) + experiment = Split::ExperimentCatalog.find_or_create('link_color', { 'blue' => 100 }, { 'red' => 0 }) expect(Split::Algorithms::WeightedSample.choose_alternative(experiment).name).to eq('blue') end it "should return one of the results" do - experiment = Split::ExperimentCatalog.find_or_create('link_color', {'blue' => 1}, {'red' => 1 }) + experiment = Split::ExperimentCatalog.find_or_create('link_color', { 'blue' => 1 }, { 'red' => 1 }) expect(['red', 'blue']).to include Split::Algorithms::WeightedSample.choose_alternative(experiment).name end end diff --git a/spec/algorithms/whiplash_spec.rb b/spec/algorithms/whiplash_spec.rb index 07ca83aa..6a5c1c26 100644 --- a/spec/algorithms/whiplash_spec.rb +++ b/spec/algorithms/whiplash_spec.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true + require "spec_helper" describe Split::Algorithms::Whiplash do - it "should return an algorithm" do - experiment = Split::ExperimentCatalog.find_or_create('link_color', {'blue' => 1}, {'red' => 1 }) + experiment = Split::ExperimentCatalog.find_or_create('link_color', { 'blue' => 1 }, { 'red' => 1 }) expect(Split::Algorithms::Whiplash.choose_alternative(experiment).class).to eq(Split::Alternative) end it "should return one of the results" do - experiment = Split::ExperimentCatalog.find_or_create('link_color', {'blue' => 1}, {'red' => 1 }) + experiment = Split::ExperimentCatalog.find_or_create('link_color', { 'blue' => 1 }, { 'red' => 1 }) expect(['red', 'blue']).to include Split::Algorithms::Whiplash.choose_alternative(experiment).name end @@ -20,5 +20,4 @@ expect(Split::Algorithms::Whiplash.send(:arm_guess, 1000, 5).class).to eq(Float) expect(Split::Algorithms::Whiplash.send(:arm_guess, 10, -2).class).to eq(Float) end - end diff --git a/spec/alternative_spec.rb b/spec/alternative_spec.rb index 8ed16fa2..e29138f4 100644 --- a/spec/alternative_spec.rb +++ b/spec/alternative_spec.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true + require 'spec_helper' require 'split/alternative' describe Split::Alternative do - let(:alternative) { Split::Alternative.new('Basket', 'basket_text') } @@ -13,7 +13,7 @@ } let!(:experiment) { - Split::ExperimentCatalog.find_or_create({"basket_text" => ["purchase", "refund"]}, "Basket", "Cart") + Split::ExperimentCatalog.find_or_create({ "basket_text" => ["purchase", "refund"] }, "Basket", "Cart") } let(:goal1) { "purchase" } @@ -29,7 +29,7 @@ describe 'weights' do it "should set the weights" do - experiment = Split::Experiment.new('basket_text', alternatives: [{'Basket' => 0.6}, {"Cart" => 0.4}]) + experiment = Split::Experiment.new('basket_text', alternatives: [{ 'Basket' => 0.6 }, { "Cart" => 0.4 }]) first = experiment.alternatives[0] expect(first.name).to eq('Basket') expect(first.weight).to eq(0.6) @@ -226,7 +226,6 @@ end describe 'z score' do - it "should return an error string when the control has 0 people" do expect(alternative2.z_score).to eq("Needs 30+ participants.") expect(alternative2.z_score(goal1)).to eq("Needs 30+ participants.") @@ -289,7 +288,7 @@ describe "extra_info" do it "reads saved value of recorded_info in redis" do - saved_recorded_info = {"key_1" => 1, "key_2" => "2"} + saved_recorded_info = { "key_1" => 1, "key_2" => "2" } Split.redis.hset "#{alternative.experiment_name}:#{alternative.name}", 'recorded_info', saved_recorded_info.to_json extra_info = alternative.extra_info diff --git a/spec/cache_spec.rb b/spec/cache_spec.rb index e411800b..0630c15e 100644 --- a/spec/cache_spec.rb +++ b/spec/cache_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true + require 'spec_helper' describe Split::Cache do - let(:namespace) { :test_namespace } let(:key) { :test_key } let(:now) { 1606189017 } @@ -10,7 +10,6 @@ before { allow(Time).to receive(:now).and_return(now) } describe 'clear' do - before { Split.configuration.cache = true } it 'clears the cache' do @@ -36,11 +35,9 @@ end describe 'fetch' do - subject { Split::Cache.fetch(namespace, key) { Time.now } } context 'when cache disabled' do - before { Split.configuration.cache = false } it 'returns the yield' do @@ -55,7 +52,6 @@ end context 'when cache enabled' do - before { Split.configuration.cache = true } it 'returns the yield' do diff --git a/spec/combined_experiments_helper_spec.rb b/spec/combined_experiments_helper_spec.rb index a5561fb5..008b5314 100644 --- a/spec/combined_experiments_helper_spec.rb +++ b/spec/combined_experiments_helper_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spec_helper' require 'split/combined_experiments_helper' @@ -7,13 +8,13 @@ describe 'ab_combined_test' do let!(:config_enabled) { true } - let!(:combined_experiments) { [:exp_1_click, :exp_1_scroll ]} + let!(:combined_experiments) { [:exp_1_click, :exp_1_scroll ] } let!(:allow_multiple_experiments) { true } before do Split.configuration.experiments = { combined_exp_1: { - alternatives: [ {"control"=> 0.5}, {"test-alt"=> 0.5} ], + alternatives: [ { "control"=> 0.5 }, { "test-alt"=> 0.5 } ], metric: :my_metric, combined_experiments: combined_experiments } @@ -26,7 +27,7 @@ let!(:config_enabled) { false } it "raises an error" do - expect { ab_combined_test :combined_exp_1 }.to raise_error(Split::InvalidExperimentsFormatError ) + expect { ab_combined_test :combined_exp_1 }.to raise_error(Split::InvalidExperimentsFormatError) end end @@ -42,14 +43,14 @@ let!(:combined_experiments) { nil } it "raises an error" do - expect { ab_combined_test :combined_exp_1 }.to raise_error(Split::InvalidExperimentsFormatError ) + expect { ab_combined_test :combined_exp_1 }.to raise_error(Split::InvalidExperimentsFormatError) end end it "uses same alternative for all sub experiments and returns the alternative" do allow(self).to receive(:get_alternative) { "test-alt" } - expect(self).to receive(:ab_test).with(:exp_1_click, {"control"=>0.5}, {"test-alt"=>0.5}) { "test-alt" } - expect(self).to receive(:ab_test).with(:exp_1_scroll, [{"control" => 0, "test-alt" => 1}]) + expect(self).to receive(:ab_test).with(:exp_1_click, { "control"=>0.5 }, { "test-alt"=>0.5 }) { "test-alt" } + expect(self).to receive(:ab_test).with(:exp_1_scroll, [{ "control" => 0, "test-alt" => 1 }]) expect(ab_combined_test('combined_exp_1')).to eq('test-alt') end diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb index 6299e949..327edb91 100644 --- a/spec/configuration_spec.rb +++ b/spec/configuration_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true + require 'spec_helper' describe Split::Configuration do - before(:each) { @config = Split::Configuration.new } it "should provide a default value for ignore_ip_addresses" do @@ -58,7 +58,7 @@ end it "should load a metric" do - @config.experiments = { my_experiment: {alternatives: ["control_opt", "other_opt"], metric: :my_metric } } + @config.experiments = { my_experiment: { alternatives: ["control_opt", "other_opt"], metric: :my_metric } } expect(@config.metrics).not_to be_nil expect(@config.metrics.keys).to eq([:my_metric]) @@ -80,7 +80,7 @@ - Alt One - Alt Two resettable: false - eos + eos @config.experiments = YAML.load(experiments_yaml) end @@ -108,7 +108,7 @@ Alt Two: text: 'Alternative Two' resettable: false - eos + eos @config.experiments = YAML.load(experiments_yaml) end @@ -136,25 +136,23 @@ alternatives: - a - b - eos + eos @config.experiments = YAML.load(experiments_yaml) end it "should normalize experiments" do - expect(@config.normalized_experiments).to eq({ my_experiment: { resettable: false, alternatives: [{"Control Opt"=>0.67}, - [{"Alt One"=>0.1}, {"Alt Two"=>0.23}]]}, another_experiment: { alternatives: ["a", ["b"]]}}) + expect(@config.normalized_experiments).to eq({ my_experiment: { resettable: false, alternatives: [{ "Control Opt"=>0.67 }, + [{ "Alt One"=>0.1 }, { "Alt Two"=>0.23 }]] }, another_experiment: { alternatives: ["a", ["b"]] } }) end it "should recognize metrics" do expect(@config.metrics).not_to be_nil expect(@config.metrics.keys).to eq([:my_metric]) end - end end context "as symbols" do - context "with valid YAML" do before do experiments_yaml = <<-eos @@ -164,17 +162,16 @@ - Alt One - Alt Two :resettable: false - eos + eos @config.experiments = YAML.load(experiments_yaml) end it "should normalize experiments" do - expect(@config.normalized_experiments).to eq({my_experiment: { resettable: false, alternatives: ["Control Opt", ["Alt One", "Alt Two"]]}}) + expect(@config.normalized_experiments).to eq({ my_experiment: { resettable: false, alternatives: ["Control Opt", ["Alt One", "Alt Two"]] } }) end end context "with invalid YAML" do - let(:yaml) { YAML.load(input) } context "with an empty string" do @@ -207,7 +204,7 @@ } } - expect(@config.normalized_experiments).to eq({ my_experiment: { alternatives: [{"control_opt"=>0.67}, [{"second_opt"=>0.1}, {"third_opt"=>0.23}]]}}) + expect(@config.normalized_experiments).to eq({ my_experiment: { alternatives: [{ "control_opt"=>0.67 }, [{ "second_opt"=>0.1 }, { "third_opt"=>0.23 }]] } }) end context "redis configuration" do @@ -254,5 +251,4 @@ expect(@config.persistence_cookie_domain).to eq('.acme.com') end end - end diff --git a/spec/dashboard/paginator_spec.rb b/spec/dashboard/paginator_spec.rb index b46bffe8..2d61dbba 100644 --- a/spec/dashboard/paginator_spec.rb +++ b/spec/dashboard/paginator_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spec_helper' require 'split/dashboard/paginator' diff --git a/spec/dashboard_helpers_spec.rb b/spec/dashboard_helpers_spec.rb index 3643c232..7b608ead 100644 --- a/spec/dashboard_helpers_spec.rb +++ b/spec/dashboard_helpers_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spec_helper' require 'split/dashboard/helpers' diff --git a/spec/dashboard_spec.rb b/spec/dashboard_spec.rb index 3bf0cfa0..59fe7ac7 100644 --- a/spec/dashboard_spec.rb +++ b/spec/dashboard_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spec_helper' require 'rack/test' require 'split/dashboard' @@ -27,7 +28,7 @@ def link(color) } let(:experiment_with_goals) { - Split::ExperimentCatalog.find_or_create({"link_color" => ["goal_1", "goal_2"]}, "blue", "red") + Split::ExperimentCatalog.find_or_create({ "link_color" => ["goal_1", "goal_2"] }, "blue", "red") } let(:metric) { diff --git a/spec/encapsulated_helper_spec.rb b/spec/encapsulated_helper_spec.rb index e6e7aa32..32a814c3 100644 --- a/spec/encapsulated_helper_spec.rb +++ b/spec/encapsulated_helper_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spec_helper' describe Split::EncapsulatedHelper do @@ -16,16 +17,15 @@ def params end it "should not raise an error when params raises an error" do - expect{ params }.to raise_error(NoMethodError) + expect { params }.to raise_error(NoMethodError) expect { ab_test('link_color', 'blue', 'red') }.not_to raise_error end it "calls the block with selected alternative" do - expect{|block| ab_test('link_color', 'red', 'red', &block) }.to yield_with_args('red', {}) + expect { |block| ab_test('link_color', 'red', 'red', &block) }.to yield_with_args('red', {}) end context "inside a view" do - it "works inside ERB" do require 'erb' template = ERB.new(<<-ERB.split(/\s+/s).map(&:strip).join(' '), nil, "%") @@ -35,18 +35,17 @@ def params ERB expect(template.result(binding)).to match(/foo static \d/) end - end end describe "context" do it 'is passed in shim' do - ctx = Class.new{ + ctx = Class.new { include Split::EncapsulatedHelper public :session }.new - expect(ctx).to receive(:session){{}} - expect{ ctx.ab_test('link_color', 'blue', 'red') }.not_to raise_error + expect(ctx).to receive(:session) { {} } + expect { ctx.ab_test('link_color', 'blue', 'red') }.not_to raise_error end end end diff --git a/spec/experiment_catalog_spec.rb b/spec/experiment_catalog_spec.rb index 16cc837c..6f5b0531 100644 --- a/spec/experiment_catalog_spec.rb +++ b/spec/experiment_catalog_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spec_helper' describe Split::ExperimentCatalog do @@ -22,12 +23,12 @@ end it "should not raise error when passed an array for goals" do - expect { subject.find_or_create({'link_color' => ["purchase", "refund"]}, 'blue', 'red') } + expect { subject.find_or_create({ 'link_color' => ["purchase", "refund"] }, 'blue', 'red') } .not_to raise_error end it "should not raise error when passed just one goal" do - expect { subject.find_or_create({'link_color' => "purchase"}, 'blue', 'red') } + expect { subject.find_or_create({ 'link_color' => "purchase" }, 'blue', 'red') } .not_to raise_error end diff --git a/spec/experiment_spec.rb b/spec/experiment_spec.rb index 7d7f27f9..93ab55d2 100644 --- a/spec/experiment_spec.rb +++ b/spec/experiment_spec.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true + require 'spec_helper' require 'time' describe Split::Experiment do - def new_experiment(goals=[]) + def new_experiment(goals = []) Split::Experiment.new('link_color', alternatives: ['blue', 'red', 'green'], goals: goals) end @@ -28,7 +29,7 @@ def alternative(color) end it "should have alternatives with correct names" do - expect(experiment.alternatives.collect{|a| a.name}).to eq(['Basket', 'Cart']) + expect(experiment.alternatives.collect { |a| a.name }).to eq(['Basket', 'Cart']) end it "should be resettable by default" do @@ -110,8 +111,8 @@ def alternative(color) describe 'initialization' do it "should set the algorithm when passed as an option to the initializer" do - experiment = Split::Experiment.new('basket_text', alternatives: ['Basket', "Cart"], algorithm: Split::Algorithms::Whiplash) - expect(experiment.algorithm).to eq(Split::Algorithms::Whiplash) + experiment = Split::Experiment.new('basket_text', alternatives: ['Basket', "Cart"], algorithm: Split::Algorithms::Whiplash) + expect(experiment.algorithm).to eq(Split::Algorithms::Whiplash) end it "should be possible to make an experiment not resettable" do @@ -138,7 +139,6 @@ def alternative(color) end describe 'persistent configuration' do - it "should persist resettable in redis" do experiment = Split::Experiment.new('basket_text', alternatives: ['Basket', "Cart"], resettable: false) experiment.save @@ -146,12 +146,11 @@ def alternative(color) e = Split::ExperimentCatalog.find('basket_text') expect(e).to eq(experiment) expect(e.resettable).to be_falsey - end describe '#metadata' do let(:experiment) { Split::Experiment.new('basket_text', alternatives: ['Basket', "Cart"], algorithm: Split::Algorithms::Whiplash, metadata: meta) } - let(:meta) { { a: 'b' }} + let(:meta) { { a: 'b' } } before do experiment.save @@ -199,7 +198,7 @@ def alternative(color) e = Split::ExperimentCatalog.find('foobar') expect(e).to eq(experiment) - expect(e.alternatives.collect{|a| a.name}).to eq(['tra', 'la']) + expect(e.alternatives.collect { |a| a.name }).to eq(['tra', 'la']) end end @@ -501,7 +500,7 @@ def same_but_different_alternative describe 'specifying weights' do let(:experiment_with_weight) { - Split::ExperimentCatalog.find_or_create('link_color', {'blue' => 1}, {'red' => 2 }) + Split::ExperimentCatalog.find_or_create('link_color', { 'blue' => 1 }, { 'red' => 2 }) } it "should work for a new experiment" do @@ -520,7 +519,7 @@ def same_but_different_alternative } context "saving experiment" do - let(:same_but_different_goals) { Split::ExperimentCatalog.find_or_create({'link_color' => ["purchase", "refund"]}, 'blue', 'red', 'green') } + let(:same_but_different_goals) { Split::ExperimentCatalog.find_or_create({ 'link_color' => ["purchase", "refund"] }, 'blue', 'red', 'green') } before { experiment.save } @@ -532,7 +531,6 @@ def same_but_different_alternative same_but_different_goals expect(Split::ExperimentCatalog.find("link_color").goals).to eq(["purchase", "refund"]) end - end it "should have goals" do @@ -541,7 +539,7 @@ def same_but_different_alternative context "find or create experiment" do it "should have correct goals" do - experiment = Split::ExperimentCatalog.find_or_create({'link_color3' => ["purchase", "refund"]}, 'blue', 'red', 'green') + experiment = Split::ExperimentCatalog.find_or_create({ 'link_color3' => ["purchase", "refund"] }, 'blue', 'red', 'green') expect(experiment.goals).to eq(["purchase", "refund"]) experiment = Split::ExperimentCatalog.find_or_create('link_color3', 'blue', 'red', 'green') expect(experiment.goals).to eq([]) @@ -563,7 +561,7 @@ def same_but_different_alternative end it "should calculate the probability of being the winning alternative separately for each goal", skip: true do - experiment = Split::ExperimentCatalog.find_or_create({'link_color3' => ["purchase", "refund"]}, 'blue', 'red', 'green') + experiment = Split::ExperimentCatalog.find_or_create({ 'link_color3' => ["purchase", "refund"] }, 'blue', 'red', 'green') goal1 = experiment.goals[0] goal2 = experiment.goals[1] experiment.alternatives.each do |alternative| @@ -579,7 +577,7 @@ def same_but_different_alternative end it "should return nil and not re-calculate probabilities if they have already been calculated today" do - experiment = Split::ExperimentCatalog.find_or_create({'link_color3' => ["purchase", "refund"]}, 'blue', 'red', 'green') + experiment = Split::ExperimentCatalog.find_or_create({ 'link_color3' => ["purchase", "refund"] }, 'blue', 'red', 'green') expect(experiment.calc_winning_alternatives).not_to be nil expect(experiment.calc_winning_alternatives).to be nil end diff --git a/spec/helper_spec.rb b/spec/helper_spec.rb index ad9c87ba..20f298cf 100755 --- a/spec/helper_spec.rb +++ b/spec/helper_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spec_helper' # TODO change some of these tests to use Rack::Test @@ -28,11 +29,11 @@ end it "should not raise error when passed an array for goals" do - expect { ab_test({'link_color' => ["purchase", "refund"]}, 'blue', 'red') }.not_to raise_error + expect { ab_test({ 'link_color' => ["purchase", "refund"] }, 'blue', 'red') }.not_to raise_error end it "should not raise error when passed just one goal" do - expect { ab_test({'link_color' => "purchase"}, 'blue', 'red') }.not_to raise_error + expect { ab_test({ 'link_color' => "purchase" }, 'blue', 'red') }.not_to raise_error end it "raises an appropriate error when processing combined expirements" do @@ -44,7 +45,7 @@ } } Split::ExperimentCatalog.find_or_create('combined_exp_1') - expect { ab_test('combined_exp_1') }.to raise_error(Split::InvalidExperimentsFormatError ) + expect { ab_test('combined_exp_1') }.to raise_error(Split::InvalidExperimentsFormatError) end it "should assign a random alternative to a new user when there are an equal number of alternatives assigned" do @@ -108,7 +109,7 @@ alternative = ab_test('link_color', 'blue', 'red') expect(alternative).to eq('blue') - alternative = ab_test('link_color', {'blue' => 1}, 'red' => 5) + alternative = ab_test('link_color', { 'blue' => 1 }, 'red' => 5) expect(alternative).to eq('blue') @params = { 'ab_test' => { 'link_color' => 'red' } } @@ -116,7 +117,7 @@ alternative = ab_test('link_color', 'blue', 'red') expect(alternative).to eq('red') - alternative = ab_test('link_color', {'blue' => 5}, 'red' => 1) + alternative = ab_test('link_color', { 'blue' => 5 }, 'red' => 1) expect(alternative).to eq('red') end @@ -133,19 +134,19 @@ end it "SPLIT_DISABLE query parameter should also force the alternative (uses control)" do - @params = {'SPLIT_DISABLE' => 'true'} + @params = { 'SPLIT_DISABLE' => 'true' } alternative = ab_test('link_color', 'blue', 'red') expect(alternative).to eq('blue') - alternative = ab_test('link_color', {'blue' => 1}, 'red' => 5) + alternative = ab_test('link_color', { 'blue' => 1 }, 'red' => 5) expect(alternative).to eq('blue') alternative = ab_test('link_color', 'red', 'blue') expect(alternative).to eq('red') - alternative = ab_test('link_color', {'red' => 5}, 'blue' => 1) + alternative = ab_test('link_color', { 'red' => 5 }, 'blue' => 1) expect(alternative).to eq('red') end it "should not store the split when Split generically disabled" do - @params = {'SPLIT_DISABLE' => 'true'} + @params = { 'SPLIT_DISABLE' => 'true' } expect(ab_user).not_to receive(:[]=) ab_test('link_color', 'blue', 'red') end @@ -175,15 +176,15 @@ end it "should allow the share of visitors see an alternative to be specified" do - ab_test('link_color', {'blue' => 0.8}, {'red' => 20}) + ab_test('link_color', { 'blue' => 0.8 }, { 'red' => 20 }) expect(['red', 'blue']).to include(ab_user['link_color']) end it "should allow alternative weighting interface as a single hash" do - ab_test('link_color', {'blue' => 0.01}, 'red' => 0.2) + ab_test('link_color', { 'blue' => 0.01 }, 'red' => 0.2) experiment = Split::ExperimentCatalog.find('link_color') expect(experiment.alternatives.map(&:name)).to eq(['blue', 'red']) - expect(experiment.alternatives.collect{|a| a.weight}).to match_array([0.01, 0.2]) + expect(experiment.alternatives.collect { |a| a.weight }).to match_array([0.01, 0.2]) end it "should only let a user participate in one experiment at a time" do @@ -214,7 +215,7 @@ config.allow_multiple_experiments = 'control' end groups = 100.times.map do |n| - ab_test("test#{n}".to_sym, {'control' => (100 - n)}, {"test#{n}-alt" => n}) + ab_test("test#{n}".to_sym, { 'control' => (100 - n) }, { "test#{n}-alt" => n }) end experiments = ab_user.active_experiments @@ -228,7 +229,7 @@ end context "when user already has experiment" do - let(:mock_user){ Split::User.new(self, {'test_0' => 'test-alt'}) } + let(:mock_user) { Split::User.new(self, { 'test_0' => 'test-alt' }) } before do Split.configure do |config| @@ -241,8 +242,8 @@ it "should restore previously selected alternative" do expect(ab_user.active_experiments.size).to eq 1 - expect(ab_test(:test_0, {'control' => 100}, {"test-alt" => 1})).to eq 'test-alt' - expect(ab_test(:test_0, {'control' => 1}, {"test-alt" => 100})).to eq 'test-alt' + expect(ab_test(:test_0, { 'control' => 100 }, { "test-alt" => 1 })).to eq 'test-alt' + expect(ab_test(:test_0, { 'control' => 1 }, { "test-alt" => 100 })).to eq 'test-alt' end it "should select the correct alternatives after experiment resets" do @@ -251,20 +252,18 @@ mock_user[experiment.key] = 'test-alt' expect(ab_user.active_experiments.size).to eq 1 - expect(ab_test(:test_0, {'control' => 100}, {"test-alt" => 1})).to eq 'test-alt' - expect(ab_test(:test_0, {'control' => 0}, {"test-alt" => 100})).to eq 'test-alt' + expect(ab_test(:test_0, { 'control' => 100 }, { "test-alt" => 1 })).to eq 'test-alt' + expect(ab_test(:test_0, { 'control' => 0 }, { "test-alt" => 100 })).to eq 'test-alt' end it "lets override existing choice" do pending "this requires user store reset on first call not depending on whelther it is current trial" @params = { 'ab_test' => { 'test_1' => 'test-alt' } } - expect(ab_test(:test_0, {'control' => 0}, {"test-alt" => 100})).to eq 'control' - expect(ab_test(:test_1, {'control' => 100}, {"test-alt" => 1})).to eq 'test-alt' + expect(ab_test(:test_0, { 'control' => 0 }, { "test-alt" => 100 })).to eq 'control' + expect(ab_test(:test_1, { 'control' => 100 }, { "test-alt" => 1 })).to eq 'test-alt' end - end - end it "should not over-write a finished key when an experiment is on a later version" do @@ -354,13 +353,13 @@ end it "should set experiment's finished key if reset is false" do - ab_finished(@experiment_name, {reset: false}) + ab_finished(@experiment_name, { reset: false }) expect(ab_user[@experiment.key]).to eq(@alternative_name) expect(ab_user[@experiment.finished_key]).to eq(true) end it 'should not increment the counter if reset is false and the experiment has been already finished' do - 2.times { ab_finished(@experiment_name, {reset: false}) } + 2.times { ab_finished(@experiment_name, { reset: false }) } new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count expect(new_completion_count).to eq(@previous_completion_count + 1) end @@ -383,7 +382,7 @@ it "should not clear out the users session if reset is false" do expect(ab_user[@experiment.key]).to eq(@alternative_name) - ab_finished(@experiment_name, {reset: false}) + ab_finished(@experiment_name, { reset: false }) expect(ab_user[@experiment.key]).to eq(@alternative_name) expect(ab_user[@experiment.finished_key]).to eq(true) end @@ -446,7 +445,7 @@ end it 'should not change the user state when reset is false' do - expect { ab_finished(:not_started_experiment, reset: false) }.not_to change { ab_user.keys}.from([]) + expect { ab_finished(:not_started_experiment, reset: false) }.not_to change { ab_user.keys }.from([]) end it 'should not change the user state when reset is true' do @@ -483,7 +482,7 @@ before { Split.configuration.experiments = {} } before { expect(Split::Alternative).to receive(:new).at_least(1).times.and_call_original } - def should_finish_experiment(experiment_name, should_finish=true) + def should_finish_experiment(experiment_name, should_finish = true) alts = Split.configuration.experiments[experiment_name][:alternatives] experiment = Split::ExperimentCatalog.find_or_create(experiment_name, *alts) alt_name = ab_user[experiment.key] = alts.first @@ -583,7 +582,7 @@ def should_finish_experiment(experiment_name, should_finish=true) it 'should show a finished test' do alternative = ab_test('def', '4', '5', '6') - ab_finished('def', {reset: false}) + ab_finished('def', { reset: false }) expect(active_experiments.count).to eq 1 expect(active_experiments.first[0]).to eq "def" expect(active_experiments.first[1]).to eq alternative @@ -605,7 +604,7 @@ def should_finish_experiment(experiment_name, should_finish=true) expect(experiment.version).to eq(10) expect(active_experiments.count).to eq 1 - expect(active_experiments).to eq({'link_color' => alternative }) + expect(active_experiments).to eq({ 'link_color' => alternative }) end it 'should show multiple tests' do @@ -650,7 +649,6 @@ def should_finish_experiment(experiment_name, should_finish=true) end it "should not increment the participation count" do - previous_red_count = Split::Alternative.new('red', 'link_color').participant_count previous_blue_count = Split::Alternative.new('blue', 'link_color').participant_count @@ -680,10 +678,9 @@ def should_finish_experiment(experiment_name, should_finish=true) describe 'when providing custom ignore logic' do context "using a proc to configure custom logic" do - before(:each) do Split.configure do |c| - c.ignore_filter = proc{|request| true } # ignore everything + c.ignore_filter = proc { |request| true } # ignore everything end end @@ -777,7 +774,7 @@ def should_finish_experiment(experiment_name, should_finish=true) it "works as usual" do alternative_name = ab_test('link_color', 'red', 'blue') - expect{ + expect { ab_finished('link_color') }.to change(Split::Alternative.new(alternative_name, 'link_color'), :completed_count).by(1) end @@ -928,8 +925,8 @@ def should_finish_experiment(experiment_name, should_finish=true) it 'should always use first alternative' do expect(ab_test('link_color', 'blue', 'red')).to eq('blue') - expect(ab_test('link_color', {'blue' => 0.01}, 'red' => 0.2)).to eq('blue') - expect(ab_test('link_color', {'blue' => 0.8}, {'red' => 20})).to eq('blue') + expect(ab_test('link_color', { 'blue' => 0.01 }, 'red' => 0.2)).to eq('blue') + expect(ab_test('link_color', { 'blue' => 0.8 }, { 'red' => 20 })).to eq('blue') expect(ab_test('link_color', 'blue', 'red') do |alternative| "shared/#{alternative}" end).to eq('shared/blue') @@ -947,8 +944,8 @@ def should_finish_experiment(experiment_name, should_finish=true) @params = { 'ab_test' => { 'link_color' => 'red' } } expect(ab_test('link_color', 'blue', 'red')).to eq('red') expect(ab_test('link_color', 'blue', 'red', 'green')).to eq('red') - expect(ab_test('link_color', {'blue' => 0.01}, 'red' => 0.2)).to eq('red') - expect(ab_test('link_color', {'blue' => 0.8}, {'red' => 20})).to eq('red') + expect(ab_test('link_color', { 'blue' => 0.01 }, 'red' => 0.2)).to eq('red') + expect(ab_test('link_color', { 'blue' => 0.8 }, { 'red' => 20 })).to eq('red') expect(ab_test('link_color', 'blue', 'red') do |alternative| "shared/#{alternative}" end).to eq('shared/red') @@ -989,7 +986,7 @@ def should_finish_experiment(experiment_name, should_finish=true) end context "with preloaded config" do - before { Split.configuration.experiments = {}} + before { Split.configuration.experiments = {} } it "pulls options from config file" do Split.configuration.experiments[:my_experiment] = { @@ -1050,7 +1047,7 @@ def should_finish_experiment(experiment_name, should_finish=true) } ab_test :my_experiment experiment = Split::Experiment.new(:my_experiment) - expect(experiment.alternatives.collect{|a| [a.name, a.weight]}).to eq([['control_opt', 0.67], ['second_opt', 0.1], ['third_opt', 0.23]]) + expect(experiment.alternatives.collect { |a| [a.name, a.weight] }).to eq([['control_opt', 0.67], ['second_opt', 0.1], ['third_opt', 0.23]]) end it "accepts probability on some alternatives" do @@ -1064,9 +1061,9 @@ def should_finish_experiment(experiment_name, should_finish=true) } ab_test :my_experiment experiment = Split::Experiment.new(:my_experiment) - names_and_weights = experiment.alternatives.collect{|a| [a.name, a.weight]} + names_and_weights = experiment.alternatives.collect { |a| [a.name, a.weight] } expect(names_and_weights).to eq([['control_opt', 0.34], ['second_opt', 0.215], ['third_opt', 0.23], ['fourth_opt', 0.215]]) - expect(names_and_weights.inject(0){|sum, nw| sum + nw[1]}).to eq(1.0) + expect(names_and_weights.inject(0) { |sum, nw| sum + nw[1] }).to eq(1.0) end it "allows name param without probability" do @@ -1079,9 +1076,9 @@ def should_finish_experiment(experiment_name, should_finish=true) } ab_test :my_experiment experiment = Split::Experiment.new(:my_experiment) - names_and_weights = experiment.alternatives.collect{|a| [a.name, a.weight]} + names_and_weights = experiment.alternatives.collect { |a| [a.name, a.weight] } expect(names_and_weights).to eq([['control_opt', 0.18], ['second_opt', 0.18], ['third_opt', 0.64]]) - expect(names_and_weights.inject(0){|sum, nw| sum + nw[1]}).to eq(1.0) + expect(names_and_weights.inject(0) { |sum, nw| sum + nw[1] }).to eq(1.0) end it "fails gracefully if config is missing experiment" do @@ -1112,7 +1109,7 @@ def should_finish_experiment(experiment_name, should_finish=true) context "with goals" do before do - @experiment = {'link_color' => ["purchase", "refund"]} + @experiment = { 'link_color' => ["purchase", "refund"] } @alternatives = ['blue', 'red'] @experiment_name, @goals = normalize_metric(@experiment) @goal1 = @goals[0] @@ -1138,9 +1135,9 @@ def should_finish_experiment(experiment_name, should_finish=true) end it "should increment the counter for the specified-goal completed alternative" do - expect{ ab_finished({"link_color" => ["purchase"]}) } - .to change{ Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal2) }.by(0) - .and change{ Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal1) }.by(1) + expect { ab_finished({ "link_color" => ["purchase"] }) } + .to change { Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal2) }.by(0) + .and change { Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal1) }.by(1) end end end diff --git a/spec/metric_spec.rb b/spec/metric_spec.rb index 3dbc9b5f..92721142 100644 --- a/spec/metric_spec.rb +++ b/spec/metric_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spec_helper' require 'split/metric' @@ -27,5 +28,4 @@ expect(Split::Metric.possible_experiments('purchase')).to include(experiment1, experiment2) end end - end diff --git a/spec/persistence/cookie_adapter_spec.rb b/spec/persistence/cookie_adapter_spec.rb index 0e8719b1..c31ec838 100644 --- a/spec/persistence/cookie_adapter_spec.rb +++ b/spec/persistence/cookie_adapter_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require "spec_helper" require 'rack/test' diff --git a/spec/persistence/dual_adapter_spec.rb b/spec/persistence/dual_adapter_spec.rb index baccc221..a1ed6461 100644 --- a/spec/persistence/dual_adapter_spec.rb +++ b/spec/persistence/dual_adapter_spec.rb @@ -162,10 +162,10 @@ describe 'when errors in config' do before { described_class.config.clear } - let(:some_proc) { ->{} } + let(:some_proc) { -> { } } it 'when no logged in adapter' do - expect{ + expect { described_class.with_config( logged_in: some_proc, logged_out_adapter: logged_out_adapter @@ -174,7 +174,7 @@ end it 'when no logged out adapter' do - expect{ + expect { described_class.with_config( logged_in: some_proc, logged_in_adapter: logged_in_adapter @@ -183,7 +183,7 @@ end it 'when no logged in detector' do - expect{ + expect { described_class.with_config( logged_in_adapter: logged_in_adapter, logged_out_adapter: logged_out_adapter diff --git a/spec/persistence/redis_adapter_spec.rb b/spec/persistence/redis_adapter_spec.rb index d7e20213..85d376d0 100644 --- a/spec/persistence/redis_adapter_spec.rb +++ b/spec/persistence/redis_adapter_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true + require "spec_helper" describe Split::Persistence::RedisAdapter do - let(:context) { double(lookup: 'blah') } subject { Split::Persistence::RedisAdapter.new(context) } @@ -12,7 +12,7 @@ context 'default' do it 'should raise error with prompt to set lookup_by' do - expect{Split::Persistence::RedisAdapter.new(context)}.to raise_error(RuntimeError) + expect { Split::Persistence::RedisAdapter.new(context) }.to raise_error(RuntimeError) end end @@ -26,7 +26,7 @@ end context 'config with lookup_by = proc { "block" }' do - before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc{'block'}) } + before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc { 'block' }) } it 'should be "persistence:block"' do expect(subject.redis_key).to eq('persistence:block') @@ -34,7 +34,7 @@ end context 'config with lookup_by = proc { |context| context.test }' do - before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc{'block'}) } + before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc { 'block' }) } let(:context) { double(test: 'block') } it 'should be "persistence:block"' do @@ -52,7 +52,7 @@ end context 'config with namespace and lookup_by' do - before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc{'frag'}, namespace: 'namer') } + before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc { 'frag' }, namespace: 'namer') } it 'should be "namer"' do expect(subject.redis_key).to eq('namer:frag') @@ -61,7 +61,7 @@ end describe '#find' do - before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc{'frag'}, namespace: 'a_namespace') } + before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc { 'frag' }, namespace: 'a_namespace') } it "should create and user from a given key" do adapter = Split::Persistence::RedisAdapter.find(2) @@ -94,6 +94,5 @@ expect(subject.keys).to match(["my_key", "my_second_key"]) end end - end end diff --git a/spec/persistence/session_adapter_spec.rb b/spec/persistence/session_adapter_spec.rb index bda18969..63074c36 100644 --- a/spec/persistence/session_adapter_spec.rb +++ b/spec/persistence/session_adapter_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true + require "spec_helper" describe Split::Persistence::SessionAdapter do - let(:context) { double(session: {}) } subject { Split::Persistence::SessionAdapter.new(context) } @@ -28,5 +28,4 @@ expect(subject.keys).to match(["my_key", "my_second_key"]) end end - end diff --git a/spec/persistence_spec.rb b/spec/persistence_spec.rb index 346c293f..24a0dc2d 100644 --- a/spec/persistence_spec.rb +++ b/spec/persistence_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true + require "spec_helper" describe Split::Persistence do - subject { Split::Persistence } describe ".adapter" do @@ -30,5 +30,4 @@ end end end - end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index beb529b0..8fa9e743 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + ENV['RACK_ENV'] = "test" require 'rubygems' @@ -15,7 +16,7 @@ module GlobalSharedContext extend RSpec::SharedContext - let(:mock_user){ Split::User.new(double(session: {})) } + let(:mock_user) { Split::User.new(double(session: {})) } before(:each) do Split.configuration = Split::Configuration.new diff --git a/spec/split_spec.rb b/spec/split_spec.rb index 51761637..2dbd2502 100644 --- a/spec/split_spec.rb +++ b/spec/split_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true + require 'spec_helper' RSpec.describe Split do - around(:each) do |ex| old_env, old_redis = [ENV.delete('REDIS_URL'), Split.redis] ex.run @@ -21,7 +21,7 @@ end it 'accepts an options hash' do - Split.redis = {host: 'localhost', port: 6379, db: 12} + Split.redis = { host: 'localhost', port: 6379, db: 12 } expect(Split.redis).to be_a(Redis) client = Split.redis.connection diff --git a/spec/support/cookies_mock.rb b/spec/support/cookies_mock.rb index 276ea694..c23fc8a0 100644 --- a/spec/support/cookies_mock.rb +++ b/spec/support/cookies_mock.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class CookiesMock +class CookiesMock def initialize @cookies = {} end @@ -16,5 +16,4 @@ def [](key) def delete(key) @cookies.delete(key) end - end diff --git a/spec/trial_spec.rb b/spec/trial_spec.rb index 80bafe04..3157ed21 100644 --- a/spec/trial_spec.rb +++ b/spec/trial_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spec_helper' require 'split/trial' @@ -245,7 +246,7 @@ def expect_alternative(trial, alternative_name) it "increments the completed count corresponding to the goals" do trial.choose! - old_completed_counts = goals.map{ |goal| [goal, trial.alternative.completed_count(goal)] }.to_h + old_completed_counts = goals.map { |goal| [goal, trial.alternative.completed_count(goal)] }.to_h trial.complete! goals.each { | goal | expect(trial.alternative.completed_count(goal)).to eq(old_completed_counts[goal] + 1) } end diff --git a/spec/user_spec.rb b/spec/user_spec.rb index 62e6ed0a..6e2e22d2 100644 --- a/spec/user_spec.rb +++ b/spec/user_spec.rb @@ -37,7 +37,7 @@ it 'does not remove other keys' do expect(@subject.keys).to include(second_experiment_version, third_experiment_version) end - end + end context '#cleanup_old_experiments!' do it 'removes key if experiment is not found' do @@ -100,7 +100,6 @@ ab_user = Split::User.find(112233, :dual_adapter) expect(ab_user).to be_nil end - end context "instantiated with custom adapter" do @@ -114,5 +113,4 @@ expect(@subject.user).to eq(custom_adapter) end end - end From 41b3126cfc4459b3a6402577f8d3d5d8c01f2607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:31:33 -0300 Subject: [PATCH 084/136] Fix Layout/EmptyLinesAroundClassBody --- .rubocop_todo.yml | 14 -------------- lib/split/goals_collection.rb | 1 - lib/split/metric.rb | 1 - lib/split/persistence/cookie_adapter.rb | 1 - lib/split/persistence/redis_adapter.rb | 1 - lib/split/persistence/session_adapter.rb | 2 -- lib/split/zscore.rb | 1 - 7 files changed, 21 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 58156a38..bb512c24 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -18,20 +18,6 @@ Layout/ElseAlignment: Exclude: - 'lib/split/experiment.rb' -# Offense count: 8 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only -Layout/EmptyLinesAroundClassBody: - Exclude: - - 'lib/split/experiment_catalog.rb' - - 'lib/split/goals_collection.rb' - - 'lib/split/metric.rb' - - 'lib/split/persistence/cookie_adapter.rb' - - 'lib/split/persistence/redis_adapter.rb' - - 'lib/split/persistence/session_adapter.rb' - - 'lib/split/zscore.rb' - # Offense count: 2 # Cop supports --auto-correct. Layout/EmptyLinesAroundMethodBody: diff --git a/lib/split/goals_collection.rb b/lib/split/goals_collection.rb index 34cd42b9..31b82589 100644 --- a/lib/split/goals_collection.rb +++ b/lib/split/goals_collection.rb @@ -2,7 +2,6 @@ module Split class GoalsCollection - def initialize(experiment_name, goals=nil) @experiment_name = experiment_name @goals = goals diff --git a/lib/split/metric.rb b/lib/split/metric.rb index 6b4f1a9a..ab67227b 100644 --- a/lib/split/metric.rb +++ b/lib/split/metric.rb @@ -97,6 +97,5 @@ def self.normalize_metric(label) return metric_name, goals end private_class_method :normalize_metric - end end diff --git a/lib/split/persistence/cookie_adapter.rb b/lib/split/persistence/cookie_adapter.rb index 80c65a58..940c65e9 100644 --- a/lib/split/persistence/cookie_adapter.rb +++ b/lib/split/persistence/cookie_adapter.rb @@ -5,7 +5,6 @@ module Split module Persistence class CookieAdapter - def initialize(context) @context = context @request, @response = context.request, context.response diff --git a/lib/split/persistence/redis_adapter.rb b/lib/split/persistence/redis_adapter.rb index 480f38ae..05af5920 100644 --- a/lib/split/persistence/redis_adapter.rb +++ b/lib/split/persistence/redis_adapter.rb @@ -56,7 +56,6 @@ def self.config def self.reset_config! @config = DEFAULT_CONFIG.dup end - end end end diff --git a/lib/split/persistence/session_adapter.rb b/lib/split/persistence/session_adapter.rb index a196d820..38a5b86b 100644 --- a/lib/split/persistence/session_adapter.rb +++ b/lib/split/persistence/session_adapter.rb @@ -3,7 +3,6 @@ module Split module Persistence class SessionAdapter - def initialize(context) @session = context.session @session[:split] ||= {} @@ -24,7 +23,6 @@ def delete(key) def keys @session[:split].keys end - end end end diff --git a/lib/split/zscore.rb b/lib/split/zscore.rb index dd9a97d1..17454d60 100644 --- a/lib/split/zscore.rb +++ b/lib/split/zscore.rb @@ -2,7 +2,6 @@ module Split class Zscore - include Math def self.calculate(p1, n1, p2, n2) From 92dc8ed8b73a49004a4bb502b92475b27f756448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:32:22 -0300 Subject: [PATCH 085/136] Fix Layout/CommentIndentation --- .rubocop_todo.yml | 5 ----- lib/split/experiment.rb | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index bb512c24..59e64db4 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,11 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 1 -# Cop supports --auto-correct. -Layout/CommentIndentation: - Exclude: - - 'lib/split/experiment.rb' # Offense count: 1 # Cop supports --auto-correct. diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 22a3c688..00022ae7 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -330,7 +330,7 @@ def calc_alternative_probabilities(winning_counts, number_of_simulations) end def count_simulated_wins(winning_alternatives) - # initialize a hash to keep track of winning alternative in simulations + # initialize a hash to keep track of winning alternative in simulations winning_counts = {} alternatives.each do |alternative| winning_counts[alternative] = 0 From 6d89af5a2d4bb44e2d3dc382773a1b61bdae6ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:33:02 -0300 Subject: [PATCH 086/136] Fix Layout/ElseAlignment --- .rubocop_todo.yml | 7 ------- lib/split/experiment.rb | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 59e64db4..ed339857 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,13 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. - -# Offense count: 1 -# Cop supports --auto-correct. -Layout/ElseAlignment: - Exclude: - - 'lib/split/experiment.rb' - # Offense count: 2 # Cop supports --auto-correct. Layout/EmptyLinesAroundMethodBody: diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 00022ae7..b43508e2 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -393,7 +393,7 @@ def calc_time def jstring(goal = nil) js_id = if goal.nil? name - else + else name + "-" + goal end js_id.gsub('/', '--') From f4de6d98e176cb41964e2eb6c9e7bf0e0ff374dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:33:29 -0300 Subject: [PATCH 087/136] Fix Layout/EmptyLinesAroundMethodBody --- .rubocop_todo.yml | 7 ------- lib/split/dashboard/helpers.rb | 1 - lib/split/zscore.rb | 1 - 3 files changed, 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ed339857..d119c99d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,13 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 2 -# Cop supports --auto-correct. -Layout/EmptyLinesAroundMethodBody: - Exclude: - - 'lib/split/dashboard/helpers.rb' - - 'lib/split/zscore.rb' - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/lib/split/dashboard/helpers.rb b/lib/split/dashboard/helpers.rb index c3b1b8ee..71a0d93f 100644 --- a/lib/split/dashboard/helpers.rb +++ b/lib/split/dashboard/helpers.rb @@ -40,7 +40,6 @@ def confidence_level(z_score) else 'Insufficient confidence' end - end end end diff --git a/lib/split/zscore.rb b/lib/split/zscore.rb index 17454d60..6df25413 100644 --- a/lib/split/zscore.rb +++ b/lib/split/zscore.rb @@ -51,7 +51,6 @@ def self.calculate(p1, n1, p2, n2) z_score = (p_1 - p_2)/(se) return z_score - end end end From 91ba5ce96dc44c1146eaf65e2d696721902b5512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:33:56 -0300 Subject: [PATCH 088/136] Fix Layout/EmptyLinesAroundModuleBody --- .rubocop_todo.yml | 8 -------- lib/split/encapsulated_helper.rb | 1 - 2 files changed, 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d119c99d..d6a20863 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,14 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines -Layout/EmptyLinesAroundModuleBody: - Exclude: - - 'lib/split/encapsulated_helper.rb' - # Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyleAlignWith, AutoCorrect, Severity. diff --git a/lib/split/encapsulated_helper.rb b/lib/split/encapsulated_helper.rb index 0a00cacd..70eef14b 100644 --- a/lib/split/encapsulated_helper.rb +++ b/lib/split/encapsulated_helper.rb @@ -15,7 +15,6 @@ # module Split module EncapsulatedHelper - class ContextShim include Split::Helper public :ab_test, :ab_finished From 4d8b5a937203046209949817b87b0307f0f7f3f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:34:28 -0300 Subject: [PATCH 089/136] Fix Layout/EndAlignment --- .rubocop_todo.yml | 10 ---------- lib/split/configuration.rb | 2 +- lib/split/experiment.rb | 2 +- lib/split/persistence/cookie_adapter.rb | 6 ++---- lib/split/trial.rb | 2 +- 5 files changed, 5 insertions(+), 17 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d6a20863..973ee8ba 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,16 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 4 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyleAlignWith, AutoCorrect, Severity. -# SupportedStylesAlignWith: keyword, variable, start_of_line -Layout/EndAlignment: - Exclude: - - 'lib/split/configuration.rb' - - 'lib/split/experiment.rb' - - 'lib/split/trial.rb' - # Offense count: 17 # Cop supports --auto-correct. # Configuration parameters: Width, IgnoredPatterns. diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index 2bcb4c34..25c85963 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -158,7 +158,7 @@ def normalized_experiments @experiments.each do |experiment_name, settings| alternatives = if (alts = value_for(settings, :alternatives)) normalize_alternatives(alts) - end + end experiment_data = { alternatives: alternatives, diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index b43508e2..d5dae875 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -395,7 +395,7 @@ def jstring(goal = nil) name else name + "-" + goal - end + end js_id.gsub('/', '--') end diff --git a/lib/split/persistence/cookie_adapter.rb b/lib/split/persistence/cookie_adapter.rb index 940c65e9..bef18550 100644 --- a/lib/split/persistence/cookie_adapter.rb +++ b/lib/split/persistence/cookie_adapter.rb @@ -68,16 +68,14 @@ def delete_cookie_header!(header, key, value) end def hash - @hash ||= begin - if cookies = @cookies[:split.to_s] + @hash ||= if cookies = @cookies[:split.to_s] begin JSON.parse(cookies) rescue JSON::ParserError {} end - else + else {} - end end end diff --git a/lib/split/trial.rb b/lib/split/trial.rb index f9ea1601..3b31a767 100644 --- a/lib/split/trial.rb +++ b/lib/split/trial.rb @@ -25,7 +25,7 @@ def metadata def alternative @alternative ||= if @experiment.has_winner? @experiment.winner - end + end end def alternative=(alternative) From c0b1af43ea1e91f33d34b2c0b77fcd477d43f008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:35:16 -0300 Subject: [PATCH 090/136] Fix Layout/SpaceAroundEqualsInParameterDefault --- .rubocop_todo.yml | 11 ----------- lib/split/goals_collection.rb | 2 +- lib/split/persistence/dual_adapter.rb | 2 +- lib/split/persistence/redis_adapter.rb | 2 +- 4 files changed, 3 insertions(+), 14 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 973ee8ba..223dd7d8 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -25,17 +25,6 @@ Layout/IndentationWidth: - 'lib/split/trial.rb' - 'lib/split/user.rb' -# Offense count: 4 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: space, no_space -Layout/SpaceAroundEqualsInParameterDefault: - Exclude: - - 'lib/split/goals_collection.rb' - - 'lib/split/persistence/dual_adapter.rb' - - 'lib/split/persistence/redis_adapter.rb' - - 'lib/split/user.rb' - # Offense count: 15 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. diff --git a/lib/split/goals_collection.rb b/lib/split/goals_collection.rb index 31b82589..d0950450 100644 --- a/lib/split/goals_collection.rb +++ b/lib/split/goals_collection.rb @@ -2,7 +2,7 @@ module Split class GoalsCollection - def initialize(experiment_name, goals=nil) + def initialize(experiment_name, goals = nil) @experiment_name = experiment_name @goals = goals end diff --git a/lib/split/persistence/dual_adapter.rb b/lib/split/persistence/dual_adapter.rb index b6c7b066..9fc56062 100644 --- a/lib/split/persistence/dual_adapter.rb +++ b/lib/split/persistence/dual_adapter.rb @@ -3,7 +3,7 @@ module Split module Persistence class DualAdapter - def self.with_config(options={}) + def self.with_config(options = {}) self.config.merge!(options) self end diff --git a/lib/split/persistence/redis_adapter.rb b/lib/split/persistence/redis_adapter.rb index 05af5920..d7879836 100644 --- a/lib/split/persistence/redis_adapter.rb +++ b/lib/split/persistence/redis_adapter.rb @@ -44,7 +44,7 @@ def self.find(user_id) new(nil, user_id) end - def self.with_config(options={}) + def self.with_config(options = {}) self.config.merge!(options) self end From f51598df493ff560056f44d9b3d3f54aa0ff9931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:36:13 -0300 Subject: [PATCH 091/136] Fix Layout/SpaceBeforeBlockBraces --- .rubocop_todo.yml | 13 ------------- lib/split/configuration.rb | 14 +++++++------- lib/split/experiment.rb | 8 ++++---- lib/split/experiment_catalog.rb | 2 +- lib/split/helper.rb | 2 +- lib/split/trial.rb | 2 +- 6 files changed, 14 insertions(+), 27 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 223dd7d8..7537461f 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -25,19 +25,6 @@ Layout/IndentationWidth: - 'lib/split/trial.rb' - 'lib/split/user.rb' -# Offense count: 15 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. -# SupportedStyles: space, no_space -# SupportedStylesForEmptyBraces: space, no_space -Layout/SpaceBeforeBlockBraces: - Exclude: - - 'lib/split/configuration.rb' - - 'lib/split/experiment.rb' - - 'lib/split/experiment_catalog.rb' - - 'lib/split/helper.rb' - - 'lib/split/trial.rb' - # Offense count: 35 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index 25c85963..0f045a3d 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -213,14 +213,14 @@ def robot_regex def initialize @ignore_ip_addresses = [] - @ignore_filter = proc{ |request| is_robot? || is_ignored_ip_address? } + @ignore_filter = proc { |request| is_robot? || is_ignored_ip_address? } @db_failover = false - @db_failover_on_db_error = proc{|error|} # e.g. use Rails logger here - @on_experiment_reset = proc{|experiment|} - @on_experiment_delete = proc{|experiment|} - @on_before_experiment_reset = proc{|experiment|} - @on_before_experiment_delete = proc{|experiment|} - @on_experiment_winner_choose = proc{|experiment|} + @db_failover_on_db_error = proc {|error|} # e.g. use Rails logger here + @on_experiment_reset = proc {|experiment|} + @on_experiment_delete = proc {|experiment|} + @on_before_experiment_reset = proc {|experiment|} + @on_before_experiment_delete = proc {|experiment|} + @on_experiment_winner_choose = proc {|experiment|} @db_failover_allow_parameter_override = false @allow_multiple_experiments = false @enabled = true diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index d5dae875..d0d09a7c 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -50,7 +50,7 @@ def extract_alternatives_from_options(options) if alts.length == 1 if alts[0].is_a? Hash - alts = alts[0].map{|k, v| {k => v} } + alts = alts[0].map {|k, v| {k => v} } end end @@ -107,7 +107,7 @@ def ==(obj) end def [](name) - alternatives.find{|a| a.name == name} + alternatives.find {|a| a.name == name} end def algorithm @@ -155,7 +155,7 @@ def winner=(winner_name) end def participant_count - alternatives.inject(0){|sum, a| sum + a.participant_count} + alternatives.inject(0) {|sum, a| sum + a.participant_count} end def control @@ -463,7 +463,7 @@ def redis_interface def persist_experiment_configuration redis_interface.add_to_set(:experiments, name) - redis_interface.persist_list(name, @alternatives.map{|alt| {alt.name => alt.weight}.to_json}) + redis_interface.persist_list(name, @alternatives.map {|alt| {alt.name => alt.weight}.to_json}) goals_collection.save if @metadata diff --git a/lib/split/experiment_catalog.rb b/lib/split/experiment_catalog.rb index 076fd3e1..a92fb915 100644 --- a/lib/split/experiment_catalog.rb +++ b/lib/split/experiment_catalog.rb @@ -10,7 +10,7 @@ def self.all # Return experiments without a winner (considered "active") first def self.all_active_first - all.partition{|e| not e.winner}.map{|es| es.sort_by(&:name)}.flatten + all.partition {|e| not e.winner}.map {|es| es.sort_by(&:name)}.flatten end def self.find(name) diff --git a/lib/split/helper.rb b/lib/split/helper.rb index 983cdded..59405c5f 100644 --- a/lib/split/helper.rb +++ b/lib/split/helper.rb @@ -95,7 +95,7 @@ def ab_record_extra_info(metric_descriptor, key, value = 1) alternative_name = ab_user[experiment.key] if alternative_name - alternative = experiment.alternatives.find{|alt| alt.name == alternative_name} + alternative = experiment.alternatives.find {|alt| alt.name == alternative_name} alternative.record_extra_info(key, value) if alternative end end diff --git a/lib/split/trial.rb b/lib/split/trial.rb index 3b31a767..89c1dfec 100644 --- a/lib/split/trial.rb +++ b/lib/split/trial.rb @@ -32,7 +32,7 @@ def alternative=(alternative) @alternative = if alternative.kind_of?(Split::Alternative) alternative else - @experiment.alternatives.find{|a| a.name == alternative } + @experiment.alternatives.find {|a| a.name == alternative } end end From b2562db35eaffd149a03a5ca5dae5efb1c2e0874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:36:53 -0300 Subject: [PATCH 092/136] Fix Layout/SpaceInsideBlockBraces --- .rubocop_todo.yml | 15 --------------- lib/split.rb | 4 ++-- lib/split/configuration.rb | 12 ++++++------ lib/split/experiment.rb | 10 +++++----- lib/split/experiment_catalog.rb | 4 ++-- lib/split/helper.rb | 2 +- lib/split/trial.rb | 4 ++-- lib/split/user.rb | 2 +- 8 files changed, 19 insertions(+), 34 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7537461f..56760c31 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -25,21 +25,6 @@ Layout/IndentationWidth: - 'lib/split/trial.rb' - 'lib/split/user.rb' -# Offense count: 35 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. -# SupportedStyles: space, no_space -# SupportedStylesForEmptyBraces: space, no_space -Layout/SpaceInsideBlockBraces: - Exclude: - - 'lib/split.rb' - - 'lib/split/configuration.rb' - - 'lib/split/experiment.rb' - - 'lib/split/experiment_catalog.rb' - - 'lib/split/helper.rb' - - 'lib/split/trial.rb' - - 'lib/split/user.rb' - # Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. diff --git a/lib/split.rb b/lib/split.rb index 5ffd5214..6d1c6c05 100755 --- a/lib/split.rb +++ b/lib/split.rb @@ -76,8 +76,8 @@ def cache(namespace, key, &block) # 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. if defined?(::Rails) class Split::Railtie < Rails::Railtie - config.before_initialize { Split.configure {} } + config.before_initialize { Split.configure { } } end else - Split.configure {} + Split.configure { } end diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index 0f045a3d..6b5b2d04 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -215,12 +215,12 @@ def initialize @ignore_ip_addresses = [] @ignore_filter = proc { |request| is_robot? || is_ignored_ip_address? } @db_failover = false - @db_failover_on_db_error = proc {|error|} # e.g. use Rails logger here - @on_experiment_reset = proc {|experiment|} - @on_experiment_delete = proc {|experiment|} - @on_before_experiment_reset = proc {|experiment|} - @on_before_experiment_delete = proc {|experiment|} - @on_experiment_winner_choose = proc {|experiment|} + @db_failover_on_db_error = proc { |error| } # e.g. use Rails logger here + @on_experiment_reset = proc { |experiment| } + @on_experiment_delete = proc { |experiment| } + @on_before_experiment_reset = proc { |experiment| } + @on_before_experiment_delete = proc { |experiment| } + @on_experiment_winner_choose = proc { |experiment| } @db_failover_allow_parameter_override = false @allow_multiple_experiments = false @enabled = true diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index d0d09a7c..d608fb7a 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -50,7 +50,7 @@ def extract_alternatives_from_options(options) if alts.length == 1 if alts[0].is_a? Hash - alts = alts[0].map {|k, v| {k => v} } + alts = alts[0].map { |k, v| {k => v} } end end @@ -94,7 +94,7 @@ def validate! if @alternatives.empty? && Split.configuration.experiment_for(@name).nil? raise ExperimentNotFound.new("Experiment #{@name} not found") end - @alternatives.each {|a| a.validate! } + @alternatives.each { |a| a.validate! } goals_collection.validate! end @@ -107,7 +107,7 @@ def ==(obj) end def [](name) - alternatives.find {|a| a.name == name} + alternatives.find { |a| a.name == name } end def algorithm @@ -155,7 +155,7 @@ def winner=(winner_name) end def participant_count - alternatives.inject(0) {|sum, a| sum + a.participant_count} + alternatives.inject(0) { |sum, a| sum + a.participant_count } end def control @@ -463,7 +463,7 @@ def redis_interface def persist_experiment_configuration redis_interface.add_to_set(:experiments, name) - redis_interface.persist_list(name, @alternatives.map {|alt| {alt.name => alt.weight}.to_json}) + redis_interface.persist_list(name, @alternatives.map { |alt| {alt.name => alt.weight}.to_json }) goals_collection.save if @metadata diff --git a/lib/split/experiment_catalog.rb b/lib/split/experiment_catalog.rb index a92fb915..dc2be47e 100644 --- a/lib/split/experiment_catalog.rb +++ b/lib/split/experiment_catalog.rb @@ -5,12 +5,12 @@ class ExperimentCatalog # Return all experiments def self.all # Call compact to prevent nil experiments from being returned -- seems to happen during gem upgrades - Split.redis.smembers(:experiments).map {|e| find(e)}.compact + Split.redis.smembers(:experiments).map { |e| find(e) }.compact end # Return experiments without a winner (considered "active") first def self.all_active_first - all.partition {|e| not e.winner}.map {|es| es.sort_by(&:name)}.flatten + all.partition { |e| not e.winner }.map { |es| es.sort_by(&:name) }.flatten end def self.find(name) diff --git a/lib/split/helper.rb b/lib/split/helper.rb index 59405c5f..5d35aaeb 100644 --- a/lib/split/helper.rb +++ b/lib/split/helper.rb @@ -95,7 +95,7 @@ def ab_record_extra_info(metric_descriptor, key, value = 1) alternative_name = ab_user[experiment.key] if alternative_name - alternative = experiment.alternatives.find {|alt| alt.name == alternative_name} + alternative = experiment.alternatives.find { |alt| alt.name == alternative_name } alternative.record_extra_info(key, value) if alternative end end diff --git a/lib/split/trial.rb b/lib/split/trial.rb index 89c1dfec..3e6a6018 100644 --- a/lib/split/trial.rb +++ b/lib/split/trial.rb @@ -32,7 +32,7 @@ def alternative=(alternative) @alternative = if alternative.kind_of?(Split::Alternative) alternative else - @experiment.alternatives.find {|a| a.name == alternative } + @experiment.alternatives.find { |a| a.name == alternative } end end @@ -41,7 +41,7 @@ def complete!(context = nil) if Array(goals).empty? alternative.increment_completion else - Array(goals).each {|g| alternative.increment_completion(g) } + Array(goals).each { |g| alternative.increment_completion(g) } end run_callback context, Split.configuration.on_trial_complete diff --git a/lib/split/user.rb b/lib/split/user.rb index ff923829..9e8d2725 100644 --- a/lib/split/user.rb +++ b/lib/split/user.rb @@ -29,7 +29,7 @@ def max_experiments_reached?(experiment_key) if Split.configuration.allow_multiple_experiments == 'control' experiments = active_experiments experiment_key_without_version = key_without_version(experiment_key) - count_control = experiments.count {|k, v| k == experiment_key_without_version || v == 'control'} + count_control = experiments.count { |k, v| k == experiment_key_without_version || v == 'control' } experiments.size > count_control else !Split.configuration.allow_multiple_experiments && From baba207dd46f991b00abbbef978e65f5c77b8cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:37:25 -0300 Subject: [PATCH 093/136] Fix Layout/SpaceInsideHashLiteralBraces --- .rubocop_todo.yml | 11 ----------- lib/split/experiment.rb | 4 ++-- lib/split/helper.rb | 4 ++-- lib/split/persistence/redis_adapter.rb | 2 +- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 56760c31..6d4a2b8c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -25,17 +25,6 @@ Layout/IndentationWidth: - 'lib/split/trial.rb' - 'lib/split/user.rb' -# Offense count: 10 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. -# SupportedStyles: space, no_space, compact -# SupportedStylesForEmptyBraces: space, no_space -Layout/SpaceInsideHashLiteralBraces: - Exclude: - - 'lib/split/experiment.rb' - - 'lib/split/helper.rb' - - 'lib/split/persistence/redis_adapter.rb' - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index d608fb7a..c4bc9beb 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -50,7 +50,7 @@ def extract_alternatives_from_options(options) if alts.length == 1 if alts[0].is_a? Hash - alts = alts[0].map { |k, v| {k => v} } + alts = alts[0].map { |k, v| { k => v } } end end @@ -463,7 +463,7 @@ def redis_interface def persist_experiment_configuration redis_interface.add_to_set(:experiments, name) - redis_interface.persist_list(name, @alternatives.map { |alt| {alt.name => alt.weight}.to_json }) + redis_interface.persist_list(name, @alternatives.map { |alt| { alt.name => alt.weight }.to_json }) goals_collection.save if @metadata diff --git a/lib/split/helper.rb b/lib/split/helper.rb index 5d35aaeb..2d0d5447 100644 --- a/lib/split/helper.rb +++ b/lib/split/helper.rb @@ -44,7 +44,7 @@ def reset!(experiment) ab_user.delete(experiment.key) end - def finish_experiment(experiment, options = {reset: true}) + def finish_experiment(experiment, options = { reset: true }) return false if active_experiments[experiment.name].nil? return true if experiment.has_winner? should_reset = experiment.resettable? && options[:reset] @@ -69,7 +69,7 @@ def finish_experiment(experiment, options = {reset: true}) end end - def ab_finished(metric_descriptor, options = {reset: true}) + def ab_finished(metric_descriptor, options = { reset: true }) return if exclude_visitor? || Split.configuration.disabled? metric_descriptor, goals = normalize_metric(metric_descriptor) experiments = Metric.possible_experiments(metric_descriptor) diff --git a/lib/split/persistence/redis_adapter.rb b/lib/split/persistence/redis_adapter.rb index d7879836..52cc0342 100644 --- a/lib/split/persistence/redis_adapter.rb +++ b/lib/split/persistence/redis_adapter.rb @@ -3,7 +3,7 @@ module Split module Persistence class RedisAdapter - DEFAULT_CONFIG = {namespace: 'persistence'}.freeze + DEFAULT_CONFIG = { namespace: 'persistence' }.freeze attr_reader :redis_key From 96ab0c01426171eb0ee5edfda2ab3c4b6e447920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:38:00 -0300 Subject: [PATCH 094/136] Fix Layout/TrailingEmptyLines --- .rubocop_todo.yml | 8 -------- Rakefile | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6d4a2b8c..62cacee6 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -25,14 +25,6 @@ Layout/IndentationWidth: - 'lib/split/trial.rb' - 'lib/split/user.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: final_newline, final_blank_line -Layout/TrailingEmptyLines: - Exclude: - - 'Rakefile' - # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: AllowInHeredoc. diff --git a/Rakefile b/Rakefile index f47f8b65..a7f213b6 100755 --- a/Rakefile +++ b/Rakefile @@ -6,4 +6,4 @@ require 'rspec/core/rake_task' RSpec::Core::RakeTask.new('spec') -task default: :spec \ No newline at end of file +task default: :spec From 67d57b1eb8f16c4ea7caf2ec0d6bf559d693f3fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:38:28 -0300 Subject: [PATCH 095/136] Fix Layout/TrailingWhitespace --- .rubocop_todo.yml | 7 ------- lib/split/helper.rb | 6 +++--- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 62cacee6..a4b0f2b3 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -25,13 +25,6 @@ Layout/IndentationWidth: - 'lib/split/trial.rb' - 'lib/split/user.rb' -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: AllowInHeredoc. -Layout/TrailingWhitespace: - Exclude: - - 'lib/split/helper.rb' - # Offense count: 1 Lint/UselessAssignment: Exclude: diff --git a/lib/split/helper.rb b/lib/split/helper.rb index 2d0d5447..6c2ed0fc 100644 --- a/lib/split/helper.rb +++ b/lib/split/helper.rb @@ -53,12 +53,12 @@ def finish_experiment(experiment, options = { reset: true }) else alternative_name = ab_user[experiment.key] trial = Trial.new( - user: ab_user, + user: ab_user, experiment: experiment, alternative: alternative_name, goals: options[:goals], - ) - + ) + trial.complete!(self) if should_reset From 003c098fe91b1f0bd635f58daa41381a35b334b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:39:35 -0300 Subject: [PATCH 096/136] Fix Layout/IndentationWidth --- .rubocop_todo.yml | 19 --- lib/split/algorithms/block_randomization.rb | 10 +- lib/split/algorithms/whiplash.rb | 32 ++--- lib/split/alternative.rb | 12 +- lib/split/configuration.rb | 16 +-- lib/split/dashboard/pagination_helpers.rb | 104 ++++++++-------- lib/split/encapsulated_helper.rb | 8 +- lib/split/experiment.rb | 128 ++++++++++---------- lib/split/goals_collection.rb | 6 +- lib/split/persistence/cookie_adapter.rb | 90 +++++++------- lib/split/persistence/dual_adapter.rb | 12 +- lib/split/redis_interface.rb | 2 +- lib/split/trial.rb | 40 +++--- lib/split/user.rb | 18 +-- 14 files changed, 239 insertions(+), 258 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index a4b0f2b3..a355bc63 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,25 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 17 -# Cop supports --auto-correct. -# Configuration parameters: Width, IgnoredPatterns. -Layout/IndentationWidth: - Exclude: - - 'lib/split/algorithms/block_randomization.rb' - - 'lib/split/algorithms/whiplash.rb' - - 'lib/split/alternative.rb' - - 'lib/split/configuration.rb' - - 'lib/split/dashboard/pagination_helpers.rb' - - 'lib/split/encapsulated_helper.rb' - - 'lib/split/experiment.rb' - - 'lib/split/goals_collection.rb' - - 'lib/split/persistence/cookie_adapter.rb' - - 'lib/split/persistence/dual_adapter.rb' - - 'lib/split/redis_interface.rb' - - 'lib/split/trial.rb' - - 'lib/split/user.rb' - # Offense count: 1 Lint/UselessAssignment: Exclude: diff --git a/lib/split/algorithms/block_randomization.rb b/lib/split/algorithms/block_randomization.rb index 145e5e5c..9cf8acbb 100644 --- a/lib/split/algorithms/block_randomization.rb +++ b/lib/split/algorithms/block_randomization.rb @@ -12,11 +12,11 @@ def choose_alternative(experiment) end private - def minimum_participant_alternatives(alternatives) - alternatives_by_count = alternatives.group_by(&:participant_count) - min_group = alternatives_by_count.min_by { |k, v| k } - min_group.last - end + def minimum_participant_alternatives(alternatives) + alternatives_by_count = alternatives.group_by(&:participant_count) + min_group = alternatives_by_count.min_by { |k, v| k } + min_group.last + end end end end diff --git a/lib/split/algorithms/whiplash.rb b/lib/split/algorithms/whiplash.rb index 997e7a0e..9308b548 100644 --- a/lib/split/algorithms/whiplash.rb +++ b/lib/split/algorithms/whiplash.rb @@ -12,25 +12,25 @@ def choose_alternative(experiment) end private - def arm_guess(participants, completions) - a = [participants, 0].max - b = [participants-completions, 0].max - Split::Algorithms.beta_distribution_rng(a + fairness_constant, b + fairness_constant) - end + def arm_guess(participants, completions) + a = [participants, 0].max + b = [participants-completions, 0].max + Split::Algorithms.beta_distribution_rng(a + fairness_constant, b + fairness_constant) + end - def best_guess(alternatives) - guesses = {} - alternatives.each do |alternative| - guesses[alternative.name] = arm_guess(alternative.participant_count, alternative.all_completed_count) + def best_guess(alternatives) + guesses = {} + alternatives.each do |alternative| + guesses[alternative.name] = arm_guess(alternative.participant_count, alternative.all_completed_count) + end + gmax = guesses.values.max + best = guesses.keys.select { |name| guesses[name] == gmax } + best.sample end - gmax = guesses.values.max - best = guesses.keys.select { |name| guesses[name] == gmax } - best.sample - end - def fairness_constant - 7 - end + def fairness_constant + 7 + end end end end diff --git a/lib/split/alternative.rb b/lib/split/alternative.rb index 6cef3766..ee962f07 100644 --- a/lib/split/alternative.rb +++ b/lib/split/alternative.rb @@ -180,12 +180,12 @@ def delete end private - def hash_with_correct_values?(name) - Hash === name && String === name.keys.first && Float(name.values.first) rescue false - end + def hash_with_correct_values?(name) + Hash === name && String === name.keys.first && Float(name.values.first) rescue false + end - def key - "#{experiment_name}:#{name}" - end + def key + "#{experiment_name}:#{name}" + end end end diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index 6b5b2d04..b06be51e 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -157,7 +157,7 @@ def normalized_experiments @experiments.each do |experiment_name, settings| alternatives = if (alts = value_for(settings, :alternatives)) - normalize_alternatives(alts) + normalize_alternatives(alts) end experiment_data = { @@ -237,14 +237,14 @@ def initialize end private - def value_for(hash, key) - if hash.kind_of?(Hash) - hash.has_key?(key.to_s) ? hash[key.to_s] : hash[key.to_sym] + def value_for(hash, key) + if hash.kind_of?(Hash) + hash.has_key?(key.to_s) ? hash[key.to_s] : hash[key.to_sym] + end end - end - def escaped_bots - bots.map { |key, _| Regexp.escape(key) } - end + def escaped_bots + bots.map { |key, _| Regexp.escape(key) } + end end end diff --git a/lib/split/dashboard/pagination_helpers.rb b/lib/split/dashboard/pagination_helpers.rb index b61b57c3..5031135e 100644 --- a/lib/split/dashboard/pagination_helpers.rb +++ b/lib/split/dashboard/pagination_helpers.rb @@ -30,57 +30,57 @@ def pagination(collection) end private - def show_first_page_tag? - page_number > 2 - end - - def first_page_tag - %Q(1) - end - - def show_first_ellipsis_tag? - page_number >= 4 - end - - def ellipsis_tag - '...' - end - - def show_prev_page_tag? - page_number > 1 - end - - def prev_page_tag - %Q(#{page_number - 1}) - end - - def current_page_tag - "#{page_number}" - end - - def show_next_page_tag?(collection) - (page_number * pagination_per) < collection.count - end - - def next_page_tag - %Q(#{page_number + 1}) - end - - def show_last_ellipsis_tag?(collection) - (total_pages(collection) - page_number) >= 3 - end - - def total_pages(collection) - collection.count / pagination_per + ((collection.count % pagination_per).zero? ? 0 : 1) - end - - def show_last_page_tag?(collection) - page_number < (total_pages(collection) - 1) - end - - def last_page_tag(collection) - total = total_pages(collection) - %Q(#{total}) - end + def show_first_page_tag? + page_number > 2 + end + + def first_page_tag + %Q(1) + end + + def show_first_ellipsis_tag? + page_number >= 4 + end + + def ellipsis_tag + '...' + end + + def show_prev_page_tag? + page_number > 1 + end + + def prev_page_tag + %Q(#{page_number - 1}) + end + + def current_page_tag + "#{page_number}" + end + + def show_next_page_tag?(collection) + (page_number * pagination_per) < collection.count + end + + def next_page_tag + %Q(#{page_number + 1}) + end + + def show_last_ellipsis_tag?(collection) + (total_pages(collection) - page_number) >= 3 + end + + def total_pages(collection) + collection.count / pagination_per + ((collection.count % pagination_per).zero? ? 0 : 1) + end + + def show_last_page_tag?(collection) + page_number < (total_pages(collection) - 1) + end + + def last_page_tag(collection) + total = total_pages(collection) + %Q(#{total}) + end end end diff --git a/lib/split/encapsulated_helper.rb b/lib/split/encapsulated_helper.rb index 70eef14b..e200e0fe 100644 --- a/lib/split/encapsulated_helper.rb +++ b/lib/split/encapsulated_helper.rb @@ -33,9 +33,9 @@ def ab_test(*arguments, &block) end private - # instantiate and memoize a context shim in case of multiple ab_test* calls - def split_context_shim - @split_context_shim ||= ContextShim.new(self) - end + # instantiate and memoize a context shim in case of multiple ab_test* calls + def split_context_shim + @split_context_shim ||= ContextShim.new(self) + end end end diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index c4bc9beb..13a429f6 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -392,9 +392,9 @@ def calc_time def jstring(goal = nil) js_id = if goal.nil? - name + name else - name + "-" + goal + name + "-" + goal end js_id.gsub('/', '--') end @@ -417,84 +417,84 @@ def enable_cohorting end protected - def experiment_config_key - "experiment_configurations/#{@name}" - end + def experiment_config_key + "experiment_configurations/#{@name}" + end - def load_metadata_from_configuration - Split.configuration.experiment_for(@name)[:metadata] - end + def load_metadata_from_configuration + Split.configuration.experiment_for(@name)[:metadata] + end - def load_metadata_from_redis - meta = redis.get(metadata_key) - JSON.parse(meta) unless meta.nil? - end + def load_metadata_from_redis + meta = redis.get(metadata_key) + JSON.parse(meta) unless meta.nil? + end - def load_alternatives_from_configuration - alts = Split.configuration.experiment_for(@name)[:alternatives] - raise ArgumentError, "Experiment configuration is missing :alternatives array" unless alts - if alts.is_a?(Hash) - alts.keys - else - alts.flatten + def load_alternatives_from_configuration + alts = Split.configuration.experiment_for(@name)[:alternatives] + raise ArgumentError, "Experiment configuration is missing :alternatives array" unless alts + if alts.is_a?(Hash) + alts.keys + else + alts.flatten + end end - end - def load_alternatives_from_redis - alternatives = redis.lrange(@name, 0, -1) - alternatives.map do |alt| - alt = begin - JSON.parse(alt) - rescue - alt - end - Split::Alternative.new(alt, @name) + def load_alternatives_from_redis + alternatives = redis.lrange(@name, 0, -1) + alternatives.map do |alt| + alt = begin + JSON.parse(alt) + rescue + alt + end + Split::Alternative.new(alt, @name) + end end - end private - def redis - Split.redis - end + def redis + Split.redis + end - def redis_interface - RedisInterface.new - end + def redis_interface + RedisInterface.new + end - def persist_experiment_configuration - redis_interface.add_to_set(:experiments, name) - redis_interface.persist_list(name, @alternatives.map { |alt| { alt.name => alt.weight }.to_json }) - goals_collection.save + def persist_experiment_configuration + redis_interface.add_to_set(:experiments, name) + redis_interface.persist_list(name, @alternatives.map { |alt| { alt.name => alt.weight }.to_json }) + goals_collection.save - if @metadata - redis.set(metadata_key, @metadata.to_json) - else - delete_metadata + if @metadata + redis.set(metadata_key, @metadata.to_json) + else + delete_metadata + end end - end - def remove_experiment_configuration - @alternatives.each(&:delete) - goals_collection.delete - delete_metadata - redis.del(@name) - end + def remove_experiment_configuration + @alternatives.each(&:delete) + goals_collection.delete + delete_metadata + redis.del(@name) + end - def experiment_configuration_has_changed? - existing_experiment = Experiment.find(@name) + def experiment_configuration_has_changed? + existing_experiment = Experiment.find(@name) - existing_experiment.alternatives.map(&:to_s) != @alternatives.map(&:to_s) || - existing_experiment.goals != @goals || - existing_experiment.metadata != @metadata - end + existing_experiment.alternatives.map(&:to_s) != @alternatives.map(&:to_s) || + existing_experiment.goals != @goals || + existing_experiment.metadata != @metadata + end - def goals_collection - Split::GoalsCollection.new(@name, @goals) - end + def goals_collection + Split::GoalsCollection.new(@name, @goals) + end - def remove_experiment_cohorting - @cohorting_disabled = false - redis.hdel(experiment_config_key, :cohorting) - end + def remove_experiment_cohorting + @cohorting_disabled = false + redis.hdel(experiment_config_key, :cohorting) + end end end diff --git a/lib/split/goals_collection.rb b/lib/split/goals_collection.rb index d0950450..ffca38bd 100644 --- a/lib/split/goals_collection.rb +++ b/lib/split/goals_collection.rb @@ -37,8 +37,8 @@ def delete end private - def goals_key - "#{@experiment_name}:goals" - end + def goals_key + "#{@experiment_name}:goals" + end end end diff --git a/lib/split/persistence/cookie_adapter.rb b/lib/split/persistence/cookie_adapter.rb index bef18550..3157ce87 100644 --- a/lib/split/persistence/cookie_adapter.rb +++ b/lib/split/persistence/cookie_adapter.rb @@ -29,67 +29,67 @@ def keys end private - def set_cookie(value = {}) - cookie_key = :split.to_s - cookie_value = default_options.merge(value: JSON.generate(value)) - if action_dispatch? - # The "send" is necessary when we call ab_test from the controller - # and thus @context is a rails controller, because then "cookies" is - # a private method. - @context.send(:cookies)[cookie_key] = cookie_value - else - set_cookie_via_rack(cookie_key, cookie_value) + def set_cookie(value = {}) + cookie_key = :split.to_s + cookie_value = default_options.merge(value: JSON.generate(value)) + if action_dispatch? + # The "send" is necessary when we call ab_test from the controller + # and thus @context is a rails controller, because then "cookies" is + # a private method. + @context.send(:cookies)[cookie_key] = cookie_value + else + set_cookie_via_rack(cookie_key, cookie_value) + end end - end - - def default_options - { expires: @expires, path: '/', domain: cookie_domain_config }.compact - end - def set_cookie_via_rack(key, value) - delete_cookie_header!(@response.header, key, value) - Rack::Utils.set_cookie_header!(@response.header, key, value) - end + def default_options + { expires: @expires, path: '/', domain: cookie_domain_config }.compact + end - # Use Rack::Utils#make_delete_cookie_header after Rack 2.0.0 - def delete_cookie_header!(header, key, value) - cookie_header = header['Set-Cookie'] - case cookie_header - when nil, '' - cookies = [] - when String - cookies = cookie_header.split("\n") - when Array - cookies = cookie_header + def set_cookie_via_rack(key, value) + delete_cookie_header!(@response.header, key, value) + Rack::Utils.set_cookie_header!(@response.header, key, value) end - cookies.reject! { |cookie| cookie =~ /\A#{Rack::Utils.escape(key)}=/ } - header['Set-Cookie'] = cookies.join("\n") - end + # Use Rack::Utils#make_delete_cookie_header after Rack 2.0.0 + def delete_cookie_header!(header, key, value) + cookie_header = header['Set-Cookie'] + case cookie_header + when nil, '' + cookies = [] + when String + cookies = cookie_header.split("\n") + when Array + cookies = cookie_header + end + + cookies.reject! { |cookie| cookie =~ /\A#{Rack::Utils.escape(key)}=/ } + header['Set-Cookie'] = cookies.join("\n") + end - def hash - @hash ||= if cookies = @cookies[:split.to_s] + def hash + @hash ||= if cookies = @cookies[:split.to_s] begin JSON.parse(cookies) rescue JSON::ParserError {} end - else + else {} + end end - end - def cookie_length_config - Split.configuration.persistence_cookie_length - end + def cookie_length_config + Split.configuration.persistence_cookie_length + end - def cookie_domain_config - Split.configuration.persistence_cookie_domain - end + def cookie_domain_config + Split.configuration.persistence_cookie_domain + end - def action_dispatch? - defined?(Rails) && @response.is_a?(ActionDispatch::Response) - end + def action_dispatch? + defined?(Rails) && @response.is_a?(ActionDispatch::Response) + end end end end diff --git a/lib/split/persistence/dual_adapter.rb b/lib/split/persistence/dual_adapter.rb index 9fc56062..82acac42 100644 --- a/lib/split/persistence/dual_adapter.rb +++ b/lib/split/persistence/dual_adapter.rb @@ -72,13 +72,13 @@ def delete(key) end private - def decrement_participation?(old_value, value) - !old_value.nil? && !value.nil? && old_value != value - end + def decrement_participation?(old_value, value) + !old_value.nil? && !value.nil? && old_value != value + end - def decrement_participation(key, value) - Split.redis.hincrby("#{key}:#{value}", 'participant_count', -1) - end + def decrement_participation(key, value) + Split.redis.hincrby("#{key}:#{value}", 'participant_count', -1) + end end end end diff --git a/lib/split/redis_interface.rb b/lib/split/redis_interface.rb index 5cc0a828..a0b0f54b 100644 --- a/lib/split/redis_interface.rb +++ b/lib/split/redis_interface.rb @@ -24,6 +24,6 @@ def add_to_set(set_name, value) end private - attr_accessor :redis + attr_accessor :redis end end diff --git a/lib/split/trial.rb b/lib/split/trial.rb index 3e6a6018..cf858b31 100644 --- a/lib/split/trial.rb +++ b/lib/split/trial.rb @@ -24,7 +24,7 @@ def metadata def alternative @alternative ||= if @experiment.has_winner? - @experiment.winner + @experiment.winner end end @@ -97,30 +97,30 @@ def choose!(context = nil) end private - def run_callback(context, callback_name) - context.send(callback_name, self) if callback_name && context.respond_to?(callback_name, true) - end + def run_callback(context, callback_name) + context.send(callback_name, self) if callback_name && context.respond_to?(callback_name, true) + end - def override_is_alternative? - @experiment.alternatives.map(&:name).include?(@options[:override]) - end + def override_is_alternative? + @experiment.alternatives.map(&:name).include?(@options[:override]) + end - def should_store_alternative? - if @options[:override] || @options[:disabled] - Split.configuration.store_override - else - !exclude_user? + def should_store_alternative? + if @options[:override] || @options[:disabled] + Split.configuration.store_override + else + !exclude_user? + end end - end - def cleanup_old_versions - if @experiment.version > 0 - @user.cleanup_old_versions!(@experiment) + def cleanup_old_versions + if @experiment.version > 0 + @user.cleanup_old_versions!(@experiment) + end end - end - def exclude_user? - @options[:exclude] || @experiment.start_time.nil? || @user.max_experiments_reached?(@experiment.key) - end + def exclude_user? + @options[:exclude] || @experiment.start_time.nil? || @user.max_experiments_reached?(@experiment.key) + end end end diff --git a/lib/split/user.rb b/lib/split/user.rb index 9e8d2725..24ca4ab1 100644 --- a/lib/split/user.rb +++ b/lib/split/user.rb @@ -65,16 +65,16 @@ def self.find(user_id, adapter) end private - def keys_without_experiment(keys, experiment_key) - keys.reject { |k| k.match(Regexp.new("^#{experiment_key}(:finished)?$")) } - end + def keys_without_experiment(keys, experiment_key) + keys.reject { |k| k.match(Regexp.new("^#{experiment_key}(:finished)?$")) } + end - def keys_without_finished(keys) - keys.reject { |k| k.include?(":finished") } - end + def keys_without_finished(keys) + keys.reject { |k| k.include?(":finished") } + end - def key_without_version(key) - key.split(/\:\d(?!\:)/)[0] - end + def key_without_version(key) + key.split(/\:\d(?!\:)/)[0] + end end end From 3ed07e3cdb2285c4b6628df7a4bf53c6b23610c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:43:22 -0300 Subject: [PATCH 097/136] Fix Lint/UselessAssignment --- .rubocop_todo.yml | 5 ----- lib/split/goals_collection.rb | 6 +++--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index a355bc63..cc501661 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,11 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 1 -Lint/UselessAssignment: - Exclude: - - 'lib/split/goals_collection.rb' - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/lib/split/goals_collection.rb b/lib/split/goals_collection.rb index ffca38bd..10b15bac 100644 --- a/lib/split/goals_collection.rb +++ b/lib/split/goals_collection.rb @@ -14,10 +14,10 @@ def load_from_redis def load_from_configuration goals = Split.configuration.experiment_for(@experiment_name)[:goals] - if goals.nil? - goals = [] - else + if goals goals.flatten + else + [] end end From 22954b99e55651b022d8422ca1e97538a45474d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:44:13 -0300 Subject: [PATCH 098/136] Add missing frozen_string_literal: true --- lib/split/algorithms.rb | 2 ++ spec/algorithms/block_randomization_spec.rb | 2 ++ spec/dashboard/pagination_helpers_spec.rb | 2 ++ spec/goals_collection_spec.rb | 2 ++ spec/redis_interface_spec.rb | 2 ++ spec/user_spec.rb | 2 ++ 6 files changed, 12 insertions(+) diff --git a/lib/split/algorithms.rb b/lib/split/algorithms.rb index defd8325..d4317b82 100644 --- a/lib/split/algorithms.rb +++ b/lib/split/algorithms.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require "matrix" rescue LoadError => error diff --git a/spec/algorithms/block_randomization_spec.rb b/spec/algorithms/block_randomization_spec.rb index e257f96d..3bab3fb5 100644 --- a/spec/algorithms/block_randomization_spec.rb +++ b/spec/algorithms/block_randomization_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Split::Algorithms::BlockRandomization do diff --git a/spec/dashboard/pagination_helpers_spec.rb b/spec/dashboard/pagination_helpers_spec.rb index 315595e0..c9a360bd 100644 --- a/spec/dashboard/pagination_helpers_spec.rb +++ b/spec/dashboard/pagination_helpers_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'split/dashboard/pagination_helpers' diff --git a/spec/goals_collection_spec.rb b/spec/goals_collection_spec.rb index a5d746ab..f7ba648f 100644 --- a/spec/goals_collection_spec.rb +++ b/spec/goals_collection_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'split/goals_collection' require 'time' diff --git a/spec/redis_interface_spec.rb b/spec/redis_interface_spec.rb index c37c3c28..2d2bd362 100644 --- a/spec/redis_interface_spec.rb +++ b/spec/redis_interface_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Split::RedisInterface do diff --git a/spec/user_spec.rb b/spec/user_spec.rb index 6e2e22d2..f121ffb6 100644 --- a/spec/user_spec.rb +++ b/spec/user_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'split/experiment_catalog' require 'split/experiment' From ac81c9c75f57eb6bbc35d3e84e9212e44878711c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 18:45:55 -0300 Subject: [PATCH 099/136] Enable rubocop for specs too --- .rubocop.yml | 1 - spec/helper_spec.rb | 2 +- spec/spec_helper.rb | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index ad011bd1..c2cf054a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -5,7 +5,6 @@ AllCops: DisabledByDefault: true Exclude: - 'gemfiles/**/*' - - 'spec/**/*.rb' Style/AndOr: Enabled: true diff --git a/spec/helper_spec.rb b/spec/helper_spec.rb index 20f298cf..62a40a68 100755 --- a/spec/helper_spec.rb +++ b/spec/helper_spec.rb @@ -847,7 +847,7 @@ def should_finish_experiment(experiment_name, should_finish = true) it "should only count completion of users on the current version" do alternative_name = ab_test('link_color', 'blue', 'red') expect(ab_user['link_color']).to eq(alternative_name) - alternative = Split::Alternative.new(alternative_name, 'link_color') + Split::Alternative.new(alternative_name, 'link_color') experiment.reset expect(experiment.version).to eq(1) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8fa9e743..bb47c76c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -25,7 +25,7 @@ module GlobalSharedContext Split.redis.flushdb Split::Cache.clear @ab_user = mock_user - params = nil + @params = nil end end From b7bf52a00f7ed669e3dd93e9b89107b4291af714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 19:24:06 -0300 Subject: [PATCH 100/136] Enable Rubocop to be executed at CI --- .github/workflows/ci.yml | 3 +++ Gemfile | 1 + gemfiles/5.2.gemfile | 1 + gemfiles/6.0.gemfile | 1 + gemfiles/6.1.gemfile | 1 + gemfiles/7.0.gemfile | 1 + 6 files changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d58a8ca..8ef56f7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,3 +69,6 @@ jobs: run: bundle exec rspec env: REDIS_URL: redis:6379 + + - name: Rubocop + run: bundle exec rubocop diff --git a/Gemfile b/Gemfile index 42dcaf57..d5afa8de 100644 --- a/Gemfile +++ b/Gemfile @@ -4,5 +4,6 @@ source "https://rubygems.org" gemspec +gem 'rubocop', require: false gem "matrix" gem "codeclimate-test-reporter" diff --git a/gemfiles/5.2.gemfile b/gemfiles/5.2.gemfile index 3cf9b401..2530b2b9 100644 --- a/gemfiles/5.2.gemfile +++ b/gemfiles/5.2.gemfile @@ -1,5 +1,6 @@ source "https://rubygems.org" +gem "rubocop", require: false gem "codeclimate-test-reporter" gem "rails", "~> 5.2" diff --git a/gemfiles/6.0.gemfile b/gemfiles/6.0.gemfile index c4f80588..1283d3c9 100644 --- a/gemfiles/6.0.gemfile +++ b/gemfiles/6.0.gemfile @@ -1,5 +1,6 @@ source "https://rubygems.org" +gem "rubocop", require: false gem "codeclimate-test-reporter" gem "rails", "~> 6.0" diff --git a/gemfiles/6.1.gemfile b/gemfiles/6.1.gemfile index 1aea2d76..1d0b5256 100644 --- a/gemfiles/6.1.gemfile +++ b/gemfiles/6.1.gemfile @@ -1,5 +1,6 @@ source "https://rubygems.org" +gem "rubocop", require: false gem "codeclimate-test-reporter" gem "rails", "~> 6.1" diff --git a/gemfiles/7.0.gemfile b/gemfiles/7.0.gemfile index 29da9d65..f9464c11 100644 --- a/gemfiles/7.0.gemfile +++ b/gemfiles/7.0.gemfile @@ -1,5 +1,6 @@ source "https://rubygems.org" +gem "rubocop", require: false gem "codeclimate-test-reporter" gem "rails", "~> 7.0" gem "matrix" From ec3a9b7b3b6fa0859939938c5bd76d3e47e3b887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 19:25:59 -0300 Subject: [PATCH 101/136] Fix remaining rubocop offenses --- spec/dashboard_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/dashboard_spec.rb b/spec/dashboard_spec.rb index a13a4ee9..f6ba1137 100644 --- a/spec/dashboard_spec.rb +++ b/spec/dashboard_spec.rb @@ -203,10 +203,10 @@ def link(color) end describe "initialize experiment" do - before do + before do Split.configuration.experiments = { - :my_experiment => { - :alternatives => [ "control", "alternative" ], + my_experiment: { + alternatives: [ "control", "alternative" ], } } end @@ -214,14 +214,14 @@ def link(color) it "initializes the experiment when the experiment is given" do expect(Split::ExperimentCatalog.find("my_experiment")).to be nil - post "/initialize_experiment", { experiment: "my_experiment"} + post "/initialize_experiment", { experiment: "my_experiment" } experiment = Split::ExperimentCatalog.find("my_experiment") expect(experiment).to be_a(Split::Experiment) end it "does not attempt to intialize the experiment when empty experiment is given" do - post "/initialize_experiment", { experiment: ""} + post "/initialize_experiment", { experiment: "" } expect(Split::ExperimentCatalog).to_not receive(:find_or_create) end From d108971cce19358aaad6ecfc2482ba4627d4e396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 19:31:26 -0300 Subject: [PATCH 102/136] Fix Style/AndOr --- .rubocop_todo.yml | 8 -------- lib/split/experiment_catalog.rb | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index cc501661..67af0dd6 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,14 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: always, conditionals -Style/AndOr: - Exclude: - - 'lib/split/experiment_catalog.rb' - # Offense count: 1 # Cop supports --auto-correct. Style/ColonMethodCall: diff --git a/lib/split/experiment_catalog.rb b/lib/split/experiment_catalog.rb index dc2be47e..052857de 100644 --- a/lib/split/experiment_catalog.rb +++ b/lib/split/experiment_catalog.rb @@ -20,7 +20,7 @@ def self.find(name) def self.find_or_initialize(metric_descriptor, control = nil, *alternatives) # Check if array is passed to ab_test # e.g. ab_test('name', ['Alt 1', 'Alt 2', 'Alt 3']) - if control.is_a? Array and alternatives.length.zero? + if control.is_a?(Array) && alternatives.length.zero? control, alternatives = control.first, control[1..-1] end From bd0e1618bcfdebccb9f01404552b3bf7afcfc15d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 19:32:03 -0300 Subject: [PATCH 103/136] Fix Style/ColonMethodCall --- .rubocop_todo.yml | 6 ------ lib/split/combined_experiments_helper.rb | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 67af0dd6..f56a8935 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,12 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 1 -# Cop supports --auto-correct. -Style/ColonMethodCall: - Exclude: - - 'lib/split/combined_experiments_helper.rb' - # Offense count: 1 # Cop supports --auto-correct. Style/DefWithParentheses: diff --git a/lib/split/combined_experiments_helper.rb b/lib/split/combined_experiments_helper.rb index b026c665..b925901f 100644 --- a/lib/split/combined_experiments_helper.rb +++ b/lib/split/combined_experiments_helper.rb @@ -32,7 +32,7 @@ def find_combined_experiment(metric_descriptor) raise(Split::InvalidExperimentsFormatError, 'Invalid descriptor class (String or Symbol required)') unless metric_descriptor.class == String || metric_descriptor.class == Symbol raise(Split::InvalidExperimentsFormatError, 'Enable configuration') unless Split.configuration.enabled raise(Split::InvalidExperimentsFormatError, 'Enable `allow_multiple_experiments`') unless Split.configuration.allow_multiple_experiments - Split::configuration.experiments[metric_descriptor.to_sym] + Split.configuration.experiments[metric_descriptor.to_sym] end end end From f723890085b633f009045ed61884599a3bd74ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 19:32:25 -0300 Subject: [PATCH 104/136] Fix Style/DefWithParentheses --- .rubocop_todo.yml | 6 ------ lib/split/helper.rb | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f56a8935..cb4033a9 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,12 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 1 -# Cop supports --auto-correct. -Style/DefWithParentheses: - Exclude: - - 'lib/split/helper.rb' - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/lib/split/helper.rb b/lib/split/helper.rb index 6c2ed0fc..97663e2a 100644 --- a/lib/split/helper.rb +++ b/lib/split/helper.rb @@ -105,7 +105,7 @@ def ab_record_extra_info(metric_descriptor, key, value = 1) Split.configuration.db_failover_on_db_error.call(e) end - def ab_active_experiments() + def ab_active_experiments ab_user.active_experiments rescue => e raise unless Split.configuration.db_failover From dcc87ec40910224b137660b0258e546f6e26a0a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 19:32:55 -0300 Subject: [PATCH 105/136] Fix Style/MethodDefParentheses --- .rubocop_todo.yml | 8 -------- lib/split/configuration.rb | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index cb4033a9..829d1b31 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,14 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: require_parentheses, require_no_parentheses, require_no_parentheses_except_multiline -Style/MethodDefParentheses: - Exclude: - - 'lib/split/configuration.rb' - # Offense count: 9 # Cop supports --auto-correct. # Configuration parameters: AllowMultipleReturnValues. diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index b06be51e..02977c3e 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -114,7 +114,7 @@ def bots } end - def experiments= experiments + def experiments=(experiments) raise InvalidExperimentsFormatError.new('Experiments must be a Hash') unless experiments.respond_to?(:keys) @experiments = experiments end From 8dc77270d642acde297e8452a66a1b8c1630055c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 19:33:36 -0300 Subject: [PATCH 106/136] Fix Style/RedundantReturn --- .rubocop_todo.yml | 10 ---------- lib/split/alternative.rb | 4 ++-- lib/split/experiment.rb | 10 +++++----- lib/split/helper.rb | 2 +- lib/split/zscore.rb | 2 +- 5 files changed, 9 insertions(+), 19 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 829d1b31..f473a0ec 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,16 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 9 -# Cop supports --auto-correct. -# Configuration parameters: AllowMultipleReturnValues. -Style/RedundantReturn: - Exclude: - - 'lib/split/alternative.rb' - - 'lib/split/experiment.rb' - - 'lib/split/helper.rb' - - 'lib/split/zscore.rb' - # Offense count: 258 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. diff --git a/lib/split/alternative.rb b/lib/split/alternative.rb index ee962f07..554a7326 100644 --- a/lib/split/alternative.rb +++ b/lib/split/alternative.rb @@ -67,13 +67,13 @@ def unfinished_count def set_field(goal) field = "completed_count" field += ":" + goal unless goal.nil? - return field + field end def set_prob_field(goal) field = "p_winner" field += ":" + goal unless goal.nil? - return field + field end def set_completed_count(count, goal = nil) diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 13a429f6..6239c50e 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -326,7 +326,7 @@ def calc_alternative_probabilities(winning_counts, number_of_simulations) winning_counts.each do |alternative, wins| alternative_probabilities[alternative] = wins / number_of_simulations.to_f end - return alternative_probabilities + alternative_probabilities end def count_simulated_wins(winning_alternatives) @@ -339,7 +339,7 @@ def count_simulated_wins(winning_alternatives) winning_alternatives.each do |alternative| winning_counts[alternative] += 1 end - return winning_counts + winning_counts end def find_simulated_winner(simulated_cr_hash) @@ -351,7 +351,7 @@ def find_simulated_winner(simulated_cr_hash) end end winner = winning_pair[0] - return winner + winner end def calc_simulated_conversion_rates(beta_params) @@ -365,7 +365,7 @@ def calc_simulated_conversion_rates(beta_params) simulated_cr_hash[alternative] = simulated_conversion_rate end - return simulated_cr_hash + simulated_cr_hash end def calc_beta_params(goal = nil) @@ -379,7 +379,7 @@ def calc_beta_params(goal = nil) beta_params[alternative] = params end - return beta_params + beta_params end def calc_time=(time) diff --git a/lib/split/helper.rb b/lib/split/helper.rb index 97663e2a..0a5700e5 100644 --- a/lib/split/helper.rb +++ b/lib/split/helper.rb @@ -49,7 +49,7 @@ def finish_experiment(experiment, options = { reset: true }) return true if experiment.has_winner? should_reset = experiment.resettable? && options[:reset] if ab_user[experiment.finished_key] && !should_reset - return true + true else alternative_name = ab_user[experiment.key] trial = Trial.new( diff --git a/lib/split/zscore.rb b/lib/split/zscore.rb index 6df25413..eac9160b 100644 --- a/lib/split/zscore.rb +++ b/lib/split/zscore.rb @@ -50,7 +50,7 @@ def self.calculate(p1, n1, p2, n2) # Calculate z-score z_score = (p_1 - p_2)/(se) - return z_score + z_score end end end From 9c2f237c0f7a2d68c63be38d78f9367ebb4b2ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 4 Mar 2022 19:44:27 -0300 Subject: [PATCH 107/136] Enforce double quotes --- .rubocop.yml | 5 +- .rubocop_todo.yml | 14 - Gemfile | 2 +- Rakefile | 6 +- lib/split.rb | 48 +- lib/split/algorithms.rb | 2 +- lib/split/alternative.rb | 26 +- lib/split/combined_experiments_helper.rb | 6 +- lib/split/configuration.rb | 132 ++--- lib/split/dashboard.rb | 50 +- lib/split/dashboard/helpers.rb | 12 +- lib/split/dashboard/pagination_helpers.rb | 4 +- lib/split/experiment.rb | 8 +- lib/split/experiment_catalog.rb | 2 +- lib/split/extensions/string.rb | 2 +- lib/split/goals_collection.rb | 2 +- lib/split/helper.rb | 8 +- lib/split/metric.rb | 4 +- lib/split/persistence.rb | 8 +- lib/split/persistence/cookie_adapter.rb | 8 +- lib/split/persistence/dual_adapter.rb | 2 +- lib/split/persistence/redis_adapter.rb | 2 +- lib/split/user.rb | 6 +- spec/algorithms/block_randomization_spec.rb | 8 +- spec/algorithms/weighted_sample_spec.rb | 10 +- spec/algorithms/whiplash_spec.rb | 6 +- spec/alternative_spec.rb | 38 +- spec/cache_spec.rb | 28 +- spec/combined_experiments_helper_spec.rb | 14 +- spec/configuration_spec.rb | 28 +- spec/dashboard/pagination_helpers_spec.rb | 134 ++--- spec/dashboard/paginator_spec.rb | 18 +- spec/dashboard_helpers_spec.rb | 36 +- spec/dashboard_spec.rb | 68 +-- spec/encapsulated_helper_spec.rb | 17 +- spec/experiment_catalog_spec.rb | 26 +- spec/experiment_spec.rb | 232 ++++---- spec/goals_collection_spec.rb | 30 +- spec/helper_spec.rb | 594 ++++++++++---------- spec/metric_spec.rb | 26 +- spec/persistence/cookie_adapter_spec.rb | 8 +- spec/persistence/dual_adapter_spec.rb | 134 ++--- spec/persistence/redis_adapter_spec.rb | 46 +- spec/redis_interface_spec.rb | 28 +- spec/spec_helper.rb | 22 +- spec/split_spec.rb | 20 +- spec/trial_spec.rb | 88 +-- spec/user_spec.rb | 64 +-- split.gemspec | 26 +- 49 files changed, 1046 insertions(+), 1062 deletions(-) delete mode 100644 .rubocop_todo.yml diff --git a/.rubocop.yml b/.rubocop.yml index c2cf054a..b2b652fb 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,8 +1,7 @@ -inherit_from: .rubocop_todo.yml - AllCops: TargetRubyVersion: 2.5 DisabledByDefault: true + SuggestExtensions: false Exclude: - 'gemfiles/**/*' @@ -112,7 +111,7 @@ Layout/SpaceInsideParens: Enabled: true Style/StringLiterals: - Enabled: false + Enabled: true EnforcedStyle: double_quotes Layout/IndentationStyle: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml deleted file mode 100644 index f473a0ec..00000000 --- a/.rubocop_todo.yml +++ /dev/null @@ -1,14 +0,0 @@ -# This configuration was generated by -# `rubocop --auto-gen-config` -# on 2020-07-05 01:43:26 UTC using RuboCop version 0.86.0. -# The point is for the user to remove these configuration records -# one by one as the offenses are removed from the code base. -# Note that changes in the inspected code, or installation of new -# versions of RuboCop, may require this file to be generated again. - -# Offense count: 258 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. -# SupportedStyles: single_quotes, double_quotes -Style/StringLiterals: - Enabled: false diff --git a/Gemfile b/Gemfile index d5afa8de..9fdc310a 100644 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,6 @@ source "https://rubygems.org" gemspec -gem 'rubocop', require: false +gem "rubocop", require: false gem "matrix" gem "codeclimate-test-reporter" diff --git a/Rakefile b/Rakefile index a7f213b6..4555c708 100755 --- a/Rakefile +++ b/Rakefile @@ -1,9 +1,9 @@ #!/usr/bin/env rake # frozen_string_literal: true -require 'bundler/gem_tasks' -require 'rspec/core/rake_task' +require "bundler/gem_tasks" +require "rspec/core/rake_task" -RSpec::Core::RakeTask.new('spec') +RSpec::Core::RakeTask.new("spec") task default: :spec diff --git a/lib/split.rb b/lib/split.rb index 6d1c6c05..1b24b787 100755 --- a/lib/split.rb +++ b/lib/split.rb @@ -1,30 +1,30 @@ # frozen_string_literal: true -require 'redis' +require "redis" -require 'split/algorithms' -require 'split/algorithms/block_randomization' -require 'split/algorithms/weighted_sample' -require 'split/algorithms/whiplash' -require 'split/alternative' -require 'split/cache' -require 'split/configuration' -require 'split/encapsulated_helper' -require 'split/exceptions' -require 'split/experiment' -require 'split/experiment_catalog' -require 'split/extensions/string' -require 'split/goals_collection' -require 'split/helper' -require 'split/combined_experiments_helper' -require 'split/metric' -require 'split/persistence' -require 'split/redis_interface' -require 'split/trial' -require 'split/user' -require 'split/version' -require 'split/zscore' -require 'split/engine' if defined?(Rails) +require "split/algorithms" +require "split/algorithms/block_randomization" +require "split/algorithms/weighted_sample" +require "split/algorithms/whiplash" +require "split/alternative" +require "split/cache" +require "split/configuration" +require "split/encapsulated_helper" +require "split/exceptions" +require "split/experiment" +require "split/experiment_catalog" +require "split/extensions/string" +require "split/goals_collection" +require "split/helper" +require "split/combined_experiments_helper" +require "split/metric" +require "split/persistence" +require "split/redis_interface" +require "split/trial" +require "split/user" +require "split/version" +require "split/zscore" +require "split/engine" if defined?(Rails) module Split extend self diff --git a/lib/split/algorithms.rb b/lib/split/algorithms.rb index d4317b82..14631597 100644 --- a/lib/split/algorithms.rb +++ b/lib/split/algorithms.rb @@ -9,7 +9,7 @@ end end -require 'rubystats' +require "rubystats" module Split module Algorithms diff --git a/lib/split/alternative.rb b/lib/split/alternative.rb index 554a7326..3d416188 100644 --- a/lib/split/alternative.rb +++ b/lib/split/alternative.rb @@ -38,11 +38,11 @@ def set_p_winner(prob, goal = nil) end def participant_count - Split.redis.hget(key, 'participant_count').to_i + Split.redis.hget(key, "participant_count").to_i end def participant_count=(count) - Split.redis.hset(key, 'participant_count', count.to_i) + Split.redis.hset(key, "participant_count", count.to_i) end def completed_count(goal = nil) @@ -82,7 +82,7 @@ def set_completed_count(count, goal = nil) end def increment_participation - Split.redis.hincrby key, 'participant_count', 1 + Split.redis.hincrby key, "participant_count", 1 end def increment_completion(goal = nil) @@ -112,7 +112,7 @@ def z_score(goal = nil) control = experiment.control alternative = self - return 'N/A' if control.name == alternative.name + return "N/A" if control.name == alternative.name p_a = alternative.conversion_rate(goal) p_c = control.conversion_rate(goal) @@ -121,13 +121,13 @@ def z_score(goal = nil) n_c = control.participant_count # can't calculate zscore for P(x) > 1 - return 'N/A' if p_a > 1 || p_c > 1 + return "N/A" if p_a > 1 || p_c > 1 Split::Zscore.calculate(p_a, n_a, p_c, n_c) end def extra_info - data = Split.redis.hget(key, 'recorded_info') + data = Split.redis.hget(key, "recorded_info") if data && data.length > 1 begin JSON.parse(data) @@ -149,24 +149,24 @@ def record_extra_info(k, value = 1) @recorded_info[k] = value end - Split.redis.hset key, 'recorded_info', (@recorded_info || {}).to_json + Split.redis.hset key, "recorded_info", (@recorded_info || {}).to_json end def save - Split.redis.hsetnx key, 'participant_count', 0 - Split.redis.hsetnx key, 'completed_count', 0 - Split.redis.hsetnx key, 'p_winner', p_winner - Split.redis.hsetnx key, 'recorded_info', (@recorded_info || {}).to_json + Split.redis.hsetnx key, "participant_count", 0 + Split.redis.hsetnx key, "completed_count", 0 + Split.redis.hsetnx key, "p_winner", p_winner + Split.redis.hsetnx key, "recorded_info", (@recorded_info || {}).to_json end def validate! unless String === @name || hash_with_correct_values?(@name) - raise ArgumentError, 'Alternative must be a string' + raise ArgumentError, "Alternative must be a string" end end def reset - Split.redis.hmset key, 'participant_count', 0, 'completed_count', 0, 'recorded_info', nil + Split.redis.hmset key, "participant_count", 0, "completed_count", 0, "recorded_info", nil unless goals.empty? goals.each do |g| field = "completed_count:#{g}" diff --git a/lib/split/combined_experiments_helper.rb b/lib/split/combined_experiments_helper.rb index b925901f..dd35d791 100644 --- a/lib/split/combined_experiments_helper.rb +++ b/lib/split/combined_experiments_helper.rb @@ -29,9 +29,9 @@ def ab_combined_test(metric_descriptor, control = nil, *alternatives) end def find_combined_experiment(metric_descriptor) - raise(Split::InvalidExperimentsFormatError, 'Invalid descriptor class (String or Symbol required)') unless metric_descriptor.class == String || metric_descriptor.class == Symbol - raise(Split::InvalidExperimentsFormatError, 'Enable configuration') unless Split.configuration.enabled - raise(Split::InvalidExperimentsFormatError, 'Enable `allow_multiple_experiments`') unless Split.configuration.allow_multiple_experiments + raise(Split::InvalidExperimentsFormatError, "Invalid descriptor class (String or Symbol required)") unless metric_descriptor.class == String || metric_descriptor.class == Symbol + raise(Split::InvalidExperimentsFormatError, "Enable configuration") unless Split.configuration.enabled + raise(Split::InvalidExperimentsFormatError, "Enable `allow_multiple_experiments`") unless Split.configuration.allow_multiple_experiments Split.configuration.experiments[metric_descriptor.to_sym] end end diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index 02977c3e..b43e5920 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -39,83 +39,83 @@ class Configuration def bots @bots ||= { # Indexers - 'AdsBot-Google' => 'Google Adwords', - 'Baidu' => 'Chinese search engine', - 'Baiduspider' => 'Chinese search engine', - 'bingbot' => 'Microsoft bing bot', - 'Butterfly' => 'Topsy Labs', - 'Gigabot' => 'Gigabot spider', - 'Googlebot' => 'Google spider', - 'MJ12bot' => 'Majestic-12 spider', - 'msnbot' => 'Microsoft bot', - 'rogerbot' => 'SeoMoz spider', - 'PaperLiBot' => 'PaperLi is another content curation service', - 'Slurp' => 'Yahoo spider', - 'Sogou' => 'Chinese search engine', - 'spider' => 'generic web spider', - 'UnwindFetchor' => 'Gnip crawler', - 'WordPress' => 'WordPress spider', - 'YandexAccessibilityBot' => 'Yandex accessibility spider', - 'YandexBot' => 'Yandex spider', - 'YandexMobileBot' => 'Yandex mobile spider', - 'ZIBB' => 'ZIBB spider', + "AdsBot-Google" => "Google Adwords", + "Baidu" => "Chinese search engine", + "Baiduspider" => "Chinese search engine", + "bingbot" => "Microsoft bing bot", + "Butterfly" => "Topsy Labs", + "Gigabot" => "Gigabot spider", + "Googlebot" => "Google spider", + "MJ12bot" => "Majestic-12 spider", + "msnbot" => "Microsoft bot", + "rogerbot" => "SeoMoz spider", + "PaperLiBot" => "PaperLi is another content curation service", + "Slurp" => "Yahoo spider", + "Sogou" => "Chinese search engine", + "spider" => "generic web spider", + "UnwindFetchor" => "Gnip crawler", + "WordPress" => "WordPress spider", + "YandexAccessibilityBot" => "Yandex accessibility spider", + "YandexBot" => "Yandex spider", + "YandexMobileBot" => "Yandex mobile spider", + "ZIBB" => "ZIBB spider", # HTTP libraries - 'Apache-HttpClient' => 'Java http library', - 'AppEngine-Google' => 'Google App Engine', - 'curl' => 'curl unix CLI http client', - 'ColdFusion' => 'ColdFusion http library', - 'EventMachine HttpClient' => 'Ruby http library', - 'Go http package' => 'Go http library', - 'Go-http-client' => 'Go http library', - 'Java' => 'Generic Java http library', - 'libwww-perl' => 'Perl client-server library loved by script kids', - 'lwp-trivial' => 'Another Perl library loved by script kids', - 'Python-urllib' => 'Python http library', - 'PycURL' => 'Python http library', - 'Test Certificate Info' => 'C http library?', - 'Typhoeus' => 'Ruby http library', - 'Wget' => 'wget unix CLI http client', + "Apache-HttpClient" => "Java http library", + "AppEngine-Google" => "Google App Engine", + "curl" => "curl unix CLI http client", + "ColdFusion" => "ColdFusion http library", + "EventMachine HttpClient" => "Ruby http library", + "Go http package" => "Go http library", + "Go-http-client" => "Go http library", + "Java" => "Generic Java http library", + "libwww-perl" => "Perl client-server library loved by script kids", + "lwp-trivial" => "Another Perl library loved by script kids", + "Python-urllib" => "Python http library", + "PycURL" => "Python http library", + "Test Certificate Info" => "C http library?", + "Typhoeus" => "Ruby http library", + "Wget" => "wget unix CLI http client", # URL expanders / previewers - 'awe.sm' => 'Awe.sm URL expander', - 'bitlybot' => 'bit.ly bot', - 'bot@linkfluence.net' => 'Linkfluence bot', - 'facebookexternalhit' => 'facebook bot', - 'Facebot' => 'Facebook crawler', - 'Feedfetcher-Google' => 'Google Feedfetcher', - 'https://developers.google.com/+/web/snippet' => 'Google+ Snippet Fetcher', - 'LinkedInBot' => 'LinkedIn bot', - 'LongURL' => 'URL expander service', - 'NING' => 'NING - Yet Another Twitter Swarmer', - 'Pinterestbot' => 'Pinterest Bot', - 'redditbot' => 'Reddit Bot', - 'ShortLinkTranslate' => 'Link shortener', - 'Slackbot' => 'Slackbot link expander', - 'TweetmemeBot' => 'TweetMeMe Crawler', - 'Twitterbot' => 'Twitter URL expander', - 'UnwindFetch' => 'Gnip URL expander', - 'vkShare' => 'VKontake Sharer', + "awe.sm" => "Awe.sm URL expander", + "bitlybot" => "bit.ly bot", + "bot@linkfluence.net" => "Linkfluence bot", + "facebookexternalhit" => "facebook bot", + "Facebot" => "Facebook crawler", + "Feedfetcher-Google" => "Google Feedfetcher", + "https://developers.google.com/+/web/snippet" => "Google+ Snippet Fetcher", + "LinkedInBot" => "LinkedIn bot", + "LongURL" => "URL expander service", + "NING" => "NING - Yet Another Twitter Swarmer", + "Pinterestbot" => "Pinterest Bot", + "redditbot" => "Reddit Bot", + "ShortLinkTranslate" => "Link shortener", + "Slackbot" => "Slackbot link expander", + "TweetmemeBot" => "TweetMeMe Crawler", + "Twitterbot" => "Twitter URL expander", + "UnwindFetch" => "Gnip URL expander", + "vkShare" => "VKontake Sharer", # Uptime monitoring - 'check_http' => 'Nagios monitor', - 'GoogleStackdriverMonitoring' => 'Google Cloud monitor', - 'NewRelicPinger' => 'NewRelic monitor', - 'Panopta' => 'Monitoring service', - 'Pingdom' => 'Pingdom monitoring', - 'SiteUptime' => 'Site monitoring services', - 'UptimeRobot' => 'Monitoring service', + "check_http" => "Nagios monitor", + "GoogleStackdriverMonitoring" => "Google Cloud monitor", + "NewRelicPinger" => "NewRelic monitor", + "Panopta" => "Monitoring service", + "Pingdom" => "Pingdom monitoring", + "SiteUptime" => "Site monitoring services", + "UptimeRobot" => "Monitoring service", # ??? - 'DigitalPersona Fingerprint Software' => 'HP Fingerprint scanner', - 'ShowyouBot' => 'Showyou iOS app spider', - 'ZyBorg' => 'Zyborg? Hmmm....', - 'ELB-HealthChecker' => 'ELB Health Check' + "DigitalPersona Fingerprint Software" => "HP Fingerprint scanner", + "ShowyouBot" => "Showyou iOS app spider", + "ZyBorg" => "Zyborg? Hmmm....", + "ELB-HealthChecker" => "ELB Health Check" } end def experiments=(experiments) - raise InvalidExperimentsFormatError.new('Experiments must be a Hash') unless experiments.respond_to?(:keys) + raise InvalidExperimentsFormatError.new("Experiments must be a Hash") unless experiments.respond_to?(:keys) @experiments = experiments end @@ -232,7 +232,7 @@ def initialize @include_rails_helper = true @beta_probability_simulations = 10000 @winning_alternative_recalculation_interval = 60 * 60 * 24 # 1 day - @redis = ENV.fetch(ENV.fetch('REDIS_PROVIDER', 'REDIS_URL'), 'redis://localhost:6379') + @redis = ENV.fetch(ENV.fetch("REDIS_PROVIDER", "REDIS_URL"), "redis://localhost:6379") @dashboard_pagination_default_per_page = 10 end diff --git a/lib/split/dashboard.rb b/lib/split/dashboard.rb index 9d57720f..fcb53c2b 100755 --- a/lib/split/dashboard.rb +++ b/lib/split/dashboard.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require 'sinatra/base' -require 'split' -require 'bigdecimal' -require 'split/dashboard/helpers' -require 'split/dashboard/pagination_helpers' +require "sinatra/base" +require "split" +require "bigdecimal" +require "split/dashboard/helpers" +require "split/dashboard/pagination_helpers" module Split class Dashboard < Sinatra::Base @@ -18,7 +18,7 @@ class Dashboard < Sinatra::Base helpers Split::DashboardHelpers helpers Split::DashboardPaginationHelpers - get '/' do + get "/" do # Display experiments without a winner at the top of the dashboard @experiments = Split::ExperimentCatalog.all_active_first @unintialized_experiments = Split.configuration.experiments.keys - @experiments.map(&:name) @@ -26,7 +26,7 @@ class Dashboard < Sinatra::Base @metrics = Split::Metric.all # Display Rails Environment mode (or Rack version if not using Rails) - if Object.const_defined?('Rails') + if Object.const_defined?("Rails") @current_env = Rails.env.titlecase else @current_env = "Rack: #{Rack.version}" @@ -34,48 +34,48 @@ class Dashboard < Sinatra::Base erb :index end - post '/initialize_experiment' do + post "/initialize_experiment" do Split::ExperimentCatalog.find_or_create(params[:experiment]) unless params[:experiment].nil? || params[:experiment].empty? - redirect url('/') + redirect url("/") end - post '/force_alternative' do + post "/force_alternative" do experiment = Split::ExperimentCatalog.find(params[:experiment]) alternative = Split::Alternative.new(params[:alternative], experiment.name) - cookies = JSON.parse(request.cookies['split_override']) rescue {} + cookies = JSON.parse(request.cookies["split_override"]) rescue {} cookies[experiment.name] = alternative.name - response.set_cookie('split_override', { value: cookies.to_json, path: '/' }) + response.set_cookie("split_override", { value: cookies.to_json, path: "/" }) - redirect url('/') + redirect url("/") end - post '/experiment' do + post "/experiment" do @experiment = Split::ExperimentCatalog.find(params[:experiment]) @alternative = Split::Alternative.new(params[:alternative], params[:experiment]) @experiment.winner = @alternative.name - redirect url('/') + redirect url("/") end - post '/start' do + post "/start" do @experiment = Split::ExperimentCatalog.find(params[:experiment]) @experiment.start - redirect url('/') + redirect url("/") end - post '/reset' do + post "/reset" do @experiment = Split::ExperimentCatalog.find(params[:experiment]) @experiment.reset - redirect url('/') + redirect url("/") end - post '/reopen' do + post "/reopen" do @experiment = Split::ExperimentCatalog.find(params[:experiment]) @experiment.reset_winner - redirect url('/') + redirect url("/") end - post '/update_cohorting' do + post "/update_cohorting" do @experiment = Split::ExperimentCatalog.find(params[:experiment]) case params[:cohorting_action].downcase when "enable" @@ -83,13 +83,13 @@ class Dashboard < Sinatra::Base when "disable" @experiment.disable_cohorting end - redirect url('/') + redirect url("/") end - delete '/experiment' do + delete "/experiment" do @experiment = Split::ExperimentCatalog.find(params[:experiment]) @experiment.delete - redirect url('/') + redirect url("/") end end end diff --git a/lib/split/dashboard/helpers.rb b/lib/split/dashboard/helpers.rb index 71a0d93f..15727c72 100644 --- a/lib/split/dashboard/helpers.rb +++ b/lib/split/dashboard/helpers.rb @@ -7,11 +7,11 @@ def h(text) end def url(*path_parts) - [ path_prefix, path_parts ].join("/").squeeze('/') + [ path_prefix, path_parts ].join("/").squeeze("/") end def path_prefix - request.env['SCRIPT_NAME'] + request.env["SCRIPT_NAME"] end def number_to_percentage(number, precision = 2) @@ -32,13 +32,13 @@ def confidence_level(z_score) z = round(z_score.to_s.to_f, 3).abs if z >= 2.58 - '99% confidence' + "99% confidence" elsif z >= 1.96 - '95% confidence' + "95% confidence" elsif z >= 1.65 - '90% confidence' + "90% confidence" else - 'Insufficient confidence' + "Insufficient confidence" end end end diff --git a/lib/split/dashboard/pagination_helpers.rb b/lib/split/dashboard/pagination_helpers.rb index 5031135e..79d6393d 100644 --- a/lib/split/dashboard/pagination_helpers.rb +++ b/lib/split/dashboard/pagination_helpers.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'split/dashboard/paginator' +require "split/dashboard/paginator" module Split module DashboardPaginationHelpers @@ -43,7 +43,7 @@ def show_first_ellipsis_tag? end def ellipsis_tag - '...' + "..." end def show_prev_page_tag? diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 6239c50e..ec78fae1 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -119,7 +119,7 @@ def algorithm=(algorithm) end def resettable=(resettable) - @resettable = resettable.is_a?(String) ? resettable == 'true' : resettable + @resettable = resettable.is_a?(String) ? resettable == "true" : resettable end def alternatives=(alts) @@ -260,8 +260,8 @@ def load_from_redis exp_config = redis.hgetall(experiment_config_key) options = { - resettable: exp_config['resettable'], - algorithm: exp_config['algorithm'], + resettable: exp_config["resettable"], + algorithm: exp_config["algorithm"], alternatives: load_alternatives_from_redis, goals: Split::GoalsCollection.new(@name).load_from_redis, metadata: load_metadata_from_redis @@ -396,7 +396,7 @@ def jstring(goal = nil) else name + "-" + goal end - js_id.gsub('/', '--') + js_id.gsub("/", "--") end def cohorting_disabled? diff --git a/lib/split/experiment_catalog.rb b/lib/split/experiment_catalog.rb index 052857de..7a8674cc 100644 --- a/lib/split/experiment_catalog.rb +++ b/lib/split/experiment_catalog.rb @@ -25,7 +25,7 @@ def self.find_or_initialize(metric_descriptor, control = nil, *alternatives) end experiment_name_with_version, goals = normalize_experiment(metric_descriptor) - experiment_name = experiment_name_with_version.to_s.split(':')[0] + experiment_name = experiment_name_with_version.to_s.split(":")[0] Split::Experiment.new(experiment_name, alternatives: [control].compact + alternatives, goals: goals) end diff --git a/lib/split/extensions/string.rb b/lib/split/extensions/string.rb index 8491c85a..e20de92c 100644 --- a/lib/split/extensions/string.rb +++ b/lib/split/extensions/string.rb @@ -4,7 +4,7 @@ class String # Constatntize is often provided by ActiveSupport, but ActiveSupport is not a dependency of Split. unless method_defined?(:constantize) def constantize - names = self.split('::') + names = self.split("::") names.shift if names.empty? || names.first.empty? constant = Object diff --git a/lib/split/goals_collection.rb b/lib/split/goals_collection.rb index 10b15bac..8aa787a4 100644 --- a/lib/split/goals_collection.rb +++ b/lib/split/goals_collection.rb @@ -28,7 +28,7 @@ def save def validate! unless @goals.nil? || @goals.kind_of?(Array) - raise ArgumentError, 'Goals must be an array' + raise ArgumentError, "Goals must be an array" end end diff --git a/lib/split/helper.rb b/lib/split/helper.rb index 0a5700e5..ae0010ad 100644 --- a/lib/split/helper.rb +++ b/lib/split/helper.rb @@ -127,14 +127,14 @@ def override_alternative_by_params(experiment_name) def override_alternative_by_cookies(experiment_name) return unless defined?(request) - if request.cookies && request.cookies.key?('split_override') - experiments = JSON.parse(request.cookies['split_override']) rescue {} + if request.cookies && request.cookies.key?("split_override") + experiments = JSON.parse(request.cookies["split_override"]) rescue {} experiments[experiment_name] end end def split_generically_disabled? - defined?(params) && params['SPLIT_DISABLE'] + defined?(params) && params["SPLIT_DISABLE"] end def ab_user @@ -150,7 +150,7 @@ def is_robot? end def is_preview? - defined?(request) && defined?(request.headers) && request.headers['x-purpose'] == 'preview' + defined?(request) && defined?(request.headers) && request.headers["x-purpose"] == "preview" end def is_ignored_ip_address? diff --git a/lib/split/metric.rb b/lib/split/metric.rb index ab67227b..ed9e48ac 100644 --- a/lib/split/metric.rb +++ b/lib/split/metric.rb @@ -16,7 +16,7 @@ def initialize(attrs = {}) def self.load_from_redis(name) metric = Split.redis.hget(:metrics, name) if metric - experiment_names = metric.split(',') + experiment_names = metric.split(",") experiments = experiment_names.collect do |experiment_name| Split::ExperimentCatalog.find(experiment_name) @@ -77,7 +77,7 @@ def self.possible_experiments(metric_name) end def save - Split.redis.hset(:metrics, name, experiments.map(&:name).join(',')) + Split.redis.hset(:metrics, name, experiments.map(&:name).join(",")) end def complete! diff --git a/lib/split/persistence.rb b/lib/split/persistence.rb index cb14e681..f9e52b33 100644 --- a/lib/split/persistence.rb +++ b/lib/split/persistence.rb @@ -2,10 +2,10 @@ module Split module Persistence - require 'split/persistence/cookie_adapter' - require 'split/persistence/dual_adapter' - require 'split/persistence/redis_adapter' - require 'split/persistence/session_adapter' + require "split/persistence/cookie_adapter" + require "split/persistence/dual_adapter" + require "split/persistence/redis_adapter" + require "split/persistence/session_adapter" ADAPTERS = { cookie: Split::Persistence::CookieAdapter, diff --git a/lib/split/persistence/cookie_adapter.rb b/lib/split/persistence/cookie_adapter.rb index 3157ce87..2eac7fca 100644 --- a/lib/split/persistence/cookie_adapter.rb +++ b/lib/split/persistence/cookie_adapter.rb @@ -43,7 +43,7 @@ def set_cookie(value = {}) end def default_options - { expires: @expires, path: '/', domain: cookie_domain_config }.compact + { expires: @expires, path: "/", domain: cookie_domain_config }.compact end def set_cookie_via_rack(key, value) @@ -53,9 +53,9 @@ def set_cookie_via_rack(key, value) # Use Rack::Utils#make_delete_cookie_header after Rack 2.0.0 def delete_cookie_header!(header, key, value) - cookie_header = header['Set-Cookie'] + cookie_header = header["Set-Cookie"] case cookie_header - when nil, '' + when nil, "" cookies = [] when String cookies = cookie_header.split("\n") @@ -64,7 +64,7 @@ def delete_cookie_header!(header, key, value) end cookies.reject! { |cookie| cookie =~ /\A#{Rack::Utils.escape(key)}=/ } - header['Set-Cookie'] = cookies.join("\n") + header["Set-Cookie"] = cookies.join("\n") end def hash diff --git a/lib/split/persistence/dual_adapter.rb b/lib/split/persistence/dual_adapter.rb index 82acac42..af8bd683 100644 --- a/lib/split/persistence/dual_adapter.rb +++ b/lib/split/persistence/dual_adapter.rb @@ -77,7 +77,7 @@ def decrement_participation?(old_value, value) end def decrement_participation(key, value) - Split.redis.hincrby("#{key}:#{value}", 'participant_count', -1) + Split.redis.hincrby("#{key}:#{value}", "participant_count", -1) end end end diff --git a/lib/split/persistence/redis_adapter.rb b/lib/split/persistence/redis_adapter.rb index 52cc0342..e314d882 100644 --- a/lib/split/persistence/redis_adapter.rb +++ b/lib/split/persistence/redis_adapter.rb @@ -3,7 +3,7 @@ module Split module Persistence class RedisAdapter - DEFAULT_CONFIG = { namespace: 'persistence' }.freeze + DEFAULT_CONFIG = { namespace: "persistence" }.freeze attr_reader :redis_key diff --git a/lib/split/user.rb b/lib/split/user.rb index 24ca4ab1..9ba7881e 100644 --- a/lib/split/user.rb +++ b/lib/split/user.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'forwardable' +require "forwardable" module Split class User @@ -26,10 +26,10 @@ def cleanup_old_experiments! end def max_experiments_reached?(experiment_key) - if Split.configuration.allow_multiple_experiments == 'control' + if Split.configuration.allow_multiple_experiments == "control" experiments = active_experiments experiment_key_without_version = key_without_version(experiment_key) - count_control = experiments.count { |k, v| k == experiment_key_without_version || v == 'control' } + count_control = experiments.count { |k, v| k == experiment_key_without_version || v == "control" } experiments.size > count_control else !Split.configuration.allow_multiple_experiments && diff --git a/spec/algorithms/block_randomization_spec.rb b/spec/algorithms/block_randomization_spec.rb index 3bab3fb5..9417710c 100644 --- a/spec/algorithms/block_randomization_spec.rb +++ b/spec/algorithms/block_randomization_spec.rb @@ -3,10 +3,10 @@ require "spec_helper" describe Split::Algorithms::BlockRandomization do - let(:experiment) { Split::Experiment.new 'experiment' } - let(:alternative_A) { Split::Alternative.new 'A', 'experiment' } - let(:alternative_B) { Split::Alternative.new 'B', 'experiment' } - let(:alternative_C) { Split::Alternative.new 'C', 'experiment' } + let(:experiment) { Split::Experiment.new "experiment" } + let(:alternative_A) { Split::Alternative.new "A", "experiment" } + let(:alternative_B) { Split::Alternative.new "B", "experiment" } + let(:alternative_C) { Split::Alternative.new "C", "experiment" } before :each do allow(experiment).to receive(:alternatives) { [alternative_A, alternative_B, alternative_C] } diff --git a/spec/algorithms/weighted_sample_spec.rb b/spec/algorithms/weighted_sample_spec.rb index cf62d40a..53a20170 100644 --- a/spec/algorithms/weighted_sample_spec.rb +++ b/spec/algorithms/weighted_sample_spec.rb @@ -4,17 +4,17 @@ describe Split::Algorithms::WeightedSample do it "should return an alternative" do - experiment = Split::ExperimentCatalog.find_or_create('link_color', { 'blue' => 100 }, { 'red' => 0 }) + experiment = Split::ExperimentCatalog.find_or_create("link_color", { "blue" => 100 }, { "red" => 0 }) expect(Split::Algorithms::WeightedSample.choose_alternative(experiment).class).to eq(Split::Alternative) end it "should always return a heavily weighted option" do - experiment = Split::ExperimentCatalog.find_or_create('link_color', { 'blue' => 100 }, { 'red' => 0 }) - expect(Split::Algorithms::WeightedSample.choose_alternative(experiment).name).to eq('blue') + experiment = Split::ExperimentCatalog.find_or_create("link_color", { "blue" => 100 }, { "red" => 0 }) + expect(Split::Algorithms::WeightedSample.choose_alternative(experiment).name).to eq("blue") end it "should return one of the results" do - experiment = Split::ExperimentCatalog.find_or_create('link_color', { 'blue' => 1 }, { 'red' => 1 }) - expect(['red', 'blue']).to include Split::Algorithms::WeightedSample.choose_alternative(experiment).name + experiment = Split::ExperimentCatalog.find_or_create("link_color", { "blue" => 1 }, { "red" => 1 }) + expect(["red", "blue"]).to include Split::Algorithms::WeightedSample.choose_alternative(experiment).name end end diff --git a/spec/algorithms/whiplash_spec.rb b/spec/algorithms/whiplash_spec.rb index 6a5c1c26..2d8b5291 100644 --- a/spec/algorithms/whiplash_spec.rb +++ b/spec/algorithms/whiplash_spec.rb @@ -4,13 +4,13 @@ describe Split::Algorithms::Whiplash do it "should return an algorithm" do - experiment = Split::ExperimentCatalog.find_or_create('link_color', { 'blue' => 1 }, { 'red' => 1 }) + experiment = Split::ExperimentCatalog.find_or_create("link_color", { "blue" => 1 }, { "red" => 1 }) expect(Split::Algorithms::Whiplash.choose_alternative(experiment).class).to eq(Split::Alternative) end it "should return one of the results" do - experiment = Split::ExperimentCatalog.find_or_create('link_color', { 'blue' => 1 }, { 'red' => 1 }) - expect(['red', 'blue']).to include Split::Algorithms::Whiplash.choose_alternative(experiment).name + experiment = Split::ExperimentCatalog.find_or_create("link_color", { "blue" => 1 }, { "red" => 1 }) + expect(["red", "blue"]).to include Split::Algorithms::Whiplash.choose_alternative(experiment).name end it "should guess floats" do diff --git a/spec/alternative_spec.rb b/spec/alternative_spec.rb index e29138f4..1714c2fb 100644 --- a/spec/alternative_spec.rb +++ b/spec/alternative_spec.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true -require 'spec_helper' -require 'split/alternative' +require "spec_helper" +require "split/alternative" describe Split::Alternative do let(:alternative) { - Split::Alternative.new('Basket', 'basket_text') + Split::Alternative.new("Basket", "basket_text") } let(:alternative2) { - Split::Alternative.new('Cart', 'basket_text') + Split::Alternative.new("Cart", "basket_text") } let!(:experiment) { @@ -24,18 +24,18 @@ end it "should have and only return the name" do - expect(alternative.name).to eq('Basket') + expect(alternative.name).to eq("Basket") end - describe 'weights' do + describe "weights" do it "should set the weights" do - experiment = Split::Experiment.new('basket_text', alternatives: [{ 'Basket' => 0.6 }, { "Cart" => 0.4 }]) + experiment = Split::Experiment.new("basket_text", alternatives: [{ "Basket" => 0.6 }, { "Cart" => 0.4 }]) first = experiment.alternatives[0] - expect(first.name).to eq('Basket') + expect(first.name).to eq("Basket") expect(first.weight).to eq(0.6) second = experiment.alternatives[1] - expect(second.name).to eq('Cart') + expect(second.name).to eq("Cart") expect(second.weight).to eq(0.4) end @@ -51,11 +51,11 @@ } experiment = Split::Experiment.new(:my_experiment) first = experiment.alternatives[0] - expect(first.name).to eq('control_opt') + expect(first.name).to eq("control_opt") expect(first.weight).to eq(0.67) second = experiment.alternatives[1] - expect(second.name).to eq('second_opt') + expect(second.name).to eq("second_opt") expect(second.weight).to eq(0.1) end @@ -126,7 +126,7 @@ it "should save to redis" do alternative.save - expect(Split.redis.exists?('basket_text:Basket')).to be true + expect(Split.redis.exists?("basket_text:Basket")).to be true end it "should increment participation count" do @@ -166,7 +166,7 @@ expect(alternative2.control?).to be_falsey end - describe 'unfinished_count' do + describe "unfinished_count" do it "should be difference between participant and completed counts" do alternative.increment_participation expect(alternative.unfinished_count).to eq(alternative.participant_count) @@ -182,7 +182,7 @@ end end - describe 'conversion rate' do + describe "conversion rate" do it "should be 0 if there are no conversions" do expect(alternative.completed_count).to eq(0) expect(alternative.conversion_rate).to eq(0) @@ -225,7 +225,7 @@ end end - describe 'z score' do + describe "z score" do it "should return an error string when the control has 0 people" do expect(alternative2.z_score).to eq("Needs 30+ participants.") expect(alternative2.z_score(goal1)).to eq("Needs 30+ participants.") @@ -268,9 +268,9 @@ it "should be N/A for the control" do control = experiment.control - expect(control.z_score).to eq('N/A') - expect(control.z_score(goal1)).to eq('N/A') - expect(control.z_score(goal2)).to eq('N/A') + expect(control.z_score).to eq("N/A") + expect(control.z_score(goal1)).to eq("N/A") + expect(control.z_score(goal2)).to eq("N/A") end it "should not blow up for Conversion Rates > 1" do @@ -289,7 +289,7 @@ describe "extra_info" do it "reads saved value of recorded_info in redis" do saved_recorded_info = { "key_1" => 1, "key_2" => "2" } - Split.redis.hset "#{alternative.experiment_name}:#{alternative.name}", 'recorded_info', saved_recorded_info.to_json + Split.redis.hset "#{alternative.experiment_name}:#{alternative.name}", "recorded_info", saved_recorded_info.to_json extra_info = alternative.extra_info expect(extra_info).to eql(saved_recorded_info) diff --git a/spec/cache_spec.rb b/spec/cache_spec.rb index 0630c15e..48b6cd46 100644 --- a/spec/cache_spec.rb +++ b/spec/cache_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" describe Split::Cache do let(:namespace) { :test_namespace } @@ -9,10 +9,10 @@ before { allow(Time).to receive(:now).and_return(now) } - describe 'clear' do + describe "clear" do before { Split.configuration.cache = true } - it 'clears the cache' do + it "clears the cache" do expect(Time).to receive(:now).and_return(now).exactly(2).times Split::Cache.fetch(namespace, key) { Time.now } Split::Cache.clear @@ -20,10 +20,10 @@ end end - describe 'clear_key' do + describe "clear_key" do before { Split.configuration.cache = true } - it 'clears the cache' do + it "clears the cache" do expect(Time).to receive(:now).and_return(now).exactly(3).times Split::Cache.fetch(namespace, :key1) { Time.now } Split::Cache.fetch(namespace, :key2) { Time.now } @@ -34,37 +34,37 @@ end end - describe 'fetch' do + describe "fetch" do subject { Split::Cache.fetch(namespace, key) { Time.now } } - context 'when cache disabled' do + context "when cache disabled" do before { Split.configuration.cache = false } - it 'returns the yield' do + it "returns the yield" do expect(subject).to eql(now) end - it 'yields every time' do + it "yields every time" do expect(Time).to receive(:now).and_return(now).exactly(2).times Split::Cache.fetch(namespace, key) { Time.now } Split::Cache.fetch(namespace, key) { Time.now } end end - context 'when cache enabled' do + context "when cache enabled" do before { Split.configuration.cache = true } - it 'returns the yield' do + it "returns the yield" do expect(subject).to eql(now) end - it 'yields once' do + it "yields once" do expect(Time).to receive(:now).and_return(now).once Split::Cache.fetch(namespace, key) { Time.now } Split::Cache.fetch(namespace, key) { Time.now } end - it 'honors namespace' do + it "honors namespace" do expect(Split::Cache.fetch(:a, key) { :a }).to eql(:a) expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b) @@ -72,7 +72,7 @@ expect(Split::Cache.fetch(:b, key) { :b }).to eql(:b) end - it 'honors key' do + it "honors key" do expect(Split::Cache.fetch(namespace, :a) { :a }).to eql(:a) expect(Split::Cache.fetch(namespace, :b) { :b }).to eql(:b) diff --git a/spec/combined_experiments_helper_spec.rb b/spec/combined_experiments_helper_spec.rb index 008b5314..651b5649 100644 --- a/spec/combined_experiments_helper_spec.rb +++ b/spec/combined_experiments_helper_spec.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true -require 'spec_helper' -require 'split/combined_experiments_helper' +require "spec_helper" +require "split/combined_experiments_helper" describe Split::CombinedExperimentsHelper do include Split::CombinedExperimentsHelper - describe 'ab_combined_test' do + describe "ab_combined_test" do let!(:config_enabled) { true } let!(:combined_experiments) { [:exp_1_click, :exp_1_scroll ] } let!(:allow_multiple_experiments) { true } @@ -23,7 +23,7 @@ Split.configuration.allow_multiple_experiments = allow_multiple_experiments end - context 'without config enabled' do + context "without config enabled" do let!(:config_enabled) { false } it "raises an error" do @@ -31,7 +31,7 @@ end end - context 'multiple experiments disabled' do + context "multiple experiments disabled" do let!(:allow_multiple_experiments) { false } it "raises an error if multiple experiments is disabled" do @@ -39,7 +39,7 @@ end end - context 'without combined experiments' do + context "without combined experiments" do let!(:combined_experiments) { nil } it "raises an error" do @@ -52,7 +52,7 @@ expect(self).to receive(:ab_test).with(:exp_1_click, { "control"=>0.5 }, { "test-alt"=>0.5 }) { "test-alt" } expect(self).to receive(:ab_test).with(:exp_1_scroll, [{ "control" => 0, "test-alt" => 1 }]) - expect(ab_combined_test('combined_exp_1')).to eq('test-alt') + expect(ab_combined_test("combined_exp_1")).to eq("test-alt") end end end diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb index 327edb91..28c8ff91 100644 --- a/spec/configuration_spec.rb +++ b/spec/configuration_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" describe Split::Configuration do before(:each) { @config = Split::Configuration.new } @@ -84,7 +84,7 @@ @config.experiments = YAML.load(experiments_yaml) end - it 'should normalize experiments' do + it "should normalize experiments" do expect(@config.normalized_experiments).to eq({ my_experiment: { resettable: false, alternatives: ["Control Opt", ["Alt One", "Alt Two"]] } }) end end @@ -112,10 +112,10 @@ @config.experiments = YAML.load(experiments_yaml) end - it 'should have metadata on the experiment' do + it "should have metadata on the experiment" do meta = @config.normalized_experiments[:my_experiment][:metadata] expect(meta).to_not be nil - expect(meta['Control Opt']['text']).to eq('Control Option') + expect(meta["Control Opt"]["text"]).to eq("Control Option") end end @@ -175,7 +175,7 @@ let(:yaml) { YAML.load(input) } context "with an empty string" do - let(:input) { '' } + let(:input) { "" } it "should raise an error" do expect { @config.experiments = yaml }.to raise_error(Split::InvalidExperimentsFormatError) @@ -183,7 +183,7 @@ end context "with just the YAML header" do - let(:input) { '---' } + let(:input) { "---" } it "should raise an error" do expect { @config.experiments = yaml }.to raise_error(Split::InvalidExperimentsFormatError) @@ -209,10 +209,10 @@ context "redis configuration" do it "should default to local redis server" do - old_redis_url = ENV['REDIS_URL'] - ENV.delete('REDIS_URL') + old_redis_url = ENV["REDIS_URL"] + ENV.delete("REDIS_URL") expect(Split::Configuration.new.redis).to eq("redis://localhost:6379") - ENV['REDIS_URL'] = old_redis_url + ENV["REDIS_URL"] = old_redis_url end it "should allow for redis url to be configured" do @@ -222,10 +222,10 @@ context "provided REDIS_URL environment variable" do it "should use the ENV variable" do - old_redis_url = ENV['REDIS_URL'] - ENV['REDIS_URL'] = "env_redis_url" + old_redis_url = ENV["REDIS_URL"] + ENV["REDIS_URL"] = "env_redis_url" expect(Split::Configuration.new.redis).to eq("env_redis_url") - ENV['REDIS_URL'] = old_redis_url + ENV["REDIS_URL"] = old_redis_url end end end @@ -247,8 +247,8 @@ end it "should allow the persistence cookie domain to be configured" do - @config.persistence_cookie_domain = '.acme.com' - expect(@config.persistence_cookie_domain).to eq('.acme.com') + @config.persistence_cookie_domain = ".acme.com" + expect(@config.persistence_cookie_domain).to eq(".acme.com") end end end diff --git a/spec/dashboard/pagination_helpers_spec.rb b/spec/dashboard/pagination_helpers_spec.rb index c9a360bd..cf5d81db 100644 --- a/spec/dashboard/pagination_helpers_spec.rb +++ b/spec/dashboard/pagination_helpers_spec.rb @@ -1,110 +1,110 @@ # frozen_string_literal: true -require 'spec_helper' -require 'split/dashboard/pagination_helpers' +require "spec_helper" +require "split/dashboard/pagination_helpers" describe Split::DashboardPaginationHelpers do include Split::DashboardPaginationHelpers - let(:url) { '/split/' } + let(:url) { "/split/" } - describe '#pagination_per' do - context 'when params empty' do + describe "#pagination_per" do + context "when params empty" do let(:params) { Hash[] } - it 'returns the default (10)' do + it "returns the default (10)" do default_per_page = Split.configuration.dashboard_pagination_default_per_page expect(pagination_per).to eql default_per_page expect(pagination_per).to eql 10 end end - context 'when params[:per] is 5' do + context "when params[:per] is 5" do let(:params) { Hash[per: 5] } - it 'returns 5' do + it "returns 5" do expect(pagination_per).to eql 5 end end end - describe '#page_number' do - context 'when params empty' do + describe "#page_number" do + context "when params empty" do let(:params) { Hash[] } - it 'returns 1' do + it "returns 1" do expect(page_number).to eql 1 end end context 'when params[:page] is "2"' do - let(:params) { Hash[page: '2'] } + let(:params) { Hash[page: "2"] } - it 'returns 2' do + it "returns 2" do expect(page_number).to eql 2 end end end - describe '#paginated' do + describe "#paginated" do let(:collection) { (1..20).to_a } - let(:params) { Hash[per: '5', page: '3'] } + let(:params) { Hash[per: "5", page: "3"] } it { expect(paginated(collection)).to eql [11, 12, 13, 14, 15] } end - describe '#show_first_page_tag?' do - context 'when page is 1' do + describe "#show_first_page_tag?" do + context "when page is 1" do it { expect(show_first_page_tag?).to be false } end - context 'when page is 3' do - let(:params) { Hash[page: '3'] } + context "when page is 3" do + let(:params) { Hash[page: "3"] } it { expect(show_first_page_tag?).to be true } end end - describe '#first_page_tag' do + describe "#first_page_tag" do it { expect(first_page_tag).to eql '1' } end - describe '#show_first_ellipsis_tag?' do - context 'when page is 1' do + describe "#show_first_ellipsis_tag?" do + context "when page is 1" do it { expect(show_first_ellipsis_tag?).to be false } end - context 'when page is 4' do - let(:params) { Hash[page: '4'] } + context "when page is 4" do + let(:params) { Hash[page: "4"] } it { expect(show_first_ellipsis_tag?).to be true } end end - describe '#ellipsis_tag' do - it { expect(ellipsis_tag).to eql '...' } + describe "#ellipsis_tag" do + it { expect(ellipsis_tag).to eql "..." } end - describe '#show_prev_page_tag?' do - context 'when page is 1' do + describe "#show_prev_page_tag?" do + context "when page is 1" do it { expect(show_prev_page_tag?).to be false } end - context 'when page is 2' do - let(:params) { Hash[page: '2'] } + context "when page is 2" do + let(:params) { Hash[page: "2"] } it { expect(show_prev_page_tag?).to be true } end end - describe '#prev_page_tag' do - context 'when page is 2' do - let(:params) { Hash[page: '2'] } + describe "#prev_page_tag" do + context "when page is 2" do + let(:params) { Hash[page: "2"] } it do expect(prev_page_tag).to eql '1' end end - context 'when page is 3' do - let(:params) { Hash[page: '3'] } + context "when page is 3" do + let(:params) { Hash[page: "3"] } it do expect(prev_page_tag).to eql '2' @@ -112,90 +112,90 @@ end end - describe '#show_prev_page_tag?' do - context 'when page is 1' do + describe "#show_prev_page_tag?" do + context "when page is 1" do it { expect(show_prev_page_tag?).to be false } end - context 'when page is 2' do - let(:params) { Hash[page: '2'] } + context "when page is 2" do + let(:params) { Hash[page: "2"] } it { expect(show_prev_page_tag?).to be true } end end - describe '#current_page_tag' do - context 'when page is 1' do - let(:params) { Hash[page: '1'] } - it { expect(current_page_tag).to eql '1' } + describe "#current_page_tag" do + context "when page is 1" do + let(:params) { Hash[page: "1"] } + it { expect(current_page_tag).to eql "1" } end - context 'when page is 2' do - let(:params) { Hash[page: '2'] } - it { expect(current_page_tag).to eql '2' } + context "when page is 2" do + let(:params) { Hash[page: "2"] } + it { expect(current_page_tag).to eql "2" } end end - describe '#show_next_page_tag?' do - context 'when page is 2' do - let(:params) { Hash[page: '2'] } + describe "#show_next_page_tag?" do + context "when page is 2" do + let(:params) { Hash[page: "2"] } - context 'when collection length is 20' do + context "when collection length is 20" do let(:collection) { (1..20).to_a } it { expect(show_next_page_tag?(collection)).to be false } end - context 'when collection length is 25' do + context "when collection length is 25" do let(:collection) { (1..25).to_a } it { expect(show_next_page_tag?(collection)).to be true } end end end - describe '#next_page_tag' do - context 'when page is 1' do - let(:params) { Hash[page: '1'] } + describe "#next_page_tag" do + context "when page is 1" do + let(:params) { Hash[page: "1"] } it { expect(next_page_tag).to eql '2' } end - context 'when page is 2' do - let(:params) { Hash[page: '2'] } + context "when page is 2" do + let(:params) { Hash[page: "2"] } it { expect(next_page_tag).to eql '3' } end end - describe '#total_pages' do - context 'when collection length is 30' do + describe "#total_pages" do + context "when collection length is 30" do let(:collection) { (1..30).to_a } it { expect(total_pages(collection)).to eql 3 } end - context 'when collection length is 35' do + context "when collection length is 35" do let(:collection) { (1..35).to_a } it { expect(total_pages(collection)).to eql 4 } end end - describe '#show_last_ellipsis_tag?' do + describe "#show_last_ellipsis_tag?" do let(:collection) { (1..30).to_a } - let(:params) { Hash[per: '5', page: '2'] } + let(:params) { Hash[per: "5", page: "2"] } it { expect(show_last_ellipsis_tag?(collection)).to be true } end - describe '#show_last_page_tag?' do + describe "#show_last_page_tag?" do let(:collection) { (1..30).to_a } - context 'when page is 5/6' do - let(:params) { Hash[per: '5', page: '5'] } + context "when page is 5/6" do + let(:params) { Hash[per: "5", page: "5"] } it { expect(show_last_page_tag?(collection)).to be false } end - context 'when page is 4/6' do - let(:params) { Hash[per: '5', page: '4'] } + context "when page is 4/6" do + let(:params) { Hash[per: "5", page: "4"] } it { expect(show_last_page_tag?(collection)).to be true } end end - describe '#last_page_tag' do + describe "#last_page_tag" do let(:collection) { (1..30).to_a } it { expect(last_page_tag(collection)).to eql '3' } end diff --git a/spec/dashboard/paginator_spec.rb b/spec/dashboard/paginator_spec.rb index 2d61dbba..634a6433 100644 --- a/spec/dashboard/paginator_spec.rb +++ b/spec/dashboard/paginator_spec.rb @@ -1,35 +1,35 @@ # frozen_string_literal: true -require 'spec_helper' -require 'split/dashboard/paginator' +require "spec_helper" +require "split/dashboard/paginator" describe Split::DashboardPaginator do - context 'when collection is 1..20' do + context "when collection is 1..20" do let(:collection) { (1..20).to_a } - context 'when per 5 for page' do + context "when per 5 for page" do let(:per) { 5 } - it 'when page number is 1 result is [1, 2, 3, 4, 5]' do + it "when page number is 1 result is [1, 2, 3, 4, 5]" do result = Split::DashboardPaginator.new(collection, 1, per).paginate expect(result).to eql [1, 2, 3, 4, 5] end - it 'when page number is 2 result is [6, 7, 8, 9, 10]' do + it "when page number is 2 result is [6, 7, 8, 9, 10]" do result = Split::DashboardPaginator.new(collection, 2, per).paginate expect(result).to eql [6, 7, 8, 9, 10] end end - context 'when per 10 for page' do + context "when per 10 for page" do let(:per) { 10 } - it 'when page number is 1 result is [1..10]' do + it "when page number is 1 result is [1..10]" do result = Split::DashboardPaginator.new(collection, 1, per).paginate expect(result).to eql [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] end - it 'when page number is 2 result is [10..20]' do + it "when page number is 2 result is [10..20]" do result = Split::DashboardPaginator.new(collection, 2, per).paginate expect(result).to eql [11, 12, 13, 14, 15, 16, 17, 18, 19, 20] end diff --git a/spec/dashboard_helpers_spec.rb b/spec/dashboard_helpers_spec.rb index 7b608ead..4894db01 100644 --- a/spec/dashboard_helpers_spec.rb +++ b/spec/dashboard_helpers_spec.rb @@ -1,42 +1,42 @@ # frozen_string_literal: true -require 'spec_helper' -require 'split/dashboard/helpers' +require "spec_helper" +require "split/dashboard/helpers" include Split::DashboardHelpers describe Split::DashboardHelpers do - describe 'confidence_level' do - it 'should handle very small numbers' do - expect(confidence_level(Complex(2e-18, -0.03))).to eq('Insufficient confidence') + describe "confidence_level" do + it "should handle very small numbers" do + expect(confidence_level(Complex(2e-18, -0.03))).to eq("Insufficient confidence") end it "should consider a z-score of 1.65 <= z < 1.96 as 90% confident" do - expect(confidence_level(1.65)).to eq('90% confidence') - expect(confidence_level(1.80)).to eq('90% confidence') + expect(confidence_level(1.65)).to eq("90% confidence") + expect(confidence_level(1.80)).to eq("90% confidence") end it "should consider a z-score of 1.96 <= z < 2.58 as 95% confident" do - expect(confidence_level(1.96)).to eq('95% confidence') - expect(confidence_level(2.00)).to eq('95% confidence') + expect(confidence_level(1.96)).to eq("95% confidence") + expect(confidence_level(2.00)).to eq("95% confidence") end it "should consider a z-score of z >= 2.58 as 99% confident" do - expect(confidence_level(2.58)).to eq('99% confidence') - expect(confidence_level(3.00)).to eq('99% confidence') + expect(confidence_level(2.58)).to eq("99% confidence") + expect(confidence_level(3.00)).to eq("99% confidence") end - describe '#round' do - it 'can round number strings' do - expect(round('3.1415')).to eq BigDecimal('3.14') + describe "#round" do + it "can round number strings" do + expect(round("3.1415")).to eq BigDecimal("3.14") end - it 'can round number strings for precsion' do - expect(round('3.1415', 1)).to eq BigDecimal('3.1') + it "can round number strings for precsion" do + expect(round("3.1415", 1)).to eq BigDecimal("3.1") end - it 'can handle invalid number strings' do - expect(round('N/A')).to be_zero + it "can handle invalid number strings" do + expect(round("N/A")).to be_zero end end end diff --git a/spec/dashboard_spec.rb b/spec/dashboard_spec.rb index f6ba1137..be28ba66 100644 --- a/spec/dashboard_spec.rb +++ b/spec/dashboard_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'spec_helper' -require 'rack/test' -require 'split/dashboard' +require "spec_helper" +require "rack/test" +require "split/dashboard" describe Split::Dashboard do include Rack::Test::Methods @@ -10,8 +10,8 @@ class TestDashboard < Split::Dashboard include Split::Helper - get '/my_experiment' do - ab_test(params[:experiment], 'blue', 'red') + get "/my_experiment" do + ab_test(params[:experiment], "blue", "red") end end @@ -32,7 +32,7 @@ def link(color) } let(:metric) { - Split::Metric.find_or_create(name: 'testmetric', experiments: [experiment, experiment_with_goals]) + Split::Metric.find_or_create(name: "testmetric", experiments: [experiment, experiment_with_goals]) } let(:red_link) { link("red") } @@ -43,7 +43,7 @@ def link(color) end it "should respond to /" do - get '/' + get "/" expect(last_response).to be_ok end @@ -55,33 +55,33 @@ def link(color) context "experiment without goals" do it "should display a Start button" do experiment - get '/' - expect(last_response.body).to include('Start') + get "/" + expect(last_response.body).to include("Start") post "/start?experiment=#{experiment.name}" - get '/' - expect(last_response.body).to include('Reset Data') - expect(last_response.body).not_to include('Metrics:') + get "/" + expect(last_response.body).to include("Reset Data") + expect(last_response.body).not_to include("Metrics:") end end context "experiment with metrics" do it "should display the names of associated metrics" do metric - get '/' - expect(last_response.body).to include('Metrics:testmetric') + get "/" + expect(last_response.body).to include("Metrics:testmetric") end end context "with goals" do it "should display a Start button" do experiment_with_goals - get '/' - expect(last_response.body).to include('Start') + get "/" + expect(last_response.body).to include("Start") post "/start?experiment=#{experiment.name}" - get '/' - expect(last_response.body).to include('Reset Data') + get "/" + expect(last_response.body).to include("Reset Data") end end end @@ -89,7 +89,7 @@ def link(color) describe "force alternative" do context "initial version" do let!(:user) do - Split::User.new(@app, { experiment.name => 'red' }) + Split::User.new(@app, { experiment.name => "red" }) end before do @@ -116,7 +116,7 @@ def link(color) context "incremented version" do let!(:user) do experiment.increment_version - Split::User.new(@app, { "#{experiment.name}:#{experiment.version}" => 'red' }) + Split::User.new(@app, { "#{experiment.name}:#{experiment.version}" => "red" }) end before do @@ -135,28 +135,28 @@ def link(color) describe "index page" do context "with winner" do - before { experiment.winner = 'red' } + before { experiment.winner = "red" } it "displays `Reopen Experiment` button" do - get '/' + get "/" - expect(last_response.body).to include('Reopen Experiment') + expect(last_response.body).to include("Reopen Experiment") end end context "without winner" do it "should not display `Reopen Experiment` button" do - get '/' + get "/" - expect(last_response.body).to_not include('Reopen Experiment') + expect(last_response.body).to_not include("Reopen Experiment") end end end describe "reopen experiment" do - before { experiment.winner = 'red' } + before { experiment.winner = "red" } - it 'redirects' do + it "redirects" do post "/reopen?experiment=#{experiment.name}" expect(last_response).to be_redirect @@ -171,7 +171,7 @@ def link(color) it "keeps existing stats" do red_link.participant_count = 5 blue_link.participant_count = 7 - experiment.winner = 'blue' + experiment.winner = "blue" post "/reopen?experiment=#{experiment.name}" @@ -236,7 +236,7 @@ def link(color) it "should reset an experiment" do red_link.participant_count = 5 blue_link.participant_count = 7 - experiment.winner = 'blue' + experiment.winner = "blue" post "/reset?experiment=#{experiment.name}" @@ -258,16 +258,16 @@ def link(color) it "should mark an alternative as the winner" do expect(experiment.winner).to be_nil - post "/experiment?experiment=#{experiment.name}", alternative: 'red' + post "/experiment?experiment=#{experiment.name}", alternative: "red" expect(last_response).to be_redirect - expect(experiment.winner.name).to eq('red') + expect(experiment.winner.name).to eq("red") end it "should display the start date" do experiment.start - get '/' + get "/" expect(last_response.body).to include("#{experiment.start_time.strftime('%Y-%m-%d')}") end @@ -275,8 +275,8 @@ def link(color) it "should handle experiments without a start date" do Split.redis.hdel(:experiment_start_times, experiment.name) - get '/' + get "/" - expect(last_response.body).to include('Unknown') + expect(last_response.body).to include("Unknown") end end diff --git a/spec/encapsulated_helper_spec.rb b/spec/encapsulated_helper_spec.rb index 32a814c3..df9dfa9a 100644 --- a/spec/encapsulated_helper_spec.rb +++ b/spec/encapsulated_helper_spec.rb @@ -1,13 +1,12 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" describe Split::EncapsulatedHelper do include Split::EncapsulatedHelper - def params - raise NoMethodError, 'This method is not really defined' + raise NoMethodError, "This method is not really defined" end describe "ab_test" do @@ -18,17 +17,17 @@ def params it "should not raise an error when params raises an error" do expect { params }.to raise_error(NoMethodError) - expect { ab_test('link_color', 'blue', 'red') }.not_to raise_error + expect { ab_test("link_color", "blue", "red") }.not_to raise_error end it "calls the block with selected alternative" do - expect { |block| ab_test('link_color', 'red', 'red', &block) }.to yield_with_args('red', {}) + expect { |block| ab_test("link_color", "red", "red", &block) }.to yield_with_args("red", {}) end context "inside a view" do it "works inside ERB" do - require 'erb' - template = ERB.new(<<-ERB.split(/\s+/s).map(&:strip).join(' '), nil, "%") + require "erb" + template = ERB.new(<<-ERB.split(/\s+/s).map(&:strip).join(" "), nil, "%") foo <% ab_test(:foo, '1', '2') do |alt, meta| %> static <%= alt %> <% end %> @@ -39,13 +38,13 @@ def params end describe "context" do - it 'is passed in shim' do + it "is passed in shim" do ctx = Class.new { include Split::EncapsulatedHelper public :session }.new expect(ctx).to receive(:session) { {} } - expect { ctx.ab_test('link_color', 'blue', 'red') }.not_to raise_error + expect { ctx.ab_test("link_color", "blue", "red") }.not_to raise_error end end end diff --git a/spec/experiment_catalog_spec.rb b/spec/experiment_catalog_spec.rb index 6f5b0531..192d0ffc 100644 --- a/spec/experiment_catalog_spec.rb +++ b/spec/experiment_catalog_spec.rb @@ -1,54 +1,54 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" describe Split::ExperimentCatalog do subject { Split::ExperimentCatalog } describe ".find_or_create" do it "should not raise an error when passed strings for alternatives" do - expect { subject.find_or_create('xyz', '1', '2', '3') }.not_to raise_error + expect { subject.find_or_create("xyz", "1", "2", "3") }.not_to raise_error end it "should not raise an error when passed an array for alternatives" do - expect { subject.find_or_create('xyz', ['1', '2', '3']) }.not_to raise_error + expect { subject.find_or_create("xyz", ["1", "2", "3"]) }.not_to raise_error end it "should raise the appropriate error when passed integers for alternatives" do - expect { subject.find_or_create('xyz', 1, 2, 3) }.to raise_error(ArgumentError) + expect { subject.find_or_create("xyz", 1, 2, 3) }.to raise_error(ArgumentError) end it "should raise the appropriate error when passed symbols for alternatives" do - expect { subject.find_or_create('xyz', :a, :b, :c) }.to raise_error(ArgumentError) + expect { subject.find_or_create("xyz", :a, :b, :c) }.to raise_error(ArgumentError) end it "should not raise error when passed an array for goals" do - expect { subject.find_or_create({ 'link_color' => ["purchase", "refund"] }, 'blue', 'red') } + expect { subject.find_or_create({ "link_color" => ["purchase", "refund"] }, "blue", "red") } .not_to raise_error end it "should not raise error when passed just one goal" do - expect { subject.find_or_create({ 'link_color' => "purchase" }, 'blue', 'red') } + expect { subject.find_or_create({ "link_color" => "purchase" }, "blue", "red") } .not_to raise_error end it "constructs a new experiment" do - expect(subject.find_or_create('my_exp', 'control me').control.to_s).to eq('control me') + expect(subject.find_or_create("my_exp", "control me").control.to_s).to eq("control me") end end - describe '.find' do + describe ".find" do it "should return an existing experiment" do - experiment = Split::Experiment.new('basket_text', alternatives: ['blue', 'red', 'green']) + experiment = Split::Experiment.new("basket_text", alternatives: ["blue", "red", "green"]) experiment.save - experiment = subject.find('basket_text') + experiment = subject.find("basket_text") - expect(experiment.name).to eq('basket_text') + expect(experiment.name).to eq("basket_text") end it "should return nil if experiment not exist" do - expect(subject.find('non_existent_experiment')).to be_nil + expect(subject.find("non_existent_experiment")).to be_nil end end end diff --git a/spec/experiment_spec.rb b/spec/experiment_spec.rb index 93ab55d2..78e930ca 100644 --- a/spec/experiment_spec.rb +++ b/spec/experiment_spec.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true -require 'spec_helper' -require 'time' +require "spec_helper" +require "time" describe Split::Experiment do def new_experiment(goals = []) - Split::Experiment.new('link_color', alternatives: ['blue', 'red', 'green'], goals: goals) + Split::Experiment.new("link_color", alternatives: ["blue", "red", "green"], goals: goals) end def alternative(color) - Split::Alternative.new(color, 'link_color') + Split::Alternative.new(color, "link_color") end let(:experiment) { new_experiment } @@ -18,10 +18,10 @@ def alternative(color) let(:green) { alternative("green") } context "with an experiment" do - let(:experiment) { Split::Experiment.new('basket_text', alternatives: ['Basket', "Cart"]) } + let(:experiment) { Split::Experiment.new("basket_text", alternatives: ["Basket", "Cart"]) } it "should have a name" do - expect(experiment.name).to eq('basket_text') + expect(experiment.name).to eq("basket_text") end it "should have alternatives" do @@ -29,7 +29,7 @@ def alternative(color) end it "should have alternatives with correct names" do - expect(experiment.alternatives.collect { |a| a.name }).to eq(['Basket', 'Cart']) + expect(experiment.alternatives.collect { |a| a.name }).to eq(["Basket", "Cart"]) end it "should be resettable by default" do @@ -38,7 +38,7 @@ def alternative(color) it "should save to redis" do experiment.save - expect(Split.redis.exists?('basket_text')).to be true + expect(Split.redis.exists?("basket_text")).to be true end it "should save the start time to redis" do @@ -46,14 +46,14 @@ def alternative(color) expect(Time).to receive(:now).and_return(experiment_start_time) experiment.save - expect(Split::ExperimentCatalog.find('basket_text').start_time).to eq(experiment_start_time) + expect(Split::ExperimentCatalog.find("basket_text").start_time).to eq(experiment_start_time) end it "should not save the start time to redis when start_manually is enabled" do expect(Split.configuration).to receive(:start_manually).and_return(true) experiment.save - expect(Split::ExperimentCatalog.find('basket_text').start_time).to be_nil + expect(Split::ExperimentCatalog.find("basket_text").start_time).to be_nil end it "should save the selected algorithm to redis" do @@ -61,7 +61,7 @@ def alternative(color) experiment.algorithm = experiment_algorithm experiment.save - expect(Split::ExperimentCatalog.find('basket_text').algorithm).to eq(experiment_algorithm) + expect(Split::ExperimentCatalog.find("basket_text").algorithm).to eq(experiment_algorithm) end it "should handle having a start time stored as a string" do @@ -70,7 +70,7 @@ def alternative(color) experiment.save Split.redis.hset(:experiment_start_times, experiment.name, experiment_start_time) - expect(Split::ExperimentCatalog.find('basket_text').start_time).to eq(experiment_start_time) + expect(Split::ExperimentCatalog.find("basket_text").start_time).to eq(experiment_start_time) end it "should handle not having a start time" do @@ -80,17 +80,17 @@ def alternative(color) Split.redis.hdel(:experiment_start_times, experiment.name) - expect(Split::ExperimentCatalog.find('basket_text').start_time).to be_nil + expect(Split::ExperimentCatalog.find("basket_text").start_time).to be_nil end it "should not create duplicates when saving multiple times" do experiment.save experiment.save - expect(Split.redis.exists?('basket_text')).to be true - expect(Split.redis.lrange('basket_text', 0, -1)).to eq(['{"Basket":1}', '{"Cart":1}']) + expect(Split.redis.exists?("basket_text")).to be true + expect(Split.redis.lrange("basket_text", 0, -1)).to eq(['{"Basket":1}', '{"Cart":1}']) end - describe 'new record?' do + describe "new record?" do it "should know if it hasn't been saved yet" do expect(experiment.new_record?).to be_truthy end @@ -101,56 +101,56 @@ def alternative(color) end end - describe 'control' do - it 'should be the first alternative' do + describe "control" do + it "should be the first alternative" do experiment.save - expect(experiment.control.name).to eq('Basket') + expect(experiment.control.name).to eq("Basket") end end end - describe 'initialization' do + describe "initialization" do it "should set the algorithm when passed as an option to the initializer" do - experiment = Split::Experiment.new('basket_text', alternatives: ['Basket', "Cart"], algorithm: Split::Algorithms::Whiplash) + experiment = Split::Experiment.new("basket_text", alternatives: ["Basket", "Cart"], algorithm: Split::Algorithms::Whiplash) expect(experiment.algorithm).to eq(Split::Algorithms::Whiplash) end it "should be possible to make an experiment not resettable" do - experiment = Split::Experiment.new('basket_text', alternatives: ['Basket', "Cart"], resettable: false) + experiment = Split::Experiment.new("basket_text", alternatives: ["Basket", "Cart"], resettable: false) expect(experiment.resettable).to be_falsey end - context 'from configuration' do + context "from configuration" do let(:experiment_name) { :my_experiment } let(:experiments) do { experiment_name => { - alternatives: ['Control Opt', 'Alt one'] + alternatives: ["Control Opt", "Alt one"] } } end before { Split.configuration.experiments = experiments } - it 'assigns default values to the experiment' do + it "assigns default values to the experiment" do expect(Split::Experiment.new(experiment_name).resettable).to eq(true) end end end - describe 'persistent configuration' do + describe "persistent configuration" do it "should persist resettable in redis" do - experiment = Split::Experiment.new('basket_text', alternatives: ['Basket', "Cart"], resettable: false) + experiment = Split::Experiment.new("basket_text", alternatives: ["Basket", "Cart"], resettable: false) experiment.save - e = Split::ExperimentCatalog.find('basket_text') + e = Split::ExperimentCatalog.find("basket_text") expect(e).to eq(experiment) expect(e.resettable).to be_falsey end - describe '#metadata' do - let(:experiment) { Split::Experiment.new('basket_text', alternatives: ['Basket', "Cart"], algorithm: Split::Algorithms::Whiplash, metadata: meta) } - let(:meta) { { a: 'b' } } + describe "#metadata" do + let(:experiment) { Split::Experiment.new("basket_text", alternatives: ["Basket", "Cart"], algorithm: Split::Algorithms::Whiplash, metadata: meta) } + let(:meta) { { a: "b" } } before do experiment.save @@ -163,20 +163,20 @@ def alternative(color) expect(Split.redis.exists?(experiment.metadata_key)).to be_falsey end - context 'simple hash' do - let(:meta) { { 'basket' => 'a', 'cart' => 'b' } } + context "simple hash" do + let(:meta) { { "basket" => "a", "cart" => "b" } } it "should persist metadata in redis" do - e = Split::ExperimentCatalog.find('basket_text') + e = Split::ExperimentCatalog.find("basket_text") expect(e).to eq(experiment) expect(e.metadata).to eq(meta) end end - context 'nested hash' do - let(:meta) { { 'basket' => { 'one' => 'two' }, 'cart' => 'b' } } + context "nested hash" do + let(:meta) { { "basket" => { "one" => "two" }, "cart" => "b" } } it "should persist metadata in redis" do - e = Split::ExperimentCatalog.find('basket_text') + e = Split::ExperimentCatalog.find("basket_text") expect(e).to eq(experiment) expect(e.metadata).to eq(meta) end @@ -184,32 +184,32 @@ def alternative(color) end it "should persist algorithm in redis" do - experiment = Split::Experiment.new('basket_text', alternatives: ['Basket', "Cart"], algorithm: Split::Algorithms::Whiplash) + experiment = Split::Experiment.new("basket_text", alternatives: ["Basket", "Cart"], algorithm: Split::Algorithms::Whiplash) experiment.save - e = Split::ExperimentCatalog.find('basket_text') + e = Split::ExperimentCatalog.find("basket_text") expect(e).to eq(experiment) expect(e.algorithm).to eq(Split::Algorithms::Whiplash) end it "should persist a new experiment in redis, that does not exist in the configuration file" do - experiment = Split::Experiment.new('foobar', alternatives: ['tra', 'la'], algorithm: Split::Algorithms::Whiplash) + experiment = Split::Experiment.new("foobar", alternatives: ["tra", "la"], algorithm: Split::Algorithms::Whiplash) experiment.save - e = Split::ExperimentCatalog.find('foobar') + e = Split::ExperimentCatalog.find("foobar") expect(e).to eq(experiment) - expect(e.alternatives.collect { |a| a.name }).to eq(['tra', 'la']) + expect(e.alternatives.collect { |a| a.name }).to eq(["tra", "la"]) end end - describe 'deleting' do - it 'should delete itself' do - experiment = Split::Experiment.new('basket_text', alternatives: [ 'Basket', "Cart"]) + describe "deleting" do + it "should delete itself" do + experiment = Split::Experiment.new("basket_text", alternatives: [ "Basket", "Cart"]) experiment.save experiment.delete - expect(Split.redis.exists?('link_color')).to be false - expect(Split::ExperimentCatalog.find('link_color')).to be_nil + expect(Split.redis.exists?("link_color")).to be false + expect(Split::ExperimentCatalog.find("link_color")).to be_nil end it "should increment the version" do @@ -228,7 +228,7 @@ def alternative(color) experiment.delete end - it 'should reset the start time if the experiment should be manually started' do + it "should reset the start time if the experiment should be manually started" do Split.configuration.start_manually = true experiment.start experiment.delete @@ -243,75 +243,75 @@ def alternative(color) end end - describe 'winner' do + describe "winner" do it "should have no winner initially" do expect(experiment.winner).to be_nil end end - describe 'winner=' do - it 'should allow you to specify a winner' do + describe "winner=" do + it "should allow you to specify a winner" do experiment.save - experiment.winner = 'red' - expect(experiment.winner.name).to eq('red') + experiment.winner = "red" + expect(experiment.winner.name).to eq("red") end - it 'should call the on_experiment_winner_choose hook' do + it "should call the on_experiment_winner_choose hook" do expect(Split.configuration.on_experiment_winner_choose).to receive(:call) - experiment.winner = 'green' + experiment.winner = "green" end - context 'when has_winner state is memoized' do + context "when has_winner state is memoized" do before { expect(experiment).to_not have_winner } - it 'should keep has_winner state consistent' do - experiment.winner = 'red' + it "should keep has_winner state consistent" do + experiment.winner = "red" expect(experiment).to have_winner end end end - describe 'reset_winner' do - before { experiment.winner = 'green' } + describe "reset_winner" do + before { experiment.winner = "green" } - it 'should reset the winner' do + it "should reset the winner" do experiment.reset_winner expect(experiment.winner).to be_nil end - context 'when has_winner state is memoized' do + context "when has_winner state is memoized" do before { expect(experiment).to have_winner } - it 'should keep has_winner state consistent' do + it "should keep has_winner state consistent" do experiment.reset_winner expect(experiment).to_not have_winner end end end - describe 'has_winner?' do - context 'with winner' do - before { experiment.winner = 'red' } + describe "has_winner?" do + context "with winner" do + before { experiment.winner = "red" } - it 'returns true' do + it "returns true" do expect(experiment).to have_winner end end - context 'without winner' do - it 'returns false' do + context "without winner" do + it "returns false" do expect(experiment).to_not have_winner end end - it 'memoizes has_winner state' do + it "memoizes has_winner state" do expect(experiment).to receive(:winner).once expect(experiment).to_not have_winner expect(experiment).to_not have_winner end end - describe 'reset' do + describe "reset" do let(:reset_manually) { false } before do @@ -321,10 +321,10 @@ def alternative(color) green.increment_participation end - it 'should reset all alternatives' do - experiment.winner = 'green' + it "should reset all alternatives" do + experiment.winner = "green" - expect(experiment.next_alternative.name).to eq('green') + expect(experiment.next_alternative.name).to eq("green") green.increment_participation experiment.reset @@ -333,10 +333,10 @@ def alternative(color) expect(green.completed_count).to eq(0) end - it 'should reset the winner' do - experiment.winner = 'green' + it "should reset the winner" do + experiment.winner = "green" - expect(experiment.next_alternative.name).to eq('green') + expect(experiment.next_alternative.name).to eq("green") green.increment_participation experiment.reset @@ -361,50 +361,50 @@ def alternative(color) end end - describe 'algorithm' do - let(:experiment) { Split::ExperimentCatalog.find_or_create('link_color', 'blue', 'red', 'green') } + describe "algorithm" do + let(:experiment) { Split::ExperimentCatalog.find_or_create("link_color", "blue", "red", "green") } - it 'should use the default algorithm if none is specified' do + it "should use the default algorithm if none is specified" do expect(experiment.algorithm).to eq(Split.configuration.algorithm) end - it 'should use the user specified algorithm for this experiment if specified' do + it "should use the user specified algorithm for this experiment if specified" do experiment.algorithm = Split::Algorithms::Whiplash expect(experiment.algorithm).to eq(Split::Algorithms::Whiplash) end end - describe '#next_alternative' do - context 'with multiple alternatives' do - let(:experiment) { Split::ExperimentCatalog.find_or_create('link_color', 'blue', 'red', 'green') } + describe "#next_alternative" do + context "with multiple alternatives" do + let(:experiment) { Split::ExperimentCatalog.find_or_create("link_color", "blue", "red", "green") } - context 'with winner' do + context "with winner" do it "should always return the winner" do - green = Split::Alternative.new('green', 'link_color') - experiment.winner = 'green' + green = Split::Alternative.new("green", "link_color") + experiment.winner = "green" - expect(experiment.next_alternative.name).to eq('green') + expect(experiment.next_alternative.name).to eq("green") green.increment_participation - expect(experiment.next_alternative.name).to eq('green') + expect(experiment.next_alternative.name).to eq("green") end end - context 'without winner' do + context "without winner" do it "should use the specified algorithm" do experiment.algorithm = Split::Algorithms::Whiplash - expect(experiment.algorithm).to receive(:choose_alternative).and_return(Split::Alternative.new('green', 'link_color')) - expect(experiment.next_alternative.name).to eq('green') + expect(experiment.algorithm).to receive(:choose_alternative).and_return(Split::Alternative.new("green", "link_color")) + expect(experiment.next_alternative.name).to eq("green") end end end - context 'with single alternative' do - let(:experiment) { Split::ExperimentCatalog.find_or_create('link_color', 'blue') } + context "with single alternative" do + let(:experiment) { Split::ExperimentCatalog.find_or_create("link_color", "blue") } it "should always return the only alternative" do - expect(experiment.next_alternative.name).to eq('blue') - expect(experiment.next_alternative.name).to eq('blue') + expect(experiment.next_alternative.name).to eq("blue") + expect(experiment.next_alternative.name).to eq("blue") end end end @@ -425,16 +425,16 @@ def alternative(color) end end - describe 'changing an existing experiment' do + describe "changing an existing experiment" do def same_but_different_alternative - Split::ExperimentCatalog.find_or_create('link_color', 'blue', 'yellow', 'orange') + Split::ExperimentCatalog.find_or_create("link_color", "blue", "yellow", "orange") end it "should reset an experiment if it is loaded with different alternatives" do experiment.save blue.participant_count = 5 same_experiment = same_but_different_alternative - expect(same_experiment.alternatives.map(&:name)).to eq(['blue', 'yellow', 'orange']) + expect(same_experiment.alternatives.map(&:name)).to eq(["blue", "yellow", "orange"]) expect(blue.participant_count).to eq(0) end @@ -450,7 +450,7 @@ def same_but_different_alternative context "when metadata is changed" do it "should increase version" do experiment.save - experiment.metadata = { 'foo' => 'bar' } + experiment.metadata = { "foo" => "bar" } expect { experiment.save }.to change { experiment.version }.by(1) end @@ -462,7 +462,7 @@ def same_but_different_alternative end end - context 'when experiment configuration is changed' do + context "when experiment configuration is changed" do let(:reset_manually) { false } before do @@ -475,15 +475,15 @@ def same_but_different_alternative experiment.save end - it 'resets all alternatives' do + it "resets all alternatives" do expect(green.participant_count).to eq(0) expect(green.completed_count).to eq(0) end - context 'when reset_manually is set' do + context "when reset_manually is set" do let(:reset_manually) { true } - it 'does not reset alternatives' do + it "does not reset alternatives" do expect(green.participant_count).to eq(2) expect(green.completed_count).to eq(0) end @@ -491,16 +491,16 @@ def same_but_different_alternative end end - describe 'alternatives passed as non-strings' do + describe "alternatives passed as non-strings" do it "should throw an exception if an alternative is passed that is not a string" do - expect { Split::ExperimentCatalog.find_or_create('link_color', :blue, :red) }.to raise_error(ArgumentError) - expect { Split::ExperimentCatalog.find_or_create('link_enabled', true, false) }.to raise_error(ArgumentError) + expect { Split::ExperimentCatalog.find_or_create("link_color", :blue, :red) }.to raise_error(ArgumentError) + expect { Split::ExperimentCatalog.find_or_create("link_enabled", true, false) }.to raise_error(ArgumentError) end end - describe 'specifying weights' do + describe "specifying weights" do let(:experiment_with_weight) { - Split::ExperimentCatalog.find_or_create('link_color', { 'blue' => 1 }, { 'red' => 2 }) + Split::ExperimentCatalog.find_or_create("link_color", { "blue" => 1 }, { "red" => 2 }) } it "should work for a new experiment" do @@ -519,7 +519,7 @@ def same_but_different_alternative } context "saving experiment" do - let(:same_but_different_goals) { Split::ExperimentCatalog.find_or_create({ 'link_color' => ["purchase", "refund"] }, 'blue', 'red', 'green') } + let(:same_but_different_goals) { Split::ExperimentCatalog.find_or_create({ "link_color" => ["purchase", "refund"] }, "blue", "red", "green") } before { experiment.save } @@ -539,9 +539,9 @@ def same_but_different_alternative context "find or create experiment" do it "should have correct goals" do - experiment = Split::ExperimentCatalog.find_or_create({ 'link_color3' => ["purchase", "refund"] }, 'blue', 'red', 'green') + experiment = Split::ExperimentCatalog.find_or_create({ "link_color3" => ["purchase", "refund"] }, "blue", "red", "green") expect(experiment.goals).to eq(["purchase", "refund"]) - experiment = Split::ExperimentCatalog.find_or_create('link_color3', 'blue', 'red', 'green') + experiment = Split::ExperimentCatalog.find_or_create("link_color3", "blue", "red", "green") expect(experiment.goals).to eq([]) end end @@ -549,19 +549,19 @@ def same_but_different_alternative describe "beta probability calculation" do it "should return a hash with the probability of each alternative being the best" do - experiment = Split::ExperimentCatalog.find_or_create('mathematicians', 'bernoulli', 'poisson', 'lagrange') + experiment = Split::ExperimentCatalog.find_or_create("mathematicians", "bernoulli", "poisson", "lagrange") experiment.calc_winning_alternatives expect(experiment.alternative_probabilities).not_to be_nil end it "should return between 46% and 54% probability for an experiment with 2 alternatives and no data" do - experiment = Split::ExperimentCatalog.find_or_create('scientists', 'einstein', 'bohr') + experiment = Split::ExperimentCatalog.find_or_create("scientists", "einstein", "bohr") experiment.calc_winning_alternatives expect(experiment.alternatives[0].p_winner).to be_within(0.04).of(0.50) end it "should calculate the probability of being the winning alternative separately for each goal", skip: true do - experiment = Split::ExperimentCatalog.find_or_create({ 'link_color3' => ["purchase", "refund"] }, 'blue', 'red', 'green') + experiment = Split::ExperimentCatalog.find_or_create({ "link_color3" => ["purchase", "refund"] }, "blue", "red", "green") goal1 = experiment.goals[0] goal2 = experiment.goals[1] experiment.alternatives.each do |alternative| @@ -577,7 +577,7 @@ def same_but_different_alternative end it "should return nil and not re-calculate probabilities if they have already been calculated today" do - experiment = Split::ExperimentCatalog.find_or_create({ 'link_color3' => ["purchase", "refund"] }, 'blue', 'red', 'green') + experiment = Split::ExperimentCatalog.find_or_create({ "link_color3" => ["purchase", "refund"] }, "blue", "red", "green") expect(experiment.calc_winning_alternatives).not_to be nil expect(experiment.calc_winning_alternatives).to be nil end diff --git a/spec/goals_collection_spec.rb b/spec/goals_collection_spec.rb index f7ba648f..26f9d223 100644 --- a/spec/goals_collection_spec.rb +++ b/spec/goals_collection_spec.rb @@ -1,43 +1,43 @@ # frozen_string_literal: true -require 'spec_helper' -require 'split/goals_collection' -require 'time' +require "spec_helper" +require "split/goals_collection" +require "time" describe Split::GoalsCollection do - let(:experiment_name) { 'experiment_name' } + let(:experiment_name) { "experiment_name" } - describe 'initialization' do + describe "initialization" do let(:goals_collection) { - Split::GoalsCollection.new('experiment_name', ['goal1', 'goal2']) + Split::GoalsCollection.new("experiment_name", ["goal1", "goal2"]) } it "should have an experiment_name" do expect(goals_collection.instance_variable_get(:@experiment_name)). - to eq('experiment_name') + to eq("experiment_name") end it "should have a list of goals" do expect(goals_collection.instance_variable_get(:@goals)). - to eq(['goal1', 'goal2']) + to eq(["goal1", "goal2"]) end end describe "#validate!" do it "should't raise ArgumentError if @goals is nil?" do - goals_collection = Split::GoalsCollection.new('experiment_name') + goals_collection = Split::GoalsCollection.new("experiment_name") expect { goals_collection.validate! }.not_to raise_error end it "should raise ArgumentError if @goals is not an Array" do goals_collection = Split::GoalsCollection. - new('experiment_name', 'not an array') + new("experiment_name", "not an array") expect { goals_collection.validate! }.to raise_error(ArgumentError) end it "should't raise ArgumentError if @goals is an array" do goals_collection = Split::GoalsCollection. - new('experiment_name', ['an array']) + new("experiment_name", ["an array"]) expect { goals_collection.validate! }.not_to raise_error end end @@ -46,7 +46,7 @@ let(:goals_key) { "#{experiment_name}:goals" } it "should delete goals from redis" do - goals_collection = Split::GoalsCollection.new(experiment_name, ['goal1']) + goals_collection = Split::GoalsCollection.new(experiment_name, ["goal1"]) goals_collection.save goals_collection.delete @@ -65,7 +65,7 @@ end it "should save goals to redis if @goals is valid" do - goals = ['valid goal 1', 'valid goal 2'] + goals = ["valid goal 1", "valid goal 2"] collection = Split::GoalsCollection.new(experiment_name, goals) collection.save @@ -74,9 +74,9 @@ it "should return @goals if @goals is valid" do goals_collection = Split::GoalsCollection. - new(experiment_name, ['valid goal']) + new(experiment_name, ["valid goal"]) - expect(goals_collection.save).to eq(['valid goal']) + expect(goals_collection.save).to eq(["valid goal"]) end end end diff --git a/spec/helper_spec.rb b/spec/helper_spec.rb index 62a40a68..33341657 100755 --- a/spec/helper_spec.rb +++ b/spec/helper_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" # TODO change some of these tests to use Rack::Test @@ -8,32 +8,32 @@ include Split::Helper let(:experiment) { - Split::ExperimentCatalog.find_or_create('link_color', 'blue', 'red') + Split::ExperimentCatalog.find_or_create("link_color", "blue", "red") } describe "ab_test" do it "should not raise an error when passed strings for alternatives" do - expect { ab_test('xyz', '1', '2', '3') }.not_to raise_error + expect { ab_test("xyz", "1", "2", "3") }.not_to raise_error end it "should not raise an error when passed an array for alternatives" do - expect { ab_test('xyz', ['1', '2', '3']) }.not_to raise_error + expect { ab_test("xyz", ["1", "2", "3"]) }.not_to raise_error end it "should raise the appropriate error when passed integers for alternatives" do - expect { ab_test('xyz', 1, 2, 3) }.to raise_error(ArgumentError) + expect { ab_test("xyz", 1, 2, 3) }.to raise_error(ArgumentError) end it "should raise the appropriate error when passed symbols for alternatives" do - expect { ab_test('xyz', :a, :b, :c) }.to raise_error(ArgumentError) + expect { ab_test("xyz", :a, :b, :c) }.to raise_error(ArgumentError) end it "should not raise error when passed an array for goals" do - expect { ab_test({ 'link_color' => ["purchase", "refund"] }, 'blue', 'red') }.not_to raise_error + expect { ab_test({ "link_color" => ["purchase", "refund"] }, "blue", "red") }.not_to raise_error end it "should not raise error when passed just one goal" do - expect { ab_test({ 'link_color' => "purchase" }, 'blue', 'red') }.not_to raise_error + expect { ab_test({ "link_color" => "purchase" }, "blue", "red") }.not_to raise_error end it "raises an appropriate error when processing combined expirements" do @@ -44,120 +44,120 @@ combined_experiments: [:combined_exp_1_sub_1] } } - Split::ExperimentCatalog.find_or_create('combined_exp_1') - expect { ab_test('combined_exp_1') }.to raise_error(Split::InvalidExperimentsFormatError) + Split::ExperimentCatalog.find_or_create("combined_exp_1") + expect { ab_test("combined_exp_1") }.to raise_error(Split::InvalidExperimentsFormatError) end it "should assign a random alternative to a new user when there are an equal number of alternatives assigned" do - ab_test('link_color', 'blue', 'red') - expect(['red', 'blue']).to include(ab_user['link_color']) + ab_test("link_color", "blue", "red") + expect(["red", "blue"]).to include(ab_user["link_color"]) end it "should increment the participation counter after assignment to a new user" do - previous_red_count = Split::Alternative.new('red', 'link_color').participant_count - previous_blue_count = Split::Alternative.new('blue', 'link_color').participant_count + previous_red_count = Split::Alternative.new("red", "link_color").participant_count + previous_blue_count = Split::Alternative.new("blue", "link_color").participant_count - ab_test('link_color', 'blue', 'red') + ab_test("link_color", "blue", "red") - new_red_count = Split::Alternative.new('red', 'link_color').participant_count - new_blue_count = Split::Alternative.new('blue', 'link_color').participant_count + new_red_count = Split::Alternative.new("red", "link_color").participant_count + new_blue_count = Split::Alternative.new("blue", "link_color").participant_count expect((new_red_count + new_blue_count)).to eq(previous_red_count + previous_blue_count + 1) end - it 'should not increment the counter for an experiment that the user is not participating in' do - ab_test('link_color', 'blue', 'red') - e = Split::ExperimentCatalog.find_or_create('button_size', 'small', 'big') + it "should not increment the counter for an experiment that the user is not participating in" do + ab_test("link_color", "blue", "red") + e = Split::ExperimentCatalog.find_or_create("button_size", "small", "big") expect { # User shouldn't participate in this second experiment - ab_test('button_size', 'small', 'big') + ab_test("button_size", "small", "big") }.not_to change { e.participant_count } end - it 'should not increment the counter for an ended experiment' do - e = Split::ExperimentCatalog.find_or_create('button_size', 'small', 'big') - e.winner = 'small' + it "should not increment the counter for an ended experiment" do + e = Split::ExperimentCatalog.find_or_create("button_size", "small", "big") + e.winner = "small" expect { - a = ab_test('button_size', 'small', 'big') - expect(a).to eq('small') + a = ab_test("button_size", "small", "big") + expect(a).to eq("small") }.not_to change { e.participant_count } end - it 'should not increment the counter for an not started experiment' do + it "should not increment the counter for an not started experiment" do expect(Split.configuration).to receive(:start_manually).and_return(true) - e = Split::ExperimentCatalog.find_or_create('button_size', 'small', 'big') + e = Split::ExperimentCatalog.find_or_create("button_size", "small", "big") expect { - a = ab_test('button_size', 'small', 'big') - expect(a).to eq('small') + a = ab_test("button_size", "small", "big") + expect(a).to eq("small") }.not_to change { e.participant_count } end it "should return the given alternative for an existing user" do - expect(ab_test('link_color', 'blue', 'red')).to eq ab_test('link_color', 'blue', 'red') + expect(ab_test("link_color", "blue", "red")).to eq ab_test("link_color", "blue", "red") end - it 'should always return the winner if one is present' do + it "should always return the winner if one is present" do experiment.winner = "orange" - expect(ab_test('link_color', 'blue', 'red')).to eq('orange') + expect(ab_test("link_color", "blue", "red")).to eq("orange") end it "should allow the alternative to be forced by passing it in the params" do # ?ab_test[link_color]=blue - @params = { 'ab_test' => { 'link_color' => 'blue' } } + @params = { "ab_test" => { "link_color" => "blue" } } - alternative = ab_test('link_color', 'blue', 'red') - expect(alternative).to eq('blue') + alternative = ab_test("link_color", "blue", "red") + expect(alternative).to eq("blue") - alternative = ab_test('link_color', { 'blue' => 1 }, 'red' => 5) - expect(alternative).to eq('blue') + alternative = ab_test("link_color", { "blue" => 1 }, "red" => 5) + expect(alternative).to eq("blue") - @params = { 'ab_test' => { 'link_color' => 'red' } } + @params = { "ab_test" => { "link_color" => "red" } } - alternative = ab_test('link_color', 'blue', 'red') - expect(alternative).to eq('red') + alternative = ab_test("link_color", "blue", "red") + expect(alternative).to eq("red") - alternative = ab_test('link_color', { 'blue' => 5 }, 'red' => 1) - expect(alternative).to eq('red') + alternative = ab_test("link_color", { "blue" => 5 }, "red" => 1) + expect(alternative).to eq("red") end it "should not allow an arbitrary alternative" do - @params = { 'ab_test' => { 'link_color' => 'pink' } } - alternative = ab_test('link_color', 'blue') - expect(alternative).to eq('blue') + @params = { "ab_test" => { "link_color" => "pink" } } + alternative = ab_test("link_color", "blue") + expect(alternative).to eq("blue") end it "should not store the split when a param forced alternative" do - @params = { 'ab_test' => { 'link_color' => 'blue' } } + @params = { "ab_test" => { "link_color" => "blue" } } expect(ab_user).not_to receive(:[]=) - ab_test('link_color', 'blue', 'red') + ab_test("link_color", "blue", "red") end it "SPLIT_DISABLE query parameter should also force the alternative (uses control)" do - @params = { 'SPLIT_DISABLE' => 'true' } - alternative = ab_test('link_color', 'blue', 'red') - expect(alternative).to eq('blue') - alternative = ab_test('link_color', { 'blue' => 1 }, 'red' => 5) - expect(alternative).to eq('blue') - alternative = ab_test('link_color', 'red', 'blue') - expect(alternative).to eq('red') - alternative = ab_test('link_color', { 'red' => 5 }, 'blue' => 1) - expect(alternative).to eq('red') + @params = { "SPLIT_DISABLE" => "true" } + alternative = ab_test("link_color", "blue", "red") + expect(alternative).to eq("blue") + alternative = ab_test("link_color", { "blue" => 1 }, "red" => 5) + expect(alternative).to eq("blue") + alternative = ab_test("link_color", "red", "blue") + expect(alternative).to eq("red") + alternative = ab_test("link_color", { "red" => 5 }, "blue" => 1) + expect(alternative).to eq("red") end it "should not store the split when Split generically disabled" do - @params = { 'SPLIT_DISABLE' => 'true' } + @params = { "SPLIT_DISABLE" => "true" } expect(ab_user).not_to receive(:[]=) - ab_test('link_color', 'blue', 'red') + ab_test("link_color", "blue", "red") end context "when store_override is set" do before { Split.configuration.store_override = true } it "should store the forced alternative" do - @params = { 'ab_test' => { 'link_color' => 'blue' } } - expect(ab_user).to receive(:[]=).with('link_color', 'blue') - ab_test('link_color', 'blue', 'red') + @params = { "ab_test" => { "link_color" => "blue" } } + expect(ab_user).to receive(:[]=).with("link_color", "blue") + ab_test("link_color", "blue", "red") end end @@ -165,35 +165,35 @@ before { Split.configuration.on_trial_choose = :some_method } it "should call the method" do expect(self).to receive(:some_method) - ab_test('link_color', 'blue', 'red') + ab_test("link_color", "blue", "red") end end it "should allow passing a block" do - alt = ab_test('link_color', 'blue', 'red') - ret = ab_test('link_color', 'blue', 'red') { |alternative| "shared/#{alternative}" } + alt = ab_test("link_color", "blue", "red") + ret = ab_test("link_color", "blue", "red") { |alternative| "shared/#{alternative}" } expect(ret).to eq("shared/#{alt}") end it "should allow the share of visitors see an alternative to be specified" do - ab_test('link_color', { 'blue' => 0.8 }, { 'red' => 20 }) - expect(['red', 'blue']).to include(ab_user['link_color']) + ab_test("link_color", { "blue" => 0.8 }, { "red" => 20 }) + expect(["red", "blue"]).to include(ab_user["link_color"]) end it "should allow alternative weighting interface as a single hash" do - ab_test('link_color', { 'blue' => 0.01 }, 'red' => 0.2) - experiment = Split::ExperimentCatalog.find('link_color') - expect(experiment.alternatives.map(&:name)).to eq(['blue', 'red']) + ab_test("link_color", { "blue" => 0.01 }, "red" => 0.2) + experiment = Split::ExperimentCatalog.find("link_color") + expect(experiment.alternatives.map(&:name)).to eq(["blue", "red"]) expect(experiment.alternatives.collect { |a| a.weight }).to match_array([0.01, 0.2]) end it "should only let a user participate in one experiment at a time" do - link_color = ab_test('link_color', 'blue', 'red') - ab_test('button_size', 'small', 'big') - expect(ab_user['link_color']).to eq(link_color) - big = Split::Alternative.new('big', 'button_size') + link_color = ab_test("link_color", "blue", "red") + ab_test("button_size", "small", "big") + expect(ab_user["link_color"]).to eq(link_color) + big = Split::Alternative.new("big", "button_size") expect(big.participant_count).to eq(0) - small = Split::Alternative.new('small', 'button_size') + small = Split::Alternative.new("small", "button_size") expect(small.participant_count).to eq(0) end @@ -201,113 +201,113 @@ Split.configure do |config| config.allow_multiple_experiments = true end - link_color = ab_test('link_color', 'blue', 'red') - button_size = ab_test('button_size', 'small', 'big') - expect(ab_user['link_color']).to eq(link_color) - expect(ab_user['button_size']).to eq(button_size) - button_size_alt = Split::Alternative.new(button_size, 'button_size') + link_color = ab_test("link_color", "blue", "red") + button_size = ab_test("button_size", "small", "big") + expect(ab_user["link_color"]).to eq(link_color) + expect(ab_user["button_size"]).to eq(button_size) + button_size_alt = Split::Alternative.new(button_size, "button_size") expect(button_size_alt.participant_count).to eq(1) end context "with allow_multiple_experiments = 'control'" do it "should let a user participate in many experiment with one non-'control' alternative" do Split.configure do |config| - config.allow_multiple_experiments = 'control' + config.allow_multiple_experiments = "control" end groups = 100.times.map do |n| - ab_test("test#{n}".to_sym, { 'control' => (100 - n) }, { "test#{n}-alt" => n }) + ab_test("test#{n}".to_sym, { "control" => (100 - n) }, { "test#{n}-alt" => n }) end experiments = ab_user.active_experiments expect(experiments.size).to be > 1 - count_control = experiments.values.count { |g| g == 'control' } + count_control = experiments.values.count { |g| g == "control" } expect(count_control).to eq(experiments.size - 1) - count_alts = groups.count { |g| g != 'control' } + count_alts = groups.count { |g| g != "control" } expect(count_alts).to eq(1) end context "when user already has experiment" do - let(:mock_user) { Split::User.new(self, { 'test_0' => 'test-alt' }) } + let(:mock_user) { Split::User.new(self, { "test_0" => "test-alt" }) } before do Split.configure do |config| - config.allow_multiple_experiments = 'control' + config.allow_multiple_experiments = "control" end - Split::ExperimentCatalog.find_or_initialize('test_0', 'control', 'test-alt').save - Split::ExperimentCatalog.find_or_initialize('test_1', 'control', 'test-alt').save + Split::ExperimentCatalog.find_or_initialize("test_0", "control", "test-alt").save + Split::ExperimentCatalog.find_or_initialize("test_1", "control", "test-alt").save end it "should restore previously selected alternative" do expect(ab_user.active_experiments.size).to eq 1 - expect(ab_test(:test_0, { 'control' => 100 }, { "test-alt" => 1 })).to eq 'test-alt' - expect(ab_test(:test_0, { 'control' => 1 }, { "test-alt" => 100 })).to eq 'test-alt' + expect(ab_test(:test_0, { "control" => 100 }, { "test-alt" => 1 })).to eq "test-alt" + expect(ab_test(:test_0, { "control" => 1 }, { "test-alt" => 100 })).to eq "test-alt" end it "should select the correct alternatives after experiment resets" do experiment = Split::ExperimentCatalog.find(:test_0) experiment.reset - mock_user[experiment.key] = 'test-alt' + mock_user[experiment.key] = "test-alt" expect(ab_user.active_experiments.size).to eq 1 - expect(ab_test(:test_0, { 'control' => 100 }, { "test-alt" => 1 })).to eq 'test-alt' - expect(ab_test(:test_0, { 'control' => 0 }, { "test-alt" => 100 })).to eq 'test-alt' + expect(ab_test(:test_0, { "control" => 100 }, { "test-alt" => 1 })).to eq "test-alt" + expect(ab_test(:test_0, { "control" => 0 }, { "test-alt" => 100 })).to eq "test-alt" end it "lets override existing choice" do pending "this requires user store reset on first call not depending on whelther it is current trial" - @params = { 'ab_test' => { 'test_1' => 'test-alt' } } + @params = { "ab_test" => { "test_1" => "test-alt" } } - expect(ab_test(:test_0, { 'control' => 0 }, { "test-alt" => 100 })).to eq 'control' - expect(ab_test(:test_1, { 'control' => 100 }, { "test-alt" => 1 })).to eq 'test-alt' + expect(ab_test(:test_0, { "control" => 0 }, { "test-alt" => 100 })).to eq "control" + expect(ab_test(:test_1, { "control" => 100 }, { "test-alt" => 1 })).to eq "test-alt" end end end it "should not over-write a finished key when an experiment is on a later version" do experiment.increment_version - ab_user = { experiment.key => 'blue', experiment.finished_key => true } + ab_user = { experiment.key => "blue", experiment.finished_key => true } finished_session = ab_user.dup - ab_test('link_color', 'blue', 'red') + ab_test("link_color", "blue", "red") expect(ab_user).to eq(finished_session) end end - describe 'metadata' do - context 'is defined' do + describe "metadata" do + context "is defined" do before do Split.configuration.experiments = { my_experiment: { alternatives: ["one", "two"], resettable: false, - metadata: { 'one' => 'Meta1', 'two' => 'Meta2' } + metadata: { "one" => "Meta1", "two" => "Meta2" } } } end - it 'should be passed to helper block' do - @params = { 'ab_test' => { 'my_experiment' => 'two' } } - expect(ab_test('my_experiment')).to eq 'two' - expect(ab_test('my_experiment') do |alternative, meta| + it "should be passed to helper block" do + @params = { "ab_test" => { "my_experiment" => "two" } } + expect(ab_test("my_experiment")).to eq "two" + expect(ab_test("my_experiment") do |alternative, meta| meta - end).to eq('Meta2') + end).to eq("Meta2") end - it 'should pass control metadata helper block if library disabled' do + it "should pass control metadata helper block if library disabled" do Split.configure do |config| config.enabled = false end - expect(ab_test('my_experiment')).to eq 'one' - expect(ab_test('my_experiment') do |_, meta| + expect(ab_test("my_experiment")).to eq "one" + expect(ab_test("my_experiment") do |_, meta| meta - end).to eq('Meta1') + end).to eq("Meta1") end end - context 'is not defined' do + context "is not defined" do before do Split.configuration.experiments = { my_experiment: { @@ -318,35 +318,35 @@ } end - it 'should be passed to helper block' do - expect(ab_test('my_experiment') do |alternative, meta| + it "should be passed to helper block" do + expect(ab_test("my_experiment") do |alternative, meta| meta end).to eq({}) end - it 'should pass control metadata helper block if library disabled' do + it "should pass control metadata helper block if library disabled" do Split.configure do |config| config.enabled = false end - expect(ab_test('my_experiment') do |_, meta| + expect(ab_test("my_experiment") do |_, meta| meta end).to eq({}) end end end - describe 'ab_finished' do - context 'for an experiment that the user participates in' do + describe "ab_finished" do + context "for an experiment that the user participates in" do before(:each) do - @experiment_name = 'link_color' - @alternatives = ['blue', 'red'] + @experiment_name = "link_color" + @alternatives = ["blue", "red"] @experiment = Split::ExperimentCatalog.find_or_create(@experiment_name, *@alternatives) @alternative_name = ab_test(@experiment_name, *@alternatives) @previous_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count end - it 'should increment the counter for the completed alternative' do + it "should increment the counter for the completed alternative" do ab_finished(@experiment_name) new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count expect(new_completion_count).to eq(@previous_completion_count + 1) @@ -358,20 +358,20 @@ expect(ab_user[@experiment.finished_key]).to eq(true) end - it 'should not increment the counter if reset is false and the experiment has been already finished' do + it "should not increment the counter if reset is false and the experiment has been already finished" do 2.times { ab_finished(@experiment_name, { reset: false }) } new_completion_count = Split::Alternative.new(@alternative_name, @experiment_name).completed_count expect(new_completion_count).to eq(@previous_completion_count + 1) end - it 'should not increment the counter for an ended experiment' do - e = Split::ExperimentCatalog.find_or_create('button_size', 'small', 'big') - e.winner = 'small' - a = ab_test('button_size', 'small', 'big') - expect(a).to eq('small') + it "should not increment the counter for an ended experiment" do + e = Split::ExperimentCatalog.find_or_create("button_size", "small", "big") + e.winner = "small" + a = ab_test("button_size", "small", "big") + expect(a).to eq("small") expect { - ab_finished('button_size') - }.not_to change { Split::Alternative.new(a, 'button_size').completed_count } + ab_finished("button_size") + }.not_to change { Split::Alternative.new(a, "button_size").completed_count } end it "should clear out the user's participation from their session" do @@ -417,46 +417,46 @@ end end - context 'for an experiment that the user is excluded from' do + context "for an experiment that the user is excluded from" do before do - alternative = ab_test('link_color', 'blue', 'red') - expect(Split::Alternative.new(alternative, 'link_color').participant_count).to eq(1) - alternative = ab_test('button_size', 'small', 'big') - expect(Split::Alternative.new(alternative, 'button_size').participant_count).to eq(0) + alternative = ab_test("link_color", "blue", "red") + expect(Split::Alternative.new(alternative, "link_color").participant_count).to eq(1) + alternative = ab_test("button_size", "small", "big") + expect(Split::Alternative.new(alternative, "button_size").participant_count).to eq(0) end - it 'should not increment the completed counter' do + it "should not increment the completed counter" do # So, user should be participating in the link_color experiment and # receive the control for button_size. As the user is not participating in # the button size experiment, finishing it should not increase the # completion count for that alternative. expect { - ab_finished('button_size') - }.not_to change { Split::Alternative.new('small', 'button_size').completed_count } + ab_finished("button_size") + }.not_to change { Split::Alternative.new("small", "button_size").completed_count } end end - context 'for an experiment that the user does not participate in' do + context "for an experiment that the user does not participate in" do before do - Split::ExperimentCatalog.find_or_create(:not_started_experiment, 'control', 'alt') + Split::ExperimentCatalog.find_or_create(:not_started_experiment, "control", "alt") end - it 'should not raise an exception' do + it "should not raise an exception" do expect { ab_finished(:not_started_experiment) }.not_to raise_exception end - it 'should not change the user state when reset is false' do + it "should not change the user state when reset is false" do expect { ab_finished(:not_started_experiment, reset: false) }.not_to change { ab_user.keys }.from([]) end - it 'should not change the user state when reset is true' do + it "should not change the user state when reset is true" do expect(self).not_to receive(:reset!) ab_finished(:not_started_experiment) end - it 'should not increment the completed counter' do + it "should not increment the completed counter" do ab_finished(:not_started_experiment) - expect(Split::Alternative.new('control', :not_started_experiment).completed_count).to eq(0) - expect(Split::Alternative.new('alt', :not_started_experiment).completed_count).to eq(0) + expect(Split::Alternative.new("control", :not_started_experiment).completed_count).to eq(0) + expect(Split::Alternative.new("alt", :not_started_experiment).completed_count).to eq(0) end end end @@ -486,7 +486,7 @@ def should_finish_experiment(experiment_name, should_finish = true) alts = Split.configuration.experiments[experiment_name][:alternatives] experiment = Split::ExperimentCatalog.find_or_create(experiment_name, *alts) alt_name = ab_user[experiment.key] = alts.first - alt = double('alternative') + alt = double("alternative") expect(alt).to receive(:name).at_most(1).times.and_return(alt_name) expect(Split::Alternative).to receive(:new).at_most(1).times.with(alt_name, experiment_name.to_s).and_return(alt) if should_finish @@ -558,125 +558,125 @@ def should_finish_experiment(experiment_name, should_finish = true) end end - describe 'conversions' do - it 'should return a conversion rate for an alternative' do - alternative_name = ab_test('link_color', 'blue', 'red') + describe "conversions" do + it "should return a conversion rate for an alternative" do + alternative_name = ab_test("link_color", "blue", "red") - previous_convertion_rate = Split::Alternative.new(alternative_name, 'link_color').conversion_rate + previous_convertion_rate = Split::Alternative.new(alternative_name, "link_color").conversion_rate expect(previous_convertion_rate).to eq(0.0) - ab_finished('link_color') + ab_finished("link_color") - new_convertion_rate = Split::Alternative.new(alternative_name, 'link_color').conversion_rate + new_convertion_rate = Split::Alternative.new(alternative_name, "link_color").conversion_rate expect(new_convertion_rate).to eq(1.0) end end - describe 'active experiments' do - it 'should show an active test' do - alternative = ab_test('def', '4', '5', '6') + describe "active experiments" do + it "should show an active test" do + alternative = ab_test("def", "4", "5", "6") expect(active_experiments.count).to eq 1 expect(active_experiments.first[0]).to eq "def" expect(active_experiments.first[1]).to eq alternative end - it 'should show a finished test' do - alternative = ab_test('def', '4', '5', '6') - ab_finished('def', { reset: false }) + it "should show a finished test" do + alternative = ab_test("def", "4", "5", "6") + ab_finished("def", { reset: false }) expect(active_experiments.count).to eq 1 expect(active_experiments.first[0]).to eq "def" expect(active_experiments.first[1]).to eq alternative end - it 'should show an active test when an experiment is on a later version' do + it "should show an active test when an experiment is on a later version" do experiment.reset expect(experiment.version).to eq(1) - ab_test('link_color', 'blue', 'red') + ab_test("link_color", "blue", "red") expect(active_experiments.count).to eq 1 expect(active_experiments.first[0]).to eq "link_color" end - it 'should show versioned tests properly' do + it "should show versioned tests properly" do 10.times { experiment.reset } - alternative = ab_test(experiment.name, 'blue', 'red') + alternative = ab_test(experiment.name, "blue", "red") ab_finished(experiment.name, reset: false) expect(experiment.version).to eq(10) expect(active_experiments.count).to eq 1 - expect(active_experiments).to eq({ 'link_color' => alternative }) + expect(active_experiments).to eq({ "link_color" => alternative }) end - it 'should show multiple tests' do + it "should show multiple tests" do Split.configure do |config| config.allow_multiple_experiments = true end - alternative = ab_test('def', '4', '5', '6') - another_alternative = ab_test('ghi', '7', '8', '9') + alternative = ab_test("def", "4", "5", "6") + another_alternative = ab_test("ghi", "7", "8", "9") expect(active_experiments.count).to eq 2 - expect(active_experiments['def']).to eq alternative - expect(active_experiments['ghi']).to eq another_alternative + expect(active_experiments["def"]).to eq alternative + expect(active_experiments["ghi"]).to eq another_alternative end - it 'should not show tests with winners' do + it "should not show tests with winners" do Split.configure do |config| config.allow_multiple_experiments = true end - e = Split::ExperimentCatalog.find_or_create('def', '4', '5', '6') - e.winner = '4' - ab_test('def', '4', '5', '6') - another_alternative = ab_test('ghi', '7', '8', '9') + e = Split::ExperimentCatalog.find_or_create("def", "4", "5", "6") + e.winner = "4" + ab_test("def", "4", "5", "6") + another_alternative = ab_test("ghi", "7", "8", "9") expect(active_experiments.count).to eq 1 expect(active_experiments.first[0]).to eq "ghi" expect(active_experiments.first[1]).to eq another_alternative end end - describe 'when user is a robot' do + describe "when user is a robot" do before(:each) do - @request = OpenStruct.new(user_agent: 'Googlebot/2.1 (+http://www.google.com/bot.html)') + @request = OpenStruct.new(user_agent: "Googlebot/2.1 (+http://www.google.com/bot.html)") end - describe 'ab_test' do - it 'should return the control' do - alternative = ab_test('link_color', 'blue', 'red') + describe "ab_test" do + it "should return the control" do + alternative = ab_test("link_color", "blue", "red") expect(alternative).to eq experiment.control.name end - it 'should not create a experiment' do - ab_test('link_color', 'blue', 'red') - expect(Split::Experiment.new('link_color')).to be_a_new_record + it "should not create a experiment" do + ab_test("link_color", "blue", "red") + expect(Split::Experiment.new("link_color")).to be_a_new_record end it "should not increment the participation count" do - previous_red_count = Split::Alternative.new('red', 'link_color').participant_count - previous_blue_count = Split::Alternative.new('blue', 'link_color').participant_count + previous_red_count = Split::Alternative.new("red", "link_color").participant_count + previous_blue_count = Split::Alternative.new("blue", "link_color").participant_count - ab_test('link_color', 'blue', 'red') + ab_test("link_color", "blue", "red") - new_red_count = Split::Alternative.new('red', 'link_color').participant_count - new_blue_count = Split::Alternative.new('blue', 'link_color').participant_count + new_red_count = Split::Alternative.new("red", "link_color").participant_count + new_blue_count = Split::Alternative.new("blue", "link_color").participant_count expect((new_red_count + new_blue_count)).to eq(previous_red_count + previous_blue_count) end end - describe 'finished' do + describe "finished" do it "should not increment the completed count" do - alternative_name = ab_test('link_color', 'blue', 'red') + alternative_name = ab_test("link_color", "blue", "red") - previous_completion_count = Split::Alternative.new(alternative_name, 'link_color').completed_count + previous_completion_count = Split::Alternative.new(alternative_name, "link_color").completed_count - ab_finished('link_color') + ab_finished("link_color") - new_completion_count = Split::Alternative.new(alternative_name, 'link_color').completed_count + new_completion_count = Split::Alternative.new(alternative_name, "link_color").completed_count expect(new_completion_count).to eq(previous_completion_count) end end end - describe 'when providing custom ignore logic' do + describe "when providing custom ignore logic" do context "using a proc to configure custom logic" do before(:each) do Split.configure do |c| @@ -685,56 +685,56 @@ def should_finish_experiment(experiment_name, should_finish = true) end it "ignores the ab_test" do - ab_test('link_color', 'blue', 'red') + ab_test("link_color", "blue", "red") - red_count = Split::Alternative.new('red', 'link_color').participant_count - blue_count = Split::Alternative.new('blue', 'link_color').participant_count + red_count = Split::Alternative.new("red", "link_color").participant_count + blue_count = Split::Alternative.new("blue", "link_color").participant_count expect((red_count + blue_count)).to be(0) end end end shared_examples_for "a disabled test" do - describe 'ab_test' do - it 'should return the control' do - alternative = ab_test('link_color', 'blue', 'red') + describe "ab_test" do + it "should return the control" do + alternative = ab_test("link_color", "blue", "red") expect(alternative).to eq experiment.control.name end it "should not increment the participation count" do - previous_red_count = Split::Alternative.new('red', 'link_color').participant_count - previous_blue_count = Split::Alternative.new('blue', 'link_color').participant_count + previous_red_count = Split::Alternative.new("red", "link_color").participant_count + previous_blue_count = Split::Alternative.new("blue", "link_color").participant_count - ab_test('link_color', 'blue', 'red') + ab_test("link_color", "blue", "red") - new_red_count = Split::Alternative.new('red', 'link_color').participant_count - new_blue_count = Split::Alternative.new('blue', 'link_color').participant_count + new_red_count = Split::Alternative.new("red", "link_color").participant_count + new_blue_count = Split::Alternative.new("blue", "link_color").participant_count expect((new_red_count + new_blue_count)).to eq(previous_red_count + previous_blue_count) end end - describe 'finished' do + describe "finished" do it "should not increment the completed count" do - alternative_name = ab_test('link_color', 'blue', 'red') + alternative_name = ab_test("link_color", "blue", "red") - previous_completion_count = Split::Alternative.new(alternative_name, 'link_color').completed_count + previous_completion_count = Split::Alternative.new(alternative_name, "link_color").completed_count - ab_finished('link_color') + ab_finished("link_color") - new_completion_count = Split::Alternative.new(alternative_name, 'link_color').completed_count + new_completion_count = Split::Alternative.new(alternative_name, "link_color").completed_count expect(new_completion_count).to eq(previous_completion_count) end end end - describe 'when ip address is ignored' do + describe "when ip address is ignored" do context "individually" do before(:each) do - @request = OpenStruct.new(ip: '81.19.48.130') + @request = OpenStruct.new(ip: "81.19.48.130") Split.configure do |c| - c.ignore_ip_addresses << '81.19.48.130' + c.ignore_ip_addresses << "81.19.48.130" end end @@ -743,7 +743,7 @@ def should_finish_experiment(experiment_name, should_finish = true) context "for a range" do before(:each) do - @request = OpenStruct.new(ip: '81.19.48.129') + @request = OpenStruct.new(ip: "81.19.48.129") Split.configure do |c| c.ignore_ip_addresses << /81\.19\.48\.[0-9]+/ end @@ -754,9 +754,9 @@ def should_finish_experiment(experiment_name, should_finish = true) context "using both a range and a specific value" do before(:each) do - @request = OpenStruct.new(ip: '81.19.48.128') + @request = OpenStruct.new(ip: "81.19.48.128") Split.configure do |c| - c.ignore_ip_addresses << '81.19.48.130' + c.ignore_ip_addresses << "81.19.48.130" c.ignore_ip_addresses << /81\.19\.48\.[0-9]+/ end end @@ -766,119 +766,119 @@ def should_finish_experiment(experiment_name, should_finish = true) context "when ignored other address" do before do - @request = OpenStruct.new(ip: '1.1.1.1') + @request = OpenStruct.new(ip: "1.1.1.1") Split.configure do |c| - c.ignore_ip_addresses << '81.19.48.130' + c.ignore_ip_addresses << "81.19.48.130" end end it "works as usual" do - alternative_name = ab_test('link_color', 'red', 'blue') + alternative_name = ab_test("link_color", "red", "blue") expect { - ab_finished('link_color') - }.to change(Split::Alternative.new(alternative_name, 'link_color'), :completed_count).by(1) + ab_finished("link_color") + }.to change(Split::Alternative.new(alternative_name, "link_color"), :completed_count).by(1) end end end - describe 'when user is previewing' do + describe "when user is previewing" do before(:each) do - @request = OpenStruct.new(headers: { 'x-purpose' => 'preview' }) + @request = OpenStruct.new(headers: { "x-purpose" => "preview" }) end it_behaves_like "a disabled test" end - describe 'versioned experiments' do + describe "versioned experiments" do it "should use version zero if no version is present" do - alternative_name = ab_test('link_color', 'blue', 'red') + alternative_name = ab_test("link_color", "blue", "red") expect(experiment.version).to eq(0) - expect(ab_user['link_color']).to eq(alternative_name) + expect(ab_user["link_color"]).to eq(alternative_name) end it "should save the version of the experiment to the session" do experiment.reset expect(experiment.version).to eq(1) - alternative_name = ab_test('link_color', 'blue', 'red') - expect(ab_user['link_color:1']).to eq(alternative_name) + alternative_name = ab_test("link_color", "blue", "red") + expect(ab_user["link_color:1"]).to eq(alternative_name) end it "should load the experiment even if the version is not 0" do experiment.reset expect(experiment.version).to eq(1) - alternative_name = ab_test('link_color', 'blue', 'red') - expect(ab_user['link_color:1']).to eq(alternative_name) - return_alternative_name = ab_test('link_color', 'blue', 'red') + alternative_name = ab_test("link_color", "blue", "red") + expect(ab_user["link_color:1"]).to eq(alternative_name) + return_alternative_name = ab_test("link_color", "blue", "red") expect(return_alternative_name).to eq(alternative_name) end it "should reset the session of a user on an older version of the experiment" do - alternative_name = ab_test('link_color', 'blue', 'red') - expect(ab_user['link_color']).to eq(alternative_name) - alternative = Split::Alternative.new(alternative_name, 'link_color') + alternative_name = ab_test("link_color", "blue", "red") + expect(ab_user["link_color"]).to eq(alternative_name) + alternative = Split::Alternative.new(alternative_name, "link_color") expect(alternative.participant_count).to eq(1) experiment.reset expect(experiment.version).to eq(1) - alternative = Split::Alternative.new(alternative_name, 'link_color') + alternative = Split::Alternative.new(alternative_name, "link_color") expect(alternative.participant_count).to eq(0) - new_alternative_name = ab_test('link_color', 'blue', 'red') - expect(ab_user['link_color:1']).to eq(new_alternative_name) - new_alternative = Split::Alternative.new(new_alternative_name, 'link_color') + new_alternative_name = ab_test("link_color", "blue", "red") + expect(ab_user["link_color:1"]).to eq(new_alternative_name) + new_alternative = Split::Alternative.new(new_alternative_name, "link_color") expect(new_alternative.participant_count).to eq(1) end it "should cleanup old versions of experiments from the session" do - alternative_name = ab_test('link_color', 'blue', 'red') - expect(ab_user['link_color']).to eq(alternative_name) - alternative = Split::Alternative.new(alternative_name, 'link_color') + alternative_name = ab_test("link_color", "blue", "red") + expect(ab_user["link_color"]).to eq(alternative_name) + alternative = Split::Alternative.new(alternative_name, "link_color") expect(alternative.participant_count).to eq(1) experiment.reset expect(experiment.version).to eq(1) - alternative = Split::Alternative.new(alternative_name, 'link_color') + alternative = Split::Alternative.new(alternative_name, "link_color") expect(alternative.participant_count).to eq(0) - new_alternative_name = ab_test('link_color', 'blue', 'red') - expect(ab_user['link_color:1']).to eq(new_alternative_name) + new_alternative_name = ab_test("link_color", "blue", "red") + expect(ab_user["link_color:1"]).to eq(new_alternative_name) end it "should only count completion of users on the current version" do - alternative_name = ab_test('link_color', 'blue', 'red') - expect(ab_user['link_color']).to eq(alternative_name) - Split::Alternative.new(alternative_name, 'link_color') + alternative_name = ab_test("link_color", "blue", "red") + expect(ab_user["link_color"]).to eq(alternative_name) + Split::Alternative.new(alternative_name, "link_color") experiment.reset expect(experiment.version).to eq(1) - ab_finished('link_color') - alternative = Split::Alternative.new(alternative_name, 'link_color') + ab_finished("link_color") + alternative = Split::Alternative.new(alternative_name, "link_color") expect(alternative.completed_count).to eq(0) end end - context 'when redis is not available' do + context "when redis is not available" do before(:each) do expect(Split).to receive(:redis).at_most(5).times.and_raise(Errno::ECONNREFUSED.new) end - context 'and db_failover config option is turned off' do + context "and db_failover config option is turned off" do before(:each) do Split.configure do |config| config.db_failover = false end end - describe 'ab_test' do - it 'should raise an exception' do - expect { ab_test('link_color', 'blue', 'red') }.to raise_error(Errno::ECONNREFUSED) + describe "ab_test" do + it "should raise an exception" do + expect { ab_test("link_color", "blue", "red") }.to raise_error(Errno::ECONNREFUSED) end end - describe 'finished' do - it 'should raise an exception' do - expect { ab_finished('link_color') }.to raise_error(Errno::ECONNREFUSED) + describe "finished" do + it "should raise an exception" do + expect { ab_finished("link_color") }.to raise_error(Errno::ECONNREFUSED) end end @@ -890,29 +890,29 @@ def should_finish_experiment(experiment_name, should_finish = true) end it "should not attempt to connect to redis" do - expect { ab_test('link_color', 'blue', 'red') }.not_to raise_error + expect { ab_test("link_color", "blue", "red") }.not_to raise_error end it "should return control variable" do - expect(ab_test('link_color', 'blue', 'red')).to eq('blue') - expect { ab_finished('link_color') }.not_to raise_error + expect(ab_test("link_color", "blue", "red")).to eq("blue") + expect { ab_finished("link_color") }.not_to raise_error end end end - context 'and db_failover config option is turned on' do + context "and db_failover config option is turned on" do before(:each) do Split.configure do |config| config.db_failover = true end end - describe 'ab_test' do - it 'should not raise an exception' do - expect { ab_test('link_color', 'blue', 'red') }.not_to raise_error + describe "ab_test" do + it "should not raise an exception" do + expect { ab_test("link_color", "blue", "red") }.not_to raise_error end - it 'should call db_failover_on_db_error proc with error as parameter' do + it "should call db_failover_on_db_error proc with error as parameter" do Split.configure do |config| config.db_failover_on_db_error = proc do |error| expect(error).to be_a(Errno::ECONNREFUSED) @@ -920,40 +920,40 @@ def should_finish_experiment(experiment_name, should_finish = true) end expect(Split.configuration.db_failover_on_db_error).to receive(:call).and_call_original - ab_test('link_color', 'blue', 'red') + ab_test("link_color", "blue", "red") end - it 'should always use first alternative' do - expect(ab_test('link_color', 'blue', 'red')).to eq('blue') - expect(ab_test('link_color', { 'blue' => 0.01 }, 'red' => 0.2)).to eq('blue') - expect(ab_test('link_color', { 'blue' => 0.8 }, { 'red' => 20 })).to eq('blue') - expect(ab_test('link_color', 'blue', 'red') do |alternative| + it "should always use first alternative" do + expect(ab_test("link_color", "blue", "red")).to eq("blue") + expect(ab_test("link_color", { "blue" => 0.01 }, "red" => 0.2)).to eq("blue") + expect(ab_test("link_color", { "blue" => 0.8 }, { "red" => 20 })).to eq("blue") + expect(ab_test("link_color", "blue", "red") do |alternative| "shared/#{alternative}" - end).to eq('shared/blue') + end).to eq("shared/blue") end - context 'and db_failover_allow_parameter_override config option is turned on' do + context "and db_failover_allow_parameter_override config option is turned on" do before(:each) do Split.configure do |config| config.db_failover_allow_parameter_override = true end end - context 'and given an override parameter' do - it 'should use given override instead of the first alternative' do - @params = { 'ab_test' => { 'link_color' => 'red' } } - expect(ab_test('link_color', 'blue', 'red')).to eq('red') - expect(ab_test('link_color', 'blue', 'red', 'green')).to eq('red') - expect(ab_test('link_color', { 'blue' => 0.01 }, 'red' => 0.2)).to eq('red') - expect(ab_test('link_color', { 'blue' => 0.8 }, { 'red' => 20 })).to eq('red') - expect(ab_test('link_color', 'blue', 'red') do |alternative| + context "and given an override parameter" do + it "should use given override instead of the first alternative" do + @params = { "ab_test" => { "link_color" => "red" } } + expect(ab_test("link_color", "blue", "red")).to eq("red") + expect(ab_test("link_color", "blue", "red", "green")).to eq("red") + expect(ab_test("link_color", { "blue" => 0.01 }, "red" => 0.2)).to eq("red") + expect(ab_test("link_color", { "blue" => 0.8 }, { "red" => 20 })).to eq("red") + expect(ab_test("link_color", "blue", "red") do |alternative| "shared/#{alternative}" - end).to eq('shared/red') + end).to eq("shared/red") end end end - context 'and preloaded config given' do + context "and preloaded config given" do before do Split.configuration.experiments[:link_color] = { alternatives: [ "blue", "red" ], @@ -966,12 +966,12 @@ def should_finish_experiment(experiment_name, should_finish = true) end end - describe 'finished' do - it 'should not raise an exception' do - expect { ab_finished('link_color') }.not_to raise_error + describe "finished" do + it "should not raise an exception" do + expect { ab_finished("link_color") }.not_to raise_error end - it 'should call db_failover_on_db_error proc with error as parameter' do + it "should call db_failover_on_db_error proc with error as parameter" do Split.configure do |config| config.db_failover_on_db_error = proc do |error| expect(error).to be_a(Errno::ECONNREFUSED) @@ -979,7 +979,7 @@ def should_finish_experiment(experiment_name, should_finish = true) end expect(Split.configuration.db_failover_on_db_error).to receive(:call).and_call_original - ab_finished('link_color') + ab_finished("link_color") end end end @@ -1047,7 +1047,7 @@ def should_finish_experiment(experiment_name, should_finish = true) } ab_test :my_experiment experiment = Split::Experiment.new(:my_experiment) - expect(experiment.alternatives.collect { |a| [a.name, a.weight] }).to eq([['control_opt', 0.67], ['second_opt', 0.1], ['third_opt', 0.23]]) + expect(experiment.alternatives.collect { |a| [a.name, a.weight] }).to eq([["control_opt", 0.67], ["second_opt", 0.1], ["third_opt", 0.23]]) end it "accepts probability on some alternatives" do @@ -1062,7 +1062,7 @@ def should_finish_experiment(experiment_name, should_finish = true) ab_test :my_experiment experiment = Split::Experiment.new(:my_experiment) names_and_weights = experiment.alternatives.collect { |a| [a.name, a.weight] } - expect(names_and_weights).to eq([['control_opt', 0.34], ['second_opt', 0.215], ['third_opt', 0.23], ['fourth_opt', 0.215]]) + expect(names_and_weights).to eq([["control_opt", 0.34], ["second_opt", 0.215], ["third_opt", 0.23], ["fourth_opt", 0.215]]) expect(names_and_weights.inject(0) { |sum, nw| sum + nw[1] }).to eq(1.0) end @@ -1077,7 +1077,7 @@ def should_finish_experiment(experiment_name, should_finish = true) ab_test :my_experiment experiment = Split::Experiment.new(:my_experiment) names_and_weights = experiment.alternatives.collect { |a| [a.name, a.weight] } - expect(names_and_weights).to eq([['control_opt', 0.18], ['second_opt', 0.18], ['third_opt', 0.64]]) + expect(names_and_weights).to eq([["control_opt", 0.18], ["second_opt", 0.18], ["third_opt", 0.64]]) expect(names_and_weights.inject(0) { |sum, nw| sum + nw[1] }).to eq(1.0) end @@ -1096,11 +1096,11 @@ def should_finish_experiment(experiment_name, should_finish = true) end end - it 'should handle multiple experiments correctly' do - experiment2 = Split::ExperimentCatalog.find_or_create('link_color2', 'blue', 'red') - ab_test('link_color', 'blue', 'red') - ab_test('link_color2', 'blue', 'red') - ab_finished('link_color2') + it "should handle multiple experiments correctly" do + experiment2 = Split::ExperimentCatalog.find_or_create("link_color2", "blue", "red") + ab_test("link_color", "blue", "red") + ab_test("link_color2", "blue", "red") + ab_finished("link_color2") experiment2.alternatives.each do |alt| expect(alt.unfinished_count).to eq(0) @@ -1109,8 +1109,8 @@ def should_finish_experiment(experiment_name, should_finish = true) context "with goals" do before do - @experiment = { 'link_color' => ["purchase", "refund"] } - @alternatives = ['blue', 'red'] + @experiment = { "link_color" => ["purchase", "refund"] } + @alternatives = ["blue", "red"] @experiment_name, @goals = normalize_metric(@experiment) @goal1 = @goals[0] @goal2 = @goals[1] @@ -1124,8 +1124,8 @@ def should_finish_experiment(experiment_name, should_finish = true) describe "ab_test" do it "should allow experiment goals interface as a single hash" do ab_test(@experiment, *@alternatives) - experiment = Split::ExperimentCatalog.find('link_color') - expect(experiment.goals).to eq(['purchase', "refund"]) + experiment = Split::ExperimentCatalog.find("link_color") + expect(experiment.goals).to eq(["purchase", "refund"]) end end diff --git a/spec/metric_spec.rb b/spec/metric_spec.rb index 92721142..c1bf63e4 100644 --- a/spec/metric_spec.rb +++ b/spec/metric_spec.rb @@ -1,31 +1,31 @@ # frozen_string_literal: true -require 'spec_helper' -require 'split/metric' +require "spec_helper" +require "split/metric" describe Split::Metric do - describe 'possible experiments' do + describe "possible experiments" do it "should load the experiment if there is one, but no metric" do - experiment = Split::ExperimentCatalog.find_or_create('color', 'red', 'blue') - expect(Split::Metric.possible_experiments('color')).to eq([experiment]) + experiment = Split::ExperimentCatalog.find_or_create("color", "red", "blue") + expect(Split::Metric.possible_experiments("color")).to eq([experiment]) end it "should load the experiments in a metric" do - experiment1 = Split::ExperimentCatalog.find_or_create('color', 'red', 'blue') - experiment2 = Split::ExperimentCatalog.find_or_create('size', 'big', 'small') + experiment1 = Split::ExperimentCatalog.find_or_create("color", "red", "blue") + experiment2 = Split::ExperimentCatalog.find_or_create("size", "big", "small") - metric = Split::Metric.new(name: 'purchase', experiments: [experiment1, experiment2]) + metric = Split::Metric.new(name: "purchase", experiments: [experiment1, experiment2]) metric.save - expect(Split::Metric.possible_experiments('purchase')).to include(experiment1, experiment2) + expect(Split::Metric.possible_experiments("purchase")).to include(experiment1, experiment2) end it "should load both the metric experiments and an experiment with the same name" do - experiment1 = Split::ExperimentCatalog.find_or_create('purchase', 'red', 'blue') - experiment2 = Split::ExperimentCatalog.find_or_create('size', 'big', 'small') + experiment1 = Split::ExperimentCatalog.find_or_create("purchase", "red", "blue") + experiment2 = Split::ExperimentCatalog.find_or_create("size", "big", "small") - metric = Split::Metric.new(name: 'purchase', experiments: [experiment2]) + metric = Split::Metric.new(name: "purchase", experiments: [experiment2]) metric.save - expect(Split::Metric.possible_experiments('purchase')).to include(experiment1, experiment2) + expect(Split::Metric.possible_experiments("purchase")).to include(experiment1, experiment2) end end end diff --git a/spec/persistence/cookie_adapter_spec.rb b/spec/persistence/cookie_adapter_spec.rb index c31ec838..e53dd88f 100644 --- a/spec/persistence/cookie_adapter_spec.rb +++ b/spec/persistence/cookie_adapter_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -require 'rack/test' +require "rack/test" describe Split::Persistence::CookieAdapter do subject { described_class.new(context) } @@ -57,7 +57,7 @@ end it "ensure other added cookies are not overriden" do - context.response.set_cookie 'dummy', 'wow' + context.response.set_cookie "dummy", "wow" subject["foo"] = "FOO" expect(context.response.headers["Set-Cookie"]).to include("dummy=wow") expect(context.response.headers["Set-Cookie"]).to include("split=") @@ -78,7 +78,7 @@ controller.send(:"request=", ActionDispatch::Request.new({})) end - response = ActionDispatch::Response.new(200, {}, '').tap do |res| + response = ActionDispatch::Response.new(200, {}, "").tap do |res| res.request = controller.request end @@ -101,7 +101,7 @@ expect(subject["foo"]).to eq("FOO") expect(subject["bar"]).to eq("BAR") cookie_jar = context.request.env["action_dispatch.cookies"] - expect(cookie_jar['split']).to eq("{\"foo\":\"FOO\",\"bar\":\"BAR\"}") + expect(cookie_jar["split"]).to eq('{"foo":"FOO","bar":"BAR"}') end end end diff --git a/spec/persistence/dual_adapter_spec.rb b/spec/persistence/dual_adapter_spec.rb index a1ed6461..a3621228 100644 --- a/spec/persistence/dual_adapter_spec.rb +++ b/spec/persistence/dual_adapter_spec.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" describe Split::Persistence::DualAdapter do - let(:context) { 'some context' } + let(:context) { "some context" } let(:logged_in_adapter_instance) { double } let(:logged_in_adapter) do @@ -14,8 +14,8 @@ Class.new.tap { |c| allow(c).to receive(:new) { logged_out_adapter_instance } } end - context 'when fallback_to_logged_out_adapter is false' do - context 'when logged in' do + context "when fallback_to_logged_out_adapter is false" do + context "when logged in" do subject do described_class.with_config( logged_in: lambda { |context| true }, @@ -25,32 +25,32 @@ ).new(context) end - it '#[]=' do - expect(logged_in_adapter_instance).to receive(:[]=).with('my_key', 'my_value') + it "#[]=" do + expect(logged_in_adapter_instance).to receive(:[]=).with("my_key", "my_value") expect_any_instance_of(logged_out_adapter).not_to receive(:[]=) - subject['my_key'] = 'my_value' + subject["my_key"] = "my_value" end - it '#[]' do - expect(logged_in_adapter_instance).to receive(:[]).with('my_key') { 'my_value' } + it "#[]" do + expect(logged_in_adapter_instance).to receive(:[]).with("my_key") { "my_value" } expect_any_instance_of(logged_out_adapter).not_to receive(:[]) - expect(subject['my_key']).to eq('my_value') + expect(subject["my_key"]).to eq("my_value") end - it '#delete' do - expect(logged_in_adapter_instance).to receive(:delete).with('my_key') { 'my_value' } + it "#delete" do + expect(logged_in_adapter_instance).to receive(:delete).with("my_key") { "my_value" } expect_any_instance_of(logged_out_adapter).not_to receive(:delete) - expect(subject.delete('my_key')).to eq('my_value') + expect(subject.delete("my_key")).to eq("my_value") end - it '#keys' do - expect(logged_in_adapter_instance).to receive(:keys) { ['my_value'] } + it "#keys" do + expect(logged_in_adapter_instance).to receive(:keys) { ["my_value"] } expect_any_instance_of(logged_out_adapter).not_to receive(:keys) - expect(subject.keys).to eq(['my_value']) + expect(subject.keys).to eq(["my_value"]) end end - context 'when logged out' do + context "when logged out" do subject do described_class.with_config( logged_in: lambda { |context| false }, @@ -60,34 +60,34 @@ ).new(context) end - it '#[]=' do + it "#[]=" do expect_any_instance_of(logged_in_adapter).not_to receive(:[]=) - expect(logged_out_adapter_instance).to receive(:[]=).with('my_key', 'my_value') - subject['my_key'] = 'my_value' + expect(logged_out_adapter_instance).to receive(:[]=).with("my_key", "my_value") + subject["my_key"] = "my_value" end - it '#[]' do + it "#[]" do expect_any_instance_of(logged_in_adapter).not_to receive(:[]) - expect(logged_out_adapter_instance).to receive(:[]).with('my_key') { 'my_value' } - expect(subject['my_key']).to eq('my_value') + expect(logged_out_adapter_instance).to receive(:[]).with("my_key") { "my_value" } + expect(subject["my_key"]).to eq("my_value") end - it '#delete' do + it "#delete" do expect_any_instance_of(logged_in_adapter).not_to receive(:delete) - expect(logged_out_adapter_instance).to receive(:delete).with('my_key') { 'my_value' } - expect(subject.delete('my_key')).to eq('my_value') + expect(logged_out_adapter_instance).to receive(:delete).with("my_key") { "my_value" } + expect(subject.delete("my_key")).to eq("my_value") end - it '#keys' do + it "#keys" do expect_any_instance_of(logged_in_adapter).not_to receive(:keys) - expect(logged_out_adapter_instance).to receive(:keys) { ['my_value', 'my_value2'] } - expect(subject.keys).to eq(['my_value', 'my_value2']) + expect(logged_out_adapter_instance).to receive(:keys) { ["my_value", "my_value2"] } + expect(subject.keys).to eq(["my_value", "my_value2"]) end end end - context 'when fallback_to_logged_out_adapter is true' do - context 'when logged in' do + context "when fallback_to_logged_out_adapter is true" do + context "when logged in" do subject do described_class.with_config( logged_in: lambda { |context| true }, @@ -97,33 +97,33 @@ ).new(context) end - it '#[]=' do - expect(logged_in_adapter_instance).to receive(:[]=).with('my_key', 'my_value') - expect(logged_out_adapter_instance).to receive(:[]=).with('my_key', 'my_value') - expect(logged_out_adapter_instance).to receive(:[]).with('my_key') { nil } - subject['my_key'] = 'my_value' + it "#[]=" do + expect(logged_in_adapter_instance).to receive(:[]=).with("my_key", "my_value") + expect(logged_out_adapter_instance).to receive(:[]=).with("my_key", "my_value") + expect(logged_out_adapter_instance).to receive(:[]).with("my_key") { nil } + subject["my_key"] = "my_value" end - it '#[]' do - expect(logged_in_adapter_instance).to receive(:[]).with('my_key') { 'my_value' } + it "#[]" do + expect(logged_in_adapter_instance).to receive(:[]).with("my_key") { "my_value" } expect_any_instance_of(logged_out_adapter).not_to receive(:[]) - expect(subject['my_key']).to eq('my_value') + expect(subject["my_key"]).to eq("my_value") end - it '#delete' do - expect(logged_in_adapter_instance).to receive(:delete).with('my_key') { 'my_value' } - expect(logged_out_adapter_instance).to receive(:delete).with('my_key') { 'my_value' } - expect(subject.delete('my_key')).to eq('my_value') + it "#delete" do + expect(logged_in_adapter_instance).to receive(:delete).with("my_key") { "my_value" } + expect(logged_out_adapter_instance).to receive(:delete).with("my_key") { "my_value" } + expect(subject.delete("my_key")).to eq("my_value") end - it '#keys' do - expect(logged_in_adapter_instance).to receive(:keys) { ['my_value'] } - expect(logged_out_adapter_instance).to receive(:keys) { ['my_value', 'my_value2'] } - expect(subject.keys).to eq(['my_value', 'my_value2']) + it "#keys" do + expect(logged_in_adapter_instance).to receive(:keys) { ["my_value"] } + expect(logged_out_adapter_instance).to receive(:keys) { ["my_value", "my_value2"] } + expect(subject.keys).to eq(["my_value", "my_value2"]) end end - context 'when logged out' do + context "when logged out" do subject do described_class.with_config( logged_in: lambda { |context| false }, @@ -133,38 +133,38 @@ ).new(context) end - it '#[]=' do + it "#[]=" do expect_any_instance_of(logged_in_adapter).not_to receive(:[]=) - expect(logged_out_adapter_instance).to receive(:[]=).with('my_key', 'my_value') - expect(logged_out_adapter_instance).to receive(:[]).with('my_key') { nil } - subject['my_key'] = 'my_value' + expect(logged_out_adapter_instance).to receive(:[]=).with("my_key", "my_value") + expect(logged_out_adapter_instance).to receive(:[]).with("my_key") { nil } + subject["my_key"] = "my_value" end - it '#[]' do + it "#[]" do expect_any_instance_of(logged_in_adapter).not_to receive(:[]) - expect(logged_out_adapter_instance).to receive(:[]).with('my_key') { 'my_value' } - expect(subject['my_key']).to eq('my_value') + expect(logged_out_adapter_instance).to receive(:[]).with("my_key") { "my_value" } + expect(subject["my_key"]).to eq("my_value") end - it '#delete' do - expect(logged_in_adapter_instance).to receive(:delete).with('my_key') { 'my_value' } - expect(logged_out_adapter_instance).to receive(:delete).with('my_key') { 'my_value' } - expect(subject.delete('my_key')).to eq('my_value') + it "#delete" do + expect(logged_in_adapter_instance).to receive(:delete).with("my_key") { "my_value" } + expect(logged_out_adapter_instance).to receive(:delete).with("my_key") { "my_value" } + expect(subject.delete("my_key")).to eq("my_value") end - it '#keys' do - expect(logged_in_adapter_instance).to receive(:keys) { ['my_value'] } - expect(logged_out_adapter_instance).to receive(:keys) { ['my_value', 'my_value2'] } - expect(subject.keys).to eq(['my_value', 'my_value2']) + it "#keys" do + expect(logged_in_adapter_instance).to receive(:keys) { ["my_value"] } + expect(logged_out_adapter_instance).to receive(:keys) { ["my_value", "my_value2"] } + expect(subject.keys).to eq(["my_value", "my_value2"]) end end end - describe 'when errors in config' do + describe "when errors in config" do before { described_class.config.clear } let(:some_proc) { -> { } } - it 'when no logged in adapter' do + it "when no logged in adapter" do expect { described_class.with_config( logged_in: some_proc, @@ -173,7 +173,7 @@ }.to raise_error(StandardError, /:logged_in_adapter/) end - it 'when no logged out adapter' do + it "when no logged out adapter" do expect { described_class.with_config( logged_in: some_proc, @@ -182,7 +182,7 @@ }.to raise_error(StandardError, /:logged_out_adapter/) end - it 'when no logged in detector' do + it "when no logged in detector" do expect { described_class.with_config( logged_in_adapter: logged_in_adapter, diff --git a/spec/persistence/redis_adapter_spec.rb b/spec/persistence/redis_adapter_spec.rb index 85d376d0..1a1eac11 100644 --- a/spec/persistence/redis_adapter_spec.rb +++ b/spec/persistence/redis_adapter_spec.rb @@ -3,65 +3,65 @@ require "spec_helper" describe Split::Persistence::RedisAdapter do - let(:context) { double(lookup: 'blah') } + let(:context) { double(lookup: "blah") } subject { Split::Persistence::RedisAdapter.new(context) } - describe '#redis_key' do + describe "#redis_key" do before { Split::Persistence::RedisAdapter.reset_config! } - context 'default' do - it 'should raise error with prompt to set lookup_by' do + context "default" do + it "should raise error with prompt to set lookup_by" do expect { Split::Persistence::RedisAdapter.new(context) }.to raise_error(RuntimeError) end end - context 'config with key' do + context "config with key" do before { Split::Persistence::RedisAdapter.reset_config! } - subject { Split::Persistence::RedisAdapter.new(context, 'manual') } + subject { Split::Persistence::RedisAdapter.new(context, "manual") } it 'should be "persistence:manual"' do - expect(subject.redis_key).to eq('persistence:manual') + expect(subject.redis_key).to eq("persistence:manual") end end context 'config with lookup_by = proc { "block" }' do - before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc { 'block' }) } + before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc { "block" }) } it 'should be "persistence:block"' do - expect(subject.redis_key).to eq('persistence:block') + expect(subject.redis_key).to eq("persistence:block") end end - context 'config with lookup_by = proc { |context| context.test }' do - before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc { 'block' }) } - let(:context) { double(test: 'block') } + context "config with lookup_by = proc { |context| context.test }" do + before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc { "block" }) } + let(:context) { double(test: "block") } it 'should be "persistence:block"' do - expect(subject.redis_key).to eq('persistence:block') + expect(subject.redis_key).to eq("persistence:block") end end context 'config with lookup_by = "method_name"' do - before { Split::Persistence::RedisAdapter.with_config(lookup_by: 'method_name') } - let(:context) { double(method_name: 'val') } + before { Split::Persistence::RedisAdapter.with_config(lookup_by: "method_name") } + let(:context) { double(method_name: "val") } it 'should be "persistence:bar"' do - expect(subject.redis_key).to eq('persistence:val') + expect(subject.redis_key).to eq("persistence:val") end end - context 'config with namespace and lookup_by' do - before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc { 'frag' }, namespace: 'namer') } + context "config with namespace and lookup_by" do + before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc { "frag" }, namespace: "namer") } it 'should be "namer"' do - expect(subject.redis_key).to eq('namer:frag') + expect(subject.redis_key).to eq("namer:frag") end end end - describe '#find' do - before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc { 'frag' }, namespace: 'a_namespace') } + describe "#find" do + before { Split::Persistence::RedisAdapter.with_config(lookup_by: proc { "frag" }, namespace: "a_namespace") } it "should create and user from a given key" do adapter = Split::Persistence::RedisAdapter.find(2) @@ -69,8 +69,8 @@ end end - context 'functional tests' do - before { Split::Persistence::RedisAdapter.with_config(lookup_by: 'lookup') } + context "functional tests" do + before { Split::Persistence::RedisAdapter.with_config(lookup_by: "lookup") } describe "#[] and #[]=" do it "should set and return the value for given key" do diff --git a/spec/redis_interface_spec.rb b/spec/redis_interface_spec.rb index 2d2bd362..578578f8 100644 --- a/spec/redis_interface_spec.rb +++ b/spec/redis_interface_spec.rb @@ -1,44 +1,44 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" describe Split::RedisInterface do - let(:list_name) { 'list_name' } - let(:set_name) { 'set_name' } + let(:list_name) { "list_name" } + let(:set_name) { "set_name" } let(:interface) { described_class.new } - describe '#persist_list' do + describe "#persist_list" do subject(:persist_list) do interface.persist_list(list_name, %w(a b c d)) end specify do expect(persist_list).to eq %w(a b c d) - expect(Split.redis.lindex(list_name, 0)).to eq 'a' - expect(Split.redis.lindex(list_name, 1)).to eq 'b' - expect(Split.redis.lindex(list_name, 2)).to eq 'c' - expect(Split.redis.lindex(list_name, 3)).to eq 'd' + expect(Split.redis.lindex(list_name, 0)).to eq "a" + expect(Split.redis.lindex(list_name, 1)).to eq "b" + expect(Split.redis.lindex(list_name, 2)).to eq "c" + expect(Split.redis.lindex(list_name, 3)).to eq "d" expect(Split.redis.llen(list_name)).to eq 4 end - context 'list is overwritten but not deleted' do + context "list is overwritten but not deleted" do specify do expect(persist_list).to eq %w(a b c d) - interface.persist_list(list_name, ['z']) - expect(Split.redis.lindex(list_name, 0)).to eq 'z' + interface.persist_list(list_name, ["z"]) + expect(Split.redis.lindex(list_name, 0)).to eq "z" expect(Split.redis.llen(list_name)).to eq 1 end end end - describe '#add_to_set' do + describe "#add_to_set" do subject(:add_to_set) do - interface.add_to_set(set_name, 'something') + interface.add_to_set(set_name, "something") end specify do add_to_set - expect(Split.redis.sismember(set_name, 'something')).to be true + expect(Split.redis.sismember(set_name, "something")).to be true end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index bb47c76c..3e9859c9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,18 +1,18 @@ # frozen_string_literal: true -ENV['RACK_ENV'] = "test" +ENV["RACK_ENV"] = "test" -require 'rubygems' -require 'bundler/setup' +require "rubygems" +require "bundler/setup" -require 'simplecov' +require "simplecov" SimpleCov.start -require 'split' -require 'ostruct' -require 'yaml' +require "split" +require "ostruct" +require "yaml" -Dir['./spec/support/*.rb'].each { |f| require f } +Dir["./spec/support/*.rb"].each { |f| require f } module GlobalSharedContext extend RSpec::SharedContext @@ -30,7 +30,7 @@ module GlobalSharedContext end RSpec.configure do |config| - config.order = 'random' + config.order = "random" config.include GlobalSharedContext config.raise_errors_for_deprecations! end @@ -43,11 +43,11 @@ def params @params ||= {} end -def request(ua = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; de-de) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27') +def request(ua = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; de-de) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27") @request ||= begin r = OpenStruct.new r.user_agent = ua - r.ip = '192.168.1.1' + r.ip = "192.168.1.1" r end end diff --git a/spec/split_spec.rb b/spec/split_spec.rb index 2dbd2502..a1361889 100644 --- a/spec/split_spec.rb +++ b/spec/split_spec.rb @@ -1,18 +1,18 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe Split do around(:each) do |ex| - old_env, old_redis = [ENV.delete('REDIS_URL'), Split.redis] + old_env, old_redis = [ENV.delete("REDIS_URL"), Split.redis] ex.run - ENV['REDIS_URL'] = old_env + ENV["REDIS_URL"] = old_env Split.redis = old_redis end - describe '#redis=' do - it 'accepts a url string' do - Split.redis = 'redis://localhost:6379' + describe "#redis=" do + it "accepts a url string" do + Split.redis = "redis://localhost:6379" expect(Split.redis).to be_a(Redis) client = Split.redis.connection @@ -20,8 +20,8 @@ expect(client[:port]).to eq(6379) end - it 'accepts an options hash' do - Split.redis = { host: 'localhost', port: 6379, db: 12 } + it "accepts an options hash" do + Split.redis = { host: "localhost", port: 6379, db: 12 } expect(Split.redis).to be_a(Redis) client = Split.redis.connection @@ -30,13 +30,13 @@ expect(client[:db]).to eq(12) end - it 'accepts a valid Redis instance' do + it "accepts a valid Redis instance" do other_redis = Redis.new(url: "redis://localhost:6379") Split.redis = other_redis expect(Split.redis).to eq(other_redis) end - it 'raises an ArgumentError when server cannot be determined' do + it "raises an ArgumentError when server cannot be determined" do expect { Split.redis = Object.new }.to raise_error(ArgumentError) end end diff --git a/spec/trial_spec.rb b/spec/trial_spec.rb index 3157ed21..24e55017 100644 --- a/spec/trial_spec.rb +++ b/spec/trial_spec.rb @@ -1,18 +1,18 @@ # frozen_string_literal: true -require 'spec_helper' -require 'split/trial' +require "spec_helper" +require "split/trial" describe Split::Trial do let(:user) { mock_user } - let(:alternatives) { ['basket', 'cart'] } + let(:alternatives) { ["basket", "cart"] } let(:experiment) do - Split::Experiment.new('basket_text', alternatives: alternatives).save + Split::Experiment.new("basket_text", alternatives: alternatives).save end it "should be initializeable" do - experiment = double('experiment') - alternative = double('alternative', kind_of?: Split::Alternative) + experiment = double("experiment") + alternative = double("alternative", kind_of?: Split::Alternative) trial = Split::Trial.new(experiment: experiment, alternative: alternative) expect(trial.experiment).to eq(experiment) expect(trial.alternative).to eq(alternative) @@ -20,35 +20,35 @@ describe "alternative" do it "should use the alternative if specified" do - alternative = double('alternative', kind_of?: Split::Alternative) - trial = Split::Trial.new(experiment: double('experiment'), + alternative = double("alternative", kind_of?: Split::Alternative) + trial = Split::Trial.new(experiment: double("experiment"), alternative: alternative, user: user) expect(trial).not_to receive(:choose) expect(trial.alternative).to eq(alternative) end it "should load the alternative when the alternative name is set" do - experiment = Split::Experiment.new('basket_text', alternatives: ['basket', 'cart']) + experiment = Split::Experiment.new("basket_text", alternatives: ["basket", "cart"]) experiment.save - trial = Split::Trial.new(experiment: experiment, alternative: 'basket') - expect(trial.alternative.name).to eq('basket') + trial = Split::Trial.new(experiment: experiment, alternative: "basket") + expect(trial.alternative.name).to eq("basket") end end describe "metadata" do let(:metadata) { Hash[alternatives.map { |k| [k, "Metadata for #{k}"] }] } let(:experiment) do - Split::Experiment.new('basket_text', alternatives: alternatives, metadata: metadata).save + Split::Experiment.new("basket_text", alternatives: alternatives, metadata: metadata).save end - it 'has metadata on each trial' do - trial = Split::Trial.new(experiment: experiment, user: user, metadata: metadata['cart'], - override: 'cart') - expect(trial.metadata).to eq(metadata['cart']) + it "has metadata on each trial" do + trial = Split::Trial.new(experiment: experiment, user: user, metadata: metadata["cart"], + override: "cart") + expect(trial.metadata).to eq(metadata["cart"]) end - it 'has metadata on each trial from the experiment' do + it "has metadata on each trial from the experiment" do trial = Split::Trial.new(experiment: experiment, user: user) trial.choose! expect(trial.metadata).to eq(metadata[trial.alternative.name]) @@ -57,24 +57,24 @@ end describe "#choose!" do - let(:context) { double(on_trial_callback: 'test callback') } + let(:context) { double(on_trial_callback: "test callback") } let(:trial) do Split::Trial.new(user: user, experiment: experiment) end - shared_examples_for 'a trial with callbacks' do - it 'does not run if on_trial callback is not respondable' do + shared_examples_for "a trial with callbacks" do + it "does not run if on_trial callback is not respondable" do Split.configuration.on_trial = :foo allow(context).to receive(:respond_to?).with(:foo, true).and_return false expect(context).to_not receive(:foo) trial.choose! context end - it 'runs on_trial callback' do + it "runs on_trial callback" do Split.configuration.on_trial = :on_trial_callback expect(context).to receive(:on_trial_callback) trial.choose! context end - it 'does not run nil on_trial callback' do + it "does not run nil on_trial callback" do Split.configuration.on_trial = nil expect(context).not_to receive(:on_trial_callback) trial.choose! context @@ -89,12 +89,12 @@ def expect_alternative(trial, alternative_name) end context "when override is present" do - let(:override) { 'cart' } + let(:override) { "cart" } let(:trial) do Split::Trial.new(user: user, experiment: experiment, override: override) end - it_behaves_like 'a trial with callbacks' + it_behaves_like "a trial with callbacks" it "picks the override" do expect(experiment).to_not receive(:next_alternative) @@ -103,7 +103,7 @@ def expect_alternative(trial, alternative_name) context "when alternative doesn't exist" do let(:override) { nil } - it 'falls back on next_alternative' do + it "falls back on next_alternative" do expect(experiment).to receive(:next_alternative).and_call_original expect_alternative(trial, alternatives) end @@ -121,7 +121,7 @@ def expect_alternative(trial, alternative_name) expect(context).not_to receive(:on_trial_callback) - expect_alternative(trial, 'basket') + expect_alternative(trial, "basket") Split.configuration.on_trial = nil end end @@ -133,7 +133,7 @@ def expect_alternative(trial, alternative_name) expect(experiment).to_not receive(:next_alternative) expect(context).not_to receive(:on_trial_callback) - expect_alternative(trial, 'basket') + expect_alternative(trial, "basket") Split.configuration.enabled = true Split.configuration.on_trial = nil @@ -145,13 +145,13 @@ def expect_alternative(trial, alternative_name) Split::Trial.new(user: user, experiment: experiment) end - it_behaves_like 'a trial with callbacks' + it_behaves_like "a trial with callbacks" it "picks the winner" do - experiment.winner = 'cart' + experiment.winner = "cart" expect(experiment).to_not receive(:next_alternative) - expect_alternative(trial, 'cart') + expect_alternative(trial, "cart") end end @@ -160,27 +160,27 @@ def expect_alternative(trial, alternative_name) Split::Trial.new(user: user, experiment: experiment, exclude: true) end - it_behaves_like 'a trial with callbacks' + it_behaves_like "a trial with callbacks" it "picks the control" do expect(experiment).to_not receive(:next_alternative) - expect_alternative(trial, 'basket') + expect_alternative(trial, "basket") end end context "when user is already participating" do - it_behaves_like 'a trial with callbacks' + it_behaves_like "a trial with callbacks" it "picks the same alternative" do - user[experiment.key] = 'basket' + user[experiment.key] = "basket" expect(experiment).to_not receive(:next_alternative) - expect_alternative(trial, 'basket') + expect_alternative(trial, "basket") end context "when alternative is not found" do it "falls back on next_alternative" do - user[experiment.key] = 'notfound' + user[experiment.key] = "notfound" expect(experiment).to receive(:next_alternative).and_call_original expect_alternative(trial, alternatives) end @@ -214,7 +214,7 @@ def expect_alternative(trial, alternative_name) expect(experiment).to_not receive(:next_alternative) expect(context).not_to receive(:on_trial_callback) - expect_alternative(trial, 'basket') + expect_alternative(trial, "basket") Split.configuration.enabled = true Split.configuration.on_trial = nil @@ -230,9 +230,9 @@ def expect_alternative(trial, alternative_name) end describe "#complete!" do - context 'when there are no goals' do + context "when there are no goals" do let(:trial) { Split::Trial.new(user: user, experiment: experiment) } - it 'should complete the trial' do + it "should complete the trial" do trial.choose! old_completed_count = trial.alternative.completed_count trial.complete! @@ -269,7 +269,7 @@ def expect_alternative(trial, alternative_name) context "when override is present" do it "stores when store_override is true" do - trial = Split::Trial.new(user: user, experiment: experiment, override: 'basket') + trial = Split::Trial.new(user: user, experiment: experiment, override: "basket") Split.configuration.store_override = true expect(user).to receive("[]=") @@ -278,7 +278,7 @@ def expect_alternative(trial, alternative_name) end it "does not store when store_override is false" do - trial = Split::Trial.new(user: user, experiment: experiment, override: 'basket') + trial = Split::Trial.new(user: user, experiment: experiment, override: "basket") expect(user).to_not receive("[]=") trial.choose! @@ -311,13 +311,13 @@ def expect_alternative(trial, alternative_name) end end - context 'when experiment has winner' do + context "when experiment has winner" do let(:trial) do - experiment.winner = 'cart' + experiment.winner = "cart" Split::Trial.new(user: user, experiment: experiment) end - it 'does not store' do + it "does not store" do expect(user).to_not receive("[]=") trial.choose! end diff --git a/spec/user_spec.rb b/spec/user_spec.rb index f121ffb6..acb51e55 100644 --- a/spec/user_spec.rb +++ b/spec/user_spec.rb @@ -1,73 +1,73 @@ # frozen_string_literal: true -require 'spec_helper' -require 'split/experiment_catalog' -require 'split/experiment' -require 'split/user' +require "spec_helper" +require "split/experiment_catalog" +require "split/experiment" +require "split/user" describe Split::User do - let(:user_keys) { { 'link_color' => 'blue' } } + let(:user_keys) { { "link_color" => "blue" } } let(:context) { double(session: { split: user_keys }) } - let(:experiment) { Split::Experiment.new('link_color') } + let(:experiment) { Split::Experiment.new("link_color") } before(:each) do @subject = described_class.new(context) end - it 'delegates methods correctly' do - expect(@subject['link_color']).to eq(@subject.user['link_color']) + it "delegates methods correctly" do + expect(@subject["link_color"]).to eq(@subject.user["link_color"]) end - context '#cleanup_old_versions!' do + context "#cleanup_old_versions!" do let(:experiment_version) { "#{experiment.name}:1" } let(:second_experiment_version) { "#{experiment.name}_another:1" } let(:third_experiment_version) { "variation_of_#{experiment.name}:1" } let(:user_keys) do { - experiment_version => 'blue', - second_experiment_version => 'red', - third_experiment_version => 'yellow' + experiment_version => "blue", + second_experiment_version => "red", + third_experiment_version => "yellow" } end before(:each) { @subject.cleanup_old_versions!(experiment) } - it 'removes key if old experiment is found' do + it "removes key if old experiment is found" do expect(@subject.keys).not_to include(experiment_version) end - it 'does not remove other keys' do + it "does not remove other keys" do expect(@subject.keys).to include(second_experiment_version, third_experiment_version) end end - context '#cleanup_old_experiments!' do - it 'removes key if experiment is not found' do + context "#cleanup_old_experiments!" do + it "removes key if experiment is not found" do @subject.cleanup_old_experiments! expect(@subject.keys).to be_empty end - it 'removes key if experiment has a winner' do - allow(Split::ExperimentCatalog).to receive(:find).with('link_color').and_return(experiment) + it "removes key if experiment has a winner" do + allow(Split::ExperimentCatalog).to receive(:find).with("link_color").and_return(experiment) allow(experiment).to receive(:start_time).and_return(Date.today) allow(experiment).to receive(:has_winner?).and_return(true) @subject.cleanup_old_experiments! expect(@subject.keys).to be_empty end - it 'removes key if experiment has not started yet' do - allow(Split::ExperimentCatalog).to receive(:find).with('link_color').and_return(experiment) + it "removes key if experiment has not started yet" do + allow(Split::ExperimentCatalog).to receive(:find).with("link_color").and_return(experiment) allow(experiment).to receive(:has_winner?).and_return(false) @subject.cleanup_old_experiments! expect(@subject.keys).to be_empty end - context 'with finished key' do - let(:user_keys) { { 'link_color' => 'blue', 'link_color:finished' => true } } + context "with finished key" do + let(:user_keys) { { "link_color" => "blue", "link_color:finished" => true } } - it 'does not remove finished key for experiment without a winner' do - allow(Split::ExperimentCatalog).to receive(:find).with('link_color').and_return(experiment) - allow(Split::ExperimentCatalog).to receive(:find).with('link_color:finished').and_return(nil) + it "does not remove finished key for experiment without a winner" do + allow(Split::ExperimentCatalog).to receive(:find).with("link_color").and_return(experiment) + allow(Split::ExperimentCatalog).to receive(:find).with("link_color:finished").and_return(nil) allow(experiment).to receive(:start_time).and_return(Date.today) allow(experiment).to receive(:has_winner?).and_return(false) @subject.cleanup_old_experiments! @@ -76,29 +76,29 @@ end end - context 'when already cleaned up' do + context "when already cleaned up" do before do @subject.cleanup_old_experiments! end - it 'does not clean up again' do + it "does not clean up again" do expect(@subject).to_not receive(:keys_without_finished) @subject.cleanup_old_experiments! end end end - context 'allows user to be loaded from adapter' do - it 'loads user from adapter (RedisAdapter)' do + context "allows user to be loaded from adapter" do + it "loads user from adapter (RedisAdapter)" do user = Split::Persistence::RedisAdapter.new(nil, 112233) - user['foo'] = 'bar' + user["foo"] = "bar" ab_user = Split::User.find(112233, :redis) - expect(ab_user['foo']).to eql('bar') + expect(ab_user["foo"]).to eql("bar") end - it 'returns nil if adapter does not implement a finder method' do + it "returns nil if adapter does not implement a finder method" do ab_user = Split::User.find(112233, :dual_adapter) expect(ab_user).to be_nil end diff --git a/split.gemspec b/split.gemspec index 626d5586..4c56739c 100644 --- a/split.gemspec +++ b/split.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.version = Split::VERSION s.platform = Gem::Platform::RUBY s.authors = ["Andrew Nesbitt"] - s.licenses = ['MIT'] + s.licenses = ["MIT"] s.email = ["andrewnez@gmail.com"] s.homepage = "https://github.com/splitrb/split" s.summary = "Rack based split testing framework" @@ -23,22 +23,22 @@ Gem::Specification.new do |s| "mailing_list_uri" => "https://groups.google.com/d/forum/split-ruby" } - s.required_ruby_version = '>= 2.5.0' - s.required_rubygems_version = '>= 2.0.0' + s.required_ruby_version = ">= 2.5.0" + s.required_rubygems_version = ">= 2.0.0" s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.require_paths = ["lib"] - s.add_dependency 'redis', '>= 4.2' - s.add_dependency 'sinatra', '>= 1.2.6' - s.add_dependency 'rubystats', '>= 0.3.0' + s.add_dependency "redis", ">= 4.2" + s.add_dependency "sinatra", ">= 1.2.6" + s.add_dependency "rubystats", ">= 0.3.0" - s.add_development_dependency 'bundler', '>= 1.17' - s.add_development_dependency 'simplecov', '~> 0.15' - s.add_development_dependency 'rack-test', '~> 1.1' - s.add_development_dependency 'rake', '~> 13' - s.add_development_dependency 'rspec', '~> 3.7' - s.add_development_dependency 'pry', '~> 0.10' - s.add_development_dependency 'rails', '>= 5.0' + s.add_development_dependency "bundler", ">= 1.17" + s.add_development_dependency "simplecov", "~> 0.15" + s.add_development_dependency "rack-test", "~> 1.1" + s.add_development_dependency "rake", "~> 13" + s.add_development_dependency "rspec", "~> 3.7" + s.add_development_dependency "pry", "~> 0.10" + s.add_development_dependency "rails", ">= 5.0" end From 6f9f8c734f3f01e53dbc8ec536cf481f0c8ab4b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sat, 2 Apr 2022 15:42:23 -0300 Subject: [PATCH 108/136] Update actions/checkout to v3 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ef56f7b..f21dde75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: --health-retries 5 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: From 64e43e416b8bb92719d371385d1aef4cf3bc75f5 Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Fri, 22 Apr 2022 09:35:26 -0400 Subject: [PATCH 109/136] Handle when Rails is partially loaded as a Gem ...and not an application ``` NoMethodError: undefined method `env' for Rails:Module /bundle/ruby/3.0.0/gems/split-3.4.1/lib/split/dashboard.rb:28:in `block in ' ``` --- lib/split/dashboard.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/split/dashboard.rb b/lib/split/dashboard.rb index fcb53c2b..2e497cf9 100755 --- a/lib/split/dashboard.rb +++ b/lib/split/dashboard.rb @@ -26,7 +26,7 @@ class Dashboard < Sinatra::Base @metrics = Split::Metric.all # Display Rails Environment mode (or Rack version if not using Rails) - if Object.const_defined?("Rails") + if Object.const_defined?("Rails") && Rails.respond_to?(:env) @current_env = Rails.env.titlecase else @current_env = "Rack: #{Rack.version}" From a60223886245b7665687a28d12d1d6e25078231f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jun 2022 19:18:41 +0000 Subject: [PATCH 110/136] Update rack-test requirement from ~> 1.1 to ~> 2.0 Updates the requirements on [rack-test](https://github.com/rack/rack-test) to permit the latest version. - [Release notes](https://github.com/rack/rack-test/releases) - [Changelog](https://github.com/rack/rack-test/blob/main/History.md) - [Commits](https://github.com/rack/rack-test/compare/v1.1.0...v2.0.0) --- updated-dependencies: - dependency-name: rack-test dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- split.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/split.gemspec b/split.gemspec index 4c56739c..6e9d1d49 100644 --- a/split.gemspec +++ b/split.gemspec @@ -36,7 +36,7 @@ Gem::Specification.new do |s| s.add_development_dependency "bundler", ">= 1.17" s.add_development_dependency "simplecov", "~> 0.15" - s.add_development_dependency "rack-test", "~> 1.1" + s.add_development_dependency "rack-test", "~> 2.0" s.add_development_dependency "rake", "~> 13" s.add_development_dependency "rspec", "~> 3.7" s.add_development_dependency "pry", "~> 0.10" From 872b5111789f792b09f8a9f1247bc174da1eb0ab Mon Sep 17 00:00:00 2001 From: Keita Urashima Date: Mon, 18 Jul 2022 02:09:35 +0900 Subject: [PATCH 111/136] Fix default branch name and gem metadata indentation --- CONTRIBUTING.md | 2 +- split.gemspec | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5bc214d3..a65a5d1f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,7 @@ Want to contribute to Split? That's great! Here are a couple of guidelines that ## Setup instructions -You can find in-depth instructions to install in our [README](https://github.com/splitrb/split/blob/master/README.md). +You can find in-depth instructions to install in our [README](https://github.com/splitrb/split/blob/main/README.md). *Note*: Split requires Ruby 1.9.2 or higher. diff --git a/split.gemspec b/split.gemspec index 6e9d1d49..1b08f9a5 100644 --- a/split.gemspec +++ b/split.gemspec @@ -15,13 +15,13 @@ Gem::Specification.new do |s| s.summary = "Rack based split testing framework" s.metadata = { - "homepage_uri" => "https://github.com/splitrb/split", - "changelog_uri" => "https://github.com/splitrb/split/blob/master/CHANGELOG.md", - "source_code_uri" => "https://github.com/splitrb/split", - "bug_tracker_uri" => "https://github.com/splitrb/split/issues", - "wiki_uri" => "https://github.com/splitrb/split/wiki", - "mailing_list_uri" => "https://groups.google.com/d/forum/split-ruby" - } + "homepage_uri" => "https://github.com/splitrb/split", + "changelog_uri" => "https://github.com/splitrb/split/blob/main/CHANGELOG.md", + "source_code_uri" => "https://github.com/splitrb/split", + "bug_tracker_uri" => "https://github.com/splitrb/split/issues", + "wiki_uri" => "https://github.com/splitrb/split/wiki", + "mailing_list_uri" => "https://groups.google.com/d/forum/split-ruby" + } s.required_ruby_version = ">= 2.5.0" s.required_rubygems_version = ">= 2.0.0" From dd8e87e6264cca0205fe24c178b9d335ab2b5fb9 Mon Sep 17 00:00:00 2001 From: Konrad Date: Mon, 12 Sep 2022 13:18:38 +0200 Subject: [PATCH 112/136] Add support for redis-client, which does not automatically cast types to strings --- lib/split/alternative.rb | 2 +- lib/split/experiment.rb | 6 +++--- spec/experiment_spec.rb | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/split/alternative.rb b/lib/split/alternative.rb index 3d416188..1ba0ebff 100644 --- a/lib/split/alternative.rb +++ b/lib/split/alternative.rb @@ -166,7 +166,7 @@ def validate! end def reset - Split.redis.hmset key, "participant_count", 0, "completed_count", 0, "recorded_info", nil + Split.redis.hmset key, "participant_count", 0, "completed_count", 0, "recorded_info", nil.to_s unless goals.empty? goals.each do |g| field = "completed_count:#{g}" diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index ec78fae1..26c1792f 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -85,7 +85,7 @@ def save persist_experiment_configuration end - redis.hmset(experiment_config_key, :resettable, resettable, + redis.hmset(experiment_config_key, :resettable, resettable.to_s, :algorithm, algorithm.to_s) self end @@ -408,12 +408,12 @@ def cohorting_disabled? def disable_cohorting @cohorting_disabled = true - redis.hset(experiment_config_key, :cohorting, true) + redis.hset(experiment_config_key, :cohorting, true.to_s) end def enable_cohorting @cohorting_disabled = false - redis.hset(experiment_config_key, :cohorting, false) + redis.hset(experiment_config_key, :cohorting, false.to_s) end protected diff --git a/spec/experiment_spec.rb b/spec/experiment_spec.rb index 78e930ca..36175d4d 100644 --- a/spec/experiment_spec.rb +++ b/spec/experiment_spec.rb @@ -68,7 +68,7 @@ def alternative(color) experiment_start_time = Time.parse("Sat Mar 03 14:01:03") expect(Time).to receive(:now).twice.and_return(experiment_start_time) experiment.save - Split.redis.hset(:experiment_start_times, experiment.name, experiment_start_time) + Split.redis.hset(:experiment_start_times, experiment.name, experiment_start_time.to_s) expect(Split::ExperimentCatalog.find("basket_text").start_time).to eq(experiment_start_time) end From 72b9d892316adb825497906470a912b9ed21043f Mon Sep 17 00:00:00 2001 From: Konrad Narewski Date: Mon, 12 Sep 2022 15:56:16 +0200 Subject: [PATCH 113/136] Use empty string directly instead of casting nil MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André Luis Leal Cardoso Junior --- lib/split/alternative.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/split/alternative.rb b/lib/split/alternative.rb index 1ba0ebff..a9a673c9 100644 --- a/lib/split/alternative.rb +++ b/lib/split/alternative.rb @@ -166,7 +166,7 @@ def validate! end def reset - Split.redis.hmset key, "participant_count", 0, "completed_count", 0, "recorded_info", nil.to_s + Split.redis.hmset key, "participant_count", 0, "completed_count", 0, "recorded_info", "" unless goals.empty? goals.each do |g| field = "completed_count:#{g}" From 4e4599f67916f6e48916b1c49bcb51501ee874ec Mon Sep 17 00:00:00 2001 From: Konrad Date: Mon, 12 Sep 2022 14:13:29 +0200 Subject: [PATCH 114/136] Fix spec for parsing invalid JSON The cookie adapter uses string as a key, so should the spec. Also, context.request.cookies is a hash in both cases, it feels that it should be set directly, not using a hash with "value" and "expires" keys. --- spec/persistence/cookie_adapter_spec.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/spec/persistence/cookie_adapter_spec.rb b/spec/persistence/cookie_adapter_spec.rb index e53dd88f..d215422d 100644 --- a/spec/persistence/cookie_adapter_spec.rb +++ b/spec/persistence/cookie_adapter_spec.rb @@ -14,10 +14,8 @@ end it "handles invalid JSON" do - context.request.cookies[:split] = { - value: '{"foo":2,', - expires: Time.now - } + context.request.cookies["split"] = "{\"foo\":2," + expect(subject["my_key"]).to be_nil subject["my_key"] = "my_value" expect(subject["my_key"]).to eq("my_value") From 231412262e25c39f9a5ef6d071a1dc50dd60e29e Mon Sep 17 00:00:00 2001 From: Konrad Date: Mon, 12 Sep 2022 14:50:36 +0200 Subject: [PATCH 115/136] Handle cookies with valid JSON of invalid type --- lib/split/persistence/cookie_adapter.rb | 3 ++- spec/persistence/cookie_adapter_spec.rb | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/split/persistence/cookie_adapter.rb b/lib/split/persistence/cookie_adapter.rb index 2eac7fca..d0f4be15 100644 --- a/lib/split/persistence/cookie_adapter.rb +++ b/lib/split/persistence/cookie_adapter.rb @@ -70,7 +70,8 @@ def delete_cookie_header!(header, key, value) def hash @hash ||= if cookies = @cookies[:split.to_s] begin - JSON.parse(cookies) + parsed = JSON.parse(cookies) + parsed.is_a?(Hash) ? parsed : {} rescue JSON::ParserError {} end diff --git a/spec/persistence/cookie_adapter_spec.rb b/spec/persistence/cookie_adapter_spec.rb index d215422d..182d6229 100644 --- a/spec/persistence/cookie_adapter_spec.rb +++ b/spec/persistence/cookie_adapter_spec.rb @@ -20,6 +20,22 @@ subject["my_key"] = "my_value" expect(subject["my_key"]).to eq("my_value") end + + it "ignores valid JSON of invalid type (integer)" do + context.request.cookies["split"] = "2" + + expect(subject["my_key"]).to be_nil + subject["my_key"] = "my_value" + expect(subject["my_key"]).to eq("my_value") + end + + it "ignores valid JSON of invalid type (array)" do + context.request.cookies["split"] = "[\"foo\", \"bar\"]" + + expect(subject["my_key"]).to be_nil + subject["my_key"] = "my_value" + expect(subject["my_key"]).to eq("my_value") + end end describe "#delete" do From 2f666acfa276663b09ec895d8c9aa4017e83614e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 2 Dec 2022 10:12:39 -0300 Subject: [PATCH 116/136] Update changelog --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4567e44..5652f1a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +# 4.0.2 (December 2nd, 2022) + +Bugfixes: +- Stop crashing on non-hash json (@knarewski, #697) +- Handle when Rails is partially loaded as a Gem (@TSMMark, #687) + +Features: +- Add support for redis-client, which does not automatically cast types to strings (@knarewski, #696) +- Add ability to initialize experiments (@robin-phung, #673) + +Misc: +- Fix default branch name and gem metadata indentation (@ursm, #693) +- Update actions/checkout to v3 (@andrehjr, #683) +- Enforce double quotes (@andrehjr, #682) +- Fix Rubocop Style/* Offenses (@andrehjr, #681) +- Enable rubocop on Github Actions (@andrehjr, #680) +- Fix all Layout issues on the project (@andrehjr, #679) +- Fix Style/HashSyntax offenses (@andrehjr, #678) +- Remove usage of deprecated implicit block expectation from specs (@andrehjr, #677) +- Remove appraisals configuration (@andrehjr, #676) +- Add Ruby 3.1 (@andrehjr, #675) +- Encapsulate Split::Algorithms at our own module to avoid explicit calling rubystats everywhere (@andrehjr, #674) + ## 4.0.1 (December 30th, 2021) Bugfixes: From 5092ef2972be65138ebe8171763c7632c8123983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Fri, 2 Dec 2022 10:21:14 -0300 Subject: [PATCH 117/136] v4.0.2 --- lib/split/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/split/version.rb b/lib/split/version.rb index 037a710e..4e0ec8de 100644 --- a/lib/split/version.rb +++ b/lib/split/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Split - VERSION = "4.0.1" + VERSION = "4.0.2" end From 42cda01e981f8e48f23d3d7b81fda48051c26492 Mon Sep 17 00:00:00 2001 From: Martin Gregoire Date: Fri, 13 Jan 2023 16:58:42 +0100 Subject: [PATCH 118/136] Fix deprecation warning with Redis 4.8.0 Fixes the following deprecation warning when using Redis 4.8.0: ``` Redis#sadd will always return an Integer in Redis 5.0.0. Use Redis#sadd? instead. ``` As the return value of `redis.sadd` is not used, we can simply replace the `.sadd` call with `.sadd?`. see https://github.com/redis/redis-rb/blob/4.x/CHANGELOG.md#480 see #700 --- lib/split/redis_interface.rb | 2 ++ spec/redis_interface_spec.rb | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/split/redis_interface.rb b/lib/split/redis_interface.rb index a0b0f54b..5307428a 100644 --- a/lib/split/redis_interface.rb +++ b/lib/split/redis_interface.rb @@ -20,6 +20,8 @@ def persist_list(list_name, list_values) end def add_to_set(set_name, value) + return redis.sadd?(set_name, value) if redis.respond_to?(:sadd?) + redis.sadd(set_name, value) end diff --git a/spec/redis_interface_spec.rb b/spec/redis_interface_spec.rb index 578578f8..15d3f2e1 100644 --- a/spec/redis_interface_spec.rb +++ b/spec/redis_interface_spec.rb @@ -40,5 +40,15 @@ add_to_set expect(Split.redis.sismember(set_name, "something")).to be true end + + context "when a Redis version is used that supports the 'sadd?' method" do + before { expect(Split.redis).to receive(:respond_to?).with(:sadd?).and_return(true) } + + it "will use this method instead of 'sadd'" do + expect(Split.redis).to receive(:sadd?).with(set_name, "something") + expect(Split.redis).not_to receive(:sadd).with(set_name, "something") + add_to_set + end + end end end From add7d5cf3d049e0db33fc59d26a814eb24d8922c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sat, 14 Jan 2023 12:21:43 -0300 Subject: [PATCH 119/136] Add ruby 3.2 to ci --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f21dde75..dbe8cad0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,8 @@ jobs: - gemfile: 7.0.gemfile ruby: '3.1' + - gemfile: 7.0.gemfile + ruby: '3.2' runs-on: ubuntu-latest From ec089adaa0ce0d3f6bad5b42c22ea7ff5d036459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 22 Jan 2023 18:19:56 -0300 Subject: [PATCH 120/136] Do not try to calculate winning probability if participant/completed can lead to negative numbers --- lib/split/experiment.rb | 9 +++++++++ spec/dashboard_spec.rb | 12 ++++++++++++ spec/experiment_spec.rb | 11 +++++++++++ 3 files changed, 32 insertions(+) diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 26c1792f..16c149a4 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -270,7 +270,16 @@ def load_from_redis set_alternatives_and_options(options) end + def can_calculate_winning_alternatives? + self.alternatives.all? do |alternative| + alternative.participant_count >= 0 && + (alternative.participant_count >= alternative.completed_count) + end + end + def calc_winning_alternatives + return unless can_calculate_winning_alternatives? + # Cache the winning alternatives so we recalculate them once per the specified interval. intervals_since_epoch = Time.now.utc.to_i / Split.configuration.winning_alternative_recalculation_interval diff --git a/spec/dashboard_spec.rb b/spec/dashboard_spec.rb index be28ba66..d985ef3d 100644 --- a/spec/dashboard_spec.rb +++ b/spec/dashboard_spec.rb @@ -279,4 +279,16 @@ def link(color) expect(last_response.body).to include("Unknown") end + + it "should be explode with experiments with invalid data" do + red_link.participant_count = 1 + red_link.set_completed_count(10) + + blue_link.participant_count = 3 + blue_link.set_completed_count(2) + + get "/" + + expect(last_response).to be_ok + end end diff --git a/spec/experiment_spec.rb b/spec/experiment_spec.rb index 36175d4d..4faf0994 100644 --- a/spec/experiment_spec.rb +++ b/spec/experiment_spec.rb @@ -576,6 +576,17 @@ def same_but_different_alternative expect(p_goal1).not_to be_within(0.04).of(p_goal2) end + it "should not calculate when data is not valid for beta distribution" do + experiment = Split::ExperimentCatalog.find_or_create("scientists", "einstein", "bohr") + + experiment.alternatives.each do |alternative| + alternative.participant_count = 9 + alternative.set_completed_count(10) + end + + expect { experiment.calc_winning_alternatives }.to_not raise_error + end + it "should return nil and not re-calculate probabilities if they have already been calculated today" do experiment = Split::ExperimentCatalog.find_or_create({ "link_color3" => ["purchase", "refund"] }, "blue", "red", "green") expect(experiment.calc_winning_alternatives).not_to be nil From cfd61ac7abb9ab3f282ef1d6161af8d6eea85493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 22 Jan 2023 18:20:10 -0300 Subject: [PATCH 121/136] Require pry on spec_helper --- spec/spec_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3e9859c9..d9ef4f69 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,6 +11,7 @@ require "split" require "ostruct" require "yaml" +require "pry" Dir["./spec/support/*.rb"].each { |f| require f } From 7c91132f35aaf2720eb53ed3a1436093476e0491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Mon, 27 Feb 2023 22:20:47 -0300 Subject: [PATCH 122/136] Add matrix as a default dependency --- Gemfile | 1 - gemfiles/7.0.gemfile | 1 - lib/split/algorithms.rb | 10 +--------- split.gemspec | 1 + 4 files changed, 2 insertions(+), 11 deletions(-) diff --git a/Gemfile b/Gemfile index 9fdc310a..8925d80d 100644 --- a/Gemfile +++ b/Gemfile @@ -5,5 +5,4 @@ source "https://rubygems.org" gemspec gem "rubocop", require: false -gem "matrix" gem "codeclimate-test-reporter" diff --git a/gemfiles/7.0.gemfile b/gemfiles/7.0.gemfile index f9464c11..b659fbf1 100644 --- a/gemfiles/7.0.gemfile +++ b/gemfiles/7.0.gemfile @@ -3,6 +3,5 @@ source "https://rubygems.org" gem "rubocop", require: false gem "codeclimate-test-reporter" gem "rails", "~> 7.0" -gem "matrix" gemspec path: "../" diff --git a/lib/split/algorithms.rb b/lib/split/algorithms.rb index 14631597..28a8cbfd 100644 --- a/lib/split/algorithms.rb +++ b/lib/split/algorithms.rb @@ -1,14 +1,6 @@ # frozen_string_literal: true -begin - require "matrix" -rescue LoadError => error - if error.message.match?(/matrix/) - $stderr.puts "You don't have matrix installed in your application. Please add it to your Gemfile and run bundle install" - raise - end -end - +require "matrix" require "rubystats" module Split diff --git a/split.gemspec b/split.gemspec index 1b08f9a5..8279f6d5 100644 --- a/split.gemspec +++ b/split.gemspec @@ -33,6 +33,7 @@ Gem::Specification.new do |s| s.add_dependency "redis", ">= 4.2" s.add_dependency "sinatra", ">= 1.2.6" s.add_dependency "rubystats", ">= 0.3.0" + s.add_dependency "matrix" s.add_development_dependency "bundler", ">= 1.17" s.add_development_dependency "simplecov", "~> 0.15" From 4ae6399262f50a9b154c033a22fbd84762c42cc0 Mon Sep 17 00:00:00 2001 From: Naveen Date: Thu, 6 Jul 2023 19:58:44 +0530 Subject: [PATCH 123/136] (fix): Fixed CROSSSLOT keys issue when using redis cluster --- lib/split/redis_interface.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/split/redis_interface.rb b/lib/split/redis_interface.rb index 5307428a..9f6f322f 100644 --- a/lib/split/redis_interface.rb +++ b/lib/split/redis_interface.rb @@ -11,6 +11,7 @@ def persist_list(list_name, list_values) if list_values.length > 0 redis.multi do |multi| tmp_list = "#{list_name}_tmp" + tmp_list += redis_namespace_used? ? "{#{Split.redis.namespace}:#{list_name}}" : "{#{list_name}}" multi.rpush(tmp_list, list_values) multi.rename(tmp_list, list_name) end @@ -27,5 +28,9 @@ def add_to_set(set_name, value) private attr_accessor :redis + + def redis_namespace_used? + Redis.const_defined?("Namespace") && Split.redis.is_a?(Redis::Namespace) + end end end From c249153a2fdebfbca87cc2f1ed756baef188b941 Mon Sep 17 00:00:00 2001 From: Oleg Marakhovsky Date: Tue, 29 Aug 2023 15:38:53 +0300 Subject: [PATCH 124/136] Convert value to string before saving --- lib/split/persistence/redis_adapter.rb | 2 +- spec/persistence/redis_adapter_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/split/persistence/redis_adapter.rb b/lib/split/persistence/redis_adapter.rb index e314d882..c69f3fe4 100644 --- a/lib/split/persistence/redis_adapter.rb +++ b/lib/split/persistence/redis_adapter.rb @@ -27,7 +27,7 @@ def [](field) end def []=(field, value) - Split.redis.hset(redis_key, field, value) + Split.redis.hset(redis_key, field, value.to_s) expire_seconds = self.class.config[:expire_seconds] Split.redis.expire(redis_key, expire_seconds) if expire_seconds end diff --git a/spec/persistence/redis_adapter_spec.rb b/spec/persistence/redis_adapter_spec.rb index 1a1eac11..f1841dd4 100644 --- a/spec/persistence/redis_adapter_spec.rb +++ b/spec/persistence/redis_adapter_spec.rb @@ -73,9 +73,9 @@ before { Split::Persistence::RedisAdapter.with_config(lookup_by: "lookup") } describe "#[] and #[]=" do - it "should set and return the value for given key" do - subject["my_key"] = "my_value" - expect(subject["my_key"]).to eq("my_value") + it "should convert to string, set and return the value for given key" do + subject["my_key"] = true + expect(subject["my_key"]).to eq("true") end end From 95052427a29e3ea436143c402ab94aa96c2835d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sat, 2 Sep 2023 22:35:29 -0300 Subject: [PATCH 125/136] Update documentation regarding finding users outside a web session --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 109b61ec..daa73a3f 100644 --- a/README.md +++ b/README.md @@ -807,10 +807,16 @@ conduct experiments that are not tied to a web session. ```ruby # create a new experiment experiment = Split::ExperimentCatalog.find_or_create('color', 'red', 'blue') + +# find the user +user = Split::User.find(user_id, :redis) + # create a new trial -trial = Split::Trial.new(:experiment => experiment) +trial = Split::Trial.new(user: user, experiment: experiment) + # run trial trial.choose! + # get the result, returns either red or blue trial.alternative.name From 9d4c9b542b39d53711138c0839adb3957eb0b75c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 3 Sep 2023 00:02:37 -0300 Subject: [PATCH 126/136] Do not persist invalid extra_info on ab_record_extra_info. If a invalid value is persisted on a given alternative, that dashboard is able to validate the data properly now. Added a few specs to ensure extra_info is persisted correctly. Co-authored-by: trostli --- lib/split/dashboard/views/_experiment.erb | 3 +- lib/split/helper.rb | 2 +- spec/helper_spec.rb | 36 +++++++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/lib/split/dashboard/views/_experiment.erb b/lib/split/dashboard/views/_experiment.erb index 2f2459ad..d23ac2b5 100644 --- a/lib/split/dashboard/views/_experiment.erb +++ b/lib/split/dashboard/views/_experiment.erb @@ -16,7 +16,8 @@ summary_texts = {} extra_columns.each do |column| extra_infos = experiment.alternatives.map(&:extra_info).select{|extra_info| extra_info && extra_info[column] } - if extra_infos[0][column].kind_of?(Numeric) + + if extra_infos.length > 0 && extra_infos.all? { |extra_info| extra_info[column].kind_of?(Numeric) } summary_texts[column] = extra_infos.inject(0){|sum, extra_info| sum += extra_info[column]} else summary_texts[column] = "N/A" diff --git a/lib/split/helper.rb b/lib/split/helper.rb index ae0010ad..e0a59e32 100644 --- a/lib/split/helper.rb +++ b/lib/split/helper.rb @@ -86,7 +86,7 @@ def ab_finished(metric_descriptor, options = { reset: true }) end def ab_record_extra_info(metric_descriptor, key, value = 1) - return if exclude_visitor? || Split.configuration.disabled? + return if exclude_visitor? || Split.configuration.disabled? || value.nil? metric_descriptor, _ = normalize_metric(metric_descriptor) experiments = Metric.possible_experiments(metric_descriptor) diff --git a/spec/helper_spec.rb b/spec/helper_spec.rb index 33341657..bc76b7d8 100755 --- a/spec/helper_spec.rb +++ b/spec/helper_spec.rb @@ -558,6 +558,42 @@ def should_finish_experiment(experiment_name, should_finish = true) end end + + describe "ab_record_extra_info" do + context "for an experiment that the user participates in" do + before(:each) do + @experiment_name = "link_color" + @alternatives = ["blue", "red"] + @experiment = Split::ExperimentCatalog.find_or_create(@experiment_name, *@alternatives) + @alternative_name = ab_test(@experiment_name, *@alternatives) + end + + it "records extra data for a given experiment" do + alternative = Split::Alternative.new(@alternative_name, "link_color") + + ab_record_extra_info(@experiment_name, "some_data", 10) + + expect(alternative.extra_info).to eql({ "some_data" => 10 }) + end + + it "records extra data for a given experiment" do + alternative = Split::Alternative.new(@alternative_name, "link_color") + + ab_record_extra_info(@experiment_name, "some_data") + + expect(alternative.extra_info).to eql({ "some_data" => 1 }) + end + + it "records extra data for a given experiment" do + alternative = Split::Alternative.new(@alternative_name, "link_color") + + ab_record_extra_info(@experiment_name, "some_data", nil) + + expect(alternative.extra_info).to eql({}) + end + end + end + describe "conversions" do it "should return a conversion rate for an alternative" do alternative_name = ab_test("link_color", "blue", "red") From f86519a8ec12cee25694a17dbe2a409a135fae84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Mon, 4 Sep 2023 20:11:45 -0300 Subject: [PATCH 127/136] Update actions/checkout to v4 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dbe8cad0..3e974948 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: --health-retries 5 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: From 3f6c1fc5bb7207a90074c9bcc499c32d8d287969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Wed, 15 Nov 2023 18:27:55 -0300 Subject: [PATCH 128/136] Bump v4.0.3 --- CHANGELOG.md | 15 +++++++++++++++ lib/split/version.rb | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5652f1a2..2d00dda6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +# 4.0.3 (November 15rd, 2023) + +Bugfixes: +- Do not throw error if alternativas have data that can lead to negative numbers for probability calculation (@andrehjr, #703) +- Do not persist invalid extra_info on ab_record_extra_info. (@trostli @andrehjr, #717) +- CROSSSLOT keys issue fix when using redis cluster (@naveen-chidhambaram, #710) +- Convert value to string before saving it in RedisAdapter (@Jealrock, #714) +- Fix deprecation warning with Redis 4.8.0 (@martingregoire, #701) + +Misc: +- Add matrix as a default dependency (@andrehjr, #705) +- Add Ruby 3.2 to Github Actions (@andrehjr, #702) +- Update documentation regarding finding users outside a web session (@andrehjr, #716) +- Update actions/checkout to v4 (@andrehjr, #718) + # 4.0.2 (December 2nd, 2022) Bugfixes: diff --git a/lib/split/version.rb b/lib/split/version.rb index 4e0ec8de..44c30f50 100644 --- a/lib/split/version.rb +++ b/lib/split/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Split - VERSION = "4.0.2" + VERSION = "4.0.3" end From d29e26a67f148aff249f5a8288c76c0fd6c01bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Wed, 15 Nov 2023 18:44:34 -0300 Subject: [PATCH 129/136] Fix typo at CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d00dda6..191a66d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 4.0.3 (November 15rd, 2023) +# 4.0.3 (November 15th, 2023) Bugfixes: - Do not throw error if alternativas have data that can lead to negative numbers for probability calculation (@andrehjr, #703) From bd2bbac84b46c41514f23e75eb276791e43354c6 Mon Sep 17 00:00:00 2001 From: "Henrique F. Teixeira" Date: Thu, 29 Feb 2024 10:08:42 -0300 Subject: [PATCH 130/136] Fix context shim override behavior --- lib/split/encapsulated_helper.rb | 8 ++++++++ lib/split/helper.rb | 22 +++++++++++++++------- spec/encapsulated_helper_spec.rb | 16 +++++++++------- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/lib/split/encapsulated_helper.rb b/lib/split/encapsulated_helper.rb index e200e0fe..9dd1750f 100644 --- a/lib/split/encapsulated_helper.rb +++ b/lib/split/encapsulated_helper.rb @@ -23,6 +23,14 @@ def initialize(context) @context = context end + def params + request.params if request_present? + end + + def request + @context.request if @context.respond_to?(:request) + end + def ab_user @ab_user ||= Split::User.new(@context) end diff --git a/lib/split/helper.rb b/lib/split/helper.rb index e0a59e32..e80c1039 100644 --- a/lib/split/helper.rb +++ b/lib/split/helper.rb @@ -121,11 +121,11 @@ def override_alternative(experiment_name) end def override_alternative_by_params(experiment_name) - defined?(params) && params[OVERRIDE_PARAM_NAME] && params[OVERRIDE_PARAM_NAME][experiment_name] + params_present? && params[OVERRIDE_PARAM_NAME] && params[OVERRIDE_PARAM_NAME][experiment_name] end def override_alternative_by_cookies(experiment_name) - return unless defined?(request) + return unless request_present? if request.cookies && request.cookies.key?("split_override") experiments = JSON.parse(request.cookies["split_override"]) rescue {} @@ -134,7 +134,7 @@ def override_alternative_by_cookies(experiment_name) end def split_generically_disabled? - defined?(params) && params["SPLIT_DISABLE"] + params_present? && params["SPLIT_DISABLE"] end def ab_user @@ -142,26 +142,34 @@ def ab_user end def exclude_visitor? - defined?(request) && (instance_exec(request, &Split.configuration.ignore_filter) || is_ignored_ip_address? || is_robot? || is_preview?) + request_present? && (instance_exec(request, &Split.configuration.ignore_filter) || is_ignored_ip_address? || is_robot? || is_preview?) end def is_robot? - defined?(request) && request.user_agent =~ Split.configuration.robot_regex + request_present? && request.user_agent =~ Split.configuration.robot_regex end def is_preview? - defined?(request) && defined?(request.headers) && request.headers["x-purpose"] == "preview" + request_present? && defined?(request.headers) && request.headers["x-purpose"] == "preview" end def is_ignored_ip_address? return false if Split.configuration.ignore_ip_addresses.empty? Split.configuration.ignore_ip_addresses.each do |ip| - return true if defined?(request) && (request.ip == ip || (ip.class == Regexp && request.ip =~ ip)) + return true if request_present? && (request.ip == ip || (ip.class == Regexp && request.ip =~ ip)) end false end + def params_present? + defined?(params) && params != nil + end + + def request_present? + defined?(request) && request != nil + end + def active_experiments ab_user.active_experiments end diff --git a/spec/encapsulated_helper_spec.rb b/spec/encapsulated_helper_spec.rb index df9dfa9a..ca301509 100644 --- a/spec/encapsulated_helper_spec.rb +++ b/spec/encapsulated_helper_spec.rb @@ -5,19 +5,21 @@ describe Split::EncapsulatedHelper do include Split::EncapsulatedHelper - def params - raise NoMethodError, "This method is not really defined" - end - describe "ab_test" do before do allow_any_instance_of(Split::EncapsulatedHelper::ContextShim).to receive(:ab_user) .and_return(mock_user) end - it "should not raise an error when params raises an error" do - expect { params }.to raise_error(NoMethodError) - expect { ab_test("link_color", "blue", "red") }.not_to raise_error + context "when params raises an error" do + before do + allow(self).to receive(:params).and_raise(NoMethodError) + end + + it "should not raise an error " do + expect { params }.to raise_error(NoMethodError) + expect { ab_test("link_color", "blue", "red") }.not_to raise_error + end end it "calls the block with selected alternative" do From e083659ef13d72463f21ffacb6c0182c1b408731 Mon Sep 17 00:00:00 2001 From: "Henrique F. Teixeira" Date: Thu, 29 Feb 2024 10:44:34 -0300 Subject: [PATCH 131/136] Add tests to context shim override behavior --- spec/encapsulated_helper_spec.rb | 35 ++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 23 ++++++++++++++------- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/spec/encapsulated_helper_spec.rb b/spec/encapsulated_helper_spec.rb index ca301509..ba04683a 100644 --- a/spec/encapsulated_helper_spec.rb +++ b/spec/encapsulated_helper_spec.rb @@ -45,8 +45,43 @@ include Split::EncapsulatedHelper public :session }.new + expect(ctx).to receive(:session) { {} } expect { ctx.ab_test("link_color", "blue", "red") }.not_to raise_error end + + context "when request is defined in context of ContextShim" do + context "when overriding by params" do + it do + ctx = Class.new { + public :session + def request + build_request(params: { + "ab_test" => { "link_color" => "blue" } + }) + end + }.new + + context_shim = Split::EncapsulatedHelper::ContextShim.new(ctx) + expect(context_shim.ab_test("link_color", "blue", "red")).to be("blue") + end + end + + context "when overriding by cookies" do + it do + ctx = Class.new { + public :session + def request + build_request(cookies: { + "split_override" => '{ "link_color": "red" }' + }) + end + }.new + + context_shim = Split::EncapsulatedHelper::ContextShim.new(ctx) + expect(context_shim.ab_test("link_color", "blue", "red")).to be("red") + end + end + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d9ef4f69..038da419 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -44,11 +44,20 @@ def params @params ||= {} end -def request(ua = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; de-de) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27") - @request ||= begin - r = OpenStruct.new - r.user_agent = ua - r.ip = "192.168.1.1" - r - end +def request + @request ||= build_request +end + +def build_request( + ua: "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; de-de) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", + ip: "192.168.1.1", + params: {}, + cookies: {} +) + r = OpenStruct.new + r.user_agent = ua + r.ip = "192.168.1.1" + r.params = params + r.cookies = cookies + r end From 18ad66a9db59d00f179329aac273bcb6c86a26d9 Mon Sep 17 00:00:00 2001 From: "Henrique F. Teixeira" Date: Thu, 29 Feb 2024 14:31:27 -0300 Subject: [PATCH 132/136] Fix params on spec_helper build_request --- spec/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 038da419..41c78a44 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -56,7 +56,7 @@ def build_request( ) r = OpenStruct.new r.user_agent = ua - r.ip = "192.168.1.1" + r.ip = ip r.params = params r.cookies = cookies r From 9135bb6ab8863e4016e3146f474eaee67f1e44c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 3 Mar 2024 11:09:10 -0300 Subject: [PATCH 133/136] Make specs compatible with newer rack --- spec/persistence/cookie_adapter_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/persistence/cookie_adapter_spec.rb b/spec/persistence/cookie_adapter_spec.rb index 182d6229..58e6e9bb 100644 --- a/spec/persistence/cookie_adapter_spec.rb +++ b/spec/persistence/cookie_adapter_spec.rb @@ -67,14 +67,14 @@ it "puts multiple experiments in a single cookie" do subject["foo"] = "FOO" subject["bar"] = "BAR" - expect(context.response.headers["Set-Cookie"]).to match(/\Asplit=%7B%22foo%22%3A%22FOO%22%2C%22bar%22%3A%22BAR%22%7D; path=\/; expires=[a-zA-Z]{3}, \d{2} [a-zA-Z]{3} \d{4} \d{2}:\d{2}:\d{2} [A-Z]{3}\Z/) + expect(Array(context.response.headers["Set-Cookie"])).to include(/\Asplit=%7B%22foo%22%3A%22FOO%22%2C%22bar%22%3A%22BAR%22%7D; path=\/; expires=[a-zA-Z]{3}, \d{2} [a-zA-Z]{3} \d{4} \d{2}:\d{2}:\d{2} [A-Z]{3}\Z/) end it "ensure other added cookies are not overriden" do context.response.set_cookie "dummy", "wow" subject["foo"] = "FOO" - expect(context.response.headers["Set-Cookie"]).to include("dummy=wow") - expect(context.response.headers["Set-Cookie"]).to include("split=") + expect(Array(context.response.headers["Set-Cookie"])).to include(/dummy=wow/) + expect(Array(context.response.headers["Set-Cookie"])).to include(/split=/) end end From 4445631243a7a8066562489d25b0fc44c8085c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 3 Mar 2024 11:01:58 -0300 Subject: [PATCH 134/136] Refactor spec/encapsulated_helper_spec.rb to not include EncapsuledHelper on itself --- spec/encapsulated_helper_spec.rb | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/spec/encapsulated_helper_spec.rb b/spec/encapsulated_helper_spec.rb index ba04683a..c44b2d62 100644 --- a/spec/encapsulated_helper_spec.rb +++ b/spec/encapsulated_helper_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" describe Split::EncapsulatedHelper do - include Split::EncapsulatedHelper + let(:context_shim) { Split::EncapsulatedHelper::ContextShim.new(double(request: request)) } describe "ab_test" do before do @@ -11,26 +11,15 @@ .and_return(mock_user) end - context "when params raises an error" do - before do - allow(self).to receive(:params).and_raise(NoMethodError) - end - - it "should not raise an error " do - expect { params }.to raise_error(NoMethodError) - expect { ab_test("link_color", "blue", "red") }.not_to raise_error - end - end - it "calls the block with selected alternative" do - expect { |block| ab_test("link_color", "red", "red", &block) }.to yield_with_args("red", {}) + expect { |block| context_shim.ab_test("link_color", "red", "red", &block) }.to yield_with_args("red", {}) end context "inside a view" do it "works inside ERB" do require "erb" template = ERB.new(<<-ERB.split(/\s+/s).map(&:strip).join(" "), nil, "%") - foo <% ab_test(:foo, '1', '2') do |alt, meta| %> + foo <% context_shim.ab_test(:foo, '1', '2') do |alt, meta| %> static <%= alt %> <% end %> ERB From 397dc477422b87a21016ce11a2f704102bbd5c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 3 Mar 2024 11:02:47 -0300 Subject: [PATCH 135/136] We can just check if request/params are there --- lib/split/encapsulated_helper.rb | 2 +- lib/split/helper.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/split/encapsulated_helper.rb b/lib/split/encapsulated_helper.rb index 9dd1750f..ab48ea23 100644 --- a/lib/split/encapsulated_helper.rb +++ b/lib/split/encapsulated_helper.rb @@ -24,7 +24,7 @@ def initialize(context) end def params - request.params if request_present? + request.params if request && request.respond_to?(:params) end def request diff --git a/lib/split/helper.rb b/lib/split/helper.rb index e80c1039..5e1b2b7f 100644 --- a/lib/split/helper.rb +++ b/lib/split/helper.rb @@ -163,11 +163,11 @@ def is_ignored_ip_address? end def params_present? - defined?(params) && params != nil + defined?(params) && params end def request_present? - defined?(request) && request != nil + defined?(request) && request end def active_experiments From cdd66da60f5897543c3b54685e5270596133529e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Luis=20Leal=20Cardoso=20Junior?= Date: Sun, 3 Mar 2024 19:59:08 -0300 Subject: [PATCH 136/136] Prepare v4.0.4 release --- CHANGELOG.md | 8 ++++++++ lib/split/version.rb | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 191a66d9..28d47674 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 4.0.4 (March 3rd, 2024) + +Bugfixes: +- Better integration for EncapsulatedHelper when needing params/request info (@henrique-ft, #721 and #723) + +Misc: +- Make specs compatible with newer Rack versions (@andrehjr, #722) + # 4.0.3 (November 15th, 2023) Bugfixes: diff --git a/lib/split/version.rb b/lib/split/version.rb index 44c30f50..92b64adf 100644 --- a/lib/split/version.rb +++ b/lib/split/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Split - VERSION = "4.0.3" + VERSION = "4.0.4" end