grape 0.16.2 → 0.17.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.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +4 -0
  3. data/CHANGELOG.md +54 -27
  4. data/Dangerfile +80 -0
  5. data/Gemfile +23 -0
  6. data/Gemfile.lock +61 -27
  7. data/README.md +135 -7
  8. data/Rakefile +34 -30
  9. data/UPGRADING.md +21 -0
  10. data/gemfiles/rack_1.5.2.gemfile +21 -0
  11. data/gemfiles/rails_3.gemfile +22 -1
  12. data/gemfiles/rails_4.gemfile +21 -0
  13. data/gemfiles/rails_5.gemfile +34 -0
  14. data/grape.gemspec +0 -14
  15. data/lib/grape.rb +2 -0
  16. data/lib/grape/api.rb +9 -2
  17. data/lib/grape/dsl/headers.rb +1 -1
  18. data/lib/grape/dsl/inside_route.rb +15 -17
  19. data/lib/grape/dsl/middleware.rb +15 -1
  20. data/lib/grape/dsl/parameters.rb +16 -14
  21. data/lib/grape/dsl/request_response.rb +24 -20
  22. data/lib/grape/dsl/routing.rb +11 -10
  23. data/lib/grape/dsl/settings.rb +16 -0
  24. data/lib/grape/endpoint.rb +77 -60
  25. data/lib/grape/exceptions/validation.rb +5 -2
  26. data/lib/grape/exceptions/validation_array_errors.rb +11 -0
  27. data/lib/grape/formatter/xml.rb +1 -1
  28. data/lib/grape/middleware/error.rb +34 -25
  29. data/lib/grape/middleware/formatter.rb +9 -9
  30. data/lib/grape/middleware/stack.rb +110 -0
  31. data/lib/grape/middleware/versioner.rb +1 -1
  32. data/lib/grape/middleware/versioner/accept_version_header.rb +1 -1
  33. data/lib/grape/middleware/versioner/header.rb +3 -3
  34. data/lib/grape/path.rb +10 -2
  35. data/lib/grape/request.rb +1 -1
  36. data/lib/grape/router.rb +10 -19
  37. data/lib/grape/router/pattern.rb +2 -2
  38. data/lib/grape/router/route.rb +3 -3
  39. data/lib/grape/util/content_types.rb +1 -1
  40. data/lib/grape/util/inheritable_setting.rb +7 -2
  41. data/lib/grape/util/reverse_stackable_values.rb +45 -0
  42. data/lib/grape/util/stackable_values.rb +10 -11
  43. data/lib/grape/validations/attributes_iterator.rb +32 -7
  44. data/lib/grape/validations/params_scope.rb +33 -21
  45. data/lib/grape/validations/types.rb +4 -4
  46. data/lib/grape/validations/types/build_coercer.rb +9 -1
  47. data/lib/grape/validations/validators/all_or_none.rb +2 -2
  48. data/lib/grape/validations/validators/allow_blank.rb +10 -11
  49. data/lib/grape/validations/validators/at_least_one_of.rb +1 -1
  50. data/lib/grape/validations/validators/base.rb +16 -6
  51. data/lib/grape/validations/validators/coerce.rb +3 -6
  52. data/lib/grape/validations/validators/default.rb +26 -1
  53. data/lib/grape/validations/validators/exactly_one_of.rb +1 -1
  54. data/lib/grape/validations/validators/mutual_exclusion.rb +1 -1
  55. data/lib/grape/validations/validators/presence.rb +1 -1
  56. data/lib/grape/validations/validators/regexp.rb +1 -1
  57. data/lib/grape/validations/validators/values.rb +1 -1
  58. data/lib/grape/version.rb +1 -1
  59. data/spec/grape/api/custom_validations_spec.rb +3 -3
  60. data/spec/grape/api/parameters_modification_spec.rb +41 -0
  61. data/spec/grape/api_spec.rb +335 -108
  62. data/spec/grape/dsl/logger_spec.rb +1 -1
  63. data/spec/grape/dsl/middleware_spec.rb +25 -5
  64. data/spec/grape/dsl/request_response_spec.rb +20 -6
  65. data/spec/grape/dsl/validations_spec.rb +1 -1
  66. data/spec/grape/endpoint_spec.rb +166 -23
  67. data/spec/grape/entity_spec.rb +0 -2
  68. data/spec/grape/exceptions/body_parse_errors_spec.rb +37 -0
  69. data/spec/grape/exceptions/validation_errors_spec.rb +5 -5
  70. data/spec/grape/exceptions/validation_spec.rb +10 -0
  71. data/spec/grape/integration/global_namespace_function_spec.rb +1 -1
  72. data/spec/grape/integration/rack_spec.rb +1 -1
  73. data/spec/grape/middleware/base_spec.rb +1 -1
  74. data/spec/grape/middleware/exception_spec.rb +2 -2
  75. data/spec/grape/middleware/formatter_spec.rb +4 -4
  76. data/spec/grape/middleware/stack_spec.rb +123 -0
  77. data/spec/grape/middleware/versioner/header_spec.rb +6 -6
  78. data/spec/grape/request_spec.rb +22 -22
  79. data/spec/grape/util/inheritable_setting_spec.rb +23 -0
  80. data/spec/grape/util/reverse_stackable_values_spec.rb +131 -0
  81. data/spec/grape/validations/params_scope_spec.rb +88 -1
  82. data/spec/grape/validations/validators/allow_blank_spec.rb +5 -0
  83. data/spec/grape/validations/validators/coerce_spec.rb +5 -5
  84. data/spec/grape/validations/validators/default_spec.rb +44 -0
  85. data/spec/grape/validations/validators/values_spec.rb +1 -1
  86. data/spec/grape/validations_spec.rb +36 -17
  87. data/spec/spec_helper.rb +1 -8
  88. data/spec/support/versioned_helpers.rb +3 -3
  89. metadata +13 -188
  90. data/gemfiles/rails_3.gemfile.lock +0 -225
  91. data/pkg/grape-0.16.1.gem +0 -0
  92. data/pkg/patch.diff +0 -24
  93. data/tmp/Gemfile.lock +0 -63
