grape 0.12.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +9 -4
  3. data/CHANGELOG.md +265 -215
  4. data/CONTRIBUTING.md +4 -4
  5. data/Gemfile +0 -1
  6. data/Gemfile.lock +166 -0
  7. data/README.md +426 -161
  8. data/RELEASING.md +14 -6
  9. data/Rakefile +30 -33
  10. data/UPGRADING.md +54 -23
  11. data/benchmark/simple.rb +27 -0
  12. data/gemfiles/rack_1.5.2.gemfile +13 -0
  13. data/gemfiles/rails_3.gemfile +2 -2
  14. data/gemfiles/rails_4.gemfile +1 -2
  15. data/grape.gemspec +6 -7
  16. data/lib/grape/api.rb +24 -4
  17. data/lib/grape/dsl/callbacks.rb +20 -0
  18. data/lib/grape/dsl/configuration.rb +59 -2
  19. data/lib/grape/dsl/helpers.rb +8 -3
  20. data/lib/grape/dsl/inside_route.rb +100 -45
  21. data/lib/grape/dsl/parameters.rb +96 -7
  22. data/lib/grape/dsl/request_response.rb +1 -1
  23. data/lib/grape/dsl/routing.rb +17 -4
  24. data/lib/grape/dsl/settings.rb +36 -1
  25. data/lib/grape/dsl/validations.rb +7 -5
  26. data/lib/grape/endpoint.rb +102 -57
  27. data/lib/grape/error_formatter/base.rb +6 -6
  28. data/lib/grape/exceptions/base.rb +5 -5
  29. data/lib/grape/exceptions/invalid_version_header.rb +10 -0
  30. data/lib/grape/exceptions/unknown_parameter.rb +10 -0
  31. data/lib/grape/exceptions/validation_errors.rb +4 -3
  32. data/lib/grape/formatter/serializable_hash.rb +3 -2
  33. data/lib/grape/http/headers.rb +0 -1
  34. data/lib/grape/locale/en.yml +5 -1
  35. data/lib/grape/middleware/auth/base.rb +2 -2
  36. data/lib/grape/middleware/auth/dsl.rb +1 -1
  37. data/lib/grape/middleware/auth/strategies.rb +1 -1
  38. data/lib/grape/middleware/base.rb +8 -4
  39. data/lib/grape/middleware/error.rb +3 -2
  40. data/lib/grape/middleware/filter.rb +1 -1
  41. data/lib/grape/middleware/formatter.rb +64 -45
  42. data/lib/grape/middleware/globals.rb +3 -3
  43. data/lib/grape/middleware/versioner/accept_version_header.rb +5 -7
  44. data/lib/grape/middleware/versioner/header.rb +113 -50
  45. data/lib/grape/middleware/versioner/param.rb +5 -8
  46. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +20 -0
  47. data/lib/grape/middleware/versioner/path.rb +3 -6
  48. data/lib/grape/namespace.rb +13 -2
  49. data/lib/grape/path.rb +4 -3
  50. data/lib/grape/request.rb +40 -0
  51. data/lib/grape/route.rb +5 -0
  52. data/lib/grape/util/content_types.rb +9 -9
  53. data/lib/grape/util/env.rb +22 -0
  54. data/lib/grape/util/file_response.rb +21 -0
  55. data/lib/grape/util/inheritable_setting.rb +23 -2
  56. data/lib/grape/util/inheritable_values.rb +1 -1
  57. data/lib/grape/util/stackable_values.rb +5 -2
  58. data/lib/grape/util/strict_hash_configuration.rb +2 -1
  59. data/lib/grape/validations/attributes_iterator.rb +8 -3
  60. data/lib/grape/validations/params_scope.rb +164 -22
  61. data/lib/grape/validations/types/build_coercer.rb +53 -0
  62. data/lib/grape/validations/types/custom_type_coercer.rb +183 -0
  63. data/lib/grape/validations/types/file.rb +28 -0
  64. data/lib/grape/validations/types/json.rb +65 -0
  65. data/lib/grape/validations/types/multiple_type_coercer.rb +76 -0
  66. data/lib/grape/validations/types/variant_collection_coercer.rb +59 -0
  67. data/lib/grape/validations/types/virtus_collection_patch.rb +16 -0
  68. data/lib/grape/validations/types.rb +144 -0
  69. data/lib/grape/validations/validators/all_or_none.rb +1 -1
  70. data/lib/grape/validations/validators/allow_blank.rb +3 -3
  71. data/lib/grape/validations/validators/base.rb +7 -0
  72. data/lib/grape/validations/validators/coerce.rb +32 -34
  73. data/lib/grape/validations/validators/presence.rb +2 -3
  74. data/lib/grape/validations/validators/regexp.rb +2 -4
  75. data/lib/grape/validations/validators/values.rb +3 -3
  76. data/lib/grape/validations.rb +5 -0
  77. data/lib/grape/version.rb +2 -1
  78. data/lib/grape.rb +15 -12
  79. data/pkg/grape-0.13.0.gem +0 -0
  80. data/spec/grape/api/custom_validations_spec.rb +5 -4
  81. data/spec/grape/api/deeply_included_options_spec.rb +7 -7
  82. data/spec/grape/api/nested_helpers_spec.rb +4 -2
  83. data/spec/grape/api/shared_helpers_spec.rb +8 -8
  84. data/spec/grape/api_spec.rb +151 -54
  85. data/spec/grape/dsl/configuration_spec.rb +13 -0
  86. data/spec/grape/dsl/helpers_spec.rb +16 -2
  87. data/spec/grape/dsl/inside_route_spec.rb +40 -4
  88. data/spec/grape/dsl/parameters_spec.rb +0 -6
  89. data/spec/grape/dsl/routing_spec.rb +1 -1
  90. data/spec/grape/dsl/validations_spec.rb +18 -0
  91. data/spec/grape/endpoint_spec.rb +130 -6
  92. data/spec/grape/entity_spec.rb +10 -8
  93. data/spec/grape/exceptions/invalid_accept_header_spec.rb +1 -15
  94. data/spec/grape/exceptions/validation_errors_spec.rb +28 -0
  95. data/spec/grape/integration/rack_spec.rb +3 -2
  96. data/spec/grape/middleware/base_spec.rb +40 -16
  97. data/spec/grape/middleware/error_spec.rb +16 -15
  98. data/spec/grape/middleware/exception_spec.rb +45 -43
  99. data/spec/grape/middleware/formatter_spec.rb +34 -5
  100. data/spec/grape/middleware/versioner/header_spec.rb +79 -47
  101. data/spec/grape/path_spec.rb +10 -10
  102. data/spec/grape/presenters/presenter_spec.rb +2 -2
  103. data/spec/grape/request_spec.rb +100 -0
  104. data/spec/grape/util/inheritable_values_spec.rb +14 -0
  105. data/spec/grape/util/stackable_values_spec.rb +10 -0
  106. data/spec/grape/validations/params_scope_spec.rb +86 -0
  107. data/spec/grape/validations/types_spec.rb +95 -0
  108. data/spec/grape/validations/validators/coerce_spec.rb +364 -10
  109. data/spec/grape/validations/validators/values_spec.rb +27 -15
  110. data/spec/grape/validations_spec.rb +53 -24
  111. data/spec/shared/versioning_examples.rb +2 -2
  112. data/spec/spec_helper.rb +0 -1
  113. data/spec/support/versioned_helpers.rb +2 -2
  114. metadata +55 -14
  115. data/.gitignore +0 -46
  116. data/.rspec +0 -2
  117. data/.rubocop.yml +0 -7
  118. data/.rubocop_todo.yml +0 -84
  119. data/.travis.yml +0 -20
  120. data/.yardopts +0 -2
  121. data/lib/backports/active_support/deep_dup.rb +0 -49
  122. data/lib/backports/active_support/duplicable.rb +0 -88
  123. data/lib/grape/http/request.rb +0 -27
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  ![grape logo](grape.png)
2
2
 
