grape 1.5.0 → 1.6.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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -2
  3. data/README.md +43 -10
  4. data/UPGRADING.md +91 -0
  5. data/grape.gemspec +5 -5
  6. data/lib/grape/api/instance.rb +13 -17
  7. data/lib/grape/api.rb +7 -14
  8. data/lib/grape/cookies.rb +2 -0
  9. data/lib/grape/dsl/callbacks.rb +1 -1
  10. data/lib/grape/dsl/desc.rb +3 -5
  11. data/lib/grape/dsl/helpers.rb +6 -4
  12. data/lib/grape/dsl/inside_route.rb +18 -9
  13. data/lib/grape/dsl/middleware.rb +4 -4
  14. data/lib/grape/dsl/parameters.rb +11 -7
  15. data/lib/grape/dsl/request_response.rb +9 -6
  16. data/lib/grape/dsl/routing.rb +7 -6
  17. data/lib/grape/dsl/settings.rb +5 -5
  18. data/lib/grape/endpoint.rb +21 -36
  19. data/lib/grape/error_formatter/json.rb +2 -6
  20. data/lib/grape/error_formatter/xml.rb +2 -6
  21. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  22. data/lib/grape/exceptions/validation.rb +2 -3
  23. data/lib/grape/exceptions/validation_errors.rb +1 -1
  24. data/lib/grape/formatter/json.rb +1 -0
  25. data/lib/grape/formatter/serializable_hash.rb +2 -1
  26. data/lib/grape/formatter/xml.rb +1 -0
  27. data/lib/grape/locale/en.yml +1 -1
  28. data/lib/grape/middleware/auth/base.rb +3 -3
  29. data/lib/grape/middleware/base.rb +4 -2
  30. data/lib/grape/middleware/error.rb +1 -1
  31. data/lib/grape/middleware/formatter.rb +4 -4
  32. data/lib/grape/middleware/stack.rb +10 -16
  33. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
  34. data/lib/grape/middleware/versioner/header.rb +6 -4
  35. data/lib/grape/middleware/versioner/param.rb +1 -0
  36. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  37. data/lib/grape/middleware/versioner/path.rb +2 -0
  38. data/lib/grape/parser/json.rb +1 -1
  39. data/lib/grape/parser/xml.rb +1 -1
  40. data/lib/grape/path.rb +1 -0
  41. data/lib/grape/request.rb +3 -0
  42. data/lib/grape/router/attribute_translator.rb +1 -1
  43. data/lib/grape/router/pattern.rb +1 -1
  44. data/lib/grape/router/route.rb +2 -2
  45. data/lib/grape/router.rb +6 -0
  46. data/lib/grape/util/inheritable_setting.rb +1 -3
  47. data/lib/grape/util/lazy_value.rb +3 -2
  48. data/lib/grape/validations/attributes_iterator.rb +8 -0
  49. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  50. data/lib/grape/validations/params_scope.rb +92 -58
  51. data/lib/grape/validations/single_attribute_iterator.rb +1 -1
  52. data/lib/grape/validations/types/custom_type_coercer.rb +3 -2
  53. data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
  54. data/lib/grape/validations/types/invalid_value.rb +24 -0
  55. data/lib/grape/validations/types/json.rb +2 -1
  56. data/lib/grape/validations/types/primitive_coercer.rb +3 -3
  57. data/lib/grape/validations/types.rb +1 -4
  58. data/lib/grape/validations/validator_factory.rb +1 -1
  59. data/lib/grape/validations/validators/all_or_none.rb +1 -0
  60. data/lib/grape/validations/validators/as.rb +4 -8
  61. data/lib/grape/validations/validators/at_least_one_of.rb +1 -0
  62. data/lib/grape/validations/validators/base.rb +12 -7
  63. data/lib/grape/validations/validators/coerce.rb +8 -9
  64. data/lib/grape/validations/validators/default.rb +1 -0
  65. data/lib/grape/validations/validators/exactly_one_of.rb +1 -0
  66. data/lib/grape/validations/validators/multiple_params_base.rb +5 -2
  67. data/lib/grape/validations/validators/mutual_exclusion.rb +1 -0
  68. data/lib/grape/validations/validators/presence.rb +1 -0
  69. data/lib/grape/validations/validators/regexp.rb +1 -0
  70. data/lib/grape/validations/validators/same_as.rb +1 -0
  71. data/lib/grape/validations/validators/values.rb +3 -0
  72. data/lib/grape/version.rb +1 -1
  73. data/lib/grape.rb +3 -1
  74. data/spec/grape/api/custom_validations_spec.rb +1 -0
  75. data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
  76. data/spec/grape/api_remount_spec.rb +9 -4
  77. data/spec/grape/api_spec.rb +203 -37
  78. data/spec/grape/dsl/callbacks_spec.rb +1 -1
  79. data/spec/grape/dsl/middleware_spec.rb +1 -1
  80. data/spec/grape/dsl/parameters_spec.rb +1 -0
  81. data/spec/grape/dsl/routing_spec.rb +1 -1
  82. data/spec/grape/endpoint/declared_spec.rb +259 -1
  83. data/spec/grape/endpoint_spec.rb +18 -5
  84. data/spec/grape/entity_spec.rb +10 -10
  85. data/spec/grape/middleware/auth/dsl_spec.rb +1 -1
  86. data/spec/grape/middleware/error_spec.rb +1 -2
  87. data/spec/grape/middleware/formatter_spec.rb +2 -2
  88. data/spec/grape/middleware/stack_spec.rb +4 -3
  89. data/spec/grape/request_spec.rb +1 -1
  90. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
  91. data/spec/grape/validations/params_scope_spec.rb +37 -3
  92. data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
  93. data/spec/grape/validations/types/primitive_coercer_spec.rb +2 -2
  94. data/spec/grape/validations/validators/coerce_spec.rb +129 -22
  95. data/spec/grape/validations/validators/except_values_spec.rb +2 -2
  96. data/spec/grape/validations/validators/values_spec.rb +15 -11
  97. data/spec/grape/validations_spec.rb +280 -0
  98. data/spec/shared/versioning_examples.rb +22 -22
  99. data/spec/spec_helper.rb +1 -1
  100. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  101. data/spec/support/versioned_helpers.rb +1 -1
  102. metadata +8 -6
