split 2.2.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,6 +25,7 @@ module Split
25
25
  attr_accessor :on_before_experiment_delete
26
26
  attr_accessor :include_rails_helper
27
27
  attr_accessor :beta_probability_simulations
28
+ attr_accessor :winning_alternative_recalculation_interval
28
29
  attr_accessor :redis
29
30
 
30
31
  attr_reader :experiments
@@ -48,7 +49,9 @@ module Split
48
49
  'spider' => 'generic web spider',
49
50
  'UnwindFetchor' => 'Gnip crawler',
50
51
  'WordPress' => 'WordPress spider',
52
+ 'YandexAccessibilityBot' => 'Yandex accessibility spider',
51
53
  'YandexBot' => 'Yandex spider',
54
+ 'YandexMobileBot' => 'Yandex mobile spider',
52
55
  'ZIBB' => 'ZIBB spider',
53
56
 
54
57
  # HTTP libraries
@@ -73,10 +76,13 @@ module Split
73
76
  'facebookexternalhit' => 'facebook bot',
74
77
  'Feedfetcher-Google' => 'Google Feedfetcher',
75
78
  'https://developers.google.com/+/web/snippet' => 'Google+ Snippet Fetcher',
79
+ 'LinkedInBot' => 'LinkedIn bot',
76
80
  'LongURL' => 'URL expander service',
77
81
  'NING' => 'NING - Yet Another Twitter Swarmer',
82
+ 'Pinterest' => 'Pinterest Bot',
78
83
  'redditbot' => 'Reddit Bot',
79
84
  'ShortLinkTranslate' => 'Link shortener',
85
+ 'Slackbot' => 'Slackbot link expander',
80
86
  'TweetmemeBot' => 'TweetMeMe Crawler',
81
87
  'Twitterbot' => 'Twitter URL expander',
82
88
  'UnwindFetch' => 'Gnip URL expander',
@@ -212,6 +218,7 @@ module Split
212
218
  @algorithm = Split::Algorithms::WeightedSample
213
219
  @include_rails_helper = true
214
220
  @beta_probability_simulations = 10000
221
+ @winning_alternative_recalculation_interval = 60 * 60 * 24 # 1 day
215
222
  @redis = ENV.fetch(ENV.fetch('REDIS_PROVIDER', 'REDIS_URL'), 'redis://localhost:6379')
216
223
  end
217
224
 
@@ -18,7 +18,11 @@ module Split
18
18
  end
19
19
 
20
20
  def round(number, precision = 2)
21
- BigDecimal.new(number.to_s).round(precision).to_f
21
+ begin
22
+ BigDecimal.new(number.to_s)
23
+ rescue ArgumentError
24
+ BigDecimal.new(0)
25
+ end.round(precision).to_f
22
26
  end
23
27
 
24
28
  def confidence_level(z_score)
@@ -5,6 +5,25 @@
5
5
  <% end %>
6
6
 
7
7
  <% experiment.calc_winning_alternatives %>
8
+ <%
9
+ extra_columns = []
10
+ experiment.alternatives.each do |alternative|
11
+ extra_info = alternative.extra_info || {}
12
+ extra_columns += extra_info.keys
13
+ end
14
+
15
+ extra_columns.uniq!
16
+ summary_texts = {}
17
+ extra_columns.each do |column|
18
+ extra_infos = experiment.alternatives.map(&:extra_info).select{|extra_info| extra_info && extra_info[column] }
19
+ if extra_infos[0][column].kind_of?(Numeric)
20
+ summary_texts[column] = extra_infos.inject(0){|sum, extra_info| sum += extra_info[column]}
21
+ else
22
+ summary_texts[column] = "N/A"
23
+ end
24
+ end
25
+ %>
26
+
8
27
 
9
28
  <div class="<%= experiment_class %>" data-name="<%= experiment.name %>" data-complete="<%= experiment.has_winner? %>">
10
29
  <div class="experiment-header">
@@ -32,6 +51,9 @@
32
51
  <th>Non-finished</th>
33
52
  <th>Completed</th>
34
53
  <th>Conversion Rate</th>
54
+ <% extra_columns.each do |column| %>
55
+ <th><%= column %></th>
56
+ <% end %>
35
57
  <th>
36
58
  <form>
37
59
  <select id="dropdown-<%=experiment.jstring(goal)%>" name="dropdown-<%=experiment.jstring(goal)%>">
@@ -82,6 +104,9 @@
82
104
  });
