grape 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
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