split 2.2.0 → 3.3.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.
Files changed (50) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +2 -2
  3. data/.travis.yml +39 -3
  4. data/Appraisals +8 -5
  5. data/CHANGELOG.md +59 -0
  6. data/CONTRIBUTING.md +54 -5
  7. data/LICENSE +1 -1
  8. data/README.md +193 -113
  9. data/gemfiles/4.2.gemfile +1 -1
  10. data/gemfiles/5.0.gemfile +1 -2
  11. data/gemfiles/{4.1.gemfile → 5.1.gemfile} +2 -2
  12. data/gemfiles/5.2.gemfile +9 -0
  13. data/lib/split/algorithms/block_randomization.rb +22 -0
  14. data/lib/split/alternative.rb +32 -7
  15. data/lib/split/combined_experiments_helper.rb +37 -0
  16. data/lib/split/configuration.rb +8 -0
  17. data/lib/split/dashboard/helpers.rb +5 -1
  18. data/lib/split/dashboard/pagination_helpers.rb +87 -0
  19. data/lib/split/dashboard/paginator.rb +16 -0
  20. data/lib/split/dashboard/public/style.css +9 -0
  21. data/lib/split/dashboard/views/_experiment.erb +31 -1
  22. data/lib/split/dashboard/views/index.erb +5 -1
  23. data/lib/split/dashboard.rb +2 -0
  24. data/lib/split/encapsulated_helper.rb +2 -0
  25. data/lib/split/engine.rb +2 -0
  26. data/lib/split/experiment.rb +5 -4
  27. data/lib/split/helper.rb +34 -1
  28. data/lib/split/persistence/cookie_adapter.rb +53 -15
  29. data/lib/split/persistence/dual_adapter.rb +3 -0
  30. data/lib/split/persistence.rb +5 -3
  31. data/lib/split/redis_interface.rb +1 -3
  32. data/lib/split/user.rb +2 -0
  33. data/lib/split/version.rb +2 -2
  34. data/lib/split/zscore.rb +1 -1
  35. data/lib/split.rb +21 -19
  36. data/spec/algorithms/block_randomization_spec.rb +32 -0
  37. data/spec/alternative_spec.rb +43 -0
  38. data/spec/combined_experiments_helper_spec.rb +57 -0
  39. data/spec/dashboard/pagination_helpers_spec.rb +198 -0
  40. data/spec/dashboard/paginator_spec.rb +37 -0
  41. data/spec/dashboard_helpers_spec.rb +14 -0
  42. data/spec/experiment_spec.rb +1 -3
  43. data/spec/helper_spec.rb +20 -0
  44. data/spec/persistence/cookie_adapter_spec.rb +90 -23
  45. data/spec/persistence/dual_adapter_spec.rb +2 -2
  46. data/spec/split_spec.rb +7 -7
  47. data/split.gemspec +16 -6
  48. metadata +36 -19
  49. data/lib/split/algorithms.rb +0 -4
  50. data/lib/split/extensions.rb +0 -4