@@ -22,7 +22,7 @@ describe Rack do
22
22
  unless RUBY_PLATFORM == 'java'
23
23
  major, minor, patch = Rack.release.split('.').map(&:to_i)
24
24
  patch ||= 0 # rack <= 1.5.2 does not specify patch version
25
- pending 'Rack 1.5.3 or 1.6.1 required' unless major >= 1 && ((minor == 5 && patch >= 3) || (minor >= 6))
25
+ pending 'Rack 1.5.3 or 1.6.1 required' unless major >= 2 || (major >= 1 && ((minor == 5 && patch >= 3) || (minor >= 6)))
26
26
  end
27
27
 
28
28
  expect(JSON.parse(app.call(env)[2].body.first)['params_keys']).to match_array('test')
@@ -30,7 +30,7 @@ describe Grape::Middleware::Base do
30
30
  end
31
31
 
32
32
  context 'callbacks on error' do
33
- let(:blank_app) { ->(_) { fail StandardError } }
33
+ let(:blank_app) { ->(_) { raise StandardError } }
34
34
 
35
35
  it 'calls #after' do
36
36
  expect(subject).to receive(:after)
@@ -6,7 +6,7 @@ describe Grape::Middleware::Error do
6
6
  class ExceptionApp
7
7
  class << self
8
8
  def call(_env)
9
- fail 'rain!'
9
+ raise 'rain!'
10
10
  end
11
11
  end
12
12
  end
@@ -44,7 +44,7 @@ describe Grape::Middleware::Error do
44
44
  class CustomErrorApp
45
45
  class << self
46
46
  def call(_env)
47
- fail CustomError, status: 400, message: 'failed validation'
47
+ raise CustomError, status: 400, message: 'failed validation'
48
48
  end
49
49
  end
50
50
  end
@@ -17,7 +17,7 @@ describe Grape::Middleware::Formatter do
17
17
  @body = ['foo']
18
18
  @body.instance_eval do