@@ -13,6 +13,7 @@ module Grape
13
13
  'Use the except validator instead.' if @excepts
14
14
 
15
15
  raise ArgumentError, 'proc must be a Proc' if @proc && !@proc.is_a?(Proc)
16
+
16
17
  warn '[DEPRECATION] The values validator proc option is deprecated. ' \
17
18
  'The lambda expression can now be assigned directly to values.' if @proc
18
19
  else
@@ -51,6 +52,7 @@ module Grape
51
52
  def check_values(param_array, attr_name)
52
53
  values = @values.is_a?(Proc) && @values.arity.zero? ? @values.call : @values
53
54
  return true if values.nil?
55
+
54
56
  begin
55
57
  return param_array.all? { |param| values.call(param) } if values.is_a? Proc
56
58
  rescue StandardError => e
@@ -63,6 +65,7 @@ module Grape
63
65
  def check_excepts(param_array)
64
66
  excepts = @excepts.is_a?(Proc) ? @excepts.call : @excepts
65
67
  return true if excepts.nil?
68
+
66
69
  param_array.none? { |param| excepts.include?(param) }
67
70
  end
68
71
 
data/lib/grape/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Grape
4
4
  # The current version of Grape.
5
- VERSION = '1.5.0'
5
+ VERSION = '1.6.0'
6
6
  end
data/lib/grape.rb CHANGED
@@ -12,6 +12,7 @@ require 'active_support/core_ext/hash/indifferent_access'
12
12
  require 'active_support/core_ext/object/blank'
13
13
  require 'active_support/core_ext/array/extract_options'
14
14
  require 'active_support/core_ext/array/wrap'
15
+ require 'active_support/core_ext/array/conversions'
15
16
  require 'active_support/core_ext/hash/deep_merge'
16
17
  require 'active_support/core_ext/hash/reverse_merge'
17
18
  require 'active_support/core_ext/hash/except'
@@ -21,7 +22,7 @@ require 'active_support/dependencies/autoload'
21
22
  require 'active_support/notifications'
22
23
  require 'i18n'
23
24
 
24
- I18n.load_path << File.expand_path('../grape/locale/en.yml', __FILE__)
25
+ I18n.load_path << File.expand_path('grape/locale/en.yml', __dir__)
25
26
 
26
27
  module Grape
27
28
  extend ::ActiveSupport::Autoload
@@ -75,6 +76,7 @@ module Grape
75
76
  autoload :InvalidVersionHeader
76
77
  autoload :MethodNotAllowed
77
78
  autoload :InvalidResponse
79
+ autoload :EmptyMessageBody
78
80
  end
79
81
  end
80
82
 