83
105
  });
84
106
  </script>
107
+ <% extra_columns.each do |column| %>
108
+ <td><%= alternative.extra_info && alternative.extra_info[column] %></td>
109
+ <% end %>
85
110
  <td>
86
111
  <div class="box-<%=experiment.jstring(goal)%> confidence-<%=experiment.jstring(goal)%>">
87
112
  <span title='z-score: <%= round(alternative.z_score(goal), 3) %>'><%= confidence_level(alternative.z_score(goal)) %></span>
@@ -90,7 +115,7 @@
90
115
  <div class="box-<%=experiment.jstring(goal)%> probability-<%=experiment.jstring(goal)%>">
91
116
  <span title="p_winner: <%= round(alternative.p_winner(goal), 3) %>"><%= number_to_percentage(round(alternative.p_winner(goal), 3)) %>%</span>
92
117
  </div>
93
- </td>
118
+ </td>
94
119
  <td>
95
120
  <% if experiment.has_winner? %>
96
121
  <% if experiment.winner.name == alternative.name %>
@@ -118,6 +143,11 @@
118
143
  <td><%= total_unfinished %></td>
119
144
  <td><%= total_completed %></td>
120
145
  <td>N/A</td>
146
+ <% extra_columns.each do |column| %>
147
+ <td>
148
+ <%= summary_texts[column] %>
149
+ </td>
150
+ <% end %>
121
151
  <td>N/A</td>
122
152
  <td>N/A</td>
123
153
  </tr>
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ require "split/helper"
3
+
2
4
  # Split's helper exposes all kinds of methods we don't want to
3
5
  # mix into our model classes.
4
6
  #
data/lib/split/engine.rb CHANGED
@@ -5,6 +5,8 @@ module Split
5
5
  if Split.configuration.include_rails_helper
6
6
  ActionController::Base.send :include, Split::Helper
7
7
  ActionController::Base.helper Split::Helper
8
+ ActionController::Base.send :include, Split::CombinedExperimentsHelper
9
+ ActionController::Base.helper Split::CombinedExperimentsHelper
8
10
  end
9
11
  end
10
12
  end
@@ -262,10 +262,11 @@ module Split
262
262
  end
263
263
 
264
264
  def calc_winning_alternatives
265
- # Super simple cache so that we only recalculate winning alternatives once per day
266
- days_since_epoch = Time.now.utc.to_i / 86400
265
+ # Cache the winning alternatives so we recalculate them once per the specified interval.
266
+ intervals_since_epoch =
267
+ Time.now.utc.to_i / Split.configuration.winning_alternative_recalculation_interval
267
268
 
268
- if self.calc_time != days_since_epoch
269
+ if self.calc_time != intervals_since_epoch
269
270
  if goals.empty?
270
271
  self.estimate_winning_alternative
271
272
  else
@@ -274,7 +275,7 @@ module Split
274
275
  end
275
276
  end
276
277
 
277
- self.calc_time = days_since_epoch
278
+ self.calc_time = intervals_since_epoch
278
279
 
279
280
  self.save
280
281
  end
data/lib/split/helper.rb CHANGED
@@ -10,6 +10,7 @@ module Split
10
10
  experiment = ExperimentCatalog.find_or_initialize(metric_descriptor, control, *alternatives)
11
11
  alternative = if Split.configuration.enabled
12
12
  experiment.save
13
+ raise(Split::InvalidExperimentsFormatError) unless (Split.configuration.experiments || {}).fetch(experiment.name.to_sym, {})[:combined_experiments].nil?
13
14
  trial = Trial.new(:user => ab_user, :experiment => experiment,
14
15
  :override => override_alternative(experiment.name), :exclude => exclude_visitor?,
15
16
  :disabled => split_generically_disabled?)
@@ -76,6 +77,34 @@ module Split
76
77
  Split.configuration.db_failover_on_db_error.call(e)
77
78
  end
78
79
 
