grape 0.2.2 → 0.2.3
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.
- data/CHANGELOG.markdown +17 -3
- data/Gemfile +1 -1
- data/README.markdown +269 -171
- data/grape.gemspec +1 -0
- data/lib/grape.rb +43 -22
- data/lib/grape/api.rb +7 -4
- data/lib/grape/endpoint.rb +48 -9
- data/lib/grape/entity.rb +1 -1
- data/lib/grape/error_formatter/base.rb +32 -0
- data/lib/grape/error_formatter/json.rb +17 -0
- data/lib/grape/error_formatter/txt.rb +18 -0
- data/lib/grape/error_formatter/xml.rb +17 -0
- data/lib/grape/exceptions/validation_error.rb +9 -5
- data/lib/grape/formatter/base.rb +33 -0
- data/lib/grape/formatter/json.rb +15 -0
- data/lib/grape/formatter/serializable_hash.rb +35 -0
- data/lib/grape/formatter/txt.rb +13 -0
- data/lib/grape/formatter/xml.rb +13 -0
- data/lib/grape/middleware/base.rb +18 -103
- data/lib/grape/middleware/error.rb +16 -32
- data/lib/grape/middleware/formatter.rb +8 -9
- data/lib/grape/middleware/versioner.rb +3 -2
- data/lib/grape/middleware/versioner/header.rb +1 -1
- data/lib/grape/parser/base.rb +31 -0
- data/lib/grape/parser/json.rb +13 -0
- data/lib/grape/parser/xml.rb +13 -0
- data/lib/grape/validations.rb +1 -1
- data/lib/grape/validations/coerce.rb +1 -1
- data/lib/grape/validations/presence.rb +1 -1
- data/lib/grape/validations/regexp.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +183 -9
- data/spec/grape/endpoint_spec.rb +27 -1
- data/spec/grape/entity_spec.rb +21 -21
- data/spec/grape/middleware/base_spec.rb +15 -15
- data/spec/grape/middleware/exception_spec.rb +38 -16
- data/spec/grape/middleware/formatter_spec.rb +6 -40
- data/spec/grape/validations/presence_spec.rb +20 -20
- data/spec/spec_helper.rb +1 -1
- metadata +132 -58
data/CHANGELOG.markdown
CHANGED
@@ -1,5 +1,19 @@
|
|
1
|
-
0.2.
|
2
|
-
|
1
|
+
0.2.3 (24/12/2012)
|
2
|
+
==================
|
3
|
+
|
4
|
+
* [#179](https://github.com/intridea/grape/issues/178): Using `content_type` will remove all default content-types - [@dblock](https://github.com/dblock).
|
5
|
+
* [#265](https://github.com/intridea/grape/issues/264): Fix: Moved `ValidationError` into `Grape::Exceptions` - [@thepumpkin1979](https://github.com/thepumpkin1979).
|
6
|
+
* [#269](https://github.com/intridea/grape/pull/269): Fix: `LocalJumpError` will not be raised when using explict return in API methods - [@simulacre](https://github.com/simulacre).
|
7
|
+
* [#86](https://github.com/intridea/grape/issues/275): Fix Path-based versioning not recognizing `/` route - [@walski](https://github.com/walski).
|
8
|
+
* [#273](https://github.com/intridea/grape/pull/273): Disabled formatting via `serializable_hash` and added support for `format :serializable_hash` - [@dblock](https://github.com/dblock).
|
9
|
+
* [#277](https://github.com/intridea/grape/pull/277): Added a DSL to declare `formatter` in API settings - [@tim-vandecasteele](https://github.com/tim-vandecasteele).
|
10
|
+
* [#284](https://github.com/intridea/grape/pull/284): Added a DSL to declare `error_formatter` in API settings - [@dblock](https://github.com/dblock).
|
11
|
+
* [#285](https://github.com/intridea/grape/pull/285): Removed `error_format` from API settings, now matches request format - [@dblock](https://github.com/dblock).
|
12
|
+
* [#290](https://github.com/intridea/grape/pull/290): The default error format for XML is now `error/message` instead of `hash/error` - [@dpsk](https://github.com/dpsk).
|
13
|
+
* [#44](https://github.com/intridea/grape/issues/44): Pass `env` into formatters to enable templating - [@dblock](https://github.com/dblock).
|
14
|
+
|
15
|
+
0.2.2
|
16
|
+
=====
|
3
17
|
|
4
18
|
Features
|
5
19
|
--------
|
@@ -21,7 +35,7 @@ Fixes
|
|
21
35
|
* [#181](https://github.com/intridea/grape/pull/181): Fix: Corrected JSON serialization of nested hashes containing `Grape::Entity` instances - [@benrosenblum](https://github.com/benrosenblum).
|
22
36
|
* [#203](https://github.com/intridea/grape/pull/203): Added a check to `Entity#serializable_hash` that verifies an entity exists on an object - [@adamgotterer](https://github.com/adamgotterer).
|
23
37
|
* [#208](https://github.com/intridea/grape/pull/208): `Entity#serializable_hash` must also check if attribute is generated by a user supplied block - [@ppadron](https://github.com/ppadron).
|
24
|
-
* [#252](https://github.com/intridea/grape/pull/252): Resources that don't respond to a requested HTTP method return 405 (Method Not Allowed) instead of 404 (Not Found) [@simulacre](https://github.com/simulacre)
|
38
|
+
* [#252](https://github.com/intridea/grape/pull/252): Resources that don't respond to a requested HTTP method return 405 (Method Not Allowed) instead of 404 (Not Found) - [@simulacre](https://github.com/simulacre)
|
25
39
|
|
26
40
|
0.2.1 (7/11/2012)
|
27
41
|
=================
|
data/Gemfile
CHANGED
data/README.markdown
CHANGED
@@ -3,9 +3,9 @@
|
|
3
3
|
## What is Grape?
|
4
4
|
|
5
5
|
Grape is a REST-like API micro-framework for Ruby. It's designed to run on Rack
|
6
|
-
or complement existing web application frameworks such as Rails and Sinatra by
|
7
|
-
providing a simple DSL to easily develop RESTful APIs. It has built-in support
|
8
|
-
for common conventions, including multiple formats, subdomain/prefix restriction,
|
6
|
+
or complement existing web application frameworks such as Rails and Sinatra by
|
7
|
+
providing a simple DSL to easily develop RESTful APIs. It has built-in support
|
8
|
+
for common conventions, including multiple formats, subdomain/prefix restriction,
|
9
9
|
content negotiation, versioning and much more.
|
10
10
|
|
11
11
|
[![Build Status](https://travis-ci.org/intridea/grape.png?branch=master)](http://travis-ci.org/intridea/grape)
|
@@ -15,6 +15,11 @@ content negotiation, versioning and much more.
|
|
15
15
|
* [Grape Google Group](http://groups.google.com/group/ruby-grape)
|
16
16
|
* [Grape Wiki](https://github.com/intridea/grape/wiki)
|
17
17
|
|
18
|
+
## Stable Release
|
19
|
+
|
20
|
+
You're reading the documentation for the next release of Grape, which should be 0.2.3.
|
21
|
+
The current stable release is [0.2.2](https://github.com/intridea/grape/blob/v0.2.2/README.markdown).
|
22
|
+
|
18
23
|
## Installation
|
19
24
|
|
20
25
|
Grape is available as a gem, to install it just install the gem:
|
@@ -49,37 +54,60 @@ class Twitter::API < Grape::API
|
|
49
54
|
end
|
50
55
|
|
51
56
|
resource :statuses do
|
52
|
-
|
53
|
-
desc "
|
57
|
+
|
58
|
+
desc "Return a public timeline."
|
54
59
|
get :public_timeline do
|
55
|
-
|
60
|
+
Status.limit(20)
|
56
61
|
end
|
57
62
|
|
58
|
-
desc "
|
63
|
+
desc "Return a personal timeline."
|
59
64
|
get :home_timeline do
|
60
65
|
authenticate!
|
61
|
-
current_user.
|
66
|
+
current_user.statuses.limit(20)
|
62
67
|
end
|
63
68
|
|
64
|
-
desc "
|
69
|
+
desc "Return a status."
|
65
70
|
params do
|
66
|
-
requires :id, :type => Integer, :desc => "
|
71
|
+
requires :id, :type => Integer, :desc => "Status id."
|
67
72
|
end
|
68
|
-
get '
|
69
|
-
|
73
|
+
get ':id' do
|
74
|
+
Status.find(params[:id])
|
75
|
+
end
|
76
|
+
|
77
|
+
desc "Create a status."
|
78
|
+
params do
|
79
|
+
requires :status, :type => String, :desc => "Your status."
|
80
|
+
end
|
81
|
+
post do
|
82
|
+
authenticate!
|
83
|
+
Status.create!({
|
84
|
+
:user => current_user,
|
85
|
+
:text => params[:status]
|
86
|
+
})
|
70
87
|
end
|
71
|
-
|
72
|
-
desc "
|
88
|
+
|
89
|
+
desc "Update a status."
|
73
90
|
params do
|
91
|
+
requires :id, :type => String, :desc => "Status ID."
|
74
92
|
requires :status, :type => String, :desc => "Your status."
|
75
93
|
end
|
76
|
-
|
94
|
+
put ':id' do
|
77
95
|
authenticate!
|
78
|
-
|
96
|
+
current_user.statuses.find(params[:id]).update({
|
79
97
|
:user => current_user,
|
80
98
|
:text => params[:status]
|
81
|
-
)
|
99
|
+
})
|
100
|
+
end
|
101
|
+
|
102
|
+
desc "Delete a status."
|
103
|
+
params do
|
104
|
+
requires :id, :type => String, :desc => "Status ID."
|
105
|
+
end
|
106
|
+
delete ':id' do
|
107
|
+
authenticate!
|
108
|
+
current_user.statuses.find(params[:id]).destroy
|
82
109
|
end
|
110
|
+
|
83
111
|
end
|
84
112
|
|
85
113
|
end
|
@@ -98,10 +126,12 @@ run Twitter::API
|
|
98
126
|
|
99
127
|
And would respond to the following routes:
|
100
128
|
|
101
|
-
GET
|
102
|
-
GET
|
103
|
-
GET
|
104
|
-
POST /statuses
|
129
|
+
GET /statuses/public_timeline(.json)
|
130
|
+
GET /statuses/home_timeline(.json)
|
131
|
+
GET /statuses/:id(.json)
|
132
|
+
POST /statuses(.json)
|
133
|
+
PUT /statuses/:id(.json)
|
134
|
+
DELETE /statuses/:id(.json)
|
105
135
|
|
106
136
|
### Rails
|
107
137
|
|
@@ -111,7 +141,7 @@ In a Rails application, modify *config/routes*:
|
|
111
141
|
mount Twitter::API => "/"
|
112
142
|
```
|
113
143
|
|
114
|
-
Note that you will need to restart
|
144
|
+
Note that when using Rails you will need to restart the server to pick up changes in your API classes
|
115
145
|
(see [Issue 131](https://github.com/intridea/grape/issues/131)).
|
116
146
|
|
117
147
|
### Modules
|
@@ -128,7 +158,7 @@ end
|
|
128
158
|
|
129
159
|
## Versioning
|
130
160
|
|
131
|
-
There are three strategies in which clients can reach your API's endpoints: `:header`,
|
161
|
+
There are three strategies in which clients can reach your API's endpoints: `:header`,
|
132
162
|
`:path` and `:param`. The default strategy is `:path`.
|
133
163
|
|
134
164
|
### Header
|
@@ -162,10 +192,10 @@ Using this versioning strategy, clients should pass the desired version in the U
|
|
162
192
|
version 'v1', :using => :param
|
163
193
|
```
|
164
194
|
|
165
|
-
Using this versioning strategy, clients should pass the desired version as a request parameter,
|
195
|
+
Using this versioning strategy, clients should pass the desired version as a request parameter,
|
166
196
|
either in the URL query string or in the request body.
|
167
197
|
|
168
|
-
curl -H http://localhost:9292/
|
198
|
+
curl -H http://localhost:9292/statuses/public_timeline?apiver=v1
|
169
199
|
|
170
200
|
The default name for the query parameter is 'apiver' but can be specified using the `:parameter` option.
|
171
201
|
|
@@ -173,16 +203,16 @@ The default name for the query parameter is 'apiver' but can be specified using
|
|
173
203
|
version 'v1', :using => :param, :parameter => "v"
|
174
204
|
```
|
175
205
|
|
176
|
-
curl -H http://localhost:9292/
|
206
|
+
curl -H http://localhost:9292/statuses/public_timeline?v=v1
|
177
207
|
|
178
208
|
## Describing Methods
|
179
209
|
|
180
210
|
You can add a description to API methods and namespaces.
|
181
211
|
|
182
212
|
``` ruby
|
183
|
-
desc "Returns
|
184
|
-
get
|
185
|
-
|
213
|
+
desc "Returns your public timeline."
|
214
|
+
get :public_timeline do
|
215
|
+
Status.limit(20)
|
186
216
|
end
|
187
217
|
```
|
188
218
|
|
@@ -192,8 +222,8 @@ Request parameters are available through the `params` hash object. This includes
|
|
192
222
|
along with any named parameters you specify in your route strings.
|
193
223
|
|
194
224
|
```ruby
|
195
|
-
get do
|
196
|
-
|
225
|
+
get :public_timeline do
|
226
|
+
Status.order(params[:sort_by])
|
197
227
|
end
|
198
228
|
```
|
199
229
|
|
@@ -201,13 +231,13 @@ Parameters are also populated from the request body on POST and PUT for JSON and
|
|
201
231
|
|
202
232
|
The Request:
|
203
233
|
|
204
|
-
```curl -d '{"
|
234
|
+
```curl -d '{"text": "140 characters"}' 'http://localhost:9292/statuses' -H Content-Type:application/json -v```
|
205
235
|
|
206
236
|
The Grape Endpoint:
|
207
237
|
|
208
238
|
```ruby
|
209
|
-
post '/
|
210
|
-
|
239
|
+
post '/statuses' do
|
240
|
+
Status.create!({ :text => params[:text] })
|
211
241
|
end
|
212
242
|
```
|
213
243
|
|
@@ -218,59 +248,58 @@ You can define validations and coercion options for your parameters using a `par
|
|
218
248
|
```ruby
|
219
249
|
params do
|
220
250
|
requires :id, type: Integer
|
221
|
-
optional :
|
222
|
-
|
223
|
-
|
224
|
-
requires :first_name
|
225
|
-
requires :last_name
|
251
|
+
optional :text, type: String, regexp: /^[a-z]+$/
|
252
|
+
group :media do
|
253
|
+
requires :url
|
226
254
|
end
|
227
255
|
end
|
228
|
-
|
256
|
+
put ':id' do
|
229
257
|
# params[:id] is an Integer
|
230
258
|
end
|
231
259
|
```
|
232
260
|
|
233
|
-
When a type is specified an implicit validation is done after the coercion to ensure
|
261
|
+
When a type is specified an implicit validation is done after the coercion to ensure
|
234
262
|
the output type is the one declared.
|
235
263
|
|
236
|
-
Parameters can be nested using `group`. In the above example, this means
|
237
|
-
`params[:
|
264
|
+
Parameters can be nested using `group`. In the above example, this means
|
265
|
+
`params[:media][:url]` is required along with `params[:id]`.
|
238
266
|
|
239
267
|
### Namespace Validation and Coercion
|
268
|
+
|
240
269
|
Namespaces allow parameter definitions and apply to every method within the namespace.
|
241
270
|
|
242
271
|
```ruby
|
243
|
-
namespace :
|
272
|
+
namespace :statuses do
|
244
273
|
params do
|
245
|
-
requires :
|
274
|
+
requires :user_id, type: Integer, desc: "A user ID."
|
246
275
|
end
|
247
|
-
namespace ":
|
248
|
-
desc "Retrieve a
|
276
|
+
namespace ":user_id" do
|
277
|
+
desc "Retrieve a user's status."
|
249
278
|
params do
|
250
|
-
requires :
|
279
|
+
requires :status_id, type: Integer, desc: "A status ID."
|
251
280
|
end
|
252
|
-
get ":
|
253
|
-
|
254
|
-
# params[:book_id] defines a book
|
281
|
+
get ":status_id" do
|
282
|
+
User.find(params[:user_id]).statuses.find(params[:status_id])
|
255
283
|
end
|
256
284
|
end
|
257
285
|
end
|
258
286
|
```
|
259
287
|
|
260
288
|
### Custom Validators
|
289
|
+
|
261
290
|
```ruby
|
262
291
|
class AlphaNumeric < Grape::Validations::Validator
|
263
292
|
def validate_param!(attr_name, params)
|
264
293
|
unless params[attr_name] =~ /^[[:alnum:]]+$/
|
265
294
|
throw :error, :status => 400, :message => "#{attr_name}: must consist of alpha-numeric characters"
|
266
|
-
end
|
295
|
+
end
|
267
296
|
end
|
268
|
-
end
|
297
|
+
end
|
269
298
|
```
|
270
299
|
|
271
300
|
```ruby
|
272
301
|
params do
|
273
|
-
requires :
|
302
|
+
requires :text, :alpha_numeric => true
|
274
303
|
end
|
275
304
|
```
|
276
305
|
|
@@ -279,33 +308,33 @@ You can also create custom classes that take parameters.
|
|
279
308
|
```ruby
|
280
309
|
class Length < Grape::Validations::SingleOptionValidator
|
281
310
|
def validate_param!(attr_name, params)
|
282
|
-
unless params[attr_name].length
|
283
|
-
throw :error, :status => 400, :message => "#{attr_name}: must be #{@option} characters long"
|
284
|
-
end
|
311
|
+
unless params[attr_name].length <= @option
|
312
|
+
throw :error, :status => 400, :message => "#{attr_name}: must be at the most #{@option} characters long"
|
313
|
+
end
|
285
314
|
end
|
286
|
-
end
|
287
|
-
```
|
315
|
+
end
|
316
|
+
```
|
288
317
|
|
289
318
|
```ruby
|
290
319
|
params do
|
291
|
-
requires :
|
320
|
+
requires :text, :length => 140
|
292
321
|
end
|
293
322
|
```
|
294
323
|
|
295
324
|
### Validation Errors
|
296
325
|
|
297
|
-
When validation and coercion
|
326
|
+
When validation and coercion errors occur an exception of type `Grape::Exceptions::ValidationError` is raised.
|
298
327
|
If the exception goes uncaught it will respond with a status of 400 and an error message.
|
299
|
-
You can rescue a `ValidationError` and respond with a custom response.
|
328
|
+
You can rescue a `Grape::Exceptions::ValidationError` and respond with a custom response.
|
300
329
|
|
301
330
|
```ruby
|
302
|
-
rescue_from ValidationError do |e|
|
331
|
+
rescue_from Grape::Exceptions::ValidationError do |e|
|
303
332
|
Rack::Response.new({
|
304
333
|
'status' => e.status,
|
305
334
|
'message' => e.message,
|
306
335
|
'param' => e.param
|
307
|
-
}.to_json, e.status)
|
308
|
-
end
|
336
|
+
}.to_json, e.status)
|
337
|
+
end
|
309
338
|
```
|
310
339
|
|
311
340
|
## Headers
|
@@ -321,7 +350,7 @@ end
|
|
321
350
|
|
322
351
|
```ruby
|
323
352
|
get do
|
324
|
-
error!
|
353
|
+
error!('Unauthorized', 401) unless env['HTTP_SECRET_PASSWORD'] == 'swordfish'
|
325
354
|
...
|
326
355
|
end
|
327
356
|
```
|
@@ -332,8 +361,8 @@ Optionally, you can define requirements for your named route parameters using re
|
|
332
361
|
expressions. The route will match only if all requirements are met.
|
333
362
|
|
334
363
|
```ruby
|
335
|
-
get '
|
336
|
-
|
364
|
+
get ':id', :requirements => { :id => /[0-9]*/ } do
|
365
|
+
Status.find(params[:id])
|
337
366
|
end
|
338
367
|
```
|
339
368
|
|
@@ -343,9 +372,9 @@ You can define helper methods that your endpoints can use with the `helpers`
|
|
343
372
|
macro by either giving a block or a module.
|
344
373
|
|
345
374
|
``` ruby
|
346
|
-
module
|
347
|
-
def
|
348
|
-
"
|
375
|
+
module StatusHelpers
|
376
|
+
def user_info(user)
|
377
|
+
"#{user} has statused #{user.statuses} status(s)"
|
349
378
|
end
|
350
379
|
end
|
351
380
|
|
@@ -358,11 +387,11 @@ class API < Grape::API
|
|
358
387
|
end
|
359
388
|
|
360
389
|
# or mix in a module
|
361
|
-
helpers
|
390
|
+
helpers StatusHelpers
|
362
391
|
|
363
|
-
get '
|
392
|
+
get 'info' do
|
364
393
|
# helpers available in your endpoint and filters
|
365
|
-
|
394
|
+
user_info(current_user)
|
366
395
|
end
|
367
396
|
end
|
368
397
|
```
|
@@ -373,28 +402,31 @@ You can set, get and delete your cookies very simply using `cookies` method.
|
|
373
402
|
|
374
403
|
``` ruby
|
375
404
|
class API < Grape::API
|
376
|
-
|
377
|
-
|
378
|
-
cookies[:
|
379
|
-
|
405
|
+
|
406
|
+
get 'status_count' do
|
407
|
+
cookies[:status_count] ||= 0
|
408
|
+
cookies[:status_count] += 1
|
409
|
+
{ :status_count => cookies[:status_count] }
|
380
410
|
end
|
381
411
|
|
382
|
-
delete '
|
383
|
-
{ :
|
412
|
+
delete 'status_count' do
|
413
|
+
{ :status_count => cookies.delete(:status_count) }
|
384
414
|
end
|
415
|
+
|
385
416
|
end
|
386
417
|
```
|
387
418
|
|
388
|
-
|
419
|
+
Use a hash-based syntax to set more than one value.
|
389
420
|
|
390
421
|
``` ruby
|
391
|
-
cookies[:
|
422
|
+
cookies[:status_count] = {
|
392
423
|
:value => 0,
|
393
424
|
:expires => Time.tomorrow,
|
394
|
-
:domain => '.
|
425
|
+
:domain => '.twitter.com',
|
395
426
|
:path => '/'
|
396
427
|
}
|
397
|
-
|
428
|
+
|
429
|
+
cookies[:status_count][:value] +=1
|
398
430
|
```
|
399
431
|
|
400
432
|
## Redirecting
|
@@ -402,53 +434,54 @@ cookies[:counter][:value] +=1
|
|
402
434
|
You can redirect to a new url temporarily (302) or permanently (301).
|
403
435
|
|
404
436
|
``` ruby
|
405
|
-
redirect "/
|
437
|
+
redirect "/statuses"
|
406
438
|
```
|
407
439
|
|
408
440
|
``` ruby
|
409
|
-
redirect "/
|
441
|
+
redirect "/statuses", :permanent => true
|
410
442
|
```
|
411
443
|
|
412
444
|
## Allowed Methods
|
413
445
|
|
414
446
|
When you add a route for a resource, a route for the HTTP OPTIONS
|
415
447
|
method will also be added. The response to an OPTIONS request will
|
416
|
-
include an Allow header listing the supported methods.
|
448
|
+
include an "Allow" header listing the supported methods.
|
417
449
|
|
418
450
|
``` ruby
|
419
451
|
class API < Grape::API
|
420
|
-
|
421
|
-
|
452
|
+
|
453
|
+
get '/rt_count' do
|
454
|
+
{ :rt_count => current_user.rt_count }
|
422
455
|
end
|
423
456
|
|
424
457
|
params do
|
425
|
-
requires :value, :type => Integer, :desc => '
|
458
|
+
requires :value, :type => Integer, :desc => 'Value to add to the rt count.'
|
426
459
|
end
|
427
|
-
put '/
|
428
|
-
|
460
|
+
put '/rt_count' do
|
461
|
+
current_user.rt_count += params[:value].to_i
|
462
|
+
{ :rt_count => current_user.rt_count }
|
429
463
|
end
|
430
464
|
end
|
431
465
|
```
|
432
466
|
|
433
467
|
``` shell
|
434
|
-
curl -v -X OPTIONS http://localhost:3000/
|
468
|
+
curl -v -X OPTIONS http://localhost:3000/rt_count
|
435
469
|
|
436
|
-
> OPTIONS /
|
437
|
-
>
|
470
|
+
> OPTIONS /rt_count HTTP/1.1
|
471
|
+
>
|
438
472
|
< HTTP/1.1 204 No Content
|
439
473
|
< Allow: OPTIONS, GET, PUT
|
440
474
|
```
|
441
475
|
|
442
|
-
|
443
476
|
If a request for a resource is made with an unsupported HTTP method, an
|
444
477
|
HTTP 405 (Method Not Allowed) response will be returned.
|
445
478
|
|
446
479
|
``` shell
|
447
|
-
curl -X DELETE -v http://localhost:3000/
|
480
|
+
curl -X DELETE -v http://localhost:3000/rt_count/
|
448
481
|
|
449
|
-
> DELETE /
|
482
|
+
> DELETE /rt_count/ HTTP/1.1
|
450
483
|
> Host: localhost:3000
|
451
|
-
>
|
484
|
+
>
|
452
485
|
< HTTP/1.1 405 Method Not Allowed
|
453
486
|
< Allow: OPTIONS, GET, PUT
|
454
487
|
```
|
@@ -470,8 +503,7 @@ error!({ "error" => "unexpected error", "detail" => "missing widget" }, 500)
|
|
470
503
|
|
471
504
|
## Exception Handling
|
472
505
|
|
473
|
-
Grape can be told to rescue all exceptions and
|
474
|
-
txt or json formats.
|
506
|
+
Grape can be told to rescue all exceptions and return them in txt or json formats.
|
475
507
|
|
476
508
|
``` ruby
|
477
509
|
class Twitter::API < Grape::API
|
@@ -487,12 +519,29 @@ class Twitter::API < Grape::API
|
|
487
519
|
end
|
488
520
|
```
|
489
521
|
|
490
|
-
The error format
|
491
|
-
|
522
|
+
The error format will match the request format. See "Content-Types" below.
|
523
|
+
|
524
|
+
Custom error formatters for existing and additional types can be defined with a proc.
|
492
525
|
|
493
526
|
``` ruby
|
494
527
|
class Twitter::API < Grape::API
|
495
|
-
|
528
|
+
error_formatter :txt, lambda { |message, backtrace, options, env|
|
529
|
+
"error: #{message} from #{backtrace}"
|
530
|
+
}
|
531
|
+
end
|
532
|
+
```
|
533
|
+
|
534
|
+
You can also use a module or class.
|
535
|
+
|
536
|
+
``` ruby
|
537
|
+
module CustomFormatter
|
538
|
+
def self.call(message, backtrace, options, env)
|
539
|
+
{ message: message, backtrace: backtrace }
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
class Twitter::API < Grape::API
|
544
|
+
error_formatter :custom, CustomFormatter
|
496
545
|
end
|
497
546
|
```
|
498
547
|
|
@@ -546,9 +595,9 @@ class API < Grape::API
|
|
546
595
|
API.logger
|
547
596
|
end
|
548
597
|
end
|
549
|
-
|
550
|
-
|
551
|
-
"
|
598
|
+
post '/statuses' do
|
599
|
+
...
|
600
|
+
logger.info "#{current_user} has statused"
|
552
601
|
end
|
553
602
|
end
|
554
603
|
```
|
@@ -569,9 +618,8 @@ class API < Grape::API
|
|
569
618
|
API.logger
|
570
619
|
end
|
571
620
|
end
|
572
|
-
get '/
|
573
|
-
logger.warning "
|
574
|
-
"hey there"
|
621
|
+
get '/statuses' do
|
622
|
+
logger.warning "#{current_user} has statused"
|
575
623
|
end
|
576
624
|
end
|
577
625
|
```
|
@@ -581,23 +629,46 @@ end
|
|
581
629
|
By default, Grape supports _XML_, _JSON_, _Atom_, _RSS_, and _text_ content-types.
|
582
630
|
Serialization takes place automatically.
|
583
631
|
|
584
|
-
Your API can declare
|
585
|
-
request's extension, an explicit `format` parameter in the query
|
632
|
+
Your API can declare which types to support by using `content_type`. Response format
|
633
|
+
is determined by the request's extension, an explicit `format` parameter in the query
|
634
|
+
string, or `Accept` header.
|
635
|
+
|
636
|
+
The following API will only respond to the JSON content-type. All other requests will
|
637
|
+
fail with an HTTP 406 error code.
|
638
|
+
|
639
|
+
``` ruby
|
640
|
+
class Twitter::API < Grape::API
|
641
|
+
format :json
|
642
|
+
content_type :json, "application/json"
|
643
|
+
end
|
644
|
+
```
|
645
|
+
|
646
|
+
Custom formatters for existing and additional types can be defined with a proc.
|
586
647
|
|
587
648
|
``` ruby
|
588
649
|
class Twitter::API < Grape::API
|
589
650
|
content_type :xls, "application/vnd.ms-excel"
|
651
|
+
formatter :xls, lambda { |object, env| object.to_xls }
|
590
652
|
end
|
591
653
|
```
|
592
654
|
|
593
|
-
You can also
|
655
|
+
You can also use a module or class.
|
594
656
|
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
657
|
+
``` ruby
|
658
|
+
module XlsFormatter
|
659
|
+
def self.call(object, env)
|
660
|
+
object.to_xls
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
664
|
+
class Twitter::API < Grape::API
|
665
|
+
content_type :xls, "application/vnd.ms-excel"
|
666
|
+
formatter :xls, XlsFormatter
|
667
|
+
end
|
668
|
+
```
|
669
|
+
|
670
|
+
The default format is `:txt`. You can set the preferred format for an API that
|
671
|
+
supports multiple formats with `format`.
|
601
672
|
|
602
673
|
``` ruby
|
603
674
|
class Twitter::API < Grape::API
|
@@ -605,19 +676,39 @@ class Twitter::API < Grape::API
|
|
605
676
|
end
|
606
677
|
```
|
607
678
|
|
679
|
+
You can set the fallback, default format with `default_format`.
|
680
|
+
|
608
681
|
``` ruby
|
609
682
|
class Twitter::API < Grape::API
|
610
683
|
default_format :json
|
611
684
|
end
|
612
685
|
```
|
613
686
|
|
614
|
-
|
687
|
+
Available formats are the following.
|
688
|
+
|
689
|
+
* `:json`: use object's `to_json` when available, otherwise call `MultiJson.dump`
|
690
|
+
* `:xml`: use object's `to_xml` when available, usually via `MultiXml`, otherwise call `to_s`
|
691
|
+
* `:txt`: use object's `to_txt` when available, otherwise `to_s`
|
692
|
+
* `:serializable_hash`: use object's `serializable_hash` when available, otherwise fallback to `:json`
|
693
|
+
|
694
|
+
The order for choosing the format is the following.
|
695
|
+
|
696
|
+
* Use the file extension, if specified. If the file is .json, choose the JSON format.
|
697
|
+
* Use the value of the `format` parameter in the query string, if specified.
|
698
|
+
* Use the format set by the `format` option, if specified.
|
699
|
+
* Attempt to find an acceptable format from the `Accept` header.
|
700
|
+
* Use the default format, if specified by the `default_format` option.
|
701
|
+
* Default to `:txt` otherwise.
|
702
|
+
|
703
|
+
## Content-type
|
704
|
+
|
705
|
+
You can override the content-type of the response by setting the `Content-Type` header.
|
615
706
|
|
616
707
|
``` ruby
|
617
708
|
class API < Grape::API
|
618
|
-
get '/
|
709
|
+
get '/home_timeline_js' do
|
619
710
|
content_type "application/javascript"
|
620
|
-
"var
|
711
|
+
"var statuses = ...;"
|
621
712
|
end
|
622
713
|
end
|
623
714
|
```
|
@@ -656,21 +747,21 @@ array.
|
|
656
747
|
``` ruby
|
657
748
|
module API
|
658
749
|
module Entities
|
659
|
-
class
|
660
|
-
expose :
|
661
|
-
expose :
|
662
|
-
expose :
|
663
|
-
expose :user_type, user_id, :if => lambda{ |
|
664
|
-
expose
|
665
|
-
expose :
|
750
|
+
class Status < Grape::Entity
|
751
|
+
expose :user_name
|
752
|
+
expose :text, :documentation => { :type => "string", :desc => "Status update text." }
|
753
|
+
expose :ip, :if => { :type => :full }
|
754
|
+
expose :user_type, user_id, :if => lambda{ |status, options| status.user.public? }
|
755
|
+
expose :digest { |status, options| Digest::MD5.hexdigest(satus.txt) }
|
756
|
+
expose :replies, :using => API::Status, :as => :replies
|
666
757
|
end
|
667
758
|
end
|
668
759
|
end
|
669
760
|
|
670
761
|
module API
|
671
762
|
module Entities
|
672
|
-
class
|
673
|
-
expose :
|
763
|
+
class StatusDetailed < API::Entities::Status
|
764
|
+
expose :internal_id
|
674
765
|
end
|
675
766
|
end
|
676
767
|
end
|
@@ -682,20 +773,18 @@ Grape ships with a DSL to easily define entities within the context
|
|
682
773
|
of an existing class:
|
683
774
|
|
684
775
|
```ruby
|
685
|
-
class
|
776
|
+
class Status
|
686
777
|
include Grape::Entity::DSL
|
687
778
|
|
688
|
-
entity :
|
689
|
-
expose :
|
779
|
+
entity :text, :user_id do
|
780
|
+
expose :detailed, if: :conditional
|
690
781
|
end
|
691
782
|
end
|
692
783
|
```
|
693
784
|
|
694
|
-
The above will automatically create a `
|
695
|
-
|
696
|
-
|
697
|
-
a block and can instead simply supply a list of comma-separated
|
698
|
-
symbols.
|
785
|
+
The above will automatically create a `Status::Entity` class and define properties on it according
|
786
|
+
to the same rules as above. If you only want to define simple exposures you don't have to supply
|
787
|
+
a block and can instead simply supply a list of comma-separated symbols.
|
699
788
|
|
700
789
|
### Using Entities
|
701
790
|
|
@@ -707,16 +796,16 @@ If the entity includes documentation it can be included in an endpoint's descrip
|
|
707
796
|
|
708
797
|
``` ruby
|
709
798
|
module API
|
710
|
-
class
|
799
|
+
class Statuses < Grape::API
|
711
800
|
version 'v1'
|
712
801
|
|
713
|
-
desc '
|
714
|
-
:object_fields => API::Entities::
|
802
|
+
desc 'Statuses index', {
|
803
|
+
:object_fields => API::Entities::Status.documentation
|
715
804
|
}
|
716
|
-
get '/
|
717
|
-
|
805
|
+
get '/statues' do
|
806
|
+
statuses = Status.all
|
718
807
|
type = current_user.admin? ? :full : :default
|
719
|
-
present
|
808
|
+
present statues, with: API::Entities::Status, :type => type
|
720
809
|
end
|
721
810
|
end
|
722
811
|
end
|
@@ -724,28 +813,25 @@ end
|
|
724
813
|
|
725
814
|
### Entity Organization
|
726
815
|
|
727
|
-
In addition to separately organizing entities, it may be useful to
|
728
|
-
|
729
|
-
For example:
|
816
|
+
In addition to separately organizing entities, it may be useful to put them as namespaced
|
817
|
+
classes underneath the model they represent.
|
730
818
|
|
731
819
|
```ruby
|
732
|
-
class
|
820
|
+
class Status
|
733
821
|
def entity
|
734
|
-
|
822
|
+
Status.new(self)
|
735
823
|
end
|
736
824
|
|
737
825
|
class Entity < Grape::Entity
|
738
|
-
expose :
|
826
|
+
expose :text, :user_id
|
739
827
|
end
|
740
828
|
end
|
741
829
|
```
|
742
830
|
|
743
|
-
If you organize your entities this way, Grape will automatically
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
class and use that as the representative entity. This can still
|
748
|
-
be overridden by using the `:with` option or an explicit
|
831
|
+
If you organize your entities this way, Grape will automatically detect the `Entity` class and
|
832
|
+
use it to present your models. In this example, if you added `present User.new` to your endpoint,
|
833
|
+
Grape would automatically detect that there is a `Status::Entity` class and use that as the
|
834
|
+
representative entity. This can still be overridden by using the `:with` option or an explicit
|
749
835
|
`represents` call.
|
750
836
|
|
751
837
|
### Caveats
|
@@ -757,7 +843,7 @@ However, when `object.check` equals "bar" both `field_b` and `foo` will be expos
|
|
757
843
|
```ruby
|
758
844
|
module API
|
759
845
|
module Entities
|
760
|
-
class
|
846
|
+
class Status < Grape::Entity
|
761
847
|
expose :field_a, :foo, :if => lambda { |object, options| object.check == "foo" }
|
762
848
|
expose :field_b, :foo, :if => lambda { |object, options| object.check == "bar" }
|
763
849
|
end
|
@@ -770,7 +856,7 @@ This can be problematic, when you have mixed collections. Using `respond_to?` is
|
|
770
856
|
```ruby
|
771
857
|
module API
|
772
858
|
module Entities
|
773
|
-
class
|
859
|
+
class Status < Grape::Entity
|
774
860
|
expose :field_a, :if => lambda { |object, options| object.check == "foo" }
|
775
861
|
expose :field_b, :if => lambda { |object, options| object.check == "bar" }
|
776
862
|
expose :foo, :if => lambda { |object, options| object.respond_to?(:foo) }
|
@@ -779,9 +865,21 @@ module API
|
|
779
865
|
end
|
780
866
|
```
|
781
867
|
|
868
|
+
## Hypermedia and other RESTful Representations
|
869
|
+
|
870
|
+
Although Grape ships with its own entity support, it's also possible to use it with other frameworks and renderers.
|
871
|
+
|
872
|
+
### Hypermedia
|
873
|
+
|
874
|
+
Use [Roar](https://github.com/apotonick/roar). Include `Roar::Representer::JSON` in your models or call `to_json` explicitly on representers in your API.
|
875
|
+
|
876
|
+
### Rabl
|
877
|
+
|
878
|
+
[Rabl](https://github.com/nesquena/rabl) is supported via the [grape-rabl](https://github.com/LTe/grape-rabl) gem.
|
879
|
+
|
782
880
|
## Describing and Inspecting an API
|
783
881
|
|
784
|
-
Grape routes can be reflected at runtime. This can notably be useful for generating
|
882
|
+
Grape routes can be reflected at runtime. This can notably be useful for generating
|
785
883
|
documentation.
|
786
884
|
|
787
885
|
Grape exposes arrays of API versions and compiled routes. Each route
|
@@ -797,7 +895,7 @@ TwitterAPI::routes[0].route_version # yields 'v1'
|
|
797
895
|
TwitterAPI::routes[0].route_description # etc.
|
798
896
|
```
|
799
897
|
|
800
|
-
It's possible to retrieve the information about the current route from within an API
|
898
|
+
It's possible to retrieve the information about the current route from within an API
|
801
899
|
call with `route`.
|
802
900
|
|
803
901
|
``` ruby
|
@@ -816,29 +914,29 @@ end
|
|
816
914
|
|
817
915
|
Grape by default anchors all request paths, which means that the request URL
|
818
916
|
should match from start to end to match, otherwise a `404 Not Found` is
|
819
|
-
returned. However, this is sometimes not what you want, because it is not always
|
917
|
+
returned. However, this is sometimes not what you want, because it is not always
|
820
918
|
known upfront what can be expected from the call. This is because Rack-mount by
|
821
|
-
default anchors requests to match from the start to the end, or not at all.
|
919
|
+
default anchors requests to match from the start to the end, or not at all.
|
822
920
|
Rails solves this problem by using a `:anchor => false` option in your routes.
|
823
921
|
In Grape this option can be used as well when a method is defined.
|
824
922
|
|
825
923
|
For instance when you're API needs to get part of an URL, for instance:
|
826
924
|
|
827
925
|
``` ruby
|
828
|
-
class
|
829
|
-
namespace :
|
830
|
-
get '/(*:
|
831
|
-
|
926
|
+
class TwitterAPI < Grape::API
|
927
|
+
namespace :statues do
|
928
|
+
get '/(*:status)', :anchor => false do
|
929
|
+
|
832
930
|
end
|
833
931
|
end
|
834
932
|
end
|
835
933
|
```
|
836
934
|
|
837
|
-
This will match all paths starting with '/
|
838
|
-
the `params[:
|
935
|
+
This will match all paths starting with '/statuses/'. There is one caveat though:
|
936
|
+
the `params[:status]` parameter only holds the first part of the request url.
|
839
937
|
Luckily this can be circumvented by using the described above syntax for path
|
840
938
|
specification and using the `PATH_INFO` Rack environment variable, using
|
841
|
-
`env["PATH_INFO"]`. This will hold everything that comes after the '/
|
939
|
+
`env["PATH_INFO"]`. This will hold everything that comes after the '/statuses/'
|
842
940
|
part.
|
843
941
|
|
844
942
|
## Writing Tests
|
@@ -895,7 +993,7 @@ describe Twitter::API do
|
|
895
993
|
it "returns a status by id" do
|
896
994
|
status = Status.create!
|
897
995
|
get "/api/v1/statuses/#{status.id}"
|
898
|
-
|
996
|
+
response.body.should == status.to_json
|
899
997
|
end
|
900
998
|
end
|
901
999
|
end
|