19
19
  def to_json
20
- "\"bar\""
20
+ '"bar"'
21
21
  end
22
22
  end
23
23
 
@@ -28,7 +28,7 @@ describe Grape::Middleware::Formatter do
28
28
  @body = { 'foos' => [{ 'bar' => 'baz' }] }
29
29
  @body.instance_eval do
30
30
  def to_json
31
- "{\"foos\":[{\"bar\":\"baz\"}] }"
31
+ '{"foos":[{"bar":"baz"}] }'
32
32
  end
33
33
  end
34
34
 
@@ -54,7 +54,7 @@ describe Grape::Middleware::Formatter do
54
54
  end
55
55
 
56
56
  it 'rescues formatter-specific exceptions' do
57
- allow(formatter).to receive(:call) { fail Grape::Exceptions::InvalidFormatter.new(String, 'xml') }
57
+ allow(formatter).to receive(:call) { raise Grape::Exceptions::InvalidFormatter.new(String, 'xml') }
58
58
 
59
59
  expect do
60
60
  catch(:error) { subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json') }
@@ -62,7 +62,7 @@ describe Grape::Middleware::Formatter do
62
62
  end
63
63
 
64
64
  it 'does not rescue other exceptions' do
65
- allow(formatter).to receive(:call) { fail StandardError }
65
+ allow(formatter).to receive(:call) { raise StandardError }
66
66
 
67
67
  expect do
68
68
  catch(:error) { subject.call('PATH_INFO' => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json') }
@@ -0,0 +1,123 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Middleware::Stack do
4
+ module StackSpec
5
+ class FooMiddleware; end
6
+ class BarMiddleware; end
7
+ class BlockMiddleware
8
+ attr_reader :block
9
+ def initialize(&block)
10
+ @block = block
11
+ end
12
+ end
13
+ end
14
+
15
+ let(:proc) { ->() {} }
16
+ let(:others) { [[:use, StackSpec::BarMiddleware], [:insert_before, StackSpec::BarMiddleware, StackSpec::BlockMiddleware, proc]] }
17
+
18
+ subject { Grape::Middleware::Stack.new }
19
+
20
+ before do
21
+ subject.use StackSpec::FooMiddleware
22
+ end
23
+
24
+ describe '#use' do
25
+ it 'pushes a middleware class onto the stack' do
26
+ expect { subject.use StackSpec::BarMiddleware }
27
+ .to change { subject.size }.by(1)
28
+ expect(subject.last).to eq(StackSpec::BarMiddleware)
29
+ end
30
+
31
+ it 'pushes a middleware class with arguments onto the stack' do
32
+ expect { subject.use StackSpec::BarMiddleware, false, my_arg: 42 }
33
+ .to change { subject.size }.by(1)
34
+ expect(subject.last).to eq(StackSpec::BarMiddleware)
35
+ expect(subject.last.args).to eq([false, { my_arg: 42 }])
36
+ end
37
+
38
+ it 'pushes a middleware class with block arguments onto the stack' do
39
+ expect { subject.use StackSpec::BlockMiddleware, &proc }
40
+ .to change { subject.size }.by(1)
41
+ expect(subject.last).to eq(StackSpec::BlockMiddleware)
42
+ expect(subject.last.args).to eq([])
43
+ expect(subject.last.block).to eq(proc)
44
+ end
45
+ end
46
+
47
+ describe '#insert' do
48
+ it 'inserts a middleware class at the integer index' do
49
+ expect { subject.insert 0, StackSpec::BarMiddleware }
50
+ .to change { subject.size }.by(1)
51
+ expect(subject[0]).to eq(StackSpec::BarMiddleware)
52
+ expect(subject[1]).to eq(StackSpec::FooMiddleware)
53
+ end
54
+ end
55
+
56
+ describe '#insert_before' do
57
+ it 'inserts a middleware before another middleware class' do
58
+ expect { subject.insert_before StackSpec::FooMiddleware, StackSpec::BarMiddleware }
59
+ .to change { subject.size }.by(1)
60
+ expect(subject[0]).to eq(StackSpec::BarMiddleware)
61
+ expect(subject[1]).to eq(StackSpec::FooMiddleware)
62
+ end
63
+
64
+ it 'raises an error on an invalid index' do
65
+ expect { subject.insert_before StackSpec::BlockMiddleware, StackSpec::BarMiddleware }
66
+ .to raise_error(RuntimeError, 'No such middleware to insert before: StackSpec::BlockMiddleware')
67
+ end
68
+ end
69
+
70
+ describe '#insert_after' do
71
+ it 'inserts a middleware after another middleware class' do
72
+ expect { subject.insert_after StackSpec::FooMiddleware, StackSpec::BarMiddleware }
73
+ .to change { subject.size }.by(1)
74
+ expect(subject[1]).to eq(StackSpec::BarMiddleware)
75
+ expect(subject[0]).to eq(StackSpec::FooMiddleware)
76
+ end
77
+
78
+ it 'raises an error on an invalid index' do
79
+ expect { subject.insert_after StackSpec::BlockMiddleware, StackSpec::BarMiddleware }
80
+ .to raise_error(RuntimeError, 'No such middleware to insert after: StackSpec::BlockMiddleware')
81
+ end
82
+ end
83
+
84
+ describe '#merge_with' do
85
+ it 'applies a collection of operations and middlewares' do
86
+ expect { subject.merge_with(others) }
87
+ .to change { subject.size }.by(2)
88
+ expect(subject[0]).to eq(StackSpec::FooMiddleware)
89
+ expect(subject[1]).to eq(StackSpec::BlockMiddleware)
90
+ expect(subject[2]).to eq(StackSpec::BarMiddleware)
91
+ end
92
+ end
93
+
94
+ describe '#build' do
95
+ it 'returns a rack builder instance' do
96
+ expect(subject.build).to be_instance_of(Rack::Builder)
97
+ end
98
+
99
+ context 'when @others are present' do
100
+ let(:others) { [[:insert_after, Grape::Middleware::Formatter, StackSpec::BarMiddleware]] }
101
+
102
+ it 'applies the middleware specs stored in @others' do
103
+ subject.concat others
104
+ subject.use Grape::Middleware::Formatter
105
+ subject.build
106
+ expect(subject[0]).to eq StackSpec::FooMiddleware
107
+ expect(subject[1]).to eq Grape::Middleware::Formatter
108
+ expect(subject[2]).to eq StackSpec::BarMiddleware
109
+ end
110
+ end
111
+ end
112
+
113
+ describe '#concat' do
114
+ it 'adds non :use specs to @others' do
115
+ expect { subject.concat others }.to change(subject, :others).from([]).to([[others.last]])
116
+ end
117
+
118
+ it 'calls +merge_with+ with the :use specs' do
119
+ expect(subject).to receive(:merge_with).with [[:use, StackSpec::BarMiddleware]]
120
+ subject.concat others
121
+ end
122
+ end
123
+ end
@@ -263,13 +263,13 @@ describe Grape::Middleware::Versioner::Header do
263
263
  end
264
264
 
265
265
  context 'when there are multiple versions with complex vendor specified with rescue_from :all' do
266
- subject {
266
+ subject do
267
267
  Class.new(Grape::API) do
268
268
  rescue_from :all
269
269
  end
270
- }
270
+ end
271
271
 
272
- let(:v1_app) {
272
+ let(:v1_app) do
273
273
  Class.new(Grape::API) do
274
274
  version 'v1', using: :header, vendor: 'test.a-cool_resource', cascade: false, strict: true
275
275
  content_type :v1_test, 'application/vnd.test.a-cool_resource-v1+json'
@@ -282,9 +282,9 @@ describe Grape::Middleware::Versioner::Header do
282
282
  end
283
283
  end
284
284
  end
285
- }
285
+ end
286
286
 
287
- let(:v2_app) {
287
+ let(:v2_app) do
288
288
  Class.new(Grape::API) do
289
289
  version 'v2', using: :header, vendor: 'test.a-cool_resource', strict: true
290
290
  content_type :v2_test, 'application/vnd.test.a-cool_resource-v2+json'
@@ -297,7 +297,7 @@ describe Grape::Middleware::Versioner::Header do
297
297
  end
298
298
  end
299
299
  end
300
- }
300
+ end
301
301
 
302
302
  def app
303
303
  subject.mount v2_app
@@ -4,47 +4,47 @@ module Grape
4
4
  describe Request do
5
5
  let(:default_method) { 'GET' }
6
6
  let(:default_params) { {} }
7
- let(:default_options) {
7
+ let(:default_options) do
8
8
  {
9
9
  method: method,
10
10
  params: params
11
11
  }
12
- }
13
- let(:default_env) {
12
+ end
13
+ let(:default_env) do
14
14
  Rack::MockRequest.env_for('/', options)
15
- }
15
+ end
16
16
  let(:method) { default_method }
17
17
  let(:params) { default_params }
18
18
  let(:options) { default_options }
19
19
  let(:env) { default_env }
20
20
 
21
- let(:request) {
21
+ let(:request) do
22
22
  Grape::Request.new(env)
23
- }
23
+ end
24
24
 
25
25
  describe '#params' do
26
- let(:params) {
26
+ let(:params) do
27
27
  {
28
28
  a: '123',
29
29
  b: 'xyz'
30
30
  }
31
- }
31
+ end
32
32
 
33
33
  it 'returns params' do
34
34
  expect(request.params).to eq('a' => '123', 'b' => 'xyz')
35
35
  end
36
36
 
37
37
  describe 'with grape.routing_args' do
38
- let(:options) {
38
+ let(:options) do
39
39
  default_options.merge('grape.routing_args' => routing_args)
40
- }
41
- let(:routing_args) {
40
+ end
41
+ let(:routing_args) do
42
42
  {
43
43
  version: '123',
44
44
  route_info: '456',
45
45
  c: 'ccc'
46
46
  }
47
- }
47
+ end
48
48
 
49
49
  it 'cuts version and route_info' do
50
50
  expect(request.params).to eq('a' => '123', 'b' => 'xyz', 'c' => 'ccc')
@@ -53,16 +53,16 @@ module Grape
53
53
  end
54
54
 
55
55
  describe '#headers' do
56
- let(:options) {
56
+ let(:options) do
57
57
  default_options.merge(request_headers)
58
- }
58
+ end
59
59
 
60
60
  describe 'with http headers in env' do
61
- let(:request_headers) {
61
+ let(:request_headers) do
62
62
  {
63
63
  'HTTP_X_GRAPE_IS_COOL' => 'yeah'
64
64
  }
65
- }
65
+ end
66
66
 
67
67
  it 'cuts HTTP_ prefix and capitalizes header name words' do
68
68
  expect(request.headers).to eq('X-Grape-Is-Cool' => 'yeah')
@@ -70,11 +70,11 @@ module Grape
70
70
  end
71
71
 
72
72
  describe 'with non-HTTP_* stuff in env' do
73
- let(:request_headers) {
73
+ let(:request_headers) do
74
74
  {
75
75
  'HTP_X_GRAPE_ENTITY_TOO' => 'but now we are testing Grape'
76
76
  }
77
- }
77
+ end
78
78
 
79
79
  it 'does not include them' do
80
80
  expect(request.headers).to eq({})
@@ -82,14 +82,14 @@ module Grape
82
82
  end
83
83
 
84
84
  describe 'with symbolic header names' do
85
- let(:request_headers) {
85
+ let(:request_headers) do
86
86
  {
87
87
  HTTP_GRAPE_LIKES_SYMBOLIC: 'it is true'
88
88
  }
89
- }
90
- let(:env) {
89
+ end
90
+ let(:env) do
91
91
  default_env.merge(request_headers)
92
- }
92
+ end
93
93
 
94
94
  it 'converts them to string' do
95
95
  expect(request.headers).to eq('Grape-Likes-Symbolic' => 'it is true')
@@ -12,6 +12,7 @@ module Grape
12
12
  settings.namespace[:namespace_thing] = :namespace_foo_bar
13
13
  settings.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar
14
14
  settings.namespace_stackable[:namespace_stackable_thing] = :namespace_stackable_foo_bar
15
+ settings.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :namespace_reverse_stackable_foo_bar
15
16
  settings.route[:route_thing] = :route_foo_bar
16
17
  end
17
18
  end
@@ -21,6 +22,7 @@ module Grape
21
22
  settings.namespace[:namespace_thing] = :namespace_foo_bar_other
22
23
  settings.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar_other
23
24
  settings.namespace_stackable[:namespace_stackable_thing] = :namespace_stackable_foo_bar_other
25
+ settings.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :namespace_reverse_stackable_foo_bar_other
24
26
  settings.route[:route_thing] = :route_foo_bar_other
25
27
  end
26
28
  end
@@ -118,6 +120,16 @@ module Grape
118
120
  end
119
121
  end
120
122
 
123
+ describe '#namespace_reverse_stackable' do
124
+ it 'works with reverse stackable values' do
125
+ expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar]
126
+
127
+ subject.inherit_from other_parent
128
+
129
+ expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar_other]
130
+ end
131
+ end
132
+
121
133
  describe '#route' do
122
134
  it 'sets a value until the next route' do
123
135
  subject.route[:some_thing] = :foo_bar
@@ -184,6 +196,14 @@ module Grape
184
196
  expect(cloned_obj.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar]
185
197
  end
186
198
 
199
+ it 'decouples namespace reverse stackable values' do
200
+ expect(cloned_obj.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar]
201
+
202
+ subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :other_thing
203
+ expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:other_thing, :namespace_reverse_stackable_foo_bar]
204
+ expect(cloned_obj.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar]
205
+ end
206
+
187
207
  it 'decouples route values' do