80
+ def ab_record_extra_info(metric_descriptor, key, value = 1)
81
+ return if exclude_visitor? || Split.configuration.disabled?
82
+ metric_descriptor, goals = normalize_metric(metric_descriptor)
83
+ experiments = Metric.possible_experiments(metric_descriptor)
84
+
85
+ if experiments.any?
86
+ experiments.each do |experiment|
87
+ alternative_name = ab_user[experiment.key]
88
+
89
+ if alternative_name
90
+ alternative = experiment.alternatives.find{|alt| alt.name == alternative_name}
91
+ alternative.record_extra_info(key, value) if alternative
92
+ end
93
+ end
94
+ end
95
+ rescue => e
96
+ raise unless Split.configuration.db_failover
97
+ Split.configuration.db_failover_on_db_error.call(e)
98
+ end
99
+
100
+ def ab_active_experiments()
101
+ ab_user.active_experiments
102
+ rescue => e
103
+ raise unless Split.configuration.db_failover
104
+ Split.configuration.db_failover_on_db_error.call(e)
105
+ end
106
+
107
+
79
108
  def override_present?(experiment_name)
80
109
  override_alternative(experiment_name)
81
110
  end
@@ -6,20 +6,21 @@ module Split
6
6
  class CookieAdapter
7
7
 
8
8
  def initialize(context)
9
- @cookies = context.send(:cookies)
9
+ @request, @response = context.request, context.response
10
+ @cookies = @request.cookies
10
11
  @expires = Time.now + cookie_length_config
11
12
  end
12
13
 
13
14
  def [](key)
14
- hash[key]
15
+ hash[key.to_s]
15
16
  end
16
17
 
17
18
  def []=(key, value)
18
- set_cookie(hash.merge(key => value))
19
+ set_cookie(hash.merge!(key.to_s => value))
19
20
  end
20
21
 
21
22
  def delete(key)
22
- set_cookie(hash.tap { |h| h.delete(key) })
23
+ set_cookie(hash.tap { |h| h.delete(key.to_s) })
23
24
  end
24
25
 
25
26
  def keys
@@ -28,22 +29,25 @@ module Split
28
29
 
29
30
  private
30
31
 
31
- def set_cookie(value)
32
- @cookies[:split] = {
33
- :value => JSON.generate(value),
34
- :expires => @expires
35
- }
32
+ def set_cookie(value = {})
33
+ @response.set_cookie :split.to_s, default_options.merge(value: JSON.generate(value))
34
+ end
35
+
36
+ def default_options
37
+ { expires: @expires, path: '/' }
36
38
  end
37
39
 
38
40
  def hash
39
- if @cookies[:split]
40
- begin
41
- JSON.parse(@cookies[:split])
42
- rescue JSON::ParserError
41
+ @hash ||= begin
42
+ if cookies = @cookies[:split.to_s]
43
+ begin
44
+ JSON.parse(cookies)
45
+ rescue JSON::ParserError
46
+ {}
47
+ end
48
+ else
43
49
  {}
44
50
  end
45
- else
46
- {}
47
51
  end
48
52
  end
49
53
 
@@ -1,4 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
2
5
  module Split
3
6
  module Persistence
4
7
  class DualAdapter
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
- %w[session_adapter cookie_adapter redis_adapter dual_adapter].each do |f|
3
- require "split/persistence/#{f}"
4
- end
5
2
 
6
3
  module Split
7
4
  module Persistence