@@ -10,6 +10,7 @@ describe Grape::Validations do
10
10
  def validate_param!(attr_name, params)
11
11
  @option = params[:max].to_i if params.key?(:max)
12
12
  return if params[attr_name].length <= @option
13
+
13
14
  raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: "must be at the most #{@option} characters long")
14
15
  end
15
16
  end
@@ -11,7 +11,7 @@ describe Grape::Endpoint do
11
11
 
12
12
  context 'get' do
13
13
  it 'routes to a namespace param with dots' do
14
- subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^\/]+} } do
14
+ subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^/]+} } do
15
15
  get '/' do
16
16
  params[:ns_with_dots]
17
17
  end
@@ -23,8 +23,8 @@ describe Grape::Endpoint do
23
23
  end
24
24
 
25
25
  it 'routes to a path with multiple params with dots' do
26
- subject.get ':id_with_dots/:another_id_with_dots', requirements: { id_with_dots: %r{[^\/]+},
27
- another_id_with_dots: %r{[^\/]+} } do
26
+ subject.get ':id_with_dots/:another_id_with_dots', requirements: { id_with_dots: %r{[^/]+},
27
+ another_id_with_dots: %r{[^/]+} } do
28
28
  "#{params[:id_with_dots]}/#{params[:another_id_with_dots]}"
29
29
  end
30
30
 
@@ -34,9 +34,9 @@ describe Grape::Endpoint do
34
34
  end
35
35
 
36
36
  it 'routes to namespace and path params with dots, with overridden requirements' do
37
- subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^\/]+} } do
38
- get ':another_id_with_dots', requirements: { ns_with_dots: %r{[^\/]+},
39
- another_id_with_dots: %r{[^\/]+} } do
37
+ subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^/]+} } do
38
+ get ':another_id_with_dots', requirements: { ns_with_dots: %r{[^/]+},
39
+ another_id_with_dots: %r{[^/]+} } do
40
40
  "#{params[:ns_with_dots]}/#{params[:another_id_with_dots]}"
41
41
  end
42
42
  end
@@ -47,8 +47,8 @@ describe Grape::Endpoint do
47
47
  end
48
48
 
49
49
  it 'routes to namespace and path params with dots, with merged requirements' do
50
- subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^\/]+} } do
51
- get ':another_id_with_dots', requirements: { another_id_with_dots: %r{[^\/]+} } do
50
+ subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^/]+} } do
51
+ get ':another_id_with_dots', requirements: { another_id_with_dots: %r{[^/]+} } do
52
52
  "#{params[:ns_with_dots]}/#{params[:another_id_with_dots]}"
53
53
  end
54
54
  end
@@ -340,19 +340,24 @@ describe Grape::API do
340
340
  context 'when the configuration is read within a namespace' do
341
341
  before do
342
342
  a_remounted_api.namespace 'api' do
343
+ params do
344
+ requires configuration[:required_param]
345
+ end
343
346
  get "/#{configuration[:path]}" do
344
347
  '10 votes'
345
348
  end
346
349
  end
347
- root_api.mount a_remounted_api, with: { path: 'votes' }
348
- root_api.mount a_remounted_api, with: { path: 'scores' }
350
+ root_api.mount a_remounted_api, with: { path: 'votes', required_param: 'param_key' }
351
+ root_api.mount a_remounted_api, with: { path: 'scores', required_param: 'param_key' }
349
352
  end
350
353
 
351
354
  it 'will use the dynamic configuration on all routes' do
352
- get 'api/votes'
355
+ get 'api/votes', param_key: 'a'
353
356
  expect(last_response.body).to eql '10 votes'
354
- get 'api/scores'
357
+ get 'api/scores', param_key: 'a'
355
358
  expect(last_response.body).to eql '10 votes'
359
+ get 'api/votes'
360
+ expect(last_response.status).to eq 400
356
361
  end
357
362
  end
358
363
 
@@ -610,6 +610,7 @@ describe Grape::API do
610
610
  subject.namespace :example do
611
611
  before do
612
612
  raise 'before filter ran twice' if already_run
613
+
613
614
  already_run = true
614
615
  header 'X-Custom-Header', 'foo'
615
616
  end
@@ -650,12 +651,12 @@ describe Grape::API do
650
651
 
651
652
  put '/example'
652
653
  expect(last_response.status).to eql 405