188
208
  expect(cloned_obj.route[:route_thing]).to eq :route_foo_bar
189
209
 
@@ -202,6 +222,7 @@ module Grape
202
222
  subject.namespace[:namespace_thing] = :namespace_foo_bar
203
223
  subject.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar
204
224
  subject.namespace_stackable[:namespace_stackable_thing] = [:namespace_stackable_foo_bar]
225
+ subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = [:namespace_reverse_stackable_foo_bar]
205
226
  subject.route[:route_thing] = :route_foo_bar
206
227
 
207
228
  expect(subject.to_hash).to include(global: { global_thing: :global_foo_bar })
@@ -209,6 +230,8 @@ module Grape
209
230
  expect(subject.to_hash).to include(namespace_inheritable: {
210
231
  namespace_inheritable_thing: :namespace_inheritable_foo_bar })
211
232
  expect(subject.to_hash).to include(namespace_stackable: { namespace_stackable_thing: [:namespace_stackable_foo_bar, [:namespace_stackable_foo_bar]] })
233
+ expect(subject.to_hash).to include(namespace_reverse_stackable:
234
+ { namespace_reverse_stackable_thing: [[:namespace_reverse_stackable_foo_bar], :namespace_reverse_stackable_foo_bar] })
212
235
  expect(subject.to_hash).to include(route: { route_thing: :route_foo_bar })