5
+ require 'split/persistence/cookie_adapter'
6
+ require 'split/persistence/dual_adapter'
7
+ require 'split/persistence/redis_adapter'
8
+ require 'split/persistence/session_adapter'
9
+
8
10
  ADAPTERS = {
9
11
  :cookie => Split::Persistence::CookieAdapter,
10
12
  :session => Split::Persistence::SessionAdapter
data/lib/split/user.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'forwardable'
2
+
1
3
  module Split
2
4
  class User
3
5
  extend Forwardable
data/lib/split/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Split
3
- MAJOR = 2
3
+ MAJOR = 3
4
4
  MINOR = 2
5
5
  PATCH = 0
6
6
  VERSION = [MAJOR, MINOR, PATCH].join('.')
data/lib/split/zscore.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Split
3
- module Zscore
3
+ class Zscore
4
4
 
5
5
  include Math
6
6
 
data/lib/split.rb CHANGED
@@ -1,24 +1,26 @@
1
1
  # frozen_string_literal: true
2
- %w[algorithms
3
- alternative
4
- configuration
5
- exceptions
6
- experiment
7
- experiment_catalog
8
- extensions
9
- goals_collection
10
- helper
11
- metric
12
- persistence
13
- encapsulated_helper
14
- redis_interface
15
- trial
16
- user
17
- version
18
- zscore].each do |f|
19
- require "split/#{f}"
20
- end
2
+ require 'redis'
21
3
 
4
+ require 'split/algorithms/block_randomization'
5
+ require 'split/algorithms/weighted_sample'
6
+ require 'split/algorithms/whiplash'
7
+ require 'split/alternative'
8
+ require 'split/configuration'
9
+ require 'split/encapsulated_helper'
10
+ require 'split/exceptions'
11
+ require 'split/experiment'
12
+ require 'split/experiment_catalog'
13
+ require 'split/extensions/string'
14
+ require 'split/goals_collection'
15
+ require 'split/helper'
16
+ require 'split/combined_experiments_helper'
17
+ require 'split/metric'
18
+ require 'split/persistence'
19
+ require 'split/redis_interface'
20
+ require 'split/trial'
21
+ require 'split/user'
22
+ require 'split/version'
23
+ require 'split/zscore'
22
24
  require 'split/engine' if defined?(Rails)
23
25
 
24
26
  module Split
@@ -0,0 +1,32 @@
1
+ require "spec_helper"
2
+
3
+ describe Split::Algorithms::BlockRandomization do
4
+
5
+ let(:experiment) { Split::Experiment.new 'experiment' }
6
+ let(:alternative_A) { Split::Alternative.new 'A', 'experiment' }
7
+ let(:alternative_B) { Split::Alternative.new 'B', 'experiment' }
8
+ let(:alternative_C) { Split::Alternative.new 'C', 'experiment' }
9
+
10
+ before :each do
11
+ allow(experiment).to receive(:alternatives) { [alternative_A, alternative_B, alternative_C] }
12
+ end
13
+
14
+ it "should return an alternative" do
15
+ expect(Split::Algorithms::BlockRandomization.choose_alternative(experiment).class).to eq(Split::Alternative)
16
+ end
17
+
18
+ it "should always return the minimum participation option" do
19
+ allow(alternative_A).to receive(:participant_count) { 1 }
20
+ allow(alternative_B).to receive(:participant_count) { 1 }
21
+ allow(alternative_C).to receive(:participant_count) { 0 }
22
+ expect(Split::Algorithms::BlockRandomization.choose_alternative(experiment)).to eq(alternative_C)
23
+ end
24
+
25
+ it "should return one of the minimum participation options when multiple" do
26
+ allow(alternative_A).to receive(:participant_count) { 0 }
27
+ allow(alternative_B).to receive(:participant_count) { 0 }
28
+ allow(alternative_C).to receive(:participant_count) { 0 }
29
+ alternative = Split::Algorithms::BlockRandomization.choose_alternative(experiment)
30
+ expect([alternative_A, alternative_B, alternative_C].include?(alternative)).to be(true)
31
+ end
32
+ end
@@ -273,5 +273,48 @@ describe Split::Alternative do
273
273
  expect(control.z_score(goal1)).to eq('N/A')
274
274
  expect(control.z_score(goal2)).to eq('N/A')
275
275
  end
276
+
277
+ it "should not blow up for Conversion Rates > 1" do
278
+ control = experiment.control
279
+ control.participant_count = 3474
280
+ control.set_completed_count(4244)
281
+
282
+ alternative2.participant_count = 3434
283
+ alternative2.set_completed_count(4358)
284
+
285
+ expect { control.z_score }.not_to raise_error
286
+ expect { alternative2.z_score }.not_to raise_error
287
+ end
288
+ end
289
+
290
+ describe "extra_info" do
291
+ it "reads saved value of recorded_info in redis" do
292
+ saved_recorded_info = {"key_1" => 1, "key_2" => "2"}
293
+ Split.redis.hset "#{alternative.experiment_name}:#{alternative.name}", 'recorded_info', saved_recorded_info.to_json
294
+ extra_info = alternative.extra_info
295
+
296
+ expect(extra_info).to eql(saved_recorded_info)
297
+ end
298
+ end
299
+
300
+ describe "record_extra_info" do
301
+ it "saves key" do
302
+ alternative.record_extra_info("signup", 1)
303
+ expect(alternative.extra_info["signup"]).to eql(1)
304
+ end
305
+
306
+ it "adds value to saved key's value second argument is number" do
307
+ alternative.record_extra_info("signup", 1)
308
+ alternative.record_extra_info("signup", 2)
309
+ expect(alternative.extra_info["signup"]).to eql(3)
310
+ end
311
+
312
+ it "sets saved's key value to the second argument if it's a string" do
313
+ alternative.record_extra_info("signup", "Value 1")
314
+ expect(alternative.extra_info["signup"]).to eql("Value 1")
315
+
316
+ alternative.record_extra_info("signup", "Value 2")
317
+ expect(alternative.extra_info["signup"]).to eql("Value 2")
318
+ end
276
319
  end
277
320
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+ require 'split/combined_experiments_helper'
4
+
5
+ describe Split::CombinedExperimentsHelper do
6
+ include Split::CombinedExperimentsHelper
7
+
8
+ describe 'ab_combined_test' do
9
+ let!(:config_enabled) { true }
10
+ let!(:combined_experiments) { [:exp_1_click, :exp_1_scroll ]}
11
+ let!(:allow_multiple_experiments) { true }
12
+
13
+ before do
14
+ Split.configuration.experiments = {
15
+ :combined_exp_1 => {
16
+ :alternatives => [ {"control"=> 0.5}, {"test-alt"=> 0.5} ],
17
+ :metric => :my_metric,
18
+ :combined_experiments => combined_experiments
19
+ }
20
+ }
21
+ Split.configuration.enabled = config_enabled
22
+ Split.configuration.allow_multiple_experiments = allow_multiple_experiments
23
+ end
24
+
25
+ context 'without config enabled' do
26
+ let!(:config_enabled) { false }
27
+
28
+ it "raises an error" do
29
+ expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError )
30
+ end
31
+ end
32
+
33
+ context 'multiple experiments disabled' do
34
+ let!(:allow_multiple_experiments) { false }
35
+
36
+ it "raises an error if multiple experiments is disabled" do
37
+ expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError)
38
+ end
39
+ end
40
+
41
+ context 'without combined experiments' do
42
+ let!(:combined_experiments) { nil }
43
+
44
+ it "raises an error" do
45
+ expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError )
46
+ end
47
+ end
48
+
49
+ it "uses same alternative for all sub experiments and returns the alternative" do
50
+ allow(self).to receive(:get_alternative) { "test-alt" }
51
+ expect(self).to receive(:ab_test).with(:exp_1_click, {"control"=>0.5}, {"test-alt"=>0.5}) { "test-alt" }
52
+ expect(self).to receive(:ab_test).with(:exp_1_scroll, [{"control" => 0, "test-alt" => 1}])
53
+
54
+ expect(ab_combined_test('combined_exp_1')).to eq('test-alt')
55
+ end
56
+ end
57
+ end
@@ -24,5 +24,19 @@ describe Split::DashboardHelpers do
24
24
  expect(confidence_level(2.58)).to eq('99% confidence')