653
- expect(last_response.body).to eq <<-XML
654
- <?xml version="1.0" encoding="UTF-8"?>
655
- <error>
656
- <message>405 Not Allowed</message>
657
- </error>
658
- XML
654
+ expect(last_response.body).to eq <<~XML
655
+ <?xml version="1.0" encoding="UTF-8"?>
656
+ <error>
657
+ <message>405 Not Allowed</message>
658
+ </error>
659
+ XML
659
660
  end
660
661
  end
661
662
 
@@ -749,6 +750,47 @@ XML
749
750
  end
750
751
  end
751
752
 
753
+ describe 'when a resource routes by POST, GET, PATCH, PUT, and DELETE' do
754
+ before do
755
+ subject.namespace :example do
756
+ get do
757
+ 'example'
758
+ end
759
+
760
+ patch do
761
+ 'example'
762
+ end
763
+
764
+ post do
765
+ 'example'
766
+ end
767
+
768
+ delete do
769
+ 'example'
770
+ end
771
+
772
+ put do
773
+ 'example'
774
+ end
775
+ end
776
+ options '/example'
777
+ end
778
+
779
+ describe 'it adds an OPTIONS route for namespaced endpoints that' do
780
+ it 'returns a 204' do
781
+ expect(last_response.status).to eql 204
782
+ end
783
+
784
+ it 'has an empty body' do
785
+ expect(last_response.body).to be_blank
786
+ end
787
+
788
+ it 'has an Allow header' do
789
+ expect(last_response.headers['Allow']).to eql 'OPTIONS, GET, PATCH, POST, DELETE, PUT, HEAD'
790
+ end
791
+ end
792
+ end
793
+
752
794
  describe 'adds an OPTIONS route for namespaced endpoints that' do
753
795
  before do
754
796
  subject.before { header 'X-Custom-Header', 'foo' }
@@ -2106,7 +2148,9 @@ XML
2106
2148
  context 'custom errors' do
2107
2149
  before do
2108
2150
  class ConnectionError < RuntimeError; end
2151
+
2109
2152
  class DatabaseError < RuntimeError; end
2153
+
2110
2154
  class CommunicationError < StandardError; end
2111
2155
  end
2112
2156
 
@@ -2262,6 +2306,7 @@ XML
2262
2306
  module ApiSpec
2263
2307
  module APIErrors
2264
2308
  class ParentError < StandardError; end
2309
+
2265
2310
  class ChildError < ParentError; end
2266
2311
  end
2267
2312
  end
@@ -3119,10 +3164,10 @@ XML
3119
3164
  subject.get 'method'
3120
3165
 
3121
3166
  expect(subject.routes.map(&:params)).to eq [{
3122
- 'group1' => { required: true, type: 'Array' },
3167
+ 'group1' => { required: true, type: 'Array' },
3123
3168
  'group1[param1]' => { required: false, desc: 'group1 param1 desc' },
3124
3169
  'group1[param2]' => { required: true, desc: 'group1 param2 desc' },
3125
- 'group2' => { required: true, type: 'Array' },
3170
+ 'group2' => { required: true, type: 'Array' },
3126
3171
  'group2[param1]' => { required: false, desc: 'group2 param1 desc' },
3127
3172
  'group2[param2]' => { required: true, desc: 'group2 param2 desc' }
3128
3173
  }]
@@ -3335,8 +3380,8 @@ XML
3335
3380
  mount app
3336
3381
  end
3337
3382
  expect(subject.routes.size).to eq(2)
3338
- expect(subject.routes.first.path).to match(%r{\/cool\/awesome})
3339
- expect(subject.routes.last.path).to match(%r{\/cool\/sauce})
3383
+ expect(subject.routes.first.path).to match(%r{/cool/awesome})
3384
+ expect(subject.routes.last.path).to match(%r{/cool/sauce})
3340
3385
  end
3341
3386
 
3342
3387
  it 'mounts on a path' do
@@ -3358,7 +3403,7 @@ XML
3358
3403
  APP2.get '/nice' do
3359
3404
  'play'
3360
3405
  end
3361
- # note that the reverse won't work, mount from outside-in
3406
+ # NOTE: that the reverse won't work, mount from outside-in
3362
3407
  APP3 = subject
3363
3408
  APP3.mount APP1 => '/app1'
3364
3409
  APP1.mount APP2 => '/app2'
@@ -3549,6 +3594,7 @@ XML
3549
3594
  def self.included(base)
3550
3595
  base.extend(ClassMethods)
3551
3596
  end
3597
+
3552
3598
  module ClassMethods
3553
3599
  def my_method
3554
3600
  @test = true