3
- [![Gem Version](http://img.shields.io/gem/v/grape.svg)](http://badge.fury.io/rb/grape)
4
- [![Build Status](http://img.shields.io/travis/intridea/grape.svg)](https://travis-ci.org/intridea/grape)
5
- [![Dependency Status](https://gemnasium.com/intridea/grape.svg)](https://gemnasium.com/intridea/grape)
6
- [![Code Climate](https://codeclimate.com/github/intridea/grape.svg)](https://codeclimate.com/github/intridea/grape)
7
- [![Inline docs](http://inch-ci.org/github/intridea/grape.svg)](http://inch-ci.org/github/intridea/grape)
3
+ [![Gem Version](https://badge.fury.io/rb/grape.svg)](http://badge.fury.io/rb/grape)
4
+ [![Build Status](https://travis-ci.org/ruby-grape/grape.svg?branch=master)](https://travis-ci.org/ruby-grape/grape)
5
+ [![Dependency Status](https://gemnasium.com/ruby-grape/grape.svg)](https://gemnasium.com/ruby-grape/grape)
6
+ [![Code Climate](https://codeclimate.com/github/ruby-grape/grape.svg)](https://codeclimate.com/github/ruby-grape/grape)
7
+ [![Inline docs](http://inch-ci.org/github/ruby-grape/grape.svg)](http://inch-ci.org/github/ruby-grape/grape)
8
8
 
9
9
  ## Table of Contents
10
10
 
@@ -29,6 +29,13 @@
29
29
  - [Declared](#declared)
30
30
  - [Include Missing](#include-missing)
31
31
  - [Parameter Validation and Coercion](#parameter-validation-and-coercion)
32
+ - [Supported Parameter Types](#supported-parameter-types)
33
+ - [Custom Types and Coercions](#custom-types-and-coercions)
34
+ - [Multipart File Parameters](#multipart-file-parameters)
35
+ - [First-Class `JSON` Types](#first-class-json-types)
36
+ - [Multiple Allowed Types](#multiple-allowed-types)
37
+ - [Validation of Nested Parameters](#validation-of-nested-parameters)
38
+ - [Dependent Parameters](#dependent-parameters)
32
39
  - [Built-in Validators](#built-in-validators)
33
40
  - [Namespace Validation and Coercion](#namespace-validation-and-coercion)
34
41
  - [Custom Validators](#custom-validators)
@@ -37,6 +44,7 @@
37
44
  - [Headers](#headers)
38
45
  - [Routes](#routes)
39
46
  - [Helpers](#helpers)
47
+ - [Path Helpers](#path-helpers)
40
48
  - [Parameter Documentation](#parameter-documentation)
41
49
  - [Cookies](#cookies)
42
50
  - [HTTP Status Code](#http-status-code)
@@ -55,7 +63,7 @@
55
63
  - [API Data Formats](#api-data-formats)
56
64
  - [RESTful Model Representations](#restful-model-representations)
57
65
  - [Grape Entities](#grape-entities)
58
- - [Hypermedia](#hypermedia)
66
+ - [Hypermedia and Roar](#hypermedia-and-roar)
59
67
  - [Rabl](#rabl)
60
68
  - [Active Model Serializers](#active-model-serializers)
61
69
  - [Sending Raw or No Data](#sending-raw-or-no-data)
@@ -66,7 +74,7 @@
66
74
  - [Anchoring](#anchoring)
67
75
  - [Using Custom Middleware](#using-custom-middleware)
68
76
  - [Rails Middleware](#rails-middleware)
69
- - [Remote IP](#remote-ip)
77
+ - [Remote IP](#remote-ip)
70
78
  - [Writing Tests](#writing-tests)
71
79
  - [Writing Tests with Rack](#writing-tests-with-rack)
72
80
  - [Writing Tests with Rails](#writing-tests-with-rails)
@@ -75,8 +83,9 @@
75
83
  - [Reloading in Rack Applications](#reloading-in-rack-applications)
76
84
  - [Reloading in Rails Applications](#reloading-in-rails-applications)
77
85
  - [Performance Monitoring](#performance-monitoring)
86
+ - [Active Support Instrumentation](#active-support-instrumentation)
87
+ - [Monitoring Products](#monitoring-products)
78
88
  - [Contributing to Grape](#contributing-to-grape)
79
- - [Hacking on Grape](#hacking-on-grape)
80
89
  - [License](#license)
81
90
  - [Copyright](#copyright)
82
91
 
@@ -90,13 +99,13 @@ content negotiation, versioning and much more.
90
99
 
91
100
  ## Stable Release
92
101
 
93
- You're reading the documentation for the stable release of Grape 0.12.0.
102
+ You're reading the documentation for the stable release of Grae [0.14.0](https://github.com/ruby-grape/grape/blob/v0.14.0/README.md).
94
103
  Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
95
104
 
96
105
  ## Project Resources
97
106
 
107
+ * [Grape Website](http://www.ruby-grape.org)
98
108
  * Need help? [Grape Google Group](http://groups.google.com/group/ruby-grape)
99
- * [Grape Wiki](https://github.com/intridea/grape/wiki)
100
109
 
101
110
  ## Installation
102
111
 
@@ -134,20 +143,20 @@ module Twitter
134
143
  end
135
144
 
136
145
  resource :statuses do
137
- desc "Return a public timeline."
146
+ desc 'Return a public timeline.'
138
147
  get :public_timeline do
139
148
  Status.limit(20)
140
149
  end
141
150
 
142
- desc "Return a personal timeline."
151
+ desc 'Return a personal timeline.'
143
152
  get :home_timeline do
144
153
  authenticate!
145
154
  current_user.statuses.limit(20)
146
155
  end
147
156
 
148
- desc "Return a status."
157
+ desc 'Return a status.'
149
158
  params do
150
- requires :id, type: Integer, desc: "Status id."
159
+ requires :id, type: Integer, desc: 'Status id.'
151
160
  end
152
161
  route_param :id do
153
162
  get do
@@ -155,9 +164,9 @@ module Twitter
155
164
  end
156
165
  end
157
166
 
158
- desc "Create a status."
167
+ desc 'Create a status.'
159
168
  params do
160
- requires :status, type: String, desc: "Your status."
169
+ requires :status, type: String, desc: 'Your status.'
161
170
  end
162
171
  post do
163
172
  authenticate!
@@ -167,10 +176,10 @@ module Twitter
167
176
  })
168
177
  end
169
178
 
170
- desc "Update a status."
179
+ desc 'Update a status.'
171
180
  params do
172
- requires :id, type: String, desc: "Status ID."
173
- requires :status, type: String, desc: "Your status."
181
+ requires :id, type: String, desc: 'Status ID.'
182
+ requires :status, type: String, desc: 'Your status.'
174
183
  end
175
184
  put ':id' do
176
185
  authenticate!
@@ -180,9 +189,9 @@ module Twitter
180
189
  })
181
190
  end
182
191
 
183
- desc "Delete a status."
192
+ desc 'Delete a status.'
184
193
  params do
185
- requires :id, type: String, desc: "Status ID."
194
+ requires :id, type: String, desc: 'Status ID.'
186
195
  end
187
196
  delete ':id' do
188
197
  authenticate!
@@ -242,13 +251,13 @@ require 'grape'
242
251
 
243
252
  class API < Grape::API
244
253
  get :hello do
245
- { hello: "world" }
254
+ { hello: 'world' }
246
255
  end
247
256
  end
248
257
 
249
258
  class Web < Sinatra::Base
250
259
  get '/' do
251
- "Hello world."
260
+ 'Hello world.'
252
261
  end
253
262
  end
254
263
 
@@ -277,7 +286,7 @@ Additionally, if the version of your Rails is 4.0+ and the application uses the
277
286
 
278
287
  ```ruby
279
288
  # Gemfile
280
- gem "hashie-forbidden_attributes"
289
+ gem 'hashie-forbidden_attributes'
281
290
  ```
282
291
 
283
292
  See [below](#reloading-api-changes-in-development) for additional code that enables reloading of API changes in development.
@@ -323,6 +332,14 @@ Using this versioning strategy, clients should pass the desired version in the U
323
332
  version 'v1', using: :header, vendor: 'twitter'
324
333
  ```
325
334
 
335
+ Currently, Grape only supports versioned media types in the following format:
336
+
337
+ ```
338
+ vnd.vendor-and-or-resource-v1234+format
339
+ ```
340
+
341
+ Basically all tokens between the final `-` and the `+` will be interpreted as the version.
342
+
326
343
  Using this versioning strategy, clients should pass the desired version in the HTTP `Accept` head.
327
344
 
328
345
  curl -H Accept:application/vnd.twitter-v1+json http://localhost:9292/statuses/public_timeline
@@ -336,29 +353,6 @@ When an invalid `Accept` header is supplied, a `406 Not Acceptable` error is ret
336
353
  option is set to `false`. Otherwise a `404 Not Found` error is returned by Rack if no other route
337
354
  matches.
338
355
 
339
- ### HTTP Status Code
340
-
341
- By default Grape returns a 200 status code for `GET`-Requests and 201 for `POST`-Requests.
342
- You can use `status` to query and set the actual HTTP Status Code
343
-
344
- ```ruby
345
- post do
346
- status 202
347
-
348
- if status == 200
349
- # do some thing
350
- end
351
- end
352
- ```
353
-
354
- You can also use one of status codes symbols that are provided by [Rack utils](http://www.rubydoc.info/github/rack/rack/Rack/Utils#HTTP_STATUS_CODES-constant)
355
-
356
- ```ruby
357
- post do
358
- status :no_content
359
- end
360
- ```
361
-
362
356
  ### Accept-Version Header
363
357
 
364
358
  ```ruby
@@ -388,7 +382,7 @@ either in the URL query string or in the request body.
388
382
  The default name for the query parameter is 'apiver' but can be specified using the `:parameter` option.
389
383
 
390
384
  ```ruby
391
- version 'v1', using: :param, parameter: "v"
385
+ version 'v1', using: :param, parameter: 'v'
392
386
  ```
393
387
 
394
388
  curl http://localhost:9292/statuses/public_timeline?v=v1
@@ -399,21 +393,21 @@ version 'v1', using: :param, parameter: "v"
399
393
  You can add a description to API methods and namespaces.
400
394
 
401
395
  ```ruby
402
- desc "Returns your public timeline." do
396
+ desc 'Returns your public timeline.' do
403
397
  detail 'more details'
404
398
  params API::Entities::Status.documentation
405
399
  success API::Entities::Entity
406
- failure [[401, 'Unauthorized', "Entities::Error"]]
400
+ failure [[401, 'Unauthorized', 'Entities::Error']]
407
401
  named 'My named route'
408
- headers [XAuthToken: {
409
- description: 'Valdates your identity',
410
- required: true
411
- },
412
- XOptionalHeader: {
413
- description: 'Not really needed',
402
+ headers XAuthToken: {
403
+ description: 'Valdates your identity',
404
+ required: true
405
+ },
406
+ XOptionalHeader: {
407
+ description: 'Not really needed',
414
408
  required: false
415
- }
416
- ]
409
+ }
410
+
417
411
  end
418
412
  get :public_timeline do
419
413
  Status.limit(20)
@@ -466,7 +460,7 @@ curl --form image_file='@image.jpg;type=image/jpg' http://localhost:9292/upload
466
460
  The Grape endpoint:
467
461
 
468
462
  ```ruby
469
- post "upload" do
463
+ post 'upload' do
470
464
  # file in params[:image_file]
471
465
  end
472
466
  ```
@@ -479,19 +473,19 @@ In the case of conflict between either of:
479
473
 
480
474
  route string parameters will have precedence.
481
475
 
482
- #### Declared
476
+ ### Declared
483
477
 
484
- Grape allows you to access only the parameters that have been declared by your `params` block. It filters out the params that have been passed, but are not allowed. Let's have the following api:
478
+ Grape allows you to access only the parameters that have been declared by your `params` block. It filters out the params that have been passed, but are not allowed. Consider the following API endpoint:
485
479
 
486
480
  ````ruby
487
481
  format :json
488
482
 
489
483
  post 'users/signup' do
490
- { "declared_params" => declared(params) }
484
+ { 'declared_params' => declared(params) }
491
485
  end
492
486
  ````
493
487
 
494
- If we do not specify any params, declared will return an empty Hashie::Mash instance.
488
+ If we do not specify any params, `declared` will return an empty `Hashie::Mash` instance.
495
489
 
496
490
  **Request**
497
491
 
@@ -521,7 +515,7 @@ params do
521
515
  end
522
516
 
523
517
  post 'users/signup' do
524
- { "declared_params" => declared(params) }
518
+ { 'declared_params' => declared(params) }
525
519
  end
526
520
  ````
527
521
 
@@ -544,15 +538,19 @@ curl -X POST -H "Content-Type: application/json" localhost:9292/users/signup -d
544
538
  }
545
539
  ````
546
540
 
547
- Returned hash is a Hashie::Mash instance so you can access parameters via dot notation:
541
+ The returned hash is a `Hashie::Mash` instance, allowing you to access parameters via dot notation:
548
542
 
549
543
  ```ruby
550
- declared(params).user == declared(params)["user"]
544
+ declared(params).user == declared(params)['user']
551
545
  ```
552
546
 
553
- #### Include missing
554
547
 
555
- By default `declared(params)` returns parameters that has `nil` value. If you want to return only the parameters that have any value, you can use the `include_missing` option. By default it is `true`. Let's have the following api:
548
+ The `#declared` method is not available to `before` filters, as those are evaluated prior
549
+ to parameter coercion.
550
+
551
+ ### Include missing
552
+
553
+ By default `declared(params)` includes parameters that have `nil` values. If you want to return only the parameters that are not `nil`, you can use the `include_missing` option. By default, `include_missing` is set to `true`. Consider the following API:
556
554
 
557
555
  ````ruby
558
556
  format :json
@@ -563,7 +561,7 @@ params do
563
561
  end
564
562
 
565
563
  post 'users/signup' do
566
- { "declared_params" => declared(params, include_missing: false) }
564
+ { 'declared_params' => declared(params, include_missing: false) }
567
565
  end
568
566
  ````
569
567
 
@@ -613,7 +611,7 @@ params do
613
611
  end
614
612
 
615
613
  post 'users/signup' do
616
- { "declared_params" => declared(params, include_missing: false) }
614
+ { 'declared_params' => declared(params, include_missing: false) }
617
615
  end
618
616
  ````
619
617
 
@@ -685,7 +683,7 @@ You can define validations and coercion options for your parameters using a `par
685
683
  ```ruby
686
684
  params do
687
685
  requires :id, type: Integer
688
- optional :text, type: String, regexp: /^[a-z]+$/
686
+ optional :text, type: String, regexp: /\A[a-z]+\z/
689
687
  group :media do
690
688
  requires :url
691
689
  end
@@ -729,7 +727,167 @@ params do
729
727
  end
730
728
  ```
731
729
 
732
- #### Validation of Nested Parameters
730
+ ### Supported Parameter Types
731
+
732
+ The following are all valid types, supported out of the box by Grape:
733
+
734
+ * Integer
735
+ * Float
736
+ * BigDecimal
737
+ * Numeric
738
+ * Date
739
+ * DateTime
740
+ * Time
741
+ * Boolean
742
+ * String
743
+ * Symbol
744
+ * Rack::Multipart::UploadedFile (alias `File`)
745
+ * JSON
746
+
747
+ ### Custom Types and Coercions
748
+
749
+ Aside from the default set of supported types listed above, any class can be
750
+ used as a type so long as an explicit coercion method is supplied. If the type
751
+ implements a class-level `parse` method, Grape will use it automatically.
752
+ This method must take one string argument and return an instance of the correct
753
+ type, or raise an exception to indicate the value was invalid. E.g.,
754
+
755
+ ```ruby
756
+ class Color
757
+ attr_reader :value
758
+ def initialize(color)
759
+ @value = color
760
+ end
761
+
762
+ def self.parse(value)
763
+ fail 'Invalid color' unless %w(blue red green).include?(value)
764
+ new(value)
765
+ end
766
+ end
767
+
768
+ # ...
769
+
770
+ params do
771
+ requires :color, type: Color, default: Color.new('blue')
772
+ end
773
+
774
+ get '/stuff' do
775
+ # params[:color] is already a Color.
776
+ params[:color].value
777
+ end
778
+ ```
779
+
780
+ Alternatively, a custom coercion method may be supplied for any type of parameter
781
+ using `coerce_with`. Any class or object may be given that implements a `parse` or
782
+ `call` method, in that order of precedence. The method must accept a single string
783
+ parameter, and the return value must match the given `type`.
784
+
785
+ ```ruby
786
+ params do
787
+ requires :passwd, type: String, coerce_with: Base64.method(:decode)
788
+ requires :loud_color, type: Color, coerce_with: ->(c) { Color.parse(c.downcase) }
789
+
790
+ requires :obj, type: Hash, coerce_with: JSON do
791
+ requires :words, type: Array[String], coerce_with: ->(val) { val.split(/\s+/) }
792
+ optional :time, type: Time, coerce_with: Chronic
793
+ end
794
+ end
795
+ ```
796
+
797
+ ### Multipart File Parameters
798
+
799
+ Grape makes use of `Rack::Request`'s built-in support for multipart
800
+ file parameters. Such parameters can be declared with `type: File`:
801
+
802
+ ```ruby
803
+ params do
804
+ requires :avatar, type: File
805
+ end
806
+ post '/' do
807
+ # Parameter will be wrapped using Hashie:
808
+ params.avatar.filename # => 'avatar.png'
809
+ params.avatar.type # => 'image/png'
810
+ params.avatar.tempfile # => #<File>
811
+ end
812
+ ```
813
+
814
+ ### First-Class `JSON` Types
815
+
816
+ Grape supports complex parameters given as JSON-formatted strings using the special `type: JSON`
817
+ declaration. JSON objects and arrays of objects are accepted equally, with nested validation
818
+ rules applied to all objects in either case:
819
+
820
+ ```ruby
821
+ params do
822
+ requires :json, type: JSON do
823
+ requires :int, type: Integer, values: [1, 2, 3]
824
+ end
825
+ end
826
+ get '/' do
827
+ params[:json].inspect
828
+ end
829
+
830
+ # ...
831
+
832
+ client.get('/', json: '{"int":1}') # => "{:int=>1}"
833
+ client.get('/', json: '[{"int":"1"}]') # => "[{:int=>1}]"
834
+
835
+ client.get('/', json: '{"int":4}') # => HTTP 400
836
+ client.get('/', json: '[{"int":4}]') # => HTTP 400
837
+ ```
838
+
839
+ Additionally `type: Array[JSON]` may be used, which explicitly marks the parameter as an array
840
+ of objects. If a single object is supplied it will be wrapped.
841
+
842
+ ```ruby
843
+ params do
844
+ requires :json, type: Array[JSON] do
845
+ requires :int, type: Integer
846
+ end
847
+ end
848
+ get '/' do
849
+ params[:json].each { |obj| ... } # always works
850
+ end
851
+ ```
852
+ For stricter control over the type of JSON structure which may be supplied,
853
+ use `type: Array, coerce_with: JSON` or `type: Hash, coerce_with: JSON`.
854
+
855
+ ### Multiple Allowed Types
856
+
857
+ Variant-type parameters can be declared using the `types` option rather than `type`:
858
+
859
+ ```ruby
860
+ params do
861
+ requires :status_code, types: [Integer, String, Array[Integer, String]]
862
+ end
863
+ get '/' do
864
+ params[:status_code].inspect
865
+ end
866
+
867
+ # ...
868
+
869
+ client.get('/', status_code: 'OK_GOOD') # => "OK_GOOD"
870
+ client.get('/', status_code: 300) # => 300
871
+ client.get('/', status_code: %w(404 NOT FOUND)) # => [404, "NOT", "FOUND"]
872
+ ```
873
+
874
+ As a special case, variant-member-type collections may also be declared, by
875
+ passing a `Set` or `Array` with more than one member to `type`:
876
+
877
+ ```ruby
878
+ params do
879
+ requires :status_codes, type: Array[Integer,String]
880
+ end
881
+ get '/' do
882
+ params[:status_codes].inspect
883
+ end
884
+
885
+ # ...
886
+
887
+ client.get('/', status_codes: %w(1 two)) # => [1, "two"]
888
+ ```
889
+
890
+ ### Validation of Nested Parameters
733
891
 
734
892
  Parameters can be nested using `group` or by calling `requires` or `optional` with a block.
735
893
  In the above example, this means `params[:media][:url]` is required along with `params[:id]`,
@@ -752,6 +910,21 @@ params do
752
910
  end
753
911
  ```
754
912
 
913
+ ### Dependent Parameters
914
+
915
+ Suppose some of your parameters are only relevant if another parameter is given;
916
+ Grape allows you to express this relationship through the `given` method in your
917
+ parameters block, like so:
918
+
919
+ ```ruby
920
+ params do
921
+ optional :shelf_id, type: Integer
922
+ given :shelf_id do
923
+ requires :bin_id, type: Integer
924
+ end
925
+ end
926
+ ```
927
+
755
928
  ### Built-in Validators
756
929
 
757
930
  #### `allow_blank`
@@ -939,14 +1112,14 @@ Namespaces allow parameter definitions and apply to every method within the name
939
1112
  ```ruby
940
1113
  namespace :statuses do
941
1114
  params do
942
- requires :user_id, type: Integer, desc: "A user ID."
1115
+ requires :user_id, type: Integer, desc: 'A user ID.'
943
1116
  end
944
- namespace ":user_id" do
1117
+ namespace ':user_id' do
945
1118
  desc "Retrieve a user's status."
946
1119
  params do
947
- requires :status_id, type: Integer, desc: "A status ID."
1120
+ requires :status_id, type: Integer, desc: 'A status ID.'
948
1121
  end
949
- get ":status_id" do
1122
+ get ':status_id' do
950
1123
  User.find(params[:user_id]).statuses.find(params[:status_id])
951
1124
  end
952
1125
  end
@@ -961,11 +1134,11 @@ You can conveniently define a route parameter as a namespace using `route_param`
961
1134
  ```ruby
962
1135
  namespace :statuses do
963
1136
  route_param :id do
964
- desc "Returns all replies for a status."
1137
+ desc 'Returns all replies for a status.'
965
1138
  get 'replies' do
966
1139
  Status.find(params[:id]).replies
967
1140
  end
968
- desc "Returns a status."
1141
+ desc 'Returns a status.'
969
1142
  get do
970
1143
  Status.find(params[:id])
971
1144
  end
@@ -978,8 +1151,8 @@ end
978
1151
  ```ruby
979
1152
  class AlphaNumeric < Grape::Validations::Base
980
1153
  def validate_param!(attr_name, params)
981
- unless params[attr_name] =~ /^[[:alnum:]]+$/
982
- fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: "must consist of alpha-numeric characters"
1154
+ unless params[attr_name] =~ /\A[[:alnum:]]+\z/
1155
+ fail Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: 'must consist of alpha-numeric characters'
983
1156
  end
984
1157
  end
985
1158
  end
@@ -1034,6 +1207,16 @@ subject.rescue_from Grape::Exceptions::ValidationErrors do |e|
1034
1207
  end
1035
1208
  ```
1036
1209
 
1210
+ `Grape::Exceptions::ValidationErrors#full_messages` returns the validation messages as an array. `Grape::Exceptions::ValidationErrors#message` joins the messages to one string.
1211
+
1212
+ For responding with an array of validation messages, you can use `Grape::Exceptions::ValidationErrors#full_messages`.
1213
+ ```ruby
1214
+ format :json
1215
+ subject.rescue_from Grape::Exceptions::ValidationErrors do |e|
1216
+ error!({ messages: e.full_messages }, 400)
1217
+ end
1218
+ ```
1219
+
1037
1220
  ### I18n
1038
1221
 
1039
1222
  Grape supports I18n for parameter-related error messages, but will fallback to English if
@@ -1081,7 +1264,7 @@ namespace :outer, requirements: { id: /[0-9]*/ } do
1081
1264
  get :id do
1082
1265
  end
1083
1266
 
1084
- get ":id/edit" do
1267
+ get ':id/edit' do
1085
1268
  end
1086
1269
  end
1087
1270
  ```
@@ -1127,7 +1310,7 @@ class API < Grape::API
1127
1310
  end
1128
1311
  end
1129
1312
 
1130
- desc "Get collection"
1313
+ desc 'Get collection'
1131
1314
  params do
1132
1315
  use :pagination # aliases: includes, use_scope
1133
1316
  end
@@ -1157,7 +1340,7 @@ end
1157
1340
  class API < Grape::API
1158
1341
  helpers SharedParams
1159
1342
 
1160
- desc "Get collection."
1343
+ desc 'Get collection.'
1161
1344
  params do
1162
1345
  use :period, :pagination
1163
1346
  end
@@ -1187,7 +1370,7 @@ end
1187
1370
  class API < Grape::API
1188
1371
  helpers SharedParams
1189
1372
 
1190
- desc "Get a sorted collection."
1373
+ desc 'Get a sorted collection.'
1191
1374
  params do
1192
1375
  use :order, order_by:%i(id created_at), default_order_by: :created_at, default_order: :asc
1193
1376
  end
@@ -1198,6 +1381,10 @@ class API < Grape::API
1198
1381
  end
1199
1382
  ```
1200
1383
 
1384
+ ## Path Helpers
1385
+
1386
+ If you need methods for generating paths inside your endpoints, please see the [grape-route-helpers](https://github.com/reprah/grape-route-helpers) gem.
1387
+
1201
1388
  ## Parameter Documentation
1202
1389
 
1203
1390
  You can attach additional documentation to `params` using a `documentation` hash.
@@ -1252,6 +1439,29 @@ Specify an optional path.
1252
1439
  cookies.delete :status_count, path: '/'
1253
1440
  ```
1254
1441
 
1442
+ ## HTTP Status Code
1443
+
1444
+ By default Grape returns a 200 status code for `GET`-Requests and 201 for `POST`-Requests.
1445
+ You can use `status` to query and set the actual HTTP Status Code
1446
+
1447
+ ```ruby
1448
+ post do
1449
+ status 202
1450
+
1451
+ if status == 200
1452
+ # do some thing
1453
+ end
1454
+ end
1455
+ ```
1456
+
1457
+ You can also use one of status codes symbols that are provided by [Rack utils](http://www.rubydoc.info/github/rack/rack/Rack/Utils#HTTP_STATUS_CODES-constant)
1458
+
1459
+ ```ruby
1460
+ post do
1461
+ status :no_content
1462
+ end
1463
+ ```
1464
+
1255
1465
  ## Redirecting
1256
1466
 
1257
1467
  You can redirect to a new url temporarily (302) or permanently (301).
@@ -1336,10 +1546,10 @@ You can also return JSON formatted objects by raising error! and passing a hash
1336
1546
  instead of a message.
1337
1547
 
1338
1548
  ```ruby
1339
- error!({ error: "unexpected error", detail: "missing widget" }, 500)
1549
+ error!({ error: 'unexpected error', detail: 'missing widget' }, 500)
1340
1550
  ```
1341
1551
 
1342
- You can present documented errors with a Grape entity using the the [grape-entity](https://github.com/intridea/grape-entity) gem.
1552
+ You can present documented errors with a Grape entity using the the [grape-entity](https://github.com/ruby-grape/grape-entity) gem.
1343
1553
 
1344
1554
  ```ruby
1345
1555
  module API
@@ -1376,7 +1586,7 @@ By default Grape returns a 500 status code from `error!`. You can change this wi
1376
1586
  class API < Grape::API
1377
1587
  default_error_status 400
1378
1588
  get '/example' do
1379
- error! "This should have http status code 400"
1589
+ error! 'This should have http status code 400'
1380
1590
  end
1381
1591
  end
1382
1592
  ```
@@ -1421,7 +1631,7 @@ Custom error formatters for existing and additional types can be defined with a
1421
1631
 
1422
1632
  ```ruby
1423
1633
  class Twitter::API < Grape::API
1424
- error_formatter :txt, lambda { |message, backtrace, options, env|
1634
+ error_formatter :txt, ->(message, backtrace, options, env) {
1425
1635
  "error: #{message} from #{backtrace}"
1426
1636
  }
1427
1637
  end
@@ -1441,8 +1651,7 @@ class Twitter::API < Grape::API
1441
1651
  end
1442
1652
  ```
1443
1653
 
1444
- You can rescue all exceptions with a code block. The `error!` wrapper
1445
- automatically sets the default error code and content-type.
1654
+ You can rescue all exceptions with a code block. The `error!` wrapper automatically sets the default error code and content-type.
1446
1655
 
1447
1656
  ```ruby
1448
1657
  class Twitter::API < Grape::API
@@ -1458,19 +1667,17 @@ Optionally, you can set the format, status code and headers.
1458
1667
  class Twitter::API < Grape::API
1459
1668
  format :json
1460
1669
  rescue_from :all do |e|
1461
- error!({ error: "Server error.", 500, { 'Content-Type' => 'text/error' } })
1670
+ error!({ error: 'Server error.' }, 500, { 'Content-Type' => 'text/error' })
1462
1671
  end
1463
1672
  end
1464
1673
  ```
1465
1674
 
1466
-
1467
- You can also rescue specific exceptions with a code block and handle the Rack
1468
- response at the lowest level.
1675
+ You can also rescue specific exceptions with a code block and handle the Rack response at the lowest level.
1469
1676
 
1470
1677
  ```ruby
1471
1678
  class Twitter::API < Grape::API
1472
1679
  rescue_from :all do |e|
1473
- Rack::Response.new([ e.message ], 500, { "Content-type" => "text/error" }).finish
1680
+ Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' }).finish
1474
1681
  end
1475
1682
  end
1476
1683
  ```
@@ -1524,9 +1731,15 @@ rescue_from RuntimeError, rescue_subclasses: false do |e|
1524
1731
  end
1525
1732
  ```
1526
1733
 
1527
- #### Rails 3.x
1734
+ The `rescue_from` block must return a `Rack::Response` object, call `error!` or re-raise an exception.
1735
+
1736
+ #### Unrescuable Exceptions
1528
1737
 
1529
- When mounted inside containers, such as Rails 3.x, errors like "404 Not Found" or
1738
+ `Grape::Exceptions::InvalidVersionHeader`, which is raised when the version in the request header doesn't match the currently evaluated version for the endpoint, will _never_ be rescued from a `rescue_from` block (even a `rescue_from :all`) This is because Grape relies on Rack to catch that error and try the next versioned-route for cases where there exist identical Grape endpoints with different versions.
1739
+
1740
+ ### Rails 3.x
1741
+
1742
+ When mounted inside containers, such as Rails 3.x, errors such as "404 Not Found" or
1530
1743
  "406 Not Acceptable" will likely be handled and rendered by Rails handlers. For instance,
1531
1744
  accessing a nonexistent route "/api/foo" raises a 404, which inside rails will ultimately
1532
1745
  be translated to an `ActionController::RoutingError`, which most likely will get rendered
@@ -1668,11 +1881,11 @@ For example, the following API will let you upload arbitrary files and return th
1668
1881
 
1669
1882
  ```ruby
1670
1883
  class Twitter::API < Grape::API
1671
- post "attachment" do
1884
+ post 'attachment' do
1672
1885
  filename = params[:file][:filename]
1673
1886
  content_type MIME::Types.type_for(filename)[0].to_s
1674
1887
  env['api.format'] = :binary # there's no formatter for :binary, data will be returned "as is"
1675
- header "Content-Disposition", "attachment; filename*=UTF-8''#{URI.escape(filename)}"
1888
+ header 'Content-Disposition', "attachment; filename*=UTF-8''#{URI.escape(filename)}"
1676
1889
  params[:file][:tempfile].read
1677
1890
  end
1678
1891
  end
@@ -1725,7 +1938,7 @@ If you do not want this behavior, set the default error formatter with `default_
1725
1938
  ```ruby
1726
1939
  class Twitter::API < Grape::API
1727
1940
  format :json
1728
- content_type :txt, "text/plain"
1941
+ content_type :txt, 'text/plain'
1729
1942
  default_error_formatter :txt
1730
1943
  end
1731
1944
  ```
@@ -1734,8 +1947,8 @@ Custom formatters for existing and additional types can be defined with a proc.
1734
1947
 
1735
1948
  ```ruby
1736
1949
  class Twitter::API < Grape::API
1737
- content_type :xls, "application/vnd.ms-excel"
1738
- formatter :xls, lambda { |object, env| object.to_xls }
1950
+ content_type :xls, 'application/vnd.ms-excel'
1951
+ formatter :xls, ->(object, env) { object.to_xls }
1739
1952
  end
1740
1953
  ```
1741
1954
 
@@ -1749,7 +1962,7 @@ module XlsFormatter
1749
1962
  end
1750
1963
 
1751
1964
  class Twitter::API < Grape::API
1752
- content_type :xls, "application/vnd.ms-excel"
1965
+ content_type :xls, 'application/vnd.ms-excel'
1753
1966
  formatter :xls, XlsFormatter
1754
1967
  end
1755
1968
  ```
@@ -1762,6 +1975,11 @@ Built-in formatters are the following.
1762
1975
  * `:serializable_hash`: use object's `serializable_hash` when available, otherwise fallback to `:json`
1763
1976
  * `:binary`: data will be returned "as is"
1764
1977
 
1978
+ Response statuses that indicate no content as defined by [Rack](https://github.com/rack)
1979
+ [here](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L567)
1980
+ will bypass serialization and the body entity - though there should be none -
1981
+ will not be modified.
1982
+
1765
1983
  ### JSONP
1766
1984
 
1767
1985
  Grape supports JSONP via [Rack::JSONP](https://github.com/rack/rack-contrib), part of the
@@ -1807,7 +2025,7 @@ by setting the `Content-Type` header.
1807
2025
  ```ruby
1808
2026
  class API < Grape::API
1809
2027
  get '/home_timeline_js' do
1810
- content_type "application/javascript"
2028
+ content_type 'application/javascript'
1811
2029
  "var statuses = ...;"
1812
2030
  end
1813
2031
  end
@@ -1835,11 +2053,11 @@ end
1835
2053
  ```
1836
2054
 
1837
2055
  ```ruby
1838
- content_type :txt, "text/plain"
1839
- content_type :custom, "text/custom"
2056
+ content_type :txt, 'text/plain'
2057
+ content_type :custom, 'text/custom'
1840
2058
  parser :custom, CustomParser
1841
2059
 
1842
- put "value" do
2060
+ put 'value' do
1843
2061
  params[:value]
1844
2062
  end
1845
2063
  ```
@@ -1860,8 +2078,8 @@ hash may include `:with`, which defines the entity to expose.
1860
2078
 
1861
2079
  ### Grape Entities
1862
2080
 
1863
- Add the [grape-entity](https://github.com/intridea/grape-entity) gem to your Gemfile.
1864
- Please refer to the [grape-entity documentation](https://github.com/intridea/grape-entity/blob/master/README.md)
2081
+ Add the [grape-entity](https://github.com/ruby-grape/grape-entity) gem to your Gemfile.
2082
+ Please refer to the [grape-entity documentation](https://github.com/ruby-grape/grape-entity/blob/master/README.md)
1865
2083
  for more details.
1866
2084
 
1867
2085
  The following example exposes statuses.
@@ -1871,9 +2089,9 @@ module API
1871
2089
  module Entities
1872
2090
  class Status < Grape::Entity
1873
2091
  expose :user_name
1874
- expose :text, documentation: { type: "string", desc: "Status update text." }
2092
+ expose :text, documentation: { type: 'string', desc: 'Status update text.' }
1875
2093
  expose :ip, if: { type: :full }
1876
- expose :user_type, :user_id, if: lambda { |status, options| status.user.public? }
2094
+ expose :user_type, :user_id, if: ->(status, options) { status.user.public? }
1877
2095
  expose :digest { |status, options| Digest::MD5.hexdigest(status.txt) }
1878
2096
  expose :replies, using: API::Status, as: :replies
1879
2097
  end
@@ -1981,12 +2199,12 @@ end
1981
2199
 
1982
2200
  ### Hypermedia and Roar
1983
2201
 
1984
- You can use [Roar](https://github.com/apotonick/roar) to render HAL or Collection+JSON with the help of [grape-roar](https://github.com/dblock/grape-roar), which defines a custom JSON formatter and enables presenting entities with Grape's `present` keyword.
2202
+ You can use [Roar](https://github.com/apotonick/roar) to render HAL or Collection+JSON with the help of [grape-roar](https://github.com/ruby-grape/grape-roar), which defines a custom JSON formatter and enables presenting entities with Grape's `present` keyword.
1985
2203
 
1986
2204
  ### Rabl
1987
2205
 
1988
2206
  You can use [Rabl](https://github.com/nesquena/rabl) templates with the help of the
1989
- [grape-rabl](https://github.com/LTe/grape-rabl) gem, which defines a custom Grape Rabl
2207
+ [grape-rabl](https://github.com/ruby-grape/grape-rabl) gem, which defines a custom Grape Rabl
1990
2208
  formatter.
1991
2209
 
1992
2210
  ### Active Model Serializers
@@ -2023,6 +2241,8 @@ end
2023
2241
  Use `body false` to return `204 No Content` without any data or content-type.
2024
2242
 
2025
2243
  You can also set the response to a file-like object with `file`.
2244
+ Note: Rack will read your entire Enumerable before returning a response. If
2245
+ you would like to stream the response, see `stream`.
2026
2246
 
2027
2247
  ```ruby
2028
2248
  class FileStreamer
@@ -2044,6 +2264,16 @@ class API < Grape::API
2044
2264
  end
2045
2265
  ```
2046
2266
 
2267
+ If you want a file-like object to be streamed using Rack::Chunked, use `stream`.
2268
+
2269
+ ```ruby
2270
+ class API < Grape::API
2271
+ get '/' do
2272
+ stream FileStreamer.new('file.bin')
2273
+ end
2274
+ end
2275
+ ```
2276
+
2047
2277
  ## Authentication
2048
2278
 
2049
2279
  ### Basic and Digest Auth
@@ -2077,7 +2307,7 @@ For registering a Middleware you need the following options:
2077
2307
  * `label` - the name for your authenticator to use it later
2078
2308
  * `MiddlewareClass` - the MiddlewareClass to use for authentication
2079
2309
  * `option_lookup_proc` - A Proc with one Argument to lookup the options at
2080
- runtime (return value is an `Array` as Paramter for the Middleware).
2310
+ runtime (return value is an `Array` as Parameter for the Middleware).
2081
2311
 
2082
2312
  Example:
2083
2313
 
@@ -2104,7 +2334,7 @@ Grape exposes arrays of API versions and compiled routes. Each route contains a
2104
2334
  ```ruby
2105
2335
  class TwitterAPI < Grape::API
2106
2336
  version 'v1'
2107
- desc "Includes custom settings."
2337
+ desc 'Includes custom settings.'
2108
2338
  route_setting :custom, key: 'value'
2109
2339
  get do
2110
2340
 
@@ -2128,11 +2358,11 @@ It's possible to retrieve the information about the current route from within an
2128
2358
 
2129
2359
  ```ruby
2130
2360
  class MyAPI < Grape::API
2131
- desc "Returns a description of a parameter."
2361
+ desc 'Returns a description of a parameter.'
2132
2362
  params do
2133
- requires :id, type: Integer, desc: "Identity."
2363
+ requires :id, type: Integer, desc: 'Identity.'
2134
2364
  end
2135
- get "params/:id" do
2365
+ get 'params/:id' do
2136
2366
  route.route_params[params[:id]] # yields the parameter description
2137
2367
  end
2138
2368
  end
@@ -2173,7 +2403,7 @@ E.g. using `before`:
2173
2403
 
2174
2404
  ```ruby
2175
2405
  before do
2176
- header "X-Robots-Tag", "noindex"
2406
+ header 'X-Robots-Tag', 'noindex'
2177
2407
  end
2178
2408
  ```
2179
2409
 
@@ -2298,12 +2528,12 @@ This will match all paths starting with '/statuses/'. There is one caveat though
2298
2528
  the `params[:status]` parameter only holds the first part of the request url.
2299
2529
  Luckily this can be circumvented by using the described above syntax for path
2300
2530
  specification and using the `PATH_INFO` Rack environment variable, using
2301
- `env["PATH_INFO"]`. This will hold everything that comes after the '/statuses/'
2531
+ `env['PATH_INFO']`. This will hold everything that comes after the '/statuses/'
2302
2532
  part.
2303
2533
 
2304
- # Using Custom Middleware
2534
+ ## Using Custom Middleware
2305
2535
 
2306
- ## Rails Middleware
2536
+ ### Rails Middleware
2307
2537
 
2308
2538
  Note that when you're using Grape mounted on Rails you don't have to use Rails middleware because it's already included into your middleware stack.
2309
2539
  You only have to implement the helpers to access the specific `env` variable.
@@ -2320,7 +2550,7 @@ class API < Grape::API
2320
2550
 
2321
2551
  helpers do
2322
2552
  def client_ip
2323
- env["action_dispatch.remote_ip"].to_s
2553
+ env['action_dispatch.remote_ip'].to_s
2324
2554
  end
2325
2555
  end
2326
2556
 
@@ -2350,20 +2580,32 @@ describe Twitter::API do
2350
2580
  Twitter::API
2351
2581
  end
2352
2582
 
2353
- describe Twitter::API do
2354
- describe "GET /api/statuses/public_timeline" do
2355
- it "returns an empty array of statuses" do
2356
- get "/api/statuses/public_timeline"
2357
- expect(last_response.status).to eq(200)
2358
- expect(JSON.parse(last_response.body)).to eq []
2359
- end
2583
+ context 'GET /api/statuses/public_timeline' do
2584
+ it 'returns an empty array of statuses' do
2585
+ get '/api/statuses/public_timeline'
2586
+ expect(last_response.status).to eq(200)
2587
+ expect(JSON.parse(last_response.body)).to eq []
2360
2588
  end
2361
- describe "GET /api/statuses/:id" do
2362
- it "returns a status by id" do
2363
- status = Status.create!
2364
- get "/api/statuses/#{status.id}"
2365
- expect(last_response.body).to eq status.to_json
2366
- end
2589
+ end
2590
+ context 'GET /api/statuses/:id' do
2591
+ it 'returns a status by id' do
2592
+ status = Status.create!
2593
+ get "/api/statuses/#{status.id}"
2594
+ expect(last_response.body).to eq status.to_json
2595
+ end
2596
+ end
2597
+ end
2598
+ ```
2599
+
2600
+ There's no standard way of sending arrays of objects via an HTTP GET, so POST JSON data and specify the correct content-type.
2601
+
2602
+ ```ruby
2603
+ describe Twitter::API do
2604
+ context 'POST /api/statuses' do
2605
+ it 'creates many statuses' do
2606
+ statuses = [{ text: '...' }, { text: '...'}]
2607
+ post '/api/statuses', statuses.to_json, 'CONTENT_TYPE' => 'application/json'
2608
+ expect(last_response.body).to eq 201
2367
2609
  end
2368
2610
  end
2369
2611
  end
@@ -2381,8 +2623,8 @@ Airborne.configure do |config|
2381
2623
  end
2382
2624
 
2383
2625
  describe Twitter::API do
2384
- describe "GET /api/statuses/:id" do
2385
- it "returns a status by id" do
2626
+ context 'GET /api/statuses/:id' do
2627
+ it 'returns a status by id' do
2386
2628
  status = Status.create!
2387
2629
  get "/api/statuses/#{status.id}"
2388
2630
  expect_json(status.as_json)
@@ -2394,7 +2636,7 @@ end
2394
2636
  #### MiniTest
2395
2637
 
2396
2638
  ```ruby
2397
- require "test_helper"
2639
+ require 'test_helper'
2398
2640
 
2399
2641
  class Twitter::APITest < MiniTest::Test
2400
2642
  include Rack::Test::Methods
@@ -2404,7 +2646,7 @@ class Twitter::APITest < MiniTest::Test
2404
2646
  end
2405
2647
 
2406
2648
  def test_get_api_statuses_public_timeline_returns_an_empty_array_of_statuses
2407
- get "/api/statuses/public_timeline"
2649
+ get '/api/statuses/public_timeline'
2408
2650
  assert last_response.ok?
2409
2651
  assert_equal [], JSON.parse(last_response.body)
2410
2652
  end
@@ -2423,15 +2665,15 @@ end
2423
2665
 
2424
2666
  ```ruby
2425
2667
  describe Twitter::API do
2426
- describe "GET /api/statuses/public_timeline" do
2427
- it "returns an empty array of statuses" do
2428
- get "/api/statuses/public_timeline"
2668
+ context 'GET /api/statuses/public_timeline' do
2669
+ it 'returns an empty array of statuses' do
2670
+ get '/api/statuses/public_timeline'
2429
2671
  expect(response.status).to eq(200)
2430
2672
  expect(JSON.parse(response.body)).to eq []
2431
2673
  end
2432
2674
  end
2433
- describe "GET /api/statuses/:id" do
2434
- it "returns a status by id" do
2675
+ context 'GET /api/statuses/:id' do
2676
+ it 'returns a status by id' do
2435
2677
  status = Status.create!
2436
2678
  get "/api/statuses/#{status.id}"
2437
2679
  expect(response.body).to eq status.to_json
@@ -2459,13 +2701,13 @@ class Twitter::APITest < ActiveSupport::TestCase
2459
2701
  Rails.application
2460
2702
  end
2461
2703
 
2462
- test "GET /api/statuses/public_timeline returns an empty array of statuses" do
2463
- get "/api/statuses/public_timeline"
2704
+ test 'GET /api/statuses/public_timeline returns an empty array of statuses' do
2705
+ get '/api/statuses/public_timeline'
2464
2706
  assert last_response.ok?
2465
2707
  assert_equal [], JSON.parse(last_response.body)
2466
2708
  end
2467
2709
 
2468
- test "GET /api/statuses/:id returns a status by id" do
2710
+ test 'GET /api/statuses/:id returns a status by id' do
2469
2711
  status = Status.create!
2470
2712
  get "/api/statuses/#{status.id}"
2471
2713
  assert_equal status.to_json, last_response.body
@@ -2510,15 +2752,15 @@ Add API paths to `config/application.rb`.
2510
2752
 
2511
2753
  ```ruby
2512
2754
  # Auto-load API and its subdirectories
2513
- config.paths.add File.join("app", "api"), glob: File.join("**", "*.rb")
2514
- config.autoload_paths += Dir[Rails.root.join("app", "api", "*")]
2755
+ config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
2756
+ config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
2515
2757
  ```
2516
2758
 
2517
2759
  Create `config/initializers/reload_api.rb`.
2518
2760
 
2519
2761
  ```ruby
2520
2762
  if Rails.env.development?
2521
- ActiveSupport::Dependencies.explicitly_unloadable_constants << "Twitter::API"
2763
+ ActiveSupport::Dependencies.explicitly_unloadable_constants << 'Twitter::API'
2522
2764
 
2523
2765
  api_files = Dir[Rails.root.join('app', 'api', '**', '*.rb')]
2524
2766
  api_reloader = ActiveSupport::FileUpdateChecker.new(api_files) do
@@ -2534,9 +2776,39 @@ See [StackOverflow #3282655](http://stackoverflow.com/questions/3282655/ruby-on-
2534
2776
 
2535
2777
  ## Performance Monitoring
2536
2778
 
2537
- Grape integrates with NewRelic via the
2538
- [newrelic-grape](https://github.com/flyerhzm/newrelic-grape) gem, and
2539
- with Librato Metrics with the [grape-librato](https://github.com/seanmoon/grape-librato) gem.
2779
+ ### Active Support Instrumentation
2780
+
2781
+ Grape has built-in support for [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) which provides simple hook points to instrument key parts of your application.
2782
+
2783
+ The following are currently supported:
2784
+
2785
+ #### endpoint_run.grape
2786
+
2787
+ The main execution of an endpoint, includes filters and rendering.
2788
+
2789
+ * *endpoint* - The endpoint instance
2790
+
2791
+ #### endpoint_render.grape
2792
+
2793
+ The execution of the main content block of the endpoint.
2794
+
2795
+ * *endpoint* - The endpoint instance
2796
+
2797
+ #### endpoint_run_filters.grape
2798
+
2799
+ * *endpoint* - The endpoint instance
2800
+ * *filters* - The filters being executed
2801
+ * *type* - The type of filters (before, before_validation, after_validation, after)
2802
+
2803
+ See the [ActiveSupport::Notifications documentation](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) for information on how to subscribe to these events.
2804
+
2805
+ ### Monitoring Products
2806
+
2807
+ Grape integrates with following third-party tools:
2808
+
2809
+ * **New Relic** - [built-in support](https://docs.newrelic.com/docs/agents/ruby-agent/frameworks/grape-instrumentation) from v3.10.0 of the official [newrelic_rpm](https://github.com/newrelic/rpm) gem, also [newrelic-grape](https://github.com/xinminlabs/newrelic-grape) gem
2810
+ * **Librato Metrics** - [grape-librato](https://github.com/seanmoon/grape-librato) gem
2811
+ * **[Skylight](https://www.skylight.io/)** - [skylight](https://github.com/skylightio/skylight-ruby) gem, [documentation](https://docs.skylight.io/grape/)
2540
2812
 
2541
2813
  ## Contributing to Grape
2542
2814
 
@@ -2545,13 +2817,6 @@ features and discuss issues.
2545
2817
 
2546
2818
  See [CONTRIBUTING](CONTRIBUTING.md).
2547
2819
 
2548
- ## Hacking on Grape
2549
-
2550
- You can start hacking on Grape on
2551
- [Nitrous.IO](https://www.nitrous.io/?utm_source=github.com&utm_campaign=grape&utm_medium=hackonnitrous) in a matter of seconds:
2552
-
2553
- [![Hack intridea/grape on Nitrous.IO](https://d3o0mnbgv6k92a.cloudfront.net/assets/hack-l-v1-3cc067e71372f6045e1949af9d96095b.png)](https://www.nitrous.io/hack_button?source=embed&runtime=rails&repo=intridea%2Fgrape&file_to_open=README.md)
2554
-
2555
2820
  ## License
2556
2821
 
2557
2822
  MIT License. See LICENSE for details.