213
236
  end
214
237
  end
@@ -0,0 +1,131 @@
1
+ require 'spec_helper'
2
+ module Grape
3
+ module Util
4
+ describe ReverseStackableValues do
5
+ let(:parent) { described_class.new }
6
+ subject { described_class.new(parent) }
7
+
8
+ describe '#keys' do
9
+ it 'returns all keys' do
10
+ subject[:some_thing] = :foo_bar
11
+ subject[:some_thing_else] = :foo_bar
12
+ expect(subject.keys).to eq [:some_thing, :some_thing_else].sort
13
+ end
14
+
15
+ it 'returns merged keys with parent' do
16
+ parent[:some_thing] = :foo
17
+ parent[:some_thing_else] = :foo
18
+
19
+ subject[:some_thing] = :foo_bar
20
+ subject[:some_thing_more] = :foo_bar
21
+
22
+ expect(subject.keys).to eq [:some_thing, :some_thing_else, :some_thing_more].sort
23
+ end
24
+ end
25
+
26
+ describe '#delete' do
27
+ it 'deletes a key' do
28
+ subject[:some_thing] = :new_foo_bar
29
+ subject.delete :some_thing
30
+ expect(subject[:some_thing]).to eq []
31
+ end
32
+
33
+ it 'does not delete parent values' do
34
+ parent[:some_thing] = :foo
35
+ subject[:some_thing] = :new_foo_bar
36
+ subject.delete :some_thing
37
+ expect(subject[:some_thing]).to eq [:foo]
38
+ end
39
+ end
40
+
41
+ describe '#[]' do
42
+ it 'returns an array of values' do
43
+ subject[:some_thing] = :foo
44
+ expect(subject[:some_thing]).to eq [:foo]
45
+ end
46
+
47
+ it 'returns parent value when no value is set' do
48
+ parent[:some_thing] = :foo
49
+ expect(subject[:some_thing]).to eq [:foo]
50
+ end
51
+
52
+ it 'combines parent and actual values (actual first)' do
53
+ parent[:some_thing] = :foo
54
+ subject[:some_thing] = :foo_bar
55
+ expect(subject[:some_thing]).to eq [:foo_bar, :foo]
56
+ end
57
+
58
+ it 'parent values are not changed' do
59
+ parent[:some_thing] = :foo
60
+ subject[:some_thing] = :foo_bar
61
+ expect(parent[:some_thing]).to eq [:foo]
62
+ end
63
+ end
64
+
65
+ describe '#[]=' do
66
+ it 'sets a value' do
67
+ subject[:some_thing] = :foo
68
+ expect(subject[:some_thing]).to eq [:foo]
69
+ end
70
+
71
+ it 'pushes further values' do
72
+ subject[:some_thing] = :foo
73
+ subject[:some_thing] = :bar
74
+ expect(subject[:some_thing]).to eq [:foo, :bar]
75
+ end
76
+
77
+ it 'can handle array values' do
78
+ subject[:some_thing] = :foo
79
+ subject[:some_thing] = [:bar, :more]
80
+ expect(subject[:some_thing]).to eq [:foo, [:bar, :more]]
81
+
82
+ parent[:some_thing_else] = [:foo, :bar]
83
+ subject[:some_thing_else] = [:some, :bar, :foo]
84
+
85
+ expect(subject[:some_thing_else]).to eq [[:some, :bar, :foo], [:foo, :bar]]
86
+ end
87
+ end
88
+
89
+ describe '#to_hash' do
90
+ it 'returns a Hash representation' do
91
+ parent[:some_thing] = :foo
92
+ subject[:some_thing] = [:bar, :more]
93
+ subject[:some_thing_more] = :foo_bar
94
+ expect(subject.to_hash).to eq(
95
+ some_thing: [[:bar, :more], :foo],
96
+ some_thing_more: [:foo_bar]
97
+ )
98
+ end
99
+ end
100
+
101
+ describe '#clone' do
102
+ let(:obj_cloned) { subject.clone }
103
+ it 'copies all values' do
104
+ parent = described_class.new
105
+ child = described_class.new parent
106
+ grandchild = described_class.new child
107
+
108
+ parent[:some_thing] = :foo
109
+ child[:some_thing] = [:bar, :more]
110
+ grandchild[:some_thing] = :grand_foo_bar
111
+ grandchild[:some_thing_more] = :foo_bar
112
+
113
+ expect(grandchild.clone.to_hash).to eq(
114
+ some_thing: [:grand_foo_bar, [:bar, :more], :foo],
115
+ some_thing_more: [:foo_bar]
116
+ )
117
+ end
118
+
119
+ context 'complex (i.e. not primitive) data types (ex. middleware, please see bug #930)' do
120
+ let(:middleware) { double }
121
+
122
+ before { subject[:middleware] = middleware }
123
+
124
+ it 'copies values; does not duplicate them' do
125
+ expect(obj_cloned[:middleware]).to eq [middleware]
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end