@@ -3793,12 +3839,12 @@ XML
3793
3839
  end
3794
3840
  get '/example'
3795
3841
  expect(last_response.status).to eq(500)
3796
- expect(last_response.body).to eq <<-XML
3797
- <?xml version="1.0" encoding="UTF-8"?>
3798
- <error>
3799
- <message>cannot convert String to xml</message>
3800
- </error>
3801
- XML
3842
+ expect(last_response.body).to eq <<~XML
3843
+ <?xml version="1.0" encoding="UTF-8"?>
3844
+ <error>
3845
+ <message>cannot convert String to xml</message>
3846
+ </error>
3847
+ XML
3802
3848
  end
3803
3849
  it 'hash' do
3804
3850
  subject.get '/example' do
@@ -3809,13 +3855,13 @@ XML
3809
3855
  end
3810
3856
  get '/example'
3811
3857
  expect(last_response.status).to eq(200)
3812
- expect(last_response.body).to eq <<-XML
3813
- <?xml version="1.0" encoding="UTF-8"?>
3814
- <hash>
3815
- <example1>example1</example1>
3816
- <example2>example2</example2>
3817
- </hash>
3818
- XML
3858
+ expect(last_response.body).to eq <<~XML
3859
+ <?xml version="1.0" encoding="UTF-8"?>
3860
+ <hash>
3861
+ <example1>example1</example1>
3862
+ <example2>example2</example2>
3863
+ </hash>
3864
+ XML
3819
3865
  end
3820
3866
  it 'array' do
3821
3867
  subject.get '/example' do
@@ -3823,13 +3869,13 @@ XML
3823
3869
  end
3824
3870
  get '/example'
3825
3871
  expect(last_response.status).to eq(200)
3826
- expect(last_response.body).to eq <<-XML
3827
- <?xml version="1.0" encoding="UTF-8"?>
3828
- <strings type="array">
3829
- <string>example1</string>
3830
- <string>example2</string>
3831
- </strings>
3832
- XML
3872
+ expect(last_response.body).to eq <<~XML
3873
+ <?xml version="1.0" encoding="UTF-8"?>
3874
+ <strings type="array">
3875
+ <string>example1</string>
3876
+ <string>example2</string>
3877
+ </strings>
3878
+ XML
3833
3879
  end
3834
3880
  it 'raised :error from middleware' do
3835
3881
  middleware = Class.new(Grape::Middleware::Base) do
@@ -3842,12 +3888,12 @@ XML
3842
3888
  end
3843
3889
  get '/'
3844
3890
  expect(last_response.status).to eq(42)
3845
- expect(last_response.body).to eq <<-XML
3846
- <?xml version="1.0" encoding="UTF-8"?>
3847
- <error>
3848
- <message>Unauthorized</message>
3849
- </error>
3850
- XML
3891
+ expect(last_response.body).to eq <<~XML
3892
+ <?xml version="1.0" encoding="UTF-8"?>
3893
+ <error>
3894
+ <message>Unauthorized</message>
3895
+ </error>
3896
+ XML
3851
3897
  end
3852
3898
  end
3853
3899
  end
@@ -4040,6 +4086,49 @@ XML
4040
4086
  end
4041
4087
  end
4042
4088
 
4089
+ describe '.inherited' do
4090
+ context 'overriding within class' do
4091
+ let(:root_api) do
4092
+ Class.new(Grape::API) do
4093
+ @bar = 'Hello, world'
4094
+
4095
+ def self.inherited(child_api)
4096
+ super
4097
+ child_api.instance_variable_set(:@foo, @bar.dup)
4098
+ end
4099
+ end
4100
+ end
4101
+
4102
+ let(:child_api) { Class.new(root_api) }
4103
+
4104
+ it 'allows overriding the hook' do
4105
+ expect(child_api.instance_variable_get(:@foo)).to eq('Hello, world')
4106
+ end
4107
+ end
4108
+
4109
+ context 'overriding via composition' do
4110
+ module Inherited
4111
+ def inherited(api)
4112
+ super
4113
+ api.instance_variable_set(:@foo, @bar.dup)
4114
+ end
4115
+ end
4116
+
4117
+ let(:root_api) do
4118
+ Class.new(Grape::API) do
4119
+ @bar = 'Hello, world'
4120
+ extend Inherited
4121
+ end
4122
+ end
4123
+
4124
+ let(:child_api) { Class.new(root_api) }
4125
+
4126
+ it 'allows overriding the hook' do
4127
+ expect(child_api.instance_variable_get(:@foo)).to eq('Hello, world')
4128
+ end
4129
+ end
4130
+ end
4131
+
4043
4132
  describe 'const_missing' do
