split 2.2.0 → 3.1.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.
@@ -1,15 +1,10 @@
1
1
  # frozen_string_literal: true
2
- require 'split/zscore'
3
-
4
- # TODO - take out require and implement using file paths?
5
-
6
2
  module Split
7
3
  class Alternative
8
4
  attr_accessor :name
9
5
  attr_accessor :experiment_name
10
6
  attr_accessor :weight
11
-
12
- include Zscore
7
+ attr_accessor :recorded_info
13
8
 
14
9
  def initialize(name, experiment_name)
15
10
  @experiment_name = experiment_name
@@ -127,10 +122,37 @@ module Split
127
122
  z_score = Split::Zscore.calculate(p_a, n_a, p_c, n_c)
128
123
  end
129
124
 
125
+ def extra_info
126
+ data = Split.redis.hget(key, 'recorded_info')
127
+ if data && data.length > 1
128
+ begin
129
+ JSON.parse(data)
130
+ rescue
131
+ {}
132
+ end
133
+ else
134
+ {}
135
+ end
136
+ end
137
+
138
+ def record_extra_info(k, value = 1)
139
+ @recorded_info = self.extra_info || {}
140
+
141
+ if value.kind_of?(Numeric)
142
+ @recorded_info[k] ||= 0
143
+ @recorded_info[k] += value
144
+ else
145
+ @recorded_info[k] = value
146
+ end
147
+
148
+ Split.redis.hset key, 'recorded_info', (@recorded_info || {}).to_json
149
+ end
150
+
130
151
  def save
131
152
  Split.redis.hsetnx key, 'participant_count', 0
132
153
  Split.redis.hsetnx key, 'completed_count', 0
133
154
  Split.redis.hsetnx key, 'p_winner', p_winner
155
+ Split.redis.hsetnx key, 'recorded_info', (@recorded_info || {}).to_json
134
156
  end
135
157
 
136
158
  def validate!
@@ -140,7 +162,7 @@ module Split
140
162
  end
141
163
 
142
164
  def reset
143
- Split.redis.hmset key, 'participant_count', 0, 'completed_count', 0
165
+ Split.redis.hmset key, 'participant_count', 0, 'completed_count', 0, 'recorded_info', nil
144
166
  unless goals.empty?
145
167
  goals.each do |g|
146
168
  field = "completed_count:#{g}"
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ module Split
3
+ module CombinedExperimentsHelper
4
+ def ab_combined_test(metric_descriptor, control = nil, *alternatives)
5
+ return nil unless experiment = find_combined_experiment(metric_descriptor)
6
+ raise(Split::InvalidExperimentsFormatError, 'Unable to find experiment #{metric_descriptor} in configuration') if experiment[:combined_experiments].nil?
7
+
8
+ alternative = nil
9
+ experiment[:combined_experiments].each do |combined_experiment|
10
+ if alternative.nil?
11
+ if control
12
+ alternative = ab_test(combined_experiment, control, alternatives)
13
+ else
14
+ normalized_alternatives = Split::Configuration.new.normalize_alternatives(experiment[:alternatives])
15
+ alternative = ab_test(combined_experiment, normalized_alternatives[0], *normalized_alternatives[1])
16
+ end
17
+ else
18
+ ab_test(combined_experiment, [{alternative => 1}])
19
+ end
20
+ end
21
+ end
22
+
23
+ def find_combined_experiment(metric_descriptor)
24
+ raise(Split::InvalidExperimentsFormatError, 'Invalid descriptor class (String or Symbol required)') unless metric_descriptor.class == String || metric_descriptor.class == Symbol
25
+ raise(Split::InvalidExperimentsFormatError, 'Enable configuration') unless Split.configuration.enabled
26
+ raise(Split::InvalidExperimentsFormatError, 'Enable `allow_multiple_experiments`') unless Split.configuration.allow_multiple_experiments
27
+ experiment = Split::configuration.experiments[metric_descriptor.to_sym]
28
+ end
29
+ end
30
+ end
@@ -48,7 +48,9 @@ module Split
48
48
  'spider' => 'generic web spider',
49
49
  'UnwindFetchor' => 'Gnip crawler',
50
50
  'WordPress' => 'WordPress spider',
51
+ 'YandexAccessibilityBot' => 'Yandex accessibility spider',
51
52
  'YandexBot' => 'Yandex spider',