25
25
  expect(confidence_level(3.00)).to eq('99% confidence')
26
26
  end
27
+
28
+ describe '#round' do
29
+ it 'can round number strings' do
30
+ expect(round('3.1415')).to eq BigDecimal.new('3.14')
31
+ end
32
+
33
+ it 'can round number strings for precsion' do
34
+ expect(round('3.1415', 1)).to eq BigDecimal.new('3.1')
35
+ end
36
+
37
+ it 'can handle invalid number strings' do
38
+ expect(round('N/A')).to be_zero
39
+ end
40
+ end
27
41
  end
28
42
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  require 'spec_helper'
3
- require 'split/experiment'
4
- require 'split/algorithms'
5
3
  require 'time'
6
4
 
7
5
  describe Split::Experiment do
@@ -460,7 +458,7 @@ describe Split::Experiment do
460
458
  expect(experiment.alternatives[0].p_winner).to be_within(0.04).of(0.50)
461
459
  end
462
460
 
463
- it "should calculate the probability of being the winning alternative separately for each goal" do
461
+ it "should calculate the probability of being the winning alternative separately for each goal", :skip => true do
464
462
  experiment = Split::ExperimentCatalog.find_or_create({'link_color3' => ["purchase", "refund"]}, 'blue', 'red', 'green')
465
463
  goal1 = experiment.goals[0]
466
464
  goal2 = experiment.goals[1]
data/spec/helper_spec.rb CHANGED
@@ -35,6 +35,18 @@ describe Split::Helper do
35
35
  expect(lambda { ab_test({'link_color' => "purchase"}, 'blue', 'red') }).not_to raise_error
36
36
  end
37
37
 
38
+ it "raises an appropriate error when processing combined expirements" do
39
+ Split.configuration.experiments = {
40
+ :combined_exp_1 => {
41
+ :alternatives => [ { name: "control", percent: 50 }, { name: "test-alt", percent: 50 } ],
42
+ :metric => :my_metric,
43
+ :combined_experiments => [:combined_exp_1_sub_1]
44
+ }
45
+ }
46
+ Split::ExperimentCatalog.find_or_create('combined_exp_1')
47
+ expect(lambda { ab_test('combined_exp_1')}).to raise_error(Split::InvalidExperimentsFormatError )
48
+ end
49
+
38
50
  it "should assign a random alternative to a new user when there are an equal number of alternatives assigned" do