4044
4133
  subject(:grape_api) { Class.new(Grape::API) }
4045
4134
  let(:mounted) do
@@ -4056,4 +4145,81 @@ XML
4056
4145
  expect { get '/const/missing' }.to raise_error(NameError).with_message(/SomeRandomConstant/)
4057
4146
  end
4058
4147
  end
4148
+
4149
+ describe 'custom route helpers on nested APIs' do
4150
+ let(:shared_api_module) do
4151
+ Module.new do
4152
+ # rubocop:disable Style/ExplicitBlockArgument because this causes
4153
+ # the underlying issue in this form
4154
+ def uniqe_id_route
4155
+ params do
4156
+ use :unique_id
4157
+ end
4158
+ route_param(:id) do
4159
+ yield
4160
+ end
4161
+ end
4162
+ # rubocop:enable Style/ExplicitBlockArgument
4163
+ end
4164
+ end
4165
+ let(:shared_api_definitions) do
4166
+ Module.new do
4167
+ extend ActiveSupport::Concern
4168
+
4169
+ included do
4170
+ helpers do
4171
+ params :unique_id do
4172
+ requires :id, type: String,
4173
+ allow_blank: false,
4174
+ regexp: /\d+-\d+/
4175
+ end
4176
+ end
4177
+ end
4178
+ end
4179
+ end
4180
+ let(:orders_root) do
4181
+ shared = shared_api_definitions
4182
+ find = orders_find_endpoint
4183
+ Class.new(Grape::API) do
4184
+ include shared
4185
+
4186
+ namespace(:orders) do
4187
+ mount find
4188
+ end
4189
+ end
4190
+ end
4191
+ let(:orders_find_endpoint) do
4192
+ shared = shared_api_definitions
4193
+ Class.new(Grape::API) do
4194
+ include shared
4195
+
4196
+ uniqe_id_route do
4197
+ desc 'Fetch a single order' do
4198
+ detail 'While specifying the order id on the route'
4199
+ end
4200
+ get { params[:id] }
4201
+ end
4202
+ end
4203
+ end
4204
+ subject(:grape_api) do
4205
+ Class.new(Grape::API) do
4206
+ version 'v1', using: :path
4207
+ end
4208
+ end
4209
+
4210
+ before do
4211
+ Grape::API::Instance.extend(shared_api_module)
4212
+ subject.mount orders_root
4213
+ end
4214
+
4215
+ it 'returns an error when the id is bad' do
4216
+ get '/v1/orders/abc'
4217
+ expect(last_response.body).to be_eql('id is invalid')
4218
+ end
4219
+
4220
+ it 'returns the given id when it is valid' do
4221
+ get '/v1/orders/1-2'
4222
+ expect(last_response.body).to be_eql('1-2')
4223
+ end
4224
+ end
4059
4225
  end
@@ -12,7 +12,7 @@ module Grape
12
12
 
13
13
  describe Callbacks do
14
14
  subject { Class.new(CallbacksSpec::Dummy) }
15
- let(:proc) { ->() {} }
15
+ let(:proc) { -> {} }
16
16
 
17
17
  describe '.before' do
18
18
  it 'adds a block to "before"' do
@@ -12,7 +12,7 @@ module Grape
12
12
 
13
13
  describe Middleware do
14
14
  subject { Class.new(MiddlewareSpec::Dummy) }
15
- let(:proc) { ->() {} }
15
+ let(:proc) { -> {} }
16
16
  let(:foo_middleware) { Class.new }
17
17
  let(:bar_middleware) { Class.new }
18
18
 
@@ -40,6 +40,7 @@ module Grape
40
40
 
41
41
  def extract_message_option(attrs)
42
42
  return nil unless attrs.is_a?(Array)
43
+
43
44
  opts = attrs.last.is_a?(Hash) ? attrs.pop : {}
44
45
  opts.key?(:message) && !opts[:message].nil? ? opts.delete(:message) : nil
45
46
  end
@@ -12,7 +12,7 @@ module Grape
12
12
 
13
13
  describe Routing do
14
14
  subject { Class.new(RoutingSpec::Dummy) }
15
- let(:proc) { ->() {} }
15
+ let(:proc) { -> {} }
16
16
  let(:options) { { a: :b } }
17
17
  let(:path) { '/dummy' }
18
18