53
+ 'YandexMobileBot' => 'Yandex mobile spider',
52
54
  'ZIBB' => 'ZIBB spider',
53
55
 
54
56
  # HTTP libraries
@@ -73,10 +75,13 @@ module Split
73
75
  'facebookexternalhit' => 'facebook bot',
74
76
  'Feedfetcher-Google' => 'Google Feedfetcher',
75
77
  'https://developers.google.com/+/web/snippet' => 'Google+ Snippet Fetcher',
78
+ 'LinkedInBot' => 'LinkedIn bot',
76
79
  'LongURL' => 'URL expander service',
77
80
  'NING' => 'NING - Yet Another Twitter Swarmer',
81
+ 'Pinterest' => 'Pinterest Bot',
78
82
  'redditbot' => 'Reddit Bot',
79
83
  'ShortLinkTranslate' => 'Link shortener',
84
+ 'Slackbot' => 'Slackbot link expander',
80
85
  'TweetmemeBot' => 'TweetMeMe Crawler',
81
86
  'Twitterbot' => 'Twitter URL expander',
82
87
  'UnwindFetch' => 'Gnip URL expander',
@@ -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
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&.dig(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,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module Split
3
- MAJOR = 2
4
- MINOR = 2
3
+ MAJOR = 3
4
+ MINOR = 1
5
5
  PATCH = 0
6
6
  VERSION = [MAJOR, MINOR, PATCH].join('.')
7
7
  end
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
@@ -274,4 +274,35 @@ describe Split::Alternative do
274
274
  expect(control.z_score(goal2)).to eq('N/A')
275
275
  end
276
276
  end
277
+
278
+ describe "extra_info" do
279
+ it "reads saved value of recorded_info in redis" do
280
+ saved_recorded_info = {"key_1" => 1, "key_2" => "2"}
281
+ Split.redis.hset "#{alternative.experiment_name}:#{alternative.name}", 'recorded_info', saved_recorded_info.to_json
282
+ extra_info = alternative.extra_info
283
+
284
+ expect(extra_info).to eql(saved_recorded_info)
285
+ end
286
+ end
287
+
288
+ describe "record_extra_info" do
289
+ it "saves key" do
290
+ alternative.record_extra_info("signup", 1)
291
+ expect(alternative.extra_info["signup"]).to eql(1)
292
+ end
293
+
294
+ it "adds value to saved key's value second argument is number" do
295
+ alternative.record_extra_info("signup", 1)
296
+ alternative.record_extra_info("signup", 2)
297
+ expect(alternative.extra_info["signup"]).to eql(3)
298
+ end
299
+
300
+ it "sets saved's key value to the second argument if it's a string" do
301
+ alternative.record_extra_info("signup", "Value 1")
302
+ expect(alternative.extra_info["signup"]).to eql("Value 1")
303
+
304
+ alternative.record_extra_info("signup", "Value 2")
305
+ expect(alternative.extra_info["signup"]).to eql("Value 2")
306
+ end
307
+ end
277
308
  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 alternatives for all sub experiments " 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, [{"test-alt" => 1}] )
53
+
54
+ ab_combined_test('combined_exp_1')
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
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")
data/split.gemspec CHANGED
@@ -12,6 +12,15 @@ 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.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
+
15
24
  s.required_ruby_version = '>= 1.9.2'
16
25
 
17
26
  s.rubyforge_project = "split"
@@ -24,10 +33,10 @@ Gem::Specification.new do |s|
24
33
  s.add_dependency 'sinatra', '>= 1.2.6'
25
34
  s.add_dependency 'simple-random', '>= 0.9.3'
26
35
 
27
- s.add_development_dependency 'bundler', '~> 1.10'
36
+ s.add_development_dependency 'bundler', '~> 1.14'
28
37
  s.add_development_dependency 'simplecov', '~> 0.12'
29
38
  s.add_development_dependency 'rack-test', '~> 0.6'
30
- s.add_development_dependency 'rake', '~> 11.1'
39
+ s.add_development_dependency 'rake', '~> 12'
31
40
  s.add_development_dependency 'rspec', '~> 3.4'
32
41
  s.add_development_dependency 'pry', '~> 0.10'
33
42
  s.add_development_dependency 'fakeredis', '~> 0.6.0'