39
51
  ab_test('link_color', 'blue', 'red')
40
52
  expect(['red', 'blue']).to include(ab_user['link_color'])
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
  require "spec_helper"
3
+ require 'rack/test'
3
4
 
4
5
  describe Split::Persistence::CookieAdapter do
5
6
 
6
- let(:context) { double(:cookies => CookiesMock.new) }
7
+ let(:env) { Rack::MockRequest.env_for("http://example.com:8080/") }
8
+ let(:request) { Rack::Request.new(env) }
9
+ let(:response) { Rack::MockResponse.new(200, {}, "") }
10
+ let(:context) { double(request: request, response: response) }
7
11
  subject { Split::Persistence::CookieAdapter.new(context) }
8
12
 
9
13
  describe "#[] and #[]=" do
@@ -30,7 +34,7 @@ describe Split::Persistence::CookieAdapter do
30
34
  end
31
35
 
32
36
  it "handles invalid JSON" do
33
- context.cookies[:split] = { :value => '{"foo":2,', :expires => Time.now }
37
+ context.request.cookies[:split] = { :value => '{"foo":2,', :expires => Time.now }
34
38
  expect(subject["my_key"]).to be_nil
35
39
  subject["my_key"] = "my_value"
36
40
  expect(subject["my_key"]).to eq("my_value")
@@ -47,7 +47,7 @@ describe Split::Persistence::DualAdapter do
47
47
  context "when logged in" do
48
48
  subject {
49
49
  described_class.with_config(
50
- logged_in: -> (context) { true },
50
+ logged_in: lambda { |context| true },
51
51
  logged_in_adapter: selected_adapter,
52
52
  logged_out_adapter: not_selected_adapter
53
53
  ).new(context)
@@ -59,7 +59,7 @@ describe Split::Persistence::DualAdapter do
59
59
  context "when not logged in" do
60
60
  subject {
61
61
  described_class.with_config(
62
- logged_in: -> (context) { false },
62
+ logged_in: lambda { |context| false },
63
63
  logged_in_adapter: not_selected_adapter,
64
64
  logged_out_adapter: selected_adapter
65
65
  ).new(context)
data/split.gemspec CHANGED
@@ -12,7 +12,17 @@ Gem::Specification.new do |s|
12
12
  s.homepage = "https://github.com/splitrb/split"
13
13
  s.summary = "Rack based split testing framework"
14
14
 
15
- s.required_ruby_version = '>= 1.9.2'
15
+ s.metadata = {
16
+ "homepage_uri" => "https://github.com/splitrb/split",
17
+ "changelog_uri" => "https://github.com/splitrb/split/blob/master/CHANGELOG.md",
18
+ "source_code_uri" => "https://github.com/splitrb/split",
19
+ "bug_tracker_uri" => "https://github.com/splitrb/split/issues",
20
+ "wiki_uri" => "https://github.com/splitrb/split/wiki",
21
+ "mailing_list_uri" => "https://groups.google.com/d/forum/split-ruby"
22
+ }
23
+
24
+ s.required_ruby_version = '>= 1.9.3'
25
+ s.required_rubygems_version = '>= 2.0.0'
16
26
 
17
27
  s.rubyforge_project = "split"
18
28
 
@@ -24,10 +34,10 @@ Gem::Specification.new do |s|
24
34
  s.add_dependency 'sinatra', '>= 1.2.6'
25
35
  s.add_dependency 'simple-random', '>= 0.9.3'
26
36
 
27
- s.add_development_dependency 'bundler', '~> 1.10'
37
+ s.add_development_dependency 'bundler', '~> 1.14'
28
38
  s.add_development_dependency 'simplecov', '~> 0.12'
29
39
  s.add_development_dependency 'rack-test', '~> 0.6'
30
- s.add_development_dependency 'rake', '~> 11.1'
40
+ s.add_development_dependency 'rake', '~> 12'
31
41
  s.add_development_dependency 'rspec', '~> 3.4'
32
42
  s.add_development_dependency 'pry', '~> 0.10'
33
43
  s.add_development_dependency 'fakeredis', '~> 0.6.0'