@@ -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
@@ -0,0 +1,198 @@
1
+ require 'spec_helper'
2
+ require 'split/dashboard/pagination_helpers'
3
+
4
+ describe Split::DashboardPaginationHelpers do
5
+ include Split::DashboardPaginationHelpers
6
+
7
+ let(:url) { '/split/' }
8
+
9
+ describe '#pagination_per' do
10
+ context 'when params empty' do
11
+ let(:params) { Hash[] }
12
+
13
+ it 'returns 10' do
14
+ expect(pagination_per).to eql 10
15
+ end
16
+ end
17
+
18
+ context 'when params[:per] is 5' do
19
+ let(:params) { Hash[per: 5] }
20
+
21
+ it 'returns 5' do
22
+ expect(pagination_per).to eql 5
23
+ end
24
+ end
25
+ end
26
+
27
+ describe '#page_number' do
28
+ context 'when params empty' do
29
+ let(:params) { Hash[] }
30
+
31
+ it 'returns 1' do
32
+ expect(page_number).to eql 1
33
+ end
34
+ end
35
+
36
+ context 'when params[:page] is "2"' do
37
+ let(:params) { Hash[page: '2'] }
38
+
39
+ it 'returns 2' do
40
+ expect(page_number).to eql 2
41
+ end
42
+ end
43
+ end
44
+
45
+ describe '#paginated' do
46
+ let(:collection) { (1..20).to_a }
47
+ let(:params) { Hash[per: '5', page: '3'] }
48
+
49
+ it { expect(paginated(collection)).to eql [11, 12, 13, 14, 15] }
50
+ end
51
+
52
+ describe '#show_first_page_tag?' do
53
+ context 'when page is 1' do
54
+ it { expect(show_first_page_tag?).to be false }
55
+ end
56
+
57
+ context 'when page is 3' do
58
+ let(:params) { Hash[page: '3'] }
59
+ it { expect(show_first_page_tag?).to be true }
60
+ end
61
+ end
62
+
63
+ describe '#first_page_tag' do
64
+ it { expect(first_page_tag).to eql '<a href="/split?page=1&per=10">1</a>' }
65
+ end
66
+
67
+ describe '#show_first_ellipsis_tag?' do
68
+ context 'when page is 1' do
69
+ it { expect(show_first_ellipsis_tag?).to be false }
70
+ end
71
+
72
+ context 'when page is 4' do
73
+ let(:params) { Hash[page: '4'] }
74
+ it { expect(show_first_ellipsis_tag?).to be true }
75
+ end
76
+ end
77
+
78
+ describe '#ellipsis_tag' do
79
+ it { expect(ellipsis_tag).to eql '<span>...</span>' }
80
+ end
81
+
82
+ describe '#show_prev_page_tag?' do
83
+ context 'when page is 1' do
84
+ it { expect(show_prev_page_tag?).to be false }
85
+ end
86
+
87
+ context 'when page is 2' do
88
+ let(:params) { Hash[page: '2'] }
89
+ it { expect(show_prev_page_tag?).to be true }
90
+ end
91
+ end
92
+
93
+ describe '#prev_page_tag' do
94
+ context 'when page is 2' do
95
+ let(:params) { Hash[page: '2'] }
96
+
97
+ it do
98
+ expect(prev_page_tag).to eql '<a href="/split?page=1&per=10">1</a>'
99
+ end
100
+ end
101
+
102
+ context 'when page is 3' do
103
+ let(:params) { Hash[page: '3'] }
104
+
105
+ it do
106
+ expect(prev_page_tag).to eql '<a href="/split?page=2&per=10">2</a>'
107
+ end
108
+ end
109
+ end
110
+
111
+ describe '#show_prev_page_tag?' do
112
+ context 'when page is 1' do
113
+ it { expect(show_prev_page_tag?).to be false }
114
+ end
115
+
116
+ context 'when page is 2' do
117
+ let(:params) { Hash[page: '2'] }
118
+ it { expect(show_prev_page_tag?).to be true }
119
+ end
120
+ end
121
+
122
+ describe '#current_page_tag' do
123
+ context 'when page is 1' do
124
+ let(:params) { Hash[page: '1'] }
125
+ it { expect(current_page_tag).to eql '<span><b>1</b></span>' }
126
+ end
127
+
128
+ context 'when page is 2' do
129
+ let(:params) { Hash[page: '2'] }
130
+ it { expect(current_page_tag).to eql '<span><b>2</b></span>' }
131
+ end
132
+ end
133
+
134
+ describe '#show_next_page_tag?' do
135
+ context 'when page is 2' do
136
+ let(:params) { Hash[page: '2'] }
137
+
138
+ context 'when collection length is 20' do
139
+ let(:collection) { (1..20).to_a }
140
+ it { expect(show_next_page_tag?(collection)).to be false }
141
+ end
142
+
143
+ context 'when collection length is 25' do
144
+ let(:collection) { (1..25).to_a }
145
+ it { expect(show_next_page_tag?(collection)).to be true }
146
+ end
147
+ end
148
+ end
149
+
150
+ describe '#next_page_tag' do
151
+ context 'when page is 1' do
152
+ let(:params) { Hash[page: '1'] }
153
+ it { expect(next_page_tag).to eql '<a href="/split?page=2&per=10">2</a>' }
154
+ end
155
+
156
+ context 'when page is 2' do
157
+ let(:params) { Hash[page: '2'] }
158
+ it { expect(next_page_tag).to eql '<a href="/split?page=3&per=10">3</a>' }
159
+ end
160
+ end
161
+
162
+ describe '#total_pages' do
163
+ context 'when collection length is 30' do
164
+ let(:collection) { (1..30).to_a }
165
+ it { expect(total_pages(collection)).to eql 3 }
166
+ end
167
+
168
+ context 'when collection length is 35' do
169
+ let(:collection) { (1..35).to_a }
170
+ it { expect(total_pages(collection)).to eql 4 }
171
+ end
172
+ end
173
+
174
+ describe '#show_last_ellipsis_tag?' do
175
+ let(:collection) { (1..30).to_a }
176
+ let(:params) { Hash[per: '5', page: '2'] }
177
+ it { expect(show_last_ellipsis_tag?(collection)).to be true }
178
+ end
179
+
180
+ describe '#show_last_page_tag?' do
181
+ let(:collection) { (1..30).to_a }
182
+
183
+ context 'when page is 5/6' do
184
+ let(:params) { Hash[per: '5', page: '5'] }
185
+ it { expect(show_last_page_tag?(collection)).to be false }
186
+ end
187
+
188
+ context 'when page is 4/6' do
189
+ let(:params) { Hash[per: '5', page: '4'] }
190
+ it { expect(show_last_page_tag?(collection)).to be true }
191
+ end
192
+ end
193
+
194
+ describe '#last_page_tag' do
195
+ let(:collection) { (1..30).to_a }
196
+ it { expect(last_page_tag(collection)).to eql '<a href="/split?page=3&per=10">3</a>' }
197
+ end
198
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+ require 'split/dashboard/paginator'
4
+
5
+ describe Split::DashboardPaginator do
6
+ context 'when collection is 1..20' do
7
+ let(:collection) { (1..20).to_a }
8
+
9
+ context 'when per 5 for page' do
10
+ let(:per) { 5 }
11
+
12
+ it 'when page number is 1 result is [1, 2, 3, 4, 5]' do
13
+ result = Split::DashboardPaginator.new(collection, 1, per).paginate
14
+ expect(result).to eql [1, 2, 3, 4, 5]
15
+ end
16
+
17
+ it 'when page number is 2 result is [6, 7, 8, 9, 10]' do
18
+ result = Split::DashboardPaginator.new(collection, 2, per).paginate
19
+ expect(result).to eql [6, 7, 8, 9, 10]
20
+ end
21
+ end
22
+
23
+ context 'when per 10 for page' do
24
+ let(:per) { 10 }
25
+
26
+ it 'when page number is 1 result is [1..10]' do
27
+ result = Split::DashboardPaginator.new(collection, 1, per).paginate
28
+ expect(result).to eql [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
29
+ end
30
+
31
+ it 'when page number is 2 result is [10..20]' do
32
+ result = Split::DashboardPaginator.new(collection, 2, per).paginate
33
+ expect(result).to eql [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
34
+ end
35
+ end
36
+ end
37
+ 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'])
@@ -687,6 +699,14 @@ describe Split::Helper do
687
699
  end
688
700
  end
689
701
 
702
+ describe 'when user is previewing' do
703
+ before(:each) do
704
+ @request = OpenStruct.new(headers: { 'x-purpose' => 'preview' })
705
+ end
706
+
707
+ it_behaves_like "a disabled test"
708
+ end
709
+
690
710
  describe 'versioned experiments' do
691
711
  it "should use version zero if no version is present" do
692
712
  alternative_name = ab_test('link_color', 'blue', 'red')
@@ -1,39 +1,106 @@
1
1
  # frozen_string_literal: true
2
2
  require "spec_helper"
3
+ require 'rack/test'
3
4
 
4
5
  describe Split::Persistence::CookieAdapter do
6
+ subject { described_class.new(context) }
5
7
 
6
- let(:context) { double(:cookies => CookiesMock.new) }
7
- subject { Split::Persistence::CookieAdapter.new(context) }
8
+ shared_examples "sets cookies correctly" do
9
+ describe "#[] and #[]=" do
10
+ it "set and return the value for given key" do
11
+ subject["my_key"] = "my_value"
12
+ expect(subject["my_key"]).to eq("my_value")
13
+ end
8
14
 
9
- describe "#[] and #[]=" do
10
- it "should set and return the value for given key" do
11
- subject["my_key"] = "my_value"
12
- expect(subject["my_key"]).to eq("my_value")
15
+ it "handles invalid JSON" do
16
+ context.request.cookies[:split] = {
17
+ :value => '{"foo":2,',
18
+ :expires => Time.now
19
+ }
20
+ expect(subject["my_key"]).to be_nil
21
+ subject["my_key"] = "my_value"
22
+ expect(subject["my_key"]).to eq("my_value")
23
+ end
13
24
  end
14
- end
15
25
 
16
- describe "#delete" do
17
- it "should delete the given key" do
18
- subject["my_key"] = "my_value"
19
- subject.delete("my_key")
20
- expect(subject["my_key"]).to be_nil
26
+ describe "#delete" do
27
+ it "should delete the given key" do
28
+ subject["my_key"] = "my_value"
29
+ subject.delete("my_key")
30
+ expect(subject["my_key"]).to be_nil
31
+ end
21
32
  end
22
- end
23
33
 
24
- describe "#keys" do
25
- it "should return an array of the session's stored keys" do
26
- subject["my_key"] = "my_value"
27
- subject["my_second_key"] = "my_second_value"
28
- expect(subject.keys).to match(["my_key", "my_second_key"])
34
+ describe "#keys" do
35
+ it "should return an array of the session's stored keys" do
36
+ subject["my_key"] = "my_value"
37
+ subject["my_second_key"] = "my_second_value"
38
+ expect(subject.keys).to match(["my_key", "my_second_key"])
39
+ end
29
40
  end
30
41
  end
31
42
 
32
- it "handles invalid JSON" do
33
- context.cookies[:split] = { :value => '{"foo":2,', :expires => Time.now }
34
- expect(subject["my_key"]).to be_nil
35
- subject["my_key"] = "my_value"
36
- expect(subject["my_key"]).to eq("my_value")
43
+
44
+ context "when using Rack" do
45
+ let(:env) { Rack::MockRequest.env_for("http://example.com:8080/") }
46
+ let(:request) { Rack::Request.new(env) }
47
+ let(:response) { Rack::MockResponse.new(200, {}, "") }
48
+ let(:context) { double(request: request, response: response, cookies: CookiesMock.new) }
49
+
50
+ include_examples "sets cookies correctly"
51
+
52
+ it "puts multiple experiments in a single cookie" do
53
+ subject["foo"] = "FOO"
54
+ subject["bar"] = "BAR"
55
+ 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} -0000\Z/)
56
+ end
57
+
58
+ it "ensure other added cookies are not overriden" do
59
+ context.response.set_cookie 'dummy', 'wow'
60
+ subject["foo"] = "FOO"
61
+ expect(context.response.headers["Set-Cookie"]).to include("dummy=wow")
62
+ expect(context.response.headers["Set-Cookie"]).to include("split=")
63
+ end
37
64
  end
38
65
 
66
+ context "when @context is an ActionController::Base" do
67
+ before :context do
68
+ require "rails"
69
+ require "action_controller/railtie"
70
+ end
71
+
72
+ let(:context) do
73
+ controller = controller_class.new
74
+ if controller.respond_to?(:set_request!)
75
+ controller.set_request!(ActionDispatch::Request.new({}))
76
+ else # Before rails 5.0
77
+ controller.send(:"request=", ActionDispatch::Request.new({}))
78
+ end
79
+
80
+ response = ActionDispatch::Response.new(200, {}, '').tap do |res|
81
+ res.request = controller.request
82
+ end
83
+
84
+ if controller.respond_to?(:set_response!)
85
+ controller.set_response!(response)
86
+ else # Before rails 5.0
87
+ controller.send(:set_response!, response)
88
+ end
89
+ controller
90
+ end
91
+
92
+ let(:controller_class) { Class.new(ActionController::Base) }
93
+
94
+ include_examples "sets cookies correctly"
95
+
96
+ it "puts multiple experiments in a single cookie" do
97
+ subject["foo"] = "FOO"
98
+ subject["bar"] = "BAR"
99
+ expect(subject.keys).to eq(["foo", "bar"])
100
+ expect(subject["foo"]).to eq("FOO")
101
+ expect(subject["bar"]).to eq("BAR")
102
+ cookie_jar = context.request.env["action_dispatch.cookies"]
103
+ expect(cookie_jar['split']).to eq("{\"foo\":\"FOO\",\"bar\":\"BAR\"}")
104
+ end
105
+ end
39
106
  end
@@ -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/spec/split_spec.rb CHANGED
@@ -15,19 +15,19 @@ RSpec.describe Split do
15
15
  Split.redis = 'redis://localhost:6379'
16
16
  expect(Split.redis).to be_a(Redis)
17
17
 
18
- client = Split.redis.client
19
- expect(client.host).to eq("localhost")
20
- expect(client.port).to eq(6379)
18
+ client = Split.redis.connection
19
+ expect(client[:host]).to eq("localhost")
20
+ expect(client[:port]).to eq(6379)
21
21
  end
22
22
 
23
23
  it 'accepts an options hash' do
24
24
  Split.redis = {host: 'localhost', port: 6379, db: 12}
25
25
  expect(Split.redis).to be_a(Redis)
26
26
 
27
- client = Split.redis.client
28
- expect(client.host).to eq("localhost")
29
- expect(client.port).to eq(6379)
30
- expect(client.db).to eq(12)
27
+ client = Split.redis.connection
28
+ expect(client[:host]).to eq("localhost")
29
+ expect(client[:port]).to eq(6379)
30
+ expect(client[:db]).to eq(12)
31
31
  end
32
32
 
33
33
  it 'accepts a valid Redis instance' do
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,11 +34,11 @@ 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'
28
- s.add_development_dependency 'simplecov', '~> 0.12'
37
+ s.add_development_dependency 'bundler', '~> 1.14'
38
+ s.add_development_dependency 'simplecov', '~> 0.15'
29
39
  s.add_development_dependency 'rack-test', '~> 0.6'
30
- s.add_development_dependency 'rake', '~> 11.1'
31
- s.add_development_dependency 'rspec', '~> 3.4'
40
+ s.add_development_dependency 'rake', '~> 12'
41
+ s.add_development_dependency 'rspec', '~> 3.7'
32
42
  s.add_development_dependency 'pry', '~> 0.10'
33
- s.add_development_dependency 'fakeredis', '~> 0.6.0'
43
+ s.add_development_dependency 'fakeredis', '~> 0.7'
